PlaidCTF 2016 butterfly

0x00:

这个题目是之前和0x9A82学弟一起看的,第一次遇到这种bit位翻转的题目,在讨论了十多分钟之后才搞明白一个国际友人的解法,当时觉得他这个解法太巧妙了~遂做个记录。

0x01:

程序大概的逻辑如下,直接放IDA F5之后的代码好了,部分变量为了阅读方便重命名了。

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
35
36
37
38
39
40
41
42
43
44
45
int __cdecl main(int argc, const char **argv, const char **envp)
{
signed int v3; // er14@1
__int64 addr; // rax@2
char bit; // bl@2
_BYTE *addr2; // rbp@2
void *base_addr; // r15@2
__int64 v8; // rax@5
char buffer; // [sp+0h] [bp-68h]@1
__int64 v11; // [sp+40h] [bp-28h]@1

v11 = *MK_FP(__FS__, 40LL);
setbuf(_bss_start, 0LL);
puts("THOU ART GOD, WHITHER CASTEST THY COSMIC RAY?");
v3 = 1;
if ( fgets(&buffer, 50, stdin) )
{
addr = strtol(&buffer, 0LL, 0);
bit = addr;
addr2 = (_BYTE *)(addr >> 3);
base_addr = (void *)((addr >> 3) & 0xFFFFFFFFFFFFF000LL);
if ( mprotect(base_addr, 0x1000uLL, 7) )
{
perror("mprotect1");
}
else
{
v3 = 1;
*addr2 ^= 1 << (bit & 7); // 0000 0111
if ( mprotect(base_addr, 0x1000uLL, 5) )
{
perror("mprotect2");
}
else
{
puts("WAS IT WORTH IT???");
v3 = 0;
}
}
}
v8 = *MK_FP(__FS__, 40LL);
if ( *MK_FP(__FS__, 40LL) == v11 )
LODWORD(v8) = v3;
return v8;
}

程序逻辑很简单,大概做了这几件事:

  1. 读取用户输入,使用strtol()把输入转成long类型的值。
  2. 把这个值右移3 bits,我标记成了addr_base。
  3. 调用mprotect(),修改base_addr指向的内存属性。
  4. addr2最低3 bits反转。
  5. 再次调用mprotect(),修改base_addr指向的内存属性。

0x02:Thinking

开始我的想法是写sc进去然后修改程序流程,跳进去执行。但是并没有搞定,在看了一份wp之后豁然开朗。
这里是main执行结束后返回的汇编代码,老外那份wp里的做法就是,使用bit翻转,把add rsp,48h编程add rbp,48h,这样的话,相当于我们可以控制返回地址。
有了上面的基础之后,第一次修改指令,后面构造循环1字节1字节的把shellcode写到合适的位置,然后在最后一次直接retn到那个位置,执行代码。

1
2
3
4
5
6
.text:0000000000400860                 add     rsp, 48h
.text:0000000000400864 pop rbx
.text:0000000000400865 pop r14
.text:0000000000400867 pop r15
.text:0000000000400869 pop rbp
.text:000000000040086A retn

0x03:Exploit

0x04:Reference