# HECTF2023 - Pwn - 题解
# 得分情况总览
共解出 = 6 / 8 = 题
一血:EZtext easyweb - 风水小狮 -
二血:signin(100% 是因为先秒的 EZtext,导致这个能够更快秒掉的题被别人秒了)
三血:fmt(奇葩脑洞,第一次见这么奇葩的思路)
༼ つ ◕_◕ ༽つ堆题是真的不会做
# WriteUp - 正文
# signin
直接打开 IDA,可以知道我们的目标是让输入的 v4 在小于 3 的同时要让低位的四字节的数值等于 B,也就是 0x42.
于是就类似于整数溢出的思想,直接送入 0xffffffffff00000042 就行了
但是这里读入用的是 scanf,所以必须以字符串或者 bytes 的形式输入数字,化为十进制就是 - 4294967230
EXP:
1 | #patchelf --set-interpreter /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --replace-needed libc.so.6 /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6 pwn |
这题直接秒了,但是我先做的 eztext 那题,所以就手速慢了,没有拿到一血。或许先做这题,一血就拿到了。
# eztext
打开 IDA 一眼扫过去直接用 SROP。模板题,具体原理不再详说。
用模板做题半分钟写好 EXP:
1 | #patchelf --set-interpreter /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --replace-needed libc.so.6 /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6 pwn |
# micro_httpd
通过简单的读题我们可以看到这里的逻辑就是输入一个 "get"+filepath+"hectf" 并且 filepath 能够通过过滤,那么就能够顺利读取文件。
但是我们如果仅仅通过构造 filepath 来让他通过过滤条件,这显然是很困难的。所以我们只能另寻他法。
注意到 main 函数第 49,50 行的 while 循环。我一开始以为这里是 IDA 反汇编识别错误了,但是后来就被狠狠地打脸了。
可以看到这里传入的三个参数分别是 v7, 0x2000LL, stdin,这里的 v7 指向的是栈里面地址低于 filepath 的地方,而且距离正好是 0x2000.
这时候我们就希望这里面读入的能够覆盖到至少 filepath 前两个字节,那么就能够轻轻松松通过过滤读到文件了。
通过读源代码发现这里的漏洞就是在 i=-1 那里。0x2000-(-0x2)=0x2002,因此我们正好可以覆盖到 filepath 前两个字节。所以我们就可以修改 filepath 前两个字节为 “/.”,而且刚开始输入的就是 “/a./flag”,经过替换之后就是 "/../flag"
于是我们就名正言顺地拿到 flag 了
注:这里调试的话需要设置参数表。因为 main 函数前面会对参数表进行检查。
1 | #patchelf --set-interpreter /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --replace-needed libc.so.6 /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6 pwn |
# magic
拿到题目直接 checksec 之后发现不对劲
扔进 IDA 里面发现这里实现了栈上开辟空间并且修改或者调用函数的功能。
经过漫长的读代码之后意识到只能进行三次操作,其中肯定有一次选项 3,那么剩下两次毋庸置疑就是一次选项 1 和 2。因此,我们的思路就是先 1,再 2,最后 3,读入 shellcode 到栈里面并且执行。
注意到这里我们为了能够顺利通过 call rax;来运行 shellcode,我们需要采用低位覆盖的方法把 v10 的最低一个字节修改(因为这里原本指向的地址和 shellcode 开始的位置的 offset 并不是很大),使 v10 指向 shellcode 开头的位置。但是这里按理来说有 1/256 的概率成功,但是我只用了一次就 get 到了 shell,感觉还是挺幸运的!
EXP:
1 | #patchelf --set-interpreter /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --replace-needed libc.so.6 /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6 pwn |
# fmt
读了题目都知道是格式化字符串漏洞了,但是正常情况下只能使用一次,这显然是无法让我们拿到 shell 的。
刚开始往格式化字符串泄露 canary 然后构造 ROP 的办法来想的,但是意识到第一步获得了 canary 又能干什么呢?啥都干不了,就 exit 了。所以我们不能这么平庸地看问题。
考虑到绕过 Canary,不如与其正面硬刚,直接让 Canary 失效。
众所周知,如果程序检测到 Canary 被更改了,那么就会在函数结束的时候调用__stk_chk_fail 这个函数,然后就退出了。但是我们正是可以利用这一点,用格式化字符串更改__stk_chk_fail 的 got 表地址为 main 函数地址,那么就可以构造较长的 payload 来修改 Canary 从而可以无限次调用 main 函数,也是可以通过较短的 payload 不修改 Canary 来达到不接着调用函数直接 ret 的效果。
既然可以控制 main 函数的执行了,那么我们也就可以控制格式化字符串漏洞的利用了。
于是我们这样利用漏洞:
1. 第一次格式化字符串漏洞:修改__stk_chk_fail 函数的 got 表地址为 main 函数地址。
2. 第二次格式化字符串漏洞:获取栈上面__libc_start_main+128 的地址。
3. 第三次格式化字符串漏洞:构建并且输入 ROP,但是这里 payload 的长度肯定会覆盖 Canary,因此我们这里一定会进入下一层 main 函数,但是在下一层 main 函数我们随便传一个短短的字符串就行了,就可以不修改 Canary 从而达到退出执行上一层 main 函数输入的 ROP 的效果。
这里输入用的是 scanf,遇到 0d 会截断,亲测 system 似乎打不通(我只试了本地,远程不知道),反正都有 libc 基址了,啥 gedgets 就都有了,而且 ROP 空间充足那么我们倒不如直接调用成功率更大的 execve
EXP:
1 | #patchelf --set-interpreter /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --replace-needed libc.so.6 /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6 pwn |
# 风水小狮
蒟蒻也想成为风水大狮。
IDA 中发现没啥意思,简单直接写 EXP。
发现 while (1) 中循环一个 fun2 () and fun3 (),而且 fun2 可以被我们利用来运行 system ("/bin/sh"),还可以输入一堆东西,而且 strlen 的绕过只要在输入的东西前面加个 \x00 就可以了。
fun3 我们只要不输入 exit 就可以了。
这里多送入几对'system','/bin/sh' 这样它通过 system 的检测的概率就大亿点。
但是这里我们要内存对齐,所以开始的 \x00 就送入了 8 个。
多运行了几次直接秒杀!一血!FirstBlood!
EXP:
1 | #patchelf --set-interpreter /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --replace-needed libc.so.6 /home/akyuu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6 pwn |
# 我不是风水大狮,但是蒟蒻也想当风水大狮。
༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ
༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ
༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ
༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