Wargames.my 2016 writeups

Challenge 9: Test your skill EP02

to begin, instead of going through the binary step by step and dive through functions, let us skim through the main routine at 0x009E12A0. we can identify that the binary expecting 1 argument

argument

we also can identify the “good-boy” block which will print “Yeah” when the condition is met

good boy

now we can analyse the main routine

1
2
3
4
5
6
7
8
9
10
11
12
13
main(argc, argv) {
if (argc == 2) {
x = check(*argv[1], 0, 10);
x = ror(x, 3);
x = bswap(x);
x += 0xFC669293;
x ^= 0xEE9579F;
x *= 0x341;
if (x == 0x68616559) {
printf("Yeah");
}
}
}

reverse the algorithm to get the expected output from the check function

1
2
3
4
5
6
7
x = 0x68616559;
x /= 0x341;
x ^= 0xEE9579F;
x -= 0xFC669293;
x = bswap(x);
x = rol(x, 3);
// x = 0x9d831097 (2642612375 decimal.)

now let’s dive into the check function. after going through the function step by step, the only block that caught my interest is a loop that start at 0x009E2CF6

loop

simplified algorithm of the loop:

1
2
3
4
5
6
7
8
9
10
result = 0;
for(i = 0; strlen(arg); i++) {
x = arg[i];
// for the sake of simplifying the algorithm, i'll use isdigit()
// it is actually a sequence of ascii values manipulation
if (!isdigit(x)) break;
if (result > 0x19999999) break;
result *= 10;
result += atoi(x);
}

from there, we can identify the argument need to be passed to the binary in order to produce the result == 2642612375. the argument is… 2642612375. trolled hard!

flag

Challenge 12: Test your skill EP03

junk

i didn’t manage to solve this during the competition because i was too focused on another challenge. not to mention the binary is heavily obfuscated which brings out the laziness in me. 500 points would be sweet to secure the victory though. anyway, the binary expected one argument to be passed. we can see a local call to 0x0040117b after the entrypoint where the stack is updated with:

  • WORD nulls
  • BYTE values
  • WORD values
  • a few others

then, the binary jump to 0x00401000 with our argument as the first parameter (esp+4). let us put a memory breakpoint on the argument to pinpoint where it will be accessed

breakpoint

continue the program (F9) and we can see there are 2 lines that access the argument, 0x004010B7 and 0x004010C6

break

from there, we can understand how the checking algorithm works

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
004020AF   MOV EDX,DWORD PTR DS:[EAX]       ; edx = &arg
004020B1 MOV EAX,DWORD PTR SS:[EBP-8C] ; eax = loop_counter
004020B7 ADD EDX,EAX ; edx += eax
<---- snip ---->
004020C2 MOVSX EAX,BYTE PTR DS:[EDX] ; eax = *(edx)[0]
<---- snip ---->
0040211F MOV WORD PTR DS:[ECX],AX ; *(0x19FF20) = ax
<---- snip ---->
00402277 MOVSX EAX,WORD PTR DS:[ECX] ; eax = *(0x19FF20)
0040227A SHL EAX,2 ; eax = eax << 2
<---- snip ---->
00402286 MOV WORD PTR DS:[ECX],AX ; *(0x19FF20) = ax
<---- snip ---->
00402358 LEA ECX,[EBP-38] ; ecx = 0x19FF20
0040235B ADD ECX,EAX ; ecx += loop_counter
<---- snip ---->
004023A3 MOVSX EAX,WORD PTR DS:[ECX] ; eax = *(0x19FF20)
004023A6 MOVSX ECX,WORD PTR DS:[EDX] ; ecx = *(ecx)
004023A9 CMP EAX,ECX
004023AB JE 004023D4 ; if (eax == ecx) loop

note that our argument are passed through a loop where every characters are shifted to the left by 2 bit and compared to a value extracted from the stack segment. the extracted values are the same WORD values which is stored in the stack during the beginning of the program:

1
3C 01 B8 01 B0 01 E4 01 7C 01 98 01 BC 01 C8 01 7C 01 D0 01 A0 01 94 01 7C 01 88 01 C8 01 84 01 D8 01 94 01 7C 01 BC 01 B8 01 94 01 CC 01

simple python code to decode it:

1
2
3
4
5
6
7
from struct import unpack
enc = "3C01B801B001E4017C019801BC01C8017C01D001A00194017C018801C8018401D80194017C01BC01B8019401CC01".decode("hex")
dec = ""
for i in range(0, len(enc), 2):
now = enc[i:i+2]
dec += chr(unpack("<H", now)[0] >> 2)
print dec

flag