+ +

最简单的一道PWN

emmmm,每天都能知道很多新东西呀,今天主要讲了栈帧平衡(stack frame)、缓冲区溢出(buffer overflow),还讲了一道据说非常简单的PWN:通过一个程序,利用上次和今天讲的,控制程序所在运行的电脑。

栈帧平衡

栈帧平衡: 调用函数前后栈的结构不变

要保证调用函数前后栈的结构不变,在开始时我们就应该记住栈的底部位置和顶部位置,方法:

EBP指向栈底,ESP指向栈顶

push EBP(将栈底地址保存在栈中,储存栈顶位置加上位置,此时栈顶为EBP的值)

MOVE EBP ESP(将栈顶赋给栈底,此时栈顶指针和栈底指针都指向栈顶,后来进栈的,栈顶指针移动)

上述操作进行结束,我们就可以进行我们所想进行的操作,进行操作过后,我们把EBP赋给ESP,此时两个指针指向原来栈的栈顶,POP函数的用法是POP出栈顶的值,POP EBP的意思是pop出栈顶的值赋给EBP。

总的来说,流程如下:






函数调用与返回

函数调用:首先存我们返回地址(函数调用结束后返回的地址),然后jump到函数的位置

返回:pop出我们存的返回地址,然后jump

缓冲区溢出

一个数组是用栈保存的,我们调用函数会把返回地址储存在栈中,我们输入值的时候,地址会从小到大,会先占用地址较小的地址,这时候我们如果不规定输入字符的数量,就有可能会覆盖掉我们存储的返回位置(retaddr:return address),函数结束后会返回到一个莫名其妙的位置。



gdb命令


GDB(GNU Debugger)是在lunix以及lunix系统下的调试工具。功能极其强大,几乎涵盖了你所需要的全部功能。
GDB主要帮忙你完成下面四个方面的功能:
1.启动你的程序,可以按照你的定制要求随心所欲的运行程序。
2.可让被调试的程序在你所指定的调置的断点处停住。
3.当程序被停住时,可以检查此时你的程序中所发生的事,以及内存状态等。
4.动态的改变你程序的执行环境。


gdb使用总旨:help指令很强大!多用help!help里面总会有你需要的信息。如果你不知道如何使用help,请在gdb里面输入:help all


首先写一下今天要用到的一些命令:

objdump -d 文件名:遍历程序,可以看到所有的汇编代码,保存之后可以寻找我们所想找的东西

gdb 文件名:进入文件

start:开始运行程序

ni:逐步运行

si:进入函数

pattc n:随机生成有规律的n个字符

patto 要查找的东西:查找


然后是其他的一些命令:

f:显示格式
hex(hex) 按十六进制格式显示变量。
d(decimal) 按十进制格式显示变量。
u(unsigned decimal) 按十进制格式显示无符号整型。
o(octal) 按八进制格式显示变量。
t(binary) 按二进制格式显示变量。
a(address) 按十六进制格式显示变量。
c(char) 按字符格式显示变量。
f(float) 按浮点数格式显示变量

  • gcc -g main.c //在目标文件加入源代码的信息
  • gdb a.out
  • (gdb) start //开始调试
  • (gdb) n //一条一条执行
  • (gdb) step/s //执行下一条,如果函数进入函数
  • (gdb) backtrace/bt //查看函数调用栈帧
  • (gdb) info/i locals //查看当前栈帧局部变量
  • (gdb) frame/f //选择栈帧,再查看局部变量
  • (gdb) print/p //打印变量的值
  • (gdb) finish //运行到当前函数返回
  • (gdb) set var sum=0 //修改变量值
  • (gdb) list/l 行号或函数名 //列出源码
  • (gdb) display/undisplay sum //每次停下显示变量的值/取消跟踪
  • (gdb) break/b 行号或函数名 //设置断点
  • (gdb) continue/c //连续运行
  • (gdb) info/i breakpoints //查看已经设置的断点
  • (gdb) delete breakpoints 2 //删除某个断点
  • (gdb) disable/enable breakpoints 3 //禁用/启用某个断点
  • (gdb) break 9 if sum != 0 //满足条件才激活断点
  • (gdb) run/r //重新从程序开头连续执行
  • (gdb) watch input[4] //设置观察点
  • (gdb) info/i watchpoints //查看设置的观察点
  • (gdb) x/7b input //打印存储器内容,b–每个字节一组,7–7组
  • (gdb) disassemble //反汇编当前函数或指定函数
  • (gdb) si // 一条指令一条指令调试 而 s 是一行一行代码
  • (gdb) info registers // 显示所有寄存器的当前值
  • (gdb) x/20 $esp //查看内存中开始的20个数

python pwntools 基本操作

PwnTools的官网如下:http://pwntools.com/

https://www.cnblogs.com/Ox9A82/p/5728149.html

  • from pwn import *表示导入pwn库
  • context(arch = ‘i386’, os = ‘linux’)用来导入pwntools库
  • r = remote(‘exploitme.example.com’, 31337)设置目标机信息,用来建立一个远程连接,url或者ip作为地址,然后指明端口
  • r = process(“./test”)使用本地文件

例题

一:
下面是源代码:


从源代码可以看出如果想进入cmd函数执行shell,由于主函数只调用了A这个函数,所以我们可以把A的返回地址给覆盖为cmd函数的地址,就可以进入cmd调用它了。

首先输入objdump -d p1浏览它的汇编代码,



我们可以看到cmd的地址为0804846b,但是一个指针四个字节,他是以小端序的方式存的,所以他在内存中存储形式为:6b 84 04 08

然后进入文件p1



start



在下图中我们可以看到此时函数运行在A



si进入函数



ni单步运行,直至找到scanf



pattc 200生成200个随机字符,输入函数之前必须输入个c(contiue)



将生成的随机字符复制粘贴,回车,可以看到有以下错误提示



利用patto找到offset,为112



然后利用pythontools,连接,输入以下东西,就是将字符串传递给我们的程序





第二种方法:return 滑行,之前一直不懂,emmm,现在懂了,就是任意一个return地址复制个很多次填充rutaddr之前的东西,要保证我们输入的字符串要覆盖掉return address,所以要输入的足够多,然后在最后加上cmd的地址。过程:到A函数返回的地方,返回了一个返回地址,相当于又执行了一遍return函数,然后一直return,直至返回cmd,执行shell.

前面找地址的步骤是一样的:

找到一个return地址:



然后在pythontools中改一下字符串:


OK啦


二、源码如下:


进入文件



start



根据源码能看出magic的地址与rand函数有关,可以看到调用rand函数之后将它产生的值赋给了magic,所以0xffffcf08-0x1c即为magic的地址



buff数组用gets函数赋值,调用函数需传给它一个参数,即数组的首地址,这时候我们把两个变量的地址都找到了,可以看到两个地址之间差了100,magic整型变量,四个字节,只要保证buf的前四个与100到104的数相等即可


利用pythontools交互得到shell