SROP
1 | GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.6) stable release version 2.27. |
1 | pwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=b9a04d5b45791b795e9c72a0f443a391a5cd591a, not stripped |
- 64位,dynamically,Full RELRO,NX
1 | 0000: 0x20 0x00 0x00 0x00000004 A = arch |
- 只能打 ORW
漏洞分析
栈溢出 + syscall:
1 | seccomp(argc, argv, envp); |
入侵思路
有栈溢出,可以打 ret2csu,先利用现成的 syscall 配合万能 pop 构造 read 函数,然后栈迁移到 bss 段打 ORW
完整 exp 如下:
1 | # -*- coding:utf-8 -*- |
HRP-CHAT
题目的 docker 启动脚本如下:
1 | echo $GZCTF_FLAG>/home/ctf/Nep_CTF_FLAG_ONE |
- 执行
/home/ctf
目录下的 serve 文件,并重定向输入到 result 文件(将标准错误 2 重定向到标准输出 &1,标准输出 &1 再被重定向输入到 result 文件中) - 本题目有4个 flag 并且提供源码
- PS:第一条命令与动态 flag 有关,需要去掉
题目开始前先修改 serve.c
,然后执行 make.sh
并重新编译:
1 | socklen_t serverLen = sizeof(struct sockaddr_in); |
修改 docker-compose.yml
并搭建 docker 环境:
1 | version: "2" |
程序分析
输入 help 即可查看程序的功能:
1 | # ./start.sh |
入侵思路 - flag1
第一个 flag 位于 Shop 模块中:
1 | char sql1[0x100]; |
- 需要满足
rows>0
这个条件,其值通过sqlite3_get_table
函数从数据库中获取
发现 sprintf 后并没有对 sql 语句进行限制,这里可能是打 sql 注入,最简单的想法就是在用户名末尾添加 '
截断,并添加注释符号 --
,从而绕过 Statement ='root'
1 | select * from user where Username='yhw'--123456' and Statement ='root' |
完整 exp 如下:
1 | # -*- coding:utf-8 -*- |
入侵思路 - flag2
第二个 flag 在 VIP 模块中:
1 | if(!strcmp(msg.message,"RemoteVIPApplicationCertificationHasPassed")) |
- 想要获取 VIP 必须让服务器返回的数据为
RemoteVIPApplicationCertificationHasPassed
- 在服务端找到对应的模块,发现服务端只能返回固定字符串
1 | else if(!strcmp(info[1],"BotRemoteHelp")&&strcmp(info[3],"back")) |
我的第一反应是伪造客户端向服务端发送伪造数据包,但该题目是直接与客户端进行交互的,没有伪造的机会,不过程序似乎提供了伪造的功能:
1 | else if(!strcmp(info[1],"Secret")) |
- 该服务器有“聊天室”这个功能,通过这个功能可以使服务端发送任何数据到任意一个客户端
- 可以先 nc 启动一个客户端执行 Bot 命令,然后再用另一个客户端的 Secret 功能向第一个客户端发送指定数据包
- 而在 Chart 功能中可以查看目标客户端的 UID
通过以下两个 exp 的配合,就可以获取 flag:
1 | # -*- coding:utf-8 -*- |
1 | # -*- coding:utf-8 -*- |
入侵思路 - flag3
第三个 flag 出现在一个简单游戏中:
1 | else if(!strcmp(info[1],"Start")) |
cNode[4].blood
和cNode[i].skill_hurt[sk]
都是正值,必须令 res 溢出为负数
查看角色的数据,发现只有 H3h3QAQ
的 skill_hurt[1]
符合条件:
1 | strcpy(cNode[0].name,"H3h3QAQ"); |
在 Shop 中没法购买 H3h3QAQ
,只能在 RollCard 中 Roll 出来
完整 exp 如下:
1 | # -*- coding:utf-8 -*- |
入侵思路 - flag4
第四个 flag 在 safebox 中:
1 | if(!strcmp(choice,"Safe_Mode_Key")) |
- 进入 “安全模式” 输入 “Safe_Mode_Key” 即可拿到 flag
想要进入安全模式,必须先令程序崩溃:
1 | // 创建子进程 |
令程序崩溃的方法正是条件竞争:
1 | void Login() |
- 当程序创建两个线程后会执行 while 自旋(类似于自旋锁)
1 | if(!strcmp(msg.message,"true")) |
- 当
login_pthread_recv
函数快执行完时,会设置quit = true
从而使主线程继续执行 - 如果在正常执行 login 流程的同时输入大量垃圾数据,就可能导致服务端崩溃,进而导致客户端的 recv 死锁(具体原因不清楚)
完整 exp 如下:
1 | # -*- coding:utf-8 -*- |
HRPVM2.0
1 | kernel: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=08cd1082abae94aa4bc1d29a144b1a27bb3e875f, for GNU/Linux 3.2.0, stripped |
- 64位,dynamically,全开
该题目是一个 Web 服务器,启动 Apache 服务器的代码如下:
1 | from flask import Flask, render_template, request, jsonify |
使用 docker 搭建环境后通过 http://172.26.0.2:5000/ 即可访问题目页面,类似于一个网页版的 shell
1 | > cat README |
漏洞分析
全局变量溢出:
1 | if ( file->fd >= 0 ) // read |
- 通过溢出 read_buf 可以覆盖 user:
1 | .bss:0000000000005040 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??+read_buf db 100h dup(?) ; DATA XREF: syscall+12E↑o |
程序提供了后门,可以执行 /bin/shell
1 |
|
入侵思路
想要获取 flag 则需要绕过两个点:
1 | if ( user->key || !strcmp(file->perm, user->name) ) |
user->key == 1
user->name == root
理论上可以通过 read_buf 的溢出来覆盖 user(我们可以通过单独执行二进制文件来进行调试)
首先看单独执行二进制文件时,打通的 payload:(概率 1/16)
1 | sl("root") |
但是打网页时 0xd1e0 会被转义,因此我们需要找寻可见字符的地址:
1 | url = "http://172.26.0.2:5000/send" |
不过这个题目还没有结束,看别人 wp 时才发现服务器上那个是假 flag,需要拿到 shell 才能拿到真 flag
看别人 wp 时学到了一种比较方便的交互方式:
1 | local = 0 |
tube
:生成一个新过程,并用管子包裹它以进行通信(将其设置为网络 IO 即可快速进行交互)
程序还有一个后门可以执行内存上的 /bin/shell
文件,而 mount 命令可以往 /bin/shell
中写入数据(同样需要满足 user->key == 1
)
1 | if ( getnum_by_name(name) == 1 ) |
我们可以在文件启动脚本中看到如下的代码:
1 | # 启动 AMD64 ELF 程序 |
- 执行如下的 shell 脚本就可以替换二进制文件 kernel 为真实的
/bin/sh
文件
1 | !/bin/sh |
于是利用的主要步骤如下:
- 利用程序的溢出修改
user
,使其指向我们可以控制的 chunk(概率为 1/16) - 利用程序功能创建
/bin/shell
,并使用 exec 往其中写入上述的 shell 脚本 - 通过 mount 功能将
/bin/shell
中的脚本写入真实的/bin/shell
文件 - 通过后门
/test_system_exec
执行/bin/shell
完成替换 - 再次连接即可拿到
/bin/sh
PS:第一次在本地进行调试时遇到 /bin
目录没有权限的问题,而 docker 中的权限是 root,没有这个问题
完整 exp 如下:
1 | # -*- coding:utf-8 -*- |
浏览器效果截图: