diary
1 | GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9) stable release version 2.31 |
1 | diary: 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.9_amd64/ld-2.31.so, for GNU/Linux 3.2.0, BuildID[sha1]=f9a0df0117a1b0f105959590bb560a21554a17e2, stripped |
- 64位,dynamically,全开
漏洞分析
程序的 “dele” 模块有点漏洞
1 | add(2022,12,10,10,30,30,b"1"*0x300) |
这里我们只释放了 chunk0,但 chunk3 也被释放了,并且堆块整体向上移动(如果我们释放最后一个 chunk,则是正常的):
1 | pwndbg> telescope 0x55555557f080 |
- 程序可以控制前3个 chunk,利用第3个 chunk 就可以完成泄露
入侵思路
利用这个程序漏洞就可以完成泄露:
- 泄露 heap_base:
1 | add(2022,12,10,10,30,30,b"1"*0x300) |
- 泄露 libc_base:
1 | dele(0) |
注意观察此时的堆风水:
1 | pwndbg> telescope 0x55555557f080 |
- chunk1 和 chunk2 都是 unsortedbin
- 也就是说,接下来程序在这个 unsortedbin 中申请的第一个 chunk 会被当做 chunkn,可以被“堆菜单”提供的各个函数控制
1 | memset(*(void **)(a1 + 16), 0, 0x300uLL); |
- 在“堆菜单”的
update
函数中,可以对 0x2F0 字节大小的空间进行控制 - 只要申请的 chunk 比 0x2F0 小,就可以完成堆溢出
接下来的思路很简单,就是利用这个堆溢出来修改 encrypt
创建的 chunk:
encrypt
会对程序进行加密,然后把原来的数据存储在一个 chunk 中- 只要覆盖了这个 chunk 中的数据,就可以在
decrypt
中把修改后的数据写回 - 于是我们直接
encrypt
tcachebin 上的 chunk,把encrypt
chunk 修改为free_hook
后使用decrypt
放回
这里的堆风水是比较困难的,因为 memset
会把 heap 上相当一部分的数据置空,导致程序出现段错误,因此在进行溢出的 chunk 后面就只能有 encrypt
chunk
但在实际操作中又会遇到一些问题,溢出的字节数不够,只能在 encrypt
chunk 上覆盖 free_hook
的前4字节,不能将完整的 free_hook
写入(不知道出题人设计好的)
这里我卡了很久,之后突然想到可以直接使用 update
来覆盖后4字节的值(程序会在 chunk 之前加上4个空格),然后就可以申请到 free_hook
了
最后记得在 “/bin/sh” 两端加上 “;” 进行间隔
完整 exp:
1 | # -*- coding:utf-8 -*- |
ez_atm
1 | GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.6) stable release version 2.27. |
1 | ez_atm: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /home/yhellow/tools/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/ld-2.27.so, for GNU/Linux 3.2.0, BuildID[sha1]=bd8945726574e2b623d0f972a2b9d04bb4fd9e3a, stripped |
- 64位,dynamically,全开
漏洞分析
本程序分为客户端和服务端,在客户端上有一个后门:(没什么用)
1 | int __cdecl __noreturn magic() |
在 cancellation
函数中有一个 UAF:
1 | for ( i = 5; i > 0; --i ) |
在 stat_query
函数中调用的 toClient
函数中有溢出:
1 | void *__fastcall toClient(int num, _QWORD *str) |
入侵思路
本地可以用如下方式运行程序:
1 | ./client 127.0.0.1 3339 |
比赛时我看了半天也不知道该如何泄露,因为 client
限制了程序的输入,导致程序的溢出利用不了(虽然客户端的 stat_query
有溢出,但是服务端的 client
接收不到)
赛后看别人 wp 才发现可以不通过 client
进行交互,从而摆脱了 client
的限制
先把本地的 docker 环境搭起来:(记得在 bin
目录中添加一个 flag
文件)
1 | docker build -t "ez_atm" . |
- 启动容器:
1 | docker run -d -p "0.0.0.0:4444:8888" -p "0.0.0.0:7777:3339" -h "ez_atm" --name="ez_atm" ez_atm |
- 然后执行
docker exec -it
连接目标 docker:
1 | docker exec -it 87a8f7705e22 /bin/sh |
- 后台运行程序:
1 | cp ./ez_atm / |
- 查看程序的 PID:
1 | ps -aux | grep "ez_atm" |
- 查看容器的 PID:
1 | docker ps |
1 | docker inspect -f '{{.State.Pid}}' 87a8f7705e22 |
如果我们不使用 client
进行连接,就需要先绕过服务端的随机数检测:
1 | __int64 recv_init() |
- 绕过该检测的脚本:
1 | def init(): |
另外我还看到一种解决办法,就是直接 path 客户端文件 client
:
1 | void __cdecl stat_query() |
1 | pwndbg> telescope 0x7ffc9b86aa00 |
获取到 libc_base
后,就直接把它写入 fastbin
中,然打 fastbin attack
就好了(最后执行 system(cat flag >&4)
)
完整 exp:(不通过 client
进行交互)
1 | from pwn import * |
完整 exp:(直接修改 client
)
1 | # -*- coding:utf-8 -*- |
game
非预期
ez_money
1 | GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9) stable release version 2.31. |
1 | ez_money: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=d3173989de9ecef646bad08f24a9d5fda1061937, for GNU/Linux 3.2.0, stripped |
- 64位,dynamically,全开
漏洞分析
在 loan
函数中有溢出:
1 | if ( loan_index > 10 ) |
- 由于
loan
函数的执行次数没有设置正确,导致程序有一个堆溢出
入侵思路
程序会先申请一个 chunk 来存放 loan_info
,它的相邻下一个 chunk 就是 chunk_list 的第一个对象
- 我们在存放
loan_info
的 chunk 中进行溢出,修改相邻下一个 chunk 的 size 大小,使其可以进入 largebin - 然后释放掉 chunk_list 的第一个对象,使其进入 largebin,然后打印
loan_info
就可以泄露 libc_base
由于 libc-2.31 有 key 值保护 tcachebin,因此我们需要用同样的方式来泄露 heap_base
- 在之前生成的 unsortedbin 中申请新的 chunk,使其覆盖原来 chunk_list 的第一个对象
- 将其释放并组织一下堆风水就可以泄露 heap_base
最后利用堆风水劫持 tcachebin 就好了
完整 exp:
1 | # -*- coding:utf-8 -*- |