tool
file xxx
checksec –file=xxx
objdump -h xxx
readelf -W -l xxx
ROPgadget –binary xxx –string ‘/bin/sh’
gdb切换:
gdb.sh
gef用于32位 (使用root)
rip
file命令可以查看elf文件的一些信息
gets危险函数
system是可以执行shell命令的函数
利用gets函数获取一个长字符串覆盖rip来控制程序流到fun()函数
函数的局部变量会存放在他的栈中,那么在main函数中,我们双击s变量,查看s分配了多少空间
在main的栈帧中给s分配了15个字节
64位的elf文件,rbp是8个字节
寄存器
rbp:栈基址寄存器,存放当前栈帧的栈底地址
rsp:栈顶寄存器,存放当前栈帧的栈顶地址
ebp:扩展基址指针寄存器,存储当前函数状态的及地址,在函数运行时不变,可以用来索引\确定函数参数或局部变量的位置
esp:栈指针寄存器,esp用来存储函数调用栈的栈顶地址,在压栈和退栈时发生变化.
eip:指令指针寄存器,eip用来存储即将执行的程序指令地址,cpu以找eip的存储内容读取指令并执行,eip随之指向相邻的下一条指令,如此反复,程序就得以连续执行指令
64位cpu对应RSP(栈顶寄存器),RBP(栈基寄存器),RIP(程序计数寄存器)三个寄存器。
32位cpu则对应ESP(栈指针寄存器),EBP(扩展基址指针寄存器),EIP(指令指针寄存器)三个寄存器。
R开头:64bit, 8字节(1字节=8bit)
E开头:32bit, 4字节
EAX(累加寄存器)是32位, AX 是EAX的低16位 , AL 和AH是AX的低8位和高8位。
AX,BX,CX:16bit, 2字节
AH, AL: 8bit, 1字节
大端序和小端序
小端序
•低地址存放数据低位、高地址存放数据高位
•我们所主要关注的格式
大端序
•低地址存放数据高位、高地址存放数据低位
所以payload里面,我们需要先填充s,再填充rbp,再填充return address
所以payload可以为’s’*15+’b’*8+p64(0x401186+1).decode(“iso-8859-1”)
1 | from pwn import * |
如果ida地址是红色,按地址,快捷键P
warmup_csaw_2016
做前两步:1.file 2.checksec
1 | file xxx |
stack: no canary found //栈溢出保护未开启
PIE: No PIE //地址随机化也没开
gets
栈溢出漏洞
给v5分配了64个字节
1 | from pwn import * |
ciscn_2019_n_1 1
这个题目的NX是开的
NX?
NX即No-eXecute(不可执行)的意思,NX(DEP)的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。
可以在v1输入一个长串,覆盖v2
找到v1和v2的位置
覆盖0x30-0x4=44个字节给v1,4个字节给v2
由于v2数值在内存中以16进制存储,所以,找到11.28125的16进制值
所以0x41348000就是那个小数啦
p64
p64
函数的作用是将一个64位的整数转换为一个8字节的字节串。这个字节串是按照小端字节序排列的,也就是说,低位字节在前,高位字节在后。
如果你直接将整数0x41348000
转换为字符串,然后添加到payload中,那么这个整数将会被转换为一个十进制的字符串,而不是一个字节串。这个字符串的长度并不固定,而且它的内容也不是你想要的字节序列。
另外,如果你直接将整数0x41348000
添加到payload中,Python会抛出一个TypeError,因为不能直接将整数和字符串相加。
所以,如果你想要将一个64位的整数作为一个8字节的字节串添加到payload中,你应该使用p64
函数。
1 | from pwn import * |
ISO-8859-1
.decode("iso-8859-1")
的作用是将字节串转换为字符串。这是因为在Python 3中,字节串和字符串是两种不同的类型,它们不能直接相加。如果你尝试将一个字节串添加到一个字符串,Python会抛出一个TypeError。
ISO-8859-1,也被称为Latin-1,是一种8位的字符编码,它可以表示西欧语言中的字符。在这种编码中,每个字符都被编码为一个8位的字节,所以它可以表示最多256个不同的字符。最重要的是,ISO-8859-1编码是与ASCII编码兼容的,也就是说,ASCII编码中的字符在ISO-8859-1编码中有相同的编码。
当你使用.decode("iso-8859-1")
时,每个字节都会被转换为ISO-8859-1编码中对应的字符。这意味着,你可以将任何字节串解码为一个ISO-8859-1编码的字符串,而不会出现解码错误。这对于处理二进制数据非常有用,因为二进制数据可能包含任何字节。
如果你不使用.decode("iso-8859-1")
,那么你需要找到另一种方法来将字节串转换为字符串。例如,你可以使用.decode("utf-8")
,但这只适用于UTF-8编码的字节串。如果字节串包含非UTF-8编码的字节,这将会抛出一个UnicodeDecodeError。
ret2text
checksec
Arch
从这行信息可以知道程序是32bit还是64bit的RELRO
Full Relro(重定位表只读)Relocation Read Only, 重定位表只读。重定位表即.got 和 .plt 两个表。
Stack
显示Stack:No canary found则表示可以利用栈溢出NX
NX enable(不可执行内存)最常见的方法为 ROP (Return-Oriented Programming 返回导向编程),利用栈溢出在栈上布置地址,每个内存地址对应一个 gadget,利用 ret 等指令进行衔接来执行某项功能,最终达到 pwn 掉程序的目的。
PIE(ASLR)
地址空间分布随机化
objdump读取plt和got表
PLT与GOT表均为动态链接过程中的重要部分
GOT
Global Offset Table, 全局偏移表,包含所有需要动态链接的外部函数的地址(在第一次执行后)
PLT
Procedure Link Table, 过程链接表,包含调用外部函数的跳转指令(跳转到GOT表中),以及初始化外部调用指令(用于链接器动态绑定dl_runtime_resolve)
Linux虚拟内存分段映射中,一般会分出三个相关的段:
.plt
: 即上文提到的过程链接表,包含全部的外部函数跳转指令信息- Attributes: Read / Execute
.got.plt
: 即下文将要表达的GOT表,与PLT表搭配使用,包含全部外部函数地址(第一次调用前为伪地址,具体见下)- Attributes: Read / Write
.got
: 存放其他全局符号信息,注意与.got.plt不同,与下文函数动态链接过程关系不大(所以了解不深请见谅,有兴趣的读者也欢迎分享)Attributes: Read / Write
简单来说,PLT表存放跳转相关指令,GOT表存放外部函数(符号)地址
1 | from pwn import * |
ezshellcode
mmap
(Memory Map)是一个用于在进程的虚拟地址空间中创建内存映射区域的系统调用。这个函数允许进程将一个文件或者匿名的内存区域映射到其地址空间,从而可以通过对内存的访问来对文件进行读写,或者在进程间共享数据。
我们输入的buf写入了0x66660000,下面跳转到了0x66660000,所以可以执行shellcode
这里也可以看作是代码注入/shellcode注入
1 | from pwn import * |
需要生成shellcode最好在linux执行(在mango使用python2)
newstar_shop
逻辑阅读
整数溢出。
1 | from pwn import * |
p1eee
ELF是按页对齐,一页是0x1000,所以低三位十六进制的值不会改变
因为后门函数的地址与有溢出的函数的地址非常接近,所以只需修改最低一字节就能控制程序流到后门
找到后门的最后一个字节0x6c
1 | from pwn import * |
Random
随机数猜数。题目是rand+time是真的随机数。可以利用python的ctyeps库同时生成随机数来猜
shell.py
random1.c
1 | gcc -shared -o random1.so random1.c |
-shared
:这个选项告诉 GCC 生成一个共享库(在 Windows 上是 DLL,在 Unix-like 系统上是 .so 文件)。-o random1.so
:这个选项指定输出文件的名称。在这里,输出文件的名称是random1.so
。random1.c
:这是要编译的源代码文件。
ret2libc1
wiki的题目
flat可以自动转换
system_plt是在ida中查看到的
在ida里发现s占用100,s名称占用4,r变量占用4,+ebp占用4,所以先填充112个垃圾值。将system_plt放到返回值(这个返回值是sys的返回值)。
不过还是没搞明白为什么只覆盖了sys的返回值不用覆盖ebp。
然后把参数/bin/sh放进去
因为子函数在父函数的栈帧内。
1 | #!/usr/bin/env python |
还有一个找偏移量的方法
找偏移量
1 | pattern create 150 |
ret2libc
是存在pop_rdi_ret的gadget可以利用
在x86_64架构中,函数调用的参数通过寄存器来传递,而不是通过栈来传递。前六个整数或者指针参数是通过
rdi
,rsi
,rdx
,rcx
,r8
,r9
这六个寄存器来传递的。如果有更多参数,剩下的参数通过栈传递。
所以要ROP(Return Oriented Programming)来调用一个函数并且操控参数时,就要找到可以控制这些寄存器的gadget。比如
- pop rdi;ret
- pop rsi;popr15;ret(官方wp中使用的)
- ret
获取pop_rdi_ret的地址
1 | ROPgadget --binary /... --only "pop|ret" |
找到偏移为0x20
buf是全局变量,被放在bss段上
在 C 语言中,一个变量是在 BSS 段还是在栈上,主要取决于它的声明位置和存储类别:
- 全局变量和静态变量:这些变量通常存储在 BSS 段。BSS 段用于存储程序中未初始化的全局变量和静态变量。
- 局部变量和函数参数:这些变量通常存储在栈上。当一个函数被调用时,它的局部变量和参数会被压入栈中。
可以使用objdump -h program//readelf -S program
查看bss
段的地址(不同环境下bss
的地址可能不同)
使用peda
的vmmap
可以查看bss
段有无执行权限
所以可以先把buf
塞满(0x20),然后塞rbp
(0x8,因为在bss
段所以可以直接塞bss
的起始位置)。rbp塞垃圾值也可以。
1 | p.sendafter( |
然后因为64位下函数的第一个参数在rdi
中,因此把栈顶的值弹出到rdi
。在x86_64中,调用函数时,函数的第一个参数是通过rdi
寄存器传递的。因此使用pop_rdi_ret
这个gadget,把它的地址放在原本的return address
处,那么程序就会跳转到pop_rdi_ret
的地址处执行它。pop_rdi
将此时的栈顶数据(puts
函数的地址)弹给rdi
,然后ret
到plt
表的puts
处调用puts
,那么puts
就会找到rdi
,打印rdi
中puts
的地址。puts
执行之后,将ret
回main_addr
。(这里不返回main也可以)
这样就完成了puts
函数地址的泄露,可以用于泄露远程libc的版本。
这里我还另外打了read的地址。只有一个地址有时会有很多个版本的libc被检索到。https://libc.rip/
只搜索三个字节(12位)的原因:
即使程序有 ASLR 保护,也只是针对于地址中间位进行随机,最低的 12 位并不会发生改变
这样就得到了libc
的版本,可以看到有system
和/bin/sh
的偏移。所以可以计算libc_base
,加上偏移得到需要的函数的地址。
1 | libc_base = puts_addr - 0x80970 |
这是手动计算的方法。还有其他两种办法写在完整代码中了。
现在的目标就是执行system('/bin/sh')
了。与上面类似,仍然利用栈溢出构造ROP链。
1 | bss-->pop_rdi_ret-->binsh_addr-->system_addr--main_addr |
所以可以:
1 | p.sendafter( |
然而这样执行程序崩溃了。为什么呢?
因为在ubuntu18版本以上调用system
函数需要栈对齐。
可以看这一篇文章:
所以我们加一个ret
:
1 | p.sendafter( |
官方wp的写法:
1 | p.sendafter( |
大功告成!
1 | from pwn import * |
pwn1_sctf_2016
找到s距离ebp有0x3C,所以0x3C+4
1 | from pwn import* |
jarvisoj_level0
buf128+rbp8
1 | from pwn import* |
第五空间pwn5
格式化字符串漏洞
题目生成了一个随机数作为密码,要求输入用户名和密码,密码和随机数匹配了就getshell
设置了canary无法栈溢出
随机数被放入了bss段的0x804C044
可以看到用户名被放在栈上第10个位置
%16$n
的意思是将已经输出的字符数赋值到第16个参数指向的地址处。那么我们可以巧妙地构造,操控bss处的值
因为%16$n
有5个字符,而地址是4位,所以我们构造四个。
1 | from pwn import * |
canary
有canary
PIE没有开,可以直接使用函数地址
第一个read进来buf,然后print出buf,可以利用格式化字符串漏洞。buf的大小是40,第二个read的限制是0x100,有栈溢出。还找到了backdoor
所以可以使用print来泄露canary的值,使用栈溢出来ret2text
那么先测试一下buf和canary在栈的哪个位置。
找到第二个read的地址
在这里打断点。
发现buf在栈的第六个位置
第十一个位置的数据最末尾是00
,很像canary的00截断
查看main的汇编
找到canary在rbp-0x8
位置
查看canary的地址
确实是0x7fffffffdb88
那么canary在栈的第11个位置
encode()
方法是将字符串转换为字节串的方法
1 | from pwn import * |