The Wanna.One Cyber Security Club shares writeup of some solved Challenges with the purpose of academic exchanges. We always welcome and look forward to comments from any of you via email: wannaone.uit@gmail.com
FWORD CTF 2021: https://ctftime.org/event/1405Sat, 28 Aug. 2021, 00:00 ICT — Sun, 29 Aug. 2021, 12:00 ICT
Author: phuonghoang
Time Machine
Description
No description needed just discover it by your self :DFlag Format: FWORDctf{}
This a Windows-reverse challenges mainly related to nanomites techniques( somewhere it’s also called debugger/debuggee or tracer/tracee technique). I have encountered with some nanomites challenges on Linux OS, but it’s first time for Windows OS. So I will present this technique most detailedly that I understand about it.
Analyzing challenge in detail
1. Debugger process( Sub_40195C)
Check the present of joezidsecret env variable
Program will check if the present of an environment variable which is named joezidsecret. If it’s available on your computer, program will run until a ‘illegal instruction’ exception in main is generated. If no, a function at 0x40195C will be called
Sub_40195C( will be renamed to debuggerFunc) is the heart of nanomites technique
- Firstly, it will _putenv(‘joezidsecret=1’)
- A new process ‘Time_Machine.exe’ will be created. This current( original) process plays a role as parent process( debugger) to debug created process. For new process, because “joezidsecret” env var was putted, “Main Function” branch will execute
- At this time, debuggerFunc() will wait for debugging events from child process, there are different tasks corresponding each of debuggerCode.
- Because as we known at main function of child process will ‘illegal instruction’ exception( ud2 instruction), so we will focus on the branch of debuggerCode = 1( EXCEPTION_DEBUG_EVENT):- sub_40173B() function will be call, it will play a role as checkFlag() function. This function uses GetThreadContext(), ReadProcessMemory() to get status of child process( register, debugger event…) and read values from memory
- Every char of input will be loaded to checkFlag() function, pass to calculate() function, the final result will be compared to a value read from child process. If they are equal and v11 will be set to 1, check var will increase 1
- If check var == 39, a string “Correct Flag :)\n” will be written to child process.checkFlag() is decompiled from IDA
2. Child process( sub_401C38- main func)
To find out the chars of challenge’s flag, we have to know how to generate compared values from child process, particularly main() function. Let’s examine the its disassembly:
Because we can’t know after call rdx, what program will do, so we’ll try debugging this main function.
The instructions of ‘call rdx‘
There are many same junkcodes which is seperated by ‘ud2’ instruction. They have a form as: r11 = ror(arg ^ 0x1337, 0xD). And if r13 is set 1( conjecturally, it’s v11 in checkFlag() function), this current value in r11 register will compared to resFromParent( result of calculate() funtion).
Solution
https://github.com/ph0xen1x0/ctf-writeups/tree/master/FwordCTF_2021/Time_Machine_chall
We will get arguments from disassembly code and pre-calculate all of values in r11 register( calling target[i]) when it is compared with resFromParent. Then, we will brute-force printable characters which satisfies calculate(input[i]) == target[i]:
Flag: FWORDctf{W3_4t_th3_t0p_4g41n_n0w_wh4t?}
Saw
Descripton:
Saw: We make our secret code consist of 400 chars to make it more secure can you break it ? Flag Format: FwordCTF{}
Analysis
Firstly, i think this writeup maybe not author’s intended solution because I trapped into anti-debugger techniques. And so, I can’t do a dynamic analysis as normally but I still discover a common technique in this challenge which is also called anti-disassembly.
And now let’s examine deeply this challenge:
After a while I stuck in debugging challnge, I decided to re-walkthrough all of segments and strings of challenge. And yeah, I discovered some special things as:
Some suspicious strings is located in .data segment but they aren’t refered to anywhere in this disassembly code of SAW.exe
The first bytes of .data segments look very familiarly, b'MZ' ==> signature of PE file. So, I extracted all of bytes of this segments and write to a new file( ‘newFile.exe’), then drop created exe file to IDA and magically, I received disassembly grap of main function:Disassembly of new file extracted from .data segment of SAW.exe
Easily, we can recognize sub_4153A7() as checkFlag() function. But, waiting, IDA fails in disassembly code of this function:
Luckily, I encountered this similar challenge in Flare-On 2015 and I know that author add redundant instructions to cause that IDA fails in disassembly. In particularly, that is two highlight jump instructions( jnz and jz) that jump the same location
Two basic mechanisms of disassembly are: linear and flow-oriented. Linear disassemble is done with one statement at a time until the end of the data in the buffer. However, because of doing so sequentially, it cannot determine where the code needs to be disass, where is the data -> easy to give wrong results.
IDA uses a flow-oriented mechanism, we can understand as condition and branching, if it encounters a condition, it will disass the wrong branch first. Taking advantage of this, the author has inserted a few statements such as: xor %reg, %reg before the jz statement (it’s always correct, so it will always jump to the branch ZF is set, but because the disass mechanism will find the wrong branch to do it before, so this leads IDA to fail disassembly), in addition to xor, there is a pair of commands {jz addrX; jnz addrX} is also used in anti-disassembly
Solution
https://github.com/ph0xen1x0/ctf-writeups/tree/master/FwordCTF_2021/Saw_chall
At time, we will extract bytecodes of loc_415790, then removing bytecodes of two instructions jnz, jz and byte 0xE9 and finally, write remaining bytes to new file, namely ‘checkFlagFunc.bin‘:
Drop new file which is just created to IDA, we will see that IDA tool success in disassembly bytecode:disassembly of checkFlag()’s bytecode( removed jz/jnz)
Reading disassembly, we easily recognize the check flag algorithm which getting bit of character in input and comparing to hardcored-bit. My solution is writing script to get index of character, get bit index and get bit. Finally, we have the following code:
Flag: FwordCTF{Wh4t_4r3_y0u_w1LL1ng_t0_L0s3?}
Omen
This is windows- reverse challenge using popf and Trap Flag to anti-debugger. Let’s analyze it and recognize how to IDA help us bypass this anti-debugger technique
Analyzing challenge
1. popf and Trap Flag anti-debugger technique
From start() function, we will trace to approach nearly function which generate the “Enter The Flag:” string. However, before getting that string, we will catch following exception, Single step exception:While debugging, we will catch an SINGLE-STEP exception
Go on analyzing deeply the flow of generating this exception associated with searching Google, we will discover some assembly instructions( highlighted below) intimately related to this exceptionThis is a common anti-debugger technique:
The trap flag, located in the Flags register, controls the tracing of a program. If this flag is set, executing an instruction will also raise a SINGLE_STEP exception. If a program is traced, a debugger will clear TF value set, and so we can’t see the activity of exception handler which can change the current flow of program.
We can refer to anti-debug.checkpoint.com and wiki to can understand thoroughly about this anti-debugger technique:Ref: https://en.wikipedia.org/wiki/Trap_flag
2. IDA supports to bypass TF anti-debugger technique
For SINGLE- STEP exception, IDA will spaw a message box to support us for choosing between if the exception handler should be generated or not.Message box from IDA when catching SINGLE- STEP exception
We should choose ‘Run’ option which means a exception handler for SINGLE-STEP should be generated. And then add a breakpoint after function which cause a SINGLE-STEP exception:Adding a breakpoint followed function generating Single-step exception
![](Passing an exception handler to application and go on debugging:)During continuous tracing, by trail and error, we catch some speciall assembly instructions as:
call ebx...call eax...call ucrtbased__initterm_e()….call ucrtbased__initterm()
Some similar functions in PE reverse appear during traing process
They are quite similar to assembly code of full PE file, at time I guess maybe author use PE Injection technique. And finally, we also reach real main() function of program:main() function finally appear
We easily get checkFlag()‘s pseudo code:
BOOL __cdecl checkFlag(_BYTE *input){ BOOL result; // eax result = 0; if ( input[33] + (input[3] ^ (unsigned __int8)(input[49] ^ input[78] ^ *input)) - input[49] - input[26] == 28 && (input[44] | (unsigned __int8)(input[69] ^ (input[43] + input[21] + (input[42] & input[58] & input[1])))) == -9 && (input[26] ^ (unsigned __int8)(input[45] + (input[64] ^ input[13] & input[2]))) - input[34] - input[70] == -54 && (input[76] & (unsigned __int8)(input[71] + input[80] + (input[82] & (input[40] | (input[3] - input[41]))))) == 49 && (input[30] ^ (unsigned __int8)(input[14] | ((input[85] ^ input[20] ^ input[35] ^ input[4]) - input[64]))) == -93 && input[70] + input[55] + (input[60] ^ (unsigned __int8)(input[65] + (input[32] ^ input[22] ^ input[5]))) == 94 && input[18] == (input[19] ^ (unsigned __int8)(input[62] ^ input[74] ^ input[12] & (input[6] - input[6]))) && (input[3] ^ (unsigned __int8)(input[81] ^ input[37] ^ input[48] ^ (input[38] + (input[52] ^ input[7])))) == -49 && (input[49] | (unsigned __int8)((input[82] & input[26] & (input[78] ^ input[8])) - input[91])) - input[53] == -96 && input[55] + (input[90] ^ (unsigned __int8)((input[63] & (input[15] ^ input[9])) - input[3])) - input[88] == -57 && (input[13] | (unsigned __int8)(input[78] + (input[84] ^ (input[72] + (input[24] ^ (input[33] | input[10])))))) == 95 ) { input[11]; if ( (input[5] | (unsigned __int8)(input[4] & ((input[78] ^ input[11]) - input[71] - input[41]))) == 99 && (input[91] ^ (unsigned __int8)(input[89] & (input[92] ^ (input[1] + (input[71] & input[12]) - input[66])))) == 1 && input[22] + (input[32] ^ (unsigned __int8)(input[21] + input[38] + (input[16] ^ input[13]))) - input[59] == 13 && (input[90] | (unsigned __int8)(input[76] ^ (input[20] + input[14] - *input))) - input[40] - input[72] == -56 && (input[76] ^ (unsigned __int8)(input[76] & (input[60] + input[8] + input[15] - input[84] - input[23]))) == 32 && (input[12] & (unsigned __int8)(input[90] & (input[39] ^ input[82] ^ input[8] & (input[5] | input[16])))) == 32 && (input[36] ^ (unsigned __int8)(input[79] | input[63] | (input[70] + input[30] + (input[43] | input[17])))) == 57 && (input[2] ^ (unsigned __int8)(input[19] | input[36] ^ (input[83] | (input[18] - input[87])))) - input[80] == -73 && (input[64] ^ (unsigned __int8)(input[6] ^ input[20] ^ (input[82] + (input[10] ^ (input[19] - input[63]))))) == -82 && (input[91] ^ (unsigned __int8)(input[80] + (input[13] | input[39] | input[71] ^ (input[57] | input[20])))) == -55 && (input[86] ^ (unsigned __int8)(input[85] ^ input[86] & (input[72] ^ (input[71] + (input[54] ^ input[21]))))) == 93 && (input[57] ^ (unsigned __int8)(input[19] ^ input[65] ^ input[14] ^ (input[22] - input[58]))) - input[38] == 94 && (input[21] ^ (unsigned __int8)(input[6] ^ ((input[26] ^ input[72] & (input[73] ^ input[23])) - input[1]))) == -12 && (input[29] & (unsigned __int8)(input[69] & (input[36] ^ (input[52] + (input[16] & (input[24] - input[76])))))) == 4 && (input[10] | (unsigned __int8)(input[12] | ((input[55] & (input[38] ^ (input[42] + input[25]))) - input[80]))) == -2 && input[24] + (input[47] & (unsigned __int8)((input[16] & (input[20] + (input[71] & input[26]))) - input[47])) == -125 && (input[88] & (unsigned __int8)(input[20] | input[91] ^ input[76] & input[80] & input[27])) - input[13] == -11 && input[12] + (input[4] & (unsigned __int8)(input[7] ^ (input[79] | input[1] & (input[89] ^ input[28])))) == 116 && (input[95] ^ (unsigned __int8)(input[32] ^ input[22] ^ (input[68] + (input[58] ^ input[29]) - input[54]))) == 76 && (input[69] ^ (unsigned __int8)(input[59] ^ input[26] & (input[40] | input[44] ^ input[41] ^ input[30]))) == 19 && (input[50] ^ (unsigned __int8)(input[28] + (input[16] & input[24] & input[31]) - input[36] - input[86])) == -64 && (input[50] ^ (unsigned __int8)(input[37] ^ input[69] ^ input[54] ^ input[36] ^ (input[32] - input[33]))) == -101 && (input[56] ^ (unsigned __int8)(input[12] & (input[52] ^ (input[94] + input[45] + input[22] + input[33])))) == 46 && (input[19] ^ (unsigned __int8)(input[89] + (input[6] ^ input[34]) - input[23] - input[20] - input[42])) == 107 && (input[10] | (unsigned __int8)(input[60] + (input[33] | input[72] | (input[27] + (input[91] ^ input[35]))))) == 105 && (input[94] ^ (unsigned __int8)(input[3] & (input[24] ^ input[24] & input[19] & (input[42] ^ input[36])))) == 42 && (input[17] ^ (unsigned __int8)(input[57] | input[71] | ((input[7] ^ (input[88] | input[37])) - input[19]))) == -114 && (input[56] ^ (unsigned __int8)(*input ^ input[76] & (input[71] ^ input[76] ^ (input[38] - input[9])))) == 57 && (input[22] ^ (unsigned __int8)(input[8] ^ (input[11] + input[20] + (input[4] ^ (input[57] + input[39]))))) == 12 && input[91] + (input[77] | (unsigned __int8)(input[85] + (input[38] ^ (input[67] + input[40])) - input[78])) == 47 && input[84] + (input[86] ^ (unsigned __int8)(input[85] ^ input[51] & (input[11] ^ (input[78] | input[41])))) == -110 && (input[12] ^ (unsigned __int8)(input[31] ^ (input[90] + (input[27] ^ *input ^ input[42]) - input[65]))) == -29 && (input[75] ^ (unsigned __int8)(input[71] ^ (input[64] + (input[63] ^ input[40] & (input[2] ^ input[43]))))) == 33 && (input[93] ^ (unsigned __int8)(input[27] ^ (input[35] | input[81] ^ (input[44] - input[9])))) - input[76] == 9 && (input[88] | (unsigned __int8)(input[61] & input[85] & (input[78] ^ input[71] & (input[30] | input[45])))) == 127 && (input[24] ^ (unsigned __int8)(input[41] ^ input[21] ^ input[76] ^ ((input[89] & input[46]) - input[90]))) == 66 && (input[86] ^ (unsigned __int8)(input[28] + (input[61] ^ input[36] ^ input[47]) - input[18])) - input[68] == -4 && (input[95] ^ (unsigned __int8)(input[24] + (input[8] | input[46] ^ input[61] ^ (input[23] | input[48])))) == -89 && input[19] + (input[25] | (unsigned __int8)(input[95] + (input[32] ^ input[67] ^ input[49]))) - input[42] == -10 && (input[67] ^ (unsigned __int8)((input[90] ^ input[85] ^ input[19] ^ input[50]) - input[36] - input[46])) == 97 && (input[29] ^ (unsigned __int8)(input[39] ^ input[4] & ((input[60] ^ (input[59] + input[51])) - input[56]))) == 67 && !(input[91] & (unsigned __int8)(input[36] & (input[91] + (input[10] ^ input[69] ^ (input[23] + input[52]))))) && (input[88] ^ (unsigned __int8)(input[28] & (input[19] ^ input[45] & (input[52] + (input[81] ^ input[53]))))) == 31 && (input[62] ^ (unsigned __int8)(input[57] ^ (input[27] + (input[44] | ((input[82] & input[54]) - input[60]))))) == -73 && (input[69] & (unsigned __int8)(input[12] ^ (input[10] + (input[11] ^ input[81] ^ input[55]) - input[17]))) == 94 && input[88] + (input[51] ^ (unsigned __int8)((input[53] ^ input[19] ^ input[93] ^ input[56]) - input[36])) == 57 && (input[61] | (unsigned __int8)(input[69] | input[16] ^ (input[13] + (input[11] ^ input[18] ^ input[57])))) == -1 && (input[89] | (unsigned __int8)(input[60] ^ input[61] ^ input[61] ^ input[39] ^ input[67] ^ input[58])) == 122 && input[10] + (input[40] ^ (unsigned __int8)(input[69] ^ input[4] & (input[59] - input[86] - input[46]))) == 108 && (input[40] & (unsigned __int8)(input[68] | input[18] ^ ((input[76] ^ input[84] ^ input[60]) - input[66]))) == 95 && (input[54] & (unsigned __int8)(input[53] ^ input[82] ^ (input[56] + (input[81] ^ (input[61] - input[87]))))) == 66 && (input[56] ^ (unsigned __int8)((input[67] ^ ((input[41] ^ input[62]) - input[42])) - input[3])) - input[44] == 120 && (input[14] ^ (unsigned __int8)(input[55] | input[30] ^ (input[90] | input[23] | input[53] ^ input[63]))) == 11 && (input[60] & (unsigned __int8)(input[59] ^ input[44] ^ input[46] ^ (input[71] | input[29] & input[64]))) == 34 && (input[85] | (unsigned __int8)((input[43] ^ input[58] & input[88] & input[65]) - input[49] - input[73])) == -75 && input[12] + input[88] + (input[40] ^ (unsigned __int8)(input[18] ^ (input[16] + input[66] - input[89]))) == -83 && (input[59] ^ (unsigned __int8)(input[90] & ((input[30] ^ input[9] ^ input[3] ^ input[67]) - input[48]))) == 127 && (input[50] & (unsigned __int8)(*input | input[51] ^ input[11] ^ input[36] ^ input[4] ^ input[68])) == 86 && (input[73] ^ (unsigned __int8)(*input ^ ((input[4] ^ input[81] ^ input[55] ^ input[69]) - input[19]))) == -77 && (input[48] ^ (unsigned __int8)(input[81] ^ input[12] ^ input[63] & (input[15] | input[37] ^ input[70]))) == 116 && (input[51] ^ (unsigned __int8)(input[57] ^ ((input[6] ^ input[77] ^ input[87] ^ input[71]) - input[80]))) == -102 && (input[12] & (unsigned __int8)(input[76] ^ input[57] ^ input[93] ^ input[89] & input[72])) - input[92] == 6 && (input[49] ^ (unsigned __int8)(input[87] + (input[6] ^ input[65] ^ input[94] ^ input[73]) - input[56])) == -56 && (input[10] ^ (unsigned __int8)(input[35] + (input[12] | input[60] ^ input[74]) - input[58])) - input[80] == 72 && (input[49] ^ (unsigned __int8)(input[68] ^ (input[94] + (input[71] ^ (input[37] + input[75] - input[50]))))) == -96 && (input[66] ^ (unsigned __int8)((input[53] | input[65] ^ input[15] & (input[83] + input[76])) - input[15])) == 44 && (input[18] ^ (unsigned __int8)(input[91] & (input[30] ^ input[8] ^ input[30] ^ (input[56] + input[77])))) == 101 && input[45] + (input[55] ^ (unsigned __int8)(input[46] ^ ((input[58] ^ (input[79] | input[78])) - input[46]))) == -32 && (input[86] ^ (unsigned __int8)(input[31] ^ input[94] ^ input[24] & (input[23] | input[93] | input[79]))) == 4 && (input[21] & (unsigned __int8)(input[7] ^ input[46] ^ input[63] & (input[67] | (input[80] - input[55])))) == 64 && (input[1] | (unsigned __int8)(input[48] ^ input[53] ^ input[44] ^ input[20] ^ input[30] & input[81])) == 119 && (input[25] | (unsigned __int8)(input[1] + (input[23] ^ input[70] ^ input[82]) - input[51] - input[14])) == 119 && (input[37] ^ (unsigned __int8)(input[22] ^ (input[58] | input[5] | input[60] ^ (input[39] | input[83])))) == 2 && (input[51] ^ (unsigned __int8)(input[51] & ((input[91] | input[74] ^ (input[9] | input[84])) - input[6]))) == 42 && (input[81] ^ (unsigned __int8)(input[2] ^ input[19] ^ input[62] & (input[47] + (input[71] ^ input[85])))) == 20 && (input[72] ^ (unsigned __int8)(input[33] & (input[94] ^ input[58] ^ input[70] ^ (input[84] + input[86])))) == 2 && (input[44] | (unsigned __int8)(input[80] & (input[88] ^ (input[20] | input[36] ^ input[44] & input[87])))) == 125 && (input[81] ^ (unsigned __int8)(input[38] ^ input[94] ^ input[25] & (input[50] ^ (input[88] - *input)))) == 56 && (input[3] | (unsigned __int8)(input[84] | input[93] ^ (input[11] | input[66] & (input[63] ^ input[89])))) == 115 && (input[52] & (unsigned __int8)(input[13] ^ input[85] & (input[16] + (input[95] ^ (input[67] | input[90]))))) == 32 && (input[44] ^ (unsigned __int8)(input[68] ^ input[78] ^ (input[74] + (input[80] ^ (input[51] + input[91]))))) == 78 && (input[18] ^ (unsigned __int8)(input[15] ^ (input[33] | input[30] & (input[87] ^ (input[92] - input[59]))))) == 104 && (input[67] & (unsigned __int8)(input[39] ^ (input[76] + (input[58] ^ input[15] ^ input[93]) - input[6]))) == 4 && (input[65] & (unsigned __int8)(input[77] & (input[66] ^ (*input | input[72] ^ input[80] ^ input[94])))) == 116 && (input[83] ^ (unsigned __int8)(input[55] | ((input[7] ^ input[70] & (input[8] ^ input[95])) - input[28]))) == -96 && input[79] == 95 && input[47] == 52 && input[61] == 117 ) { result = 1; } } return result;}
Writting python-script( convertZ3format.py) to convert all of constraints in checkFlag() function to z3 instruction format and add additional condition as input[:9] = 'FWORDctf{' to help z3 solve quickly. This is our full-flag below:
Flag: FWORDctf{Wh4t_4b0ut_th1s_w31rd_L0ng_fL4g_th4t_m4k3_n0_s3ns3_but_st1LL_w1LL_g1v3_y0u_s0m3_p01ntz}