常用函数系统调用号表
64位中execve为59
切换python版本
1 | sudo update-alternatives --config python |
工具安装
关于vmtools的安装
安装vmtools 进入vm disk 打开终端 cp ······.tzr.gz /opt 然后 cd /opt | tar -zxvf ····tar.gz ls cd ····disturb ./···.pl
在当前目录打开终端:sudo apt-get install nautilus-open-terminal
libcsearcher:
1 | git clone https://github.com/lieanu/LibcSearcher.git |
libc在线网站:https://libc.blukat.me/
sftp:
使用sftp可以很方便的在window与虚拟机或者虚拟机与qemu虚拟机之间传输文件,
1 | #首先使用ifconfig 查看主机的inet后的ip,并用whoami查看usename |
pwndbg安装
1 | sudo apt-get install python3.5 |
peda安装
1 | git clone https://github.com/longld/peda.git ~/peda |
ubuntu换源
1 | sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak |
mips环境搭建
qemu安装
1 | sudo apt-get install qemu |
qemu通网(系统模式)
1 | sudo apt-get install uml-utilities bridge-utils |
安装镜像与内核(根据文件大小端):https://people.debian.org/~aurel32/qemu/mipsel/
启动镜像
1 | sudo qemu-system-mipsel -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mipsel_standard.qcow2 -append "root=/dev/sda1 console=tty0" -net nic -net tap,ifname=tap0,script=no,downscript=no -nographic |
修改绑定连接
1 | chroot . ./pwn2 #.表示当前目录,需要新建lib并把相关libc文件放到lib中 |
qemu用户模式+gdb-multiarch
1 | cp $(which qemu-mipsel-static) /home/archer/pwn2/ |
使用脚本调试mips
配置环境
1 | sudo apt update |
检查路径
1 | from pwn import * |
调试
1 | from pwn import *#方法1 |
libc
got表的基地址+libc.sym中对应函数的地址==got中的地址
elf=ELF(“./level3”)
libc=ELF(“./libc_32.so.6”)这两句是引入plt和got表以及symbols
write_plt=elf.plt[‘write’]
write_got=elf.got[‘write’]
/bin/sh的地址可以在libc_32.so.6中找,通过strings -a -t x libc_32.so.6 | grep “/bin/sh”或者libc.seach[‘/bin/sh’].next()
main函数plt和got表调用不了,只能通过elf.sym[‘main’]调用
这些为引入相应的函数
write=libc_base-libc.sym[‘write’]
关于plt表与got表的理解:
1.plt与got表的原理
通过调用plt表,可以跳转到got表,从而得到函数的真实地址,通过这种调用函数的方式可以提高效率,不用一次把所有函数都加载到程序中去
2.elf.got得到的只是got表的表地址,通过write函数等泄露出来的got表地址才是真实地址
libc=addr_got-obj.dump[‘对应函数’]
真实地址=libc+obj.dump[‘对应函数’]或者elf.sym[‘对应函数’]
gcc编译命令
1 | gcc -fno-stack-protector -m32(默认64位) -z relro -no-pie rof.c -o 生成的可执行文件名 |
相关保护的开启与关闭
1 | NX:-z execstack / -z noexecstack (关闭 / 开启) 不让执行栈上的数据,于是JMP ESP就不能用了 |
checksec
关于checksec的各个保护机制
1.RELRO主要针对got改写的保护方式,full RELRO表示got表不可被改写,partial RELEO无plt指向的got表是只读的
2.stack-canary栈溢出保护(通过向栈中插入信息然后查看该信息是否被改写)
3.NX开启后栈中的数据没有执行权限,但是可以通过rop绕过
4.ASLR使运行时栈堆中的数据的加载地址以及共享库的加载地址随机化分为0,1,2三个等级,PIE则在编译时将程序编译为位置无关,即程序运行时各个段(如代码段等)加载的虚拟地址也是在装载时才确定。
5.FORTIFY
加了这个保护之后,一些敏感函数如read, fgets,memcpy, printf等等可能
导致漏洞出现的函数都会被替换成__read_chk,__ fgets_chk, __ memcpy_chk, __ printf_chk等。
这些带了chk的函数会检查读取/复制的字节长度是否超过缓冲区长度,
通过检查诸如%n之类的字符串位置是否位于可能被用户修改的可写地址,
避免了格式化字符串跳过某些参数(如直接%7$x)等方式来避免漏洞出现。
开启了FORTIFY保护的程序会被checksec检出,此外,在反汇编时直接查看got表也会发现chk函数的存在
x86中,函数调用是直接将参数压栈,需要用的时候直接将参数放在栈上,调用的函数就能直接取得参数并运算,而x64中,参数多于6个才会用栈,依次是rdi,rsi,rdx,rcx,r8,r9
RELEO
为NO RELRO的时候,init.array、fini.array、got.plt均可读可写
为PARTIAL RELRO的时候,ini.array、fini.array可读不可写,got.plt可读可写
为FULL RELRO时,init.array、fini.array、got.plt均可读不可写。
ini.array与fini.array
当程序加载是会调用ini.array,结束时,调用fini.array,当fini.array可写时,可以通过改写成system、main等函数,
来劫持程序
ida
db表示一个字节,dw表示2个字节,dd表示4个字节
unk表示未处理字节,algn表示对齐指令,stru表示结构体或者结构体数组
tbyte表示浮点数,80位(或扩展精度浮点数)
dbl浮点数,64位(或双精度数组)
flt表示浮点数据,32位(或浮点数组)
qword数据,64位数据(或4字节数组)
dword数据,32位数据(或双字数组)
word数据,16位数据(或字数组)
byte数,字节(或字节数组)
asc数据ASCII字符串
seg数据,包含段地址值
off数据,包含偏移量
loc指令
locret返回指令
sub指令和子函数起点
load:
.init
.plt
.text
.fini
.jcr
.bss:通常指用来存放程序中未初始化的全局变量的一块区域
.got
.rodata:只读数据段
.text:分析代码段
.data:数据段
gdb调试:
r 可读 w 可写 x 可执行
si汇编层次的下一步,s源码层面的一步
b * (0x123456)给0x123456地址处的指令下断点 b+函数名字
ldd pwnme,查看libc文件的加载位置是否会变
echo 0 > /proc/sys/kernel/randomize_va_space,关闭整个系统的地址随机化保护。
layout asm打开汇编调试窗口
程序中存在fork的调试方式
在fork之前
1 | set follow-fork-mode child 命令设置gdb在fork之后跟踪子进程。 |
关于x的命令
1 | /nuf 0x123456 //常用, |
find+”字符串”
stack查看栈
canary直接看canary的值
backtrace
show args
vmmap 文件名查看程序映射的情况
打印指令p(print):
1 |
|
关于peda的小技巧
通过pattern create + 长度 可以生成字符串
pattern offest + 栈顶的字符串可以显示偏移
不过最后4个或8个为ip
流程gdb ./pwn -> pattern create 200 -> start -> c -> 输入字符 ->pattern offest aaaa
在调试时关于gdb有个小技巧,如果在有源码的情况下,在编译生成可执行文件时,可以加入-ggdb选项
,这样在调试时,gdb附有源码方便调试
Dynelf模板:
p = remote(ip, port)
def leak(addr):
payload2leak_addr = “” + pack(addr) + “”
p.send(payload2leak_addr)
data = p.recv(4)
return data
d = DynELF(leak, pointer = pointer_into_ELF_file, elf = ELFObject)
system_addr = d.lookup(‘system’, ‘libc’)
read_add = d.lookup(‘read’,’libc’)
内存映射
在 ELF 内存映射时,bss 段会被映射两次,例如bss:600D21 aCtfHereSTheFla db ‘CTF{Here’,27h,’s the flag on server}’,0 可以看到0x600D21即为flag的地址,但是这里的flag后面会被程序覆盖 此时只能另一个地址来得到flag
gdb命令:vmmap smashes查看程序映射的情况
函数调用本质
关于函数指针的介绍http://c.biancheng.net/view/228.html
函数的调用:https://www.cnblogs.com/clover-toeic/p/3755401.html 这篇blog讲的非常好
大小端
小端与大端:
小端:
0x00 78
0x01 56
0x02 34
0x03 12
大端与小端相反
shellcode
shellcode编写:https://blog.csdn.net/qq_35495684/article/details/79583232
32位
“\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x00\x68\x2f\x62\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80”(25)
“\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80”(23)
64位
“\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05”
shellcode对应字节码,有时需要使用别的有效的字节码绕过:见buupwn中102.starctf_2019_babyshell
github
当使用github无法下载文件时,见https://www.cnblogs.com/july-sunny/p/13697156.html
github镜像网站:https://hub.fastgit.org
使用pwntools中的shellcraft生成shellcraft:
asm(shellcraft.sh())
bss/bin_ad
获得bss_addr
elf=ELF(‘./level3’) bss_addr=elf.bss()(无libc文件)
strings -a -t x libc_32.so.6 | grep “/bin/sh”
查看libc支持的版本
strings libc.so.6 | grep GLIBC
context
context设置context(os=’linux’, arch=’amd64’, log_level=’debug’)
read函数
原型
ssize_t read(int fd, void * buf, size_t count);
fd 设为0时就可以从输入端读取内容 设为0
buf 设为我们想要执行的内存地址 设为我们已找到的内存地址0x80EB000
size 适当大小就可以 只要够读入shellcode就可以,设置大点无所谓
mprotect
mprotect函数可以修改内存权限为可读可写可执行
原型
int mprotect(void * addr, size_t len, int prot);
addr 内存启始地址
len 修改内存的长度
prot 内存的权限为7可执行
关闭输出输出重定向
1 | exec 1>&0 |
工具
ROPgadget使用
命令ROPgadget –binary 文件名 –only ‘汇编指令’ | grep ‘一定包含的内容’
寻找syscall;ret: ROPgadget –binary ./libc.so.6 –only “syscall|ret” | grep syscall
ROPgadget 查找字符串: ROPgadget –binary pwn –string ‘sh’
生成ROPchain
需要的库:from struct import pack
ROPgadget –binary pwn –ropchain
使用–opcode查找字节码(当使用–only找不到时):
ROPgadget –binary ./libc.so.6 –opcode 0f05c3
ROPgadget –binary 文件名 –only “pop|ret” | grep rdi
onegadget使用
one_gadget libc-2.23.so
ctf_all_in_one使用
先进入all_in_one的目录,然后cat list 可以查看可下载的libc
./download + cat list中的libc名 即可下载对应libc
LibcSearcher的使用:
from LibcSearcher import *
libc = LibcSearcher(“write”,write_addr)
libcbase = write_addr - libc.dump(‘write’)
system = libcbase + libc.dump(‘system’)
binsh = libcbase + libc.dump(‘str_bin_sh’)
pwntools-signalframe使用
1 | frame=SigreturnFrame() |
seccomp-tools使用
seccomp-tools可以查看程序禁用了那些系统调用,命令如下
1 | seccomp-tools dump + ./文件名 |
官方文档:https://github.com/david942j/seccomp-tools
工具:算小数的16进制等:
http://www.binaryconvert.com/result_double.html?decimal=048046049
alpha3的使用:
作用:可打印shellcode
安装:git clone https://github.com/TaQini/alpha3.git
python ./ALPHA3.py x64 ascii mixedcase rax –input=”shellcode”
x64对应系统,rax对应取决于跳到shellcode的方式,比如rax
x86中:
x86 ascii uppercase (数字+大写字母)
x86 ascii lowercase (数字+小写字母)
x86 ascii mixedcase (数字+大小写字母)
AE64的使用:
作用:64位的alphga shellcode,更加灵活的使用寄存器
安装:git clone https://github.com/veritas501/ae64.git
例子
1 | from pwn import * |
基础知识
无符号数和有符号数比较会转化为无符号数的比较,而-1能表示成很大的无符号数,有时能够通过
-1构造溢出
signed int 的范围:[-2147483648,2147483647]
signal相关知识
signal函数原型void(* signal(int sig,void(* func)(int)))(int);
sig为对应的信号,func函数则表示,当遇到sig的信号时,执行func函数
在64位中,当参数少于6个时,优先使用rdi,rsi,rdx,rcx,r8,r9几个寄存器保存参数。
不同的数据类型字节数
int:4B
char:1B
word:2B
Qword:8B
bup:1B
dp:1B
db:1B
dw:2B
dd:4B
dq:8B
dt:10B
argc:命令的条数
argv[]:输入的每条命令
argv[0]为程序的名称
学到一个小知识点,原来system中的参数为sh也能拿到权限,不一定非要/bin/sh
学到一个小技巧,在程序开启pie时,不太方便查看内存,可以通过输入字符串,然后通过在gdb中通过find命令,查找该字符串
32位系统调用与64位系统调用执行system
32位:
传参方式:首先将系统调用号 传入 eax,然后将参数 从左到右 依次存入 ebx,ecx,edx寄存器中,返回值存在eax寄存器
调用号:sys_read 的调用号 为 3 sys_write 的调用号 为 4 system 11
调用方式: 使用 int 80h 中断进行系统调用
64位:
传参方式:首先将系统调用号 传入 rax,然后将参数 从左到右 依次存入 rdi,rsi,rdx寄存器中,返回值存在rax寄存器
调用号:sys_read 的调用号 为 0 sys_write 的调用号 为 1
stub_execve 的调用号 为 59 stub_rt_sigreturn 的调用号 为 15
调用方式: 使用 syscall 进行系统调用
系统调用exceive(“/bin/sh\x00”,0,0)
orw和execve系统调用
64位中,sys_read 的调用号为0,sys_write 的调用号为1,sys_open的调用号为2,
在使用orw来获取flag时,需要注意read,write的参数,
原型是read/write(fd,ad,size);
fd为0时,表示stdin,1表示stout,2表示stderr
当使用fopen时,会创建一个文件描述符,当open调用完成之后rax会被设置为open返回的文件描述符,此时
read/write的文件描述符设置为open就能对文件进行操作
例子
1 |
|
system系统调用:
当程序中缺少write函数等可以泄露libc的函数时,这时大概率需要使用系统调用,关于system系统调用的知识如下:
system系统调用号:11,存放在eax中
参数/bin/sh的地址,存放在ebx中
ecx:0
edx:0
int80(11,”/bin/sh”,null,null)
dup2 用来复制文件描述符:int dup2 (int oldfd,int newfd)
_ fileno 是用来规定 fd 所指向的文件流的,dup2(fd,666)将flag的fd改成了666,所以我们只要想办法把 stdin 的 _ fileno 改成 666 即可,这样在之后 scanf 就会将 fd == 666 (flag)的内容输入到 & v0 中,然后我们就可以输出 flag 的内容了
不同整数类型字节数
alarm
去除程序的alarm函数,以及alarm在某些题目中的作用
https://blog.csdn.net/A951860555/article/details/111214951
拟态
关于拟态,在pwn中拟态表示的是通过fork,同时开启32位以及64位的程序,监测输入,而要拿到权限
需要保证在32位和64位中都能拿到权限,并且输出也一样,例题buu 强网杯2019 拟态 STKOF
使用指定版本的libc运行/修改libc
pwntools
1 | from pwn import * |
终端
1 | # 指定libc |
patch_elf
1 | sudo patchelf --set-interpreter /home/hacker/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/ld-linux-x86-64.so.2 ./pwn |
汇编转字节码
先在.s文件中编写汇编指令,然后使用命令
1 | gcc -m32 -c test.s |
或者
linux关闭pie
1 | sudo -s echo 0 > /proc/sys/kernel/randomize_va_space |
ret2dl_module
1 | from pwn import * |
虚拟机恢复网络
1 | sudo service network-manager stop |
python3+pwntools
安装
1 | sudo apt-get update |
使用p64等报错解决如下
1 | payload += p64(rdi_addr)#.decode('iso-8859-15') |
python2+pwntools
安装
1 | apt-get install python2 |
使用docker调试pwn
在电脑连接上服务器后,如果使用python脚本调试pwn,会发现一个错误
报错:[ERROR] Could not find a terminal binary to use. Set context.terminal to your terminal
解决方式是使用tmux
安装tmux
1 | apt-get install tmux |
调试
1 | tmux |
需要在exp.py中加入如下语句
1 | context.terminal = ['tmux','splitw','-h'] |
进入tmux中翻页
1 | crtl + b + [ |
pwn相关环境集成+docker
环境为ubuntu18.04
1 | apt update |
pwntools设置程序运行的参数等
1 | p = process(argv=['./sandbox',"./sandboxheap"]) |