wannaShare | Writeups FwordCTF 2021 | Reverse Engineering

PHAPHA_JIàN
22:02 04/09/2021

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 variableCheck 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

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:

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}

Appendix

Source and solution of this challenge

TIN LIÊN QUAN
Sau 4 buổi học với đa dạng góc nhìn về đề tài, gợi mở nhiều kiến thức liên quan đến An toàn thông tin (ATTT), khoá huấn luyện WannaQuest.Q2023.02 đã tạo tiền đề cho nhiều sinh viên tham gia hiểu rõ hơn tầm quan trọng của việc tham gia nghiên...
Thông qua nhiều sự kiện, hoạt động học thuật sôi nổi, WannaQuest được đánh giá cao khi trở lại với mùa thứ 2 với khóa huấn luyện WannaQuest.Q2023.02, một nơi đã trở thành nơi kết nối, giao lưu, tiếp nhận kiến thức An toàn thông tin (ATTT) theo cách cởi...
WannaGame Weekly UTCTF, ångstromCTF, Grey Cat The Flag, ImaginaryCTF, SekaiCTF, Downunder CTF, TeamItaly CTF, CTFZone, Asis Final, SEETF, Bauhinia... UIT Honor dice, Real World, bi0s, Seccon, pbctf, Kalmarctf, hxp, Plaid, m-leCon, HackTM, p4ctf, justCTF, codegate, Google, zer0pts, Defcon, HITCON, Hack.lu, N1CTF, Brics+, 0CTF/TCTF, Balsn, RuCTF (AD), FAUST (AD), saarCTF (AD)......