声明
本文是B站你想有多PWN学习的笔记,包含一些视频外的扩展知识。
问题源码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char sh[]="/bin/sh";
int func(char *cmd){
system(cmd);
return 0;
}
int main(){
char a[8] = {};
char b[8] = {};
puts("input:");
gets(a); // gets函数可以读取超过8个字节的数据,然后写入a,造成越界写b
printf(a);
if(b[0]=='a'){ // b数组出现'a',即进入获取shell的分支
func(sh);
}
return 0;
}
编译调试
编译32位的程序
说明:默认64位系统编译64位的程序,如果要编译32位需要添加-m参数,同时需要安装32位的编译支持,参考【pwn入门】基础知识
gcc -m32 question_1.c -o question_1_x86
直接hack
用gdb调试理解这个过程
- 反汇编main函数
disassemble main
可以看到32位的地址长度只有64位的一半
注意这里的0x00001362是反汇编出来的地址偏移,并不是内存中的实际地址。 - 获取可以打断点的内存地址
要获取内存实际的地址,可以用x命令
x/40i $eip
- 打断点
b *0x56556379
- 输入8个b,一个a
5. 查看内存中的值
6. eax赋值前查看寄存器的值
没有执行赋值之前,查看eax寄存器的值
7. 查看执行赋值后eax的值
8. 查看程序的执行流程9. 验证
多次执行ni命令
32位跟64位的区别
1.地址长度不一样
2.寄存器的名字不一样
32位中以e开头,64位中以r开头。
32位寄存器
eax:通常用来存放函数调用的返回值
esp:栈顶指针,指向栈的顶部
ebp:栈底指针,指向栈的底部,通常用ebp+偏移量的形式来定位函数存放在栈中的局部变量
eip:指针寄存器(instruction pointer) 这个寄存器保持着下一条要执行的指令的地址
64位寄存器
rax:通常用于存储函数调用返回值
rsp:栈顶指针,指向栈的顶部
rbp:存放当前栈帧的栈底地址
rip:指针寄存器(instruction pointer) 这个寄存器保持着下一条要执行的指令的地址
3.同一个源码,编译出的汇编指令不一样。