Rating:

# Reversing - runme

This sweet little program just wants us to run it, and in exchange for this
unweighty trifle it will give us the flag! How nice of it!

But maybe let's first have a short look in IDA? It starts with the quite
predictable `open`-`malloc`-`read`-`close` dance, but then it forks,
increments a 16-bit counter:

```
.text:080489E3 memset_0_done:
.text:080489E3 call _fork
.text:080489E8 mov [esp+0F0h+pid], eax
.text:080489EC movzx eax, [esp+0F0h+incme_after_fork]
.text:080489F1 add eax, 1
.text:080489F4 mov [esp+0F0h+incme_after_fork], ax
.text:080489F9 cmp [esp+0F0h+pid], 0
.text:080489FE jz in_child
```

hashes a 16-byte buffer we've just `memset` to all zeroes:

```
.text:08048A04 in_parent:
.text:08048A04 mov [esp+0F0h+arg1], 10h
.text:08048A0C lea eax, [esp+0F0h+buf]
.text:08048A10 mov [esp+0F0h+arg0], eax
.text:08048A13 call hash_md5_val
.text:08048A18 cmp [esp+0F0h+incme_after_fork], 0
.text:08048A1E jns incme_after_fork_ge_0 ; 2**15 times
```

goes through a bunch of `nop`s:

```
.text:08048B2A in_child: ; CODE XREF: main+101↑j
.text:08048B2A nop
```

falls through to waiting for its child:

```
.text:08048B3F incme_after_fork_ge_0: ; CODE XREF: main+121↑j
.text:08048B3F call _wait
.text:08048B44 jmp memset_0_done
```

and repeats the whole sequence until 16-bit counter's sign bit does not become
1, that is, 2 ** 15 times. This means that that many processes will be spawned
at a time. That's not a proper fork bomb yet, but it's not hard to imagine that
a reasonable person would still find this rude and not acceptable.

So, let's defuse the evil contraption?

```
# skip fork
b *0x080489E3
commands
set $eip = 0x080489E8
set $eax = 1
c
end

# skip wait
b *0x08048B3F
commands
set $eip = 0x08048B44
c
end
```

The code runs quite quickly and orderly under this gdbscript. Instead of
creating new processes, it does all the hashing inside of the main one, and
then, as intended, uses the result as an AES key to decrypt the flag.
Unfortunately, this produces total garbage. Where is the catch?

Let's try to recap how the program is supposed to work, given infinite time
and resources:

```
main:
memset(buf, 0, sizeof(buf))
for _ in range(2 ** 15):
hash(buf)
child0:
wait() # should return -1, errno=ECHILD
for _ in range(2 ** 15 - 1):
hash(buf)
child1:
...
exit(0)
wait(child1) # should return 0 and set *wstatus = 0
decrypt()
exit(0)
wait(child0) # should return 0 and set *wstatus = 0
decrypt()
exit(0)
```

So, the whole action is sequential, despite the number of involved processes.
Children will create each other recursively, each ultimately hashing the buffer
2 ** 15 times and saving the decrypted flag to the disk, overwriting each
other's work. Finally, the parent would save its own decrypted version,
overriding what the last child did. This should be the correct flag.

The bothersome thing is:

```
.text:08048B3F call _wait
.text:08048B44 jmp memset_0_done
```

Where is the argument? Where will `wstatus` be saved? This is a 32-bit program,
so all the arguments are passed on stack. The last time something was put on
top of the stack was:

```
.text:08048A0C lea eax, [esp+0F0h+buf]
.text:08048A10 mov [esp+0F0h+arg0], eax
.text:08048A13 call hash_md5_val
```

This is the buffer that is being hashed! So, sneakily, in addition to hashing,
the code overwrites the first 32 bits of the buffer with wstatus, which is
supposed to always be zero. It could be possible to enhance gdbscript to
simulate that, but now it's also possible to simply implement the whole logic in
Python. Doing this correctly decrypts the flag:
`SECCON{infinite_fork_is_not_good_for_system}`. It sure isn't.

Original writeup (https://github.com/mephi42/ctf/tree/master/2019.10.19-SECCON_CTF_2019/reversing-runme).