Watchmen
So first glance at this binary, I noticed that it is a windows binary (ewww!) so that was kinda annoying. I also took a quick glance at the code, I saw some sort of assembly instructions (did the orgs link this binary to some assembler/disassembler code?). Initially, I thought I would have to actually reverse this code, but turns out, it wasn't necessary (either it wasn't used or doesn't play an essential role, I don't know).
So initially, digging throught the code, IDA has easily demarcated the main function (that was cool). Initally I found that the code was very simple, and I was wondering when the code would start getting hairy. So in the main function I get this code:
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
Okay so looks simple enough. It looks like it is creating some sort of mutex so that... oh noes... can it be... is it trying to do some sort of debugger inception like the keygenme problem that I tried to solve in Google CTF??? Gasp!
Back in keygenme, I found that the parent "debugger" was actually hot-patching
some parts of the child "debuggee" as soon as the child reaches that
instruction. In that place a few int3
instructions are carefully placed to do
just that, notifying the parent that the child has reached that spot.
A slight note: the fork
and ptrace
functions that I have labeled here are
actually my own naming, as that is what I think those two functions are doing
(in relation to keygenme) but in reality, there are no fork and ptrace functions
in Windows xD xD.
Apparently the mutex is used here to cause the child to fork to a separate
function myst()
. Initially that function had a ud2
instruction, but then I
noticed in do_debug()
the parent will then hot-patch those locations as soon
as it receives a SIGILL
(or EXCEPTION_ILLEGAL_INSTRUCTION
in Windows lingo).
Okay, so far, it looks very much like the same thing, but when I looked at the
code for handling exceptions, I noticed two branches:
1 | int __cdecl dbg_handle_exc(HANDLE hProcess, HANDLE hThread, _DEBUG_EVENT *a3) |
Judging from this code, it looks like that the code is actually single stepping
after it reaches the SIGILL, and each single step, some sort of patching is
occurring, i.e. being done by WriteProcessMemory
. Another annoying thing is
that it will revert previous patches before making new patches.
Okay, at this point, I did not want to actually go and reverse that
huge junk of code, so I was bent on using some strace
equivalent on Windows. I
had a bit of issue trying to set it up. I found that DynamoRIO is probably
one of the de-facto instrumentation tools used on Windows, but I couldn't find a
easy way to use it. I also used drstrace, but I realized that it apparently
failed to print out the actual data (and it also produced a lot of other noise,
as each patch was paired up with an unpatch, and it quickly got confusing.
Anyways a quick disclaimer: this is kinda one of my first takes on reversing Windows binaries; that being said, I have very little experience as to how to properly do this...
At this point, I was lazy to learn DynamoRIO, so I decided to do a bit of low
assembly monkey patching. I decided to hook both the WriteProcessMemory
function and the tail end of the debug exception handler function
(dbg_handle_exc
here). I then printed out hexdump of the WriteProcessMemory
and location, along with whenever the process continues execution, so that way I
know when the changes invoked by WriteProcessMemory
are commited and the
instruction pointer is advanced once forward.
Here's the [final form] of my patch code (and after the n-th time of trying to mess with IDA's gui, I decided to fully automate the patch part with a script.
I decided to place the bulk of my hook code at address 0x431BF4
, since it
appears that code is some dead code. Anyways, hopefully that did not cause any
issues down the line. After this, I ran the program, cross my fingers (it didn't
work initially), and got this:
1 | Cont 0x80000003 from @ 779A0F74 |
Which is very nice, because now I can go into python and process these patches.
What I ended up doing is run through each patch in python and stepping one
instruction through, printing that instruction to output and then rinse and
repeat. Here's my parse python script. I ended up with this interesting
instruction trace (with a lack of other words to say...):
1
2
3
4
5
6
7
8
900401e6a+2: mov ebp, esp
00401e6c+3: sub esp, 0x48
00401e6f+7: mov dword ptr [esp], 0x43a0cc
00401e76+5: call 0x436e44
00436e44+0: ???
00436e44+6: jmp dword ptr [0x43d1e0]
774e8d2c+0: ???
00401e7b+3: lea eax, [ebp - 0x2a]
...
which was a freaking cool display. It is basically what you would get if you flattened out ALL the calls and jumps into a linear line. I also ended up removing duplicates that would printed out, as there were many nested loops inside this thing.
Anyways, I ended up spending a bit of time trying to reconstruct and repatch the binary in IDA (manually), as I was having a bit too much fun just unravelling this code, teehee, but I ended up with this decompiled code:
1 | void __noreturn myst() |
And my final solve code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50#!/usr/bin/python3
tbl_shuff = b"I am tired of Earth, these people. I'm tired of being caught in the tangle of their lives."
lookup = [i for i in range(32)]
for i in range(len(tbl_shuff)):
j = tbl_shuff[i] & 0x1f
i &= 0x1f
tmp = lookup[j]
lookup[j] = lookup[i]
lookup[i] = tmp
def shuf(inp):
arr = list(inp)
for i in range(len(inp)):
inp[i] = arr[lookup[i]]
def unshuf(inp):
arr = list(inp)
for i in range(len(inp)):
inp[lookup[i]] = arr[i]
def shift(inp):
tmp = inp[0]
for i in range(len(inp) - 1):
inp[i] = (inp[i] >> 4 | inp[i+1] << 4) & 0xff
inp[31] = (inp[31] >> 4 | tmp << 4) & 0xff
def unshift(inp):
tmp = inp[31]
for i in reversed(range(1, len(inp))):
inp[i] = (inp[i-1] >> 4 | inp[i] << 4) & 0xff
inp[0] = (tmp >> 4 | inp[0] << 4) & 0xff
def key_xor(inp):
key = b'October 12th, 1985. Tonight, a comedian died in New York'
for i in range(len(inp)):
inp[i] ^= key[i]
code = [0xE8, 0xF4, 0xDA, 0xF1, 0x15, 0xC6, 0xB8, 0xBD, 0x77, 0x8C, 0xC1, 0xF9,
0x74, 0x46, 0x78, 0xBA, 0xD1, 0x4E, 0xBC, 0x3A, 0xF3, 0x6D, 0xA9, 0x61,
0x44, 0x61, 0x65, 0x13, 0x6D, 0x3D, 0xCE, 0x7B]
for i in range(16):
unshuf(code)
unshift(code)
key_xor(code)
print(bytes(code))
Needless to say, this was quite a fun challenge to unravel, though I think spent MOST of my time trying to find a way to dump the patches :( I need to go and do more Windows reversing and learn more tools...