2009년 8월 8일 토요일

DEFCON17 CTF deuced solution

This is the solution of DEFCON17 CTF deuce(deuced) problem.

You cannot use this article for any commercial purpose without writer's permission. You can use this article for non-commercial purpose only with the source.

First of all, Open the binary with IDA-Pro and follow the flow. deuced calls '_recv' for 4 bytes and the received 4-byte-data is the size of following data. if I send '\x00000001', then the following data should be 1 byte long. Next, deuced calls '_malloc' to allocate buffer with the received size to contain following data and calls '_recv' again to do receive data.

After receiving data, 'dup2' is called 3 times to duplicate the socket descriptor. After 3 'dup2' calls, file descriptor 0,1 and 2 become socket descriptor. Don't care about '_close' call. It closes the socket descriptor, but we can use file descriptor 0,1 and 2 as the socket descriptor to read or write through the opened socket. We'll use the descriptor 0.

[pic 1 : dup2 is called 3 times]

Let's follow sub_8048E80 -> sub_8048E30. Here global variable 0x80519A0 appears for the first time. The received data is copied to 0x80519A0.

[pic 2 : Received data is copied to 0x80519A0]

Go back to [pic 1] and follow the next call, sub_8049000. Parameter is a pointer of global variable. It seems like setting global variables. I'll mention sub_8049000 again. Let's pass the next call, sub_8049200.

The parameter of sub_8049200 is also the global variable passed to sub_8049000. And when enter the sub_8049200, IDA will show us a very strange graph.

[pic 3 : (click to enlarge) OH MY GOD!!! TOO MANY BRANCHES!!!]

Each small box is a code branch. There are too many branches to be analyzed by human being. I think a monster can. We tried to search somewhere vulnarable, but it was a waste of time. We don't need to analyze everything by ourselves. Because we have very strong tool, IDA-Pro. Don't care about anything and just open 'Strings' tab.

[pic 4 : Strings window]

We can see the string, '[M6502 %lX] Unrecognized instruction: $%02X at PC=$%04X\n'. We tried to google that string, and found a source code about something!

