静态链接

ret2text & ret2shellcode

当程序中存在打印flag的地址或是可以得到shell的地址,我们就可以想办法覆盖返回地址到其地址处。也就是ret2text.

若程序中有明显的溢出且无保护措施,我们可以自己写shellcode并覆盖返回地址至shellcode处改变程序流程。不过要注意写入shellcode的区域要有可执行权限,否则写了也白写。关于shellcode的布置可以查看
shellcode的布置

查看程序段的可读写执行情况:

  1. gdb:vmmap

  2. ./file & (后台运行,返回pid0)
    cat /proc/pid0/maps

ret2syscall

栈不可执行开启的情况下,我们可以在栈上写入gadgets,使通过系统中断执行系统调用,从而达到获取shell的目的;

查找gadget的方法:

ROPgadget --binary ./file

ROPgadget --binary file  --only 'pop|ret' | grep eax     
ROPgadget --binary file  --opcode cd80c3(int 0x80 ; ret )
ROPgadget --binary file  --string '/bin/sh'

32位linux下的系统调用.
64位linux下的系统调用.

动态链接

ret2libc

程序不是静态编译,通常就不会有int 0x80; ret2syscall 就无法实现,因而使用ret2libc。

利用过程:

ASLR使得每次载入的函数地址(base)都不同;实际地址 Address = base +offset

若地址为 0xf… 则其一般为实际地址;

而各函数offset在libc库中是固定的;

  • 查看libc库版本:
1
ldd  ./file
  • 寻找偏移地址 :
1
readelf -a  /lib32/libc.so.6 | grep gets@
  • 目标是寻找libc 的 base :

1.通过程序中的其他函数leak 程序libc的实际地址(Address),base = Address - offset

2.从stack残渣中获取libc地址信息

延迟绑定

简而言之就是程序在未调用过该函数时,其got地址处的值不是其真实地址;当程序调用一次该函数后,真实地址才会被初始化,此时got地址处的值才是真实地址。

给文件加载目标libc的方法:

  1. 加载环境变量:(64为UBUNTU调试32位程序会无法加载)
1
2
3
4
export LD_LIBRARY_PATH=`pwd`       #当前目录为加载目录
export LD_PRELOAD= libc #加载本地pwn题目下的libc

unset LD_PRELOAD #调试完删除环境变量
  1. exp调试时使用
1
sh = process([‘./bin‘],env={"LD_PRELOAD":"./libc-2.23.so"})

需要注意的函数

read 读取截至\x00,\n不会自动忽略
gets

write
puts 截止符为\x00,没有截断可以泄露,最后会自动加上\n

源码级别的调试

glibc 源码下载:http://mirrors.ustc.edu.cn/gnu/libc/

下载源码, tar解压

进入源码文件,mkdir build ; cd build

1
2
3
4
5
CFLAGS="-g -g3 -ggdb -gdwarf-4 -Og" CXXFLAGS="-g -g3 -ggdb -gdwarf-4 -Og" ../configure --prefix=/home/sirius/tools/source/glibc-2.23/64

make -j8

sudo make install

在程序编译时可以直接更改ld为编译完的libc,这样在调试时可以直接调用编译出来的libc,也就可以直接调试源码。

调试源码除了使用gdb以外,更方便的方法就是使用linux 下的vscode,可以像windows下一样直接对源码进行调试。

当然也可以使用gdbtui,但是不是特别的好用,加了pwndbg插件后显示会出现乱码。