source code : download (http://fms.komkon.org/EMUL8/)
assembly list : link

deuced is the binary built with M6502 source code. We can label sub-functions above using the source code.

[pic 5 : Labeled [pic 1]]

In 'Codes.h' of M6502 source code, there are many macros. We can find Rd6502, Wr6502 and Op6502 by following macros. We can label those functions, too. For instance, ROR (Rotate Right) instruction use all of Op6502, Rd6502 and Wr6502.

[pic 6 : A part of many cases. Macros are used very often]

And there is an interesting part in the source code.

#define M_PUSH(Rg)    Wr6502(0x0100|R->S,Rg);R->S--
#define M_POP(Rg)     R->S++;Rg=Op6502(0x0100|R->S)

Above push/pop implementation means that stack section is only 256 bytes size from 0x0100 to 0x01FF and R->S is the stack pointer.

If you looked at Rd6502 and Wr6502 very well, you won't miss that the data section is only 65536 bytes size from 0x0000 to 0xFFFF. Stack section is a part of data section. So the memory structure is like following.

[pic 7 : M6502 Memory area]

Do you remember the address 0x80519A0? It's the address the received data is copied to.

OK. Let's go back to [pic 6]. We can see every branch in 'Codes.h'. but we don't need to look into every branch there. Just go into the function Op6502 (sub_8048BF0). Then we can see some strange branches.

[pic 8 : Inside Op6502]

We can reach to the [jmp eax] instruction with some condition satisfied. First, the parameter of Op6502 must be 0xFFF0. Second, some data relative to 0x80517A0 must be 0x80, And last, the first 1 byte of R must less than or equal to 6. Then, we can jump to somewhere.

(gdb) x/7w 0x804FDF8
0x804fdf8:      0x08048dc1      0x08048c72      0x08048dc1      0x08048c9a
0x804fe08:      0x08048cf3      0x08048d49      0x08048d9e
(gdb)

When eax is 0 or 2, it just jump to the end of Op6502. Then, let's disassemble 0x8048c72.

*** Caution : The disassembled code of gdb is not like one of IDA-Pro. In binary operations, the order of two parameters are different from each other. ***

where eax == 1 : exit
(gdb) x/100i 0x8048c72
0x8048c72:      movzwl 0xfffffff4(%ebp),%eax

0x8048c76:      movzbl 0x80517a0(%eax),%eax
0x8048c7d:      movzbl %al,%eax
0x8048c80:      mov    %eax,0x4(%esp)
0x8048c84:      movl   $0x1,(%esp)
0x8048c8b:      call   0x8048b60
0x8048c90:      mov    %al,0x8051760
0x8048c95:      jmp    0x8048dc1

(gdb) x/100i 0x8048b60
0x8048b60:      pop    %ecx
0x8048b61:      pop    %eax
0x8048b62:      push   %ecx
0x8048b63:      int    $0x80
0x8048b65:      pushl  (%esp)
0x8048b68:      ret

Wow! interrupt 0x80 means calling a systemcall. If we can make the input data to satisfy the condition, we can execute systemcalls! Of course in this case, available systemcall number is 1.

Let's look at every available systemcall.

syscall 3 : read


syscall 4 : write


syscall 5 : open


syscall 6 : close


There are two kinds of data read. One is loading data. And another is loading pointer.
0x8048b70 and 0x8048b90 are them.

And we can see a pattern like this.

0x8048c9a:      movzwl 0xfffffff4(%ebp),%eax
0x8048c9e:      add    $0x3,%eax
0x8048ca1:      movzwl %ax,%eax
0x8048ca4:      mov    %eax,(%esp)
0x8048ca7:      call   0x8048b70
0x8048cac:      movzwl %ax,%ebx

You have not enough information about this pattern. The only thing you should know about this is 'what is at 0xFFFFFFF4(%ebp)?'. Go back and look at the second top box of [pic 8]. There is something about 0x0104. That value is there.

read 2-byte-long data at (data_section + offset)


read 2-byte-long pointer at (data_section + offset)


It took very long time to prepare attacking. Now it's the time to make an attack code.

Let's execute 'open' systemcall. Final conditions to execute 'open' systemcall are following.

1. 2-byte-data at (data_section + stack pointer + 0x0106) will become the third parameter of 'open' systemcall, but it's not necessary. Just fill 0.
2. 2-byte-data at (data_section + stack pointer + 0x0108) should be 0 (O_RDONLY) or something including read permission.
3. 2-byte-pointer at (data_section + stack pointer + 0x010A) should point the string 'key'.

I'll design the stack like this and set R->S (stack pointer) 0.

0x80518a0 : 0x80    0x21    0x14    0x01    0x0c    0x01    0x00    0x00
0x80518a8 : 0x00    0x00    0x0c    0x01    0x6b    0x65    0x79    0x00
0x80518b0 : 0x80    0x80    0x80    0x80    0x80    0x80    0x80    0x80
0x80518b8 : 0x80    0x80    0x80    0x80    0x80    0x80    0x80    0x80
0x80518c0 : 0x80    ...

The violet data describes the third parameter. It has no special meaning. The blue one describes the second parameter, and the red one describes the first parameter. As we see, the green data at (data_section + stack pointer + 0x010C) is 'key\0'.

Rest of data is filled with 0x80 to make (data_section + stack pointer + (gray data value - 1)) points always '0x80'. I cannot control the whole data of gray data because it's a part of return address. We need to the code length should be longer than 0x1400.

Return address? What's that?
We should execute open-read-write with one packet. So we should design subroutines. Remember that M6502 supports subroutine and it's able with JSR(Jump to Subroutine) and RTS(Return from Subroutine). Of course in this case we are trying to execute only 'open' systemcall, so we don't need to consider the return address. But doing this now will help the real attack.

So the code to execute 'open' systemcall is like this.

python code


Executing 'read' systemcall and 'write' systemcall are a little more difficult than executing 'open' systemcall because of the offsets of parameters. If you want to know exactly, you can revisit above 'systemcall 3 : read' and 'systemcall 4 : write'.

Finally, the final exploit codes are below.

steal.py (python code)


deface.py (python code)


In deface.py, there is a wrong value somewhere. Of course it works but the overwritten key file might not be like what we put.

It was very hard time to analyze deuced and to make the attack code. But the last day, we finally breakthrough the deuced!!!

I hope this helps the hackers preparing DEFCON CTF.

Thank you for reading very long article.

*** I think code highlighting is not supported yet. I'll correct as soon as it's supported. ***

written by proXima, a member of PLUS@postech team.

댓글 없음:

댓글 쓰기