how got filling in runtime
GLOBAL_OFFSET_TALBE 이하 got가 libc 상에 함수 주소를 가리키고 있는 것은 모두 알고 있을 것이다.
그렇다면 이 실제 함수 주소를 어떤 방식으로 가져 올까? 이 과정에 대해서 자세히 알아보자
https://rond-o.tistory.com/216
다음 사이트를 참조했다.
pwndbg> disass 0x400770
Dump of assembler code for function read@plt:
0x0000000000400770 <+0>: jmp *0x2018ca(%rip) # 0x602040 <read@got.plt>
0x0000000000400776 <+6>: push $0x5
0x000000000040077b <+11>: jmp 0x400710일단 시작은 plt함수로 부터 시작된다. 해당 함수는 명령어 3개로 되어 있는데 맨처음
jmp *0x2018ca(%rip) 에서 해당 함수의 got에 있는 값으로 jmp명령어를 수행한다. lazy binding을 하고 있으니깐 만약 이 함수가 이미 한번 실행 되었다면, got에는 libc 상에서 read 함수의 위치를 가리킬 것이다. 만약 이 함수가 전에 한번도 실행되지 않았자면, 이제 lazy binding이 시작할 차례다
lazy binding
그럼 이제 이 과정을 알아보자
pwndbg> disass 0x400770
Dump of assembler code for function read@plt:
0x0000000000400770 <+0>: jmp *0x2018ca(%rip) # 0x602040 <read@got.plt>
0x0000000000400776 <+6>: push $0x5
0x000000000040077b <+11>: jmp 0x400710
End of assembler dump.
pwndbg> x/gx 0x602040
0x602040 <read@got.plt>: 0x0000000000400776
pwndbg> 현재 read 함수가 한번도 실행된적이 없으니깐 read@got.plt 에는 0x0000000000400776 즉,
read@plt 상에서 다음 명령어를 가리키고 있다.
0x0000000000400776 <+6>: push $0x5
0x000000000040077b <+11>: jmp 0x400710그러면서 다음 명령어를 실행하는데
push $0x5 에서 0x5 값은 read 함수의 got 상에서 오프셋이다.
좀더 자세히 설명하자면,
(pwn) amemoyoi ~$ objdump -T ./Notepad
./Notepad: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.2.5) remove
0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.2.5) puts
0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.2.5) fclose
0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.2.5) system
0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.2.5) strchr
0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.2.5) read
0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.2.5) __libc_start_main
0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.2.5) ftell
0000000000000000 w D *UND* 0000000000000000 __gmon_start__
0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.2.5) fseek
0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.2.5) setvbuf
0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.2.5) fopen
0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.2.5) sprintf
0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.2.5) strstr
00000000006020a0 g DO .bss 0000000000000008 (GLIBC_2.2.5) stdout
00000000006020b0 g DO .bss 0000000000000008 (GLIBC_2.2.5) stdin
00000000006020c0 g DO .bss 0000000000000008 (GLIBC_2.2.5) stderr.got.plt 섹션 상에서 read가 여섯번째에 위치하기 때문에 table[5]와 같은 index 느낌으로 5가 push 된다.
(나중에 나오지만 이 index를 정식명칭으로 reloc_offset 라고 한다. )
그럼 이제 jmp 0x400710 를 해보면
pwndbg> x/5i 0x400710
0x400710: push 0x2018f2(%rip) # 0x602008
0x400716: jmp *0x2018f4(%rip) # 0x602010
0x40071c: nopl 0x0(%rax)
0x400720 <remove@plt>: jmp *0x2018f2(%rip) # 0x602018 <remove@got.plt>
0x400726 <remove@plt+6>: push $0x0다음과 같은 명령어를 거치게 된다. 먼저 0x602008 값을 push 하게 되는데 이값은
pwndbg> info symbol 0x602008
_GLOBAL_OFFSET_TABLE_ + 8 in section .got.plt of /home/amemoyoi/1day_2pwn/Notepad.got.plt 의 _GLOBAL_OFFSET_TABLE_ + 8 이라고 한다.
해당 글을 참조해서 가져오면
// 이하 0x10은 .got section
0x600ff0: 0x00007ffff7a03b10 // & __libc_start_main()
0x600ff8: 0x0000000000000000
// 이하 0x38은 .got.plt section
0x601000: 0x0000000000600e20 // & .dynamic section
0x601008: 0x00007ffff7ffe170 // & link_map
0x601010: 0x00007ffff7dea8f0 // & _dl_runtime_resolve_xsavec()
0x601018: ?????????????????? // 우리가 아는 got 시작
0x601020: ??????????????????
0x601028: ??????????????????
0x601030: ??????????????????_GLOBAL_OFFSET_TABLE_ + 8 는 link_map 구조체의 주소를 의미한다.
해당 구조체는
struct link_map
{
ElfW(Addr) l_addr; /* Difference between the address in the ELF
file and the addresses in memory. */
char *l_name; /* Absolute file name object was found in. */
ElfW(Dyn) *l_ld; /* Dynamic section of the shared object. */
struct link_map *l_next; /* Chain of loaded objects. */
struct link_map *l_prev;
};https://elixir.bootlin.com/glibc/glibc-2.27/source/elf/link.h#L84
다음과 같다. 다시 코드로 돌아가서
pwndbg> x/5i 0x400710
0x400710: push 0x2018f2(%rip) # 0x602008
0x400716: jmp *0x2018f4(%rip) # 0x602010
0x40071c: nopl 0x0(%rax)
0x400720 <remove@plt>: jmp *0x2018f2(%rip) # 0x602018 <remove@got.plt>
0x400726 <remove@plt+6>: push $0x0link_map 구조체의 주소를 push 한 다음에 0x602010에 있는 값으로 점프한다.
0x602010는 GLOBAL_OFFSET_TABLE + 16 을 의미하며
_dl_runtime_resolve_xsavec 함수의 위치를 나타낸다.
따라서 _dl_runtime_resolve_xsavec 가 실행이 되며, 이 함수는 레지스터 세팅을 위한 stub 함수이다.
0x00007ffff7fda361 <+113>: mov 0x10(%rbx),%rsi
0x00007ffff7fda365 <+117>: mov 0x8(%rbx),%rdi
0x00007ffff7fda369 <+121>: call 0x7ffff7fd7af0 <_dl_fixup>결론적으로는 다음과 같이 %rdi에 앞서 push 했던 link_map 구조체의 주소와
%rsi에 앞서 push 했던 read 함수의 got상의 index 값을 가지고 _dl_fixup 함수를 실행한다.

먼저 사진을 첨부하겠다.
_dl_fixup(&link_map, reloc_offset) 으로 함수를 실행하고
reloc_offset 은 위에서 내가 계속 말하던 index에 대한 정식 명칭이다.
_dl_fixup 함수에서는
007ffff7fd7bf7 <+263>: call 0x7ffff7fd00d0 <_dl_lookup_symbol_x>link_map 구조체에 존재하는 l_ld 필드를 참조해서 .dynamic 섹션을 알게된다. 이후
.dynamic 섹션에 있는 구조체 배열중
0x0000000000000005 (STRTAB) 0x400478다음과 같은 STRTAB에 대한 정보를 가지고 있는 구조체를 참조한다.
따라서 .dynstr 섹션에 접근할 수 있게 된다.
지금 부터의 분석은 실제 binary 내에 setvbuf 함수에 대한 got 채우는 과정을 따라갈 것이다.
그리고 이제 어떤 위치에 있는 got를 수정해야 되는지 알기 위해서는 .rela.plt 에 대해서도 참조해야 된다.
해당 부분에 대한 코드는 다음과 같다.
0x00007ffff7fd7b42 <+82>: lea (%rdx,%rcx,8),%r13
0x00007ffff7fd7b46 <+86>: add %r9,%r13
0x00007ffff7fd7b49 <+89>: mov 0x8(%r13),%rsi ; r_addend
0x00007ffff7fd7b4d <+93>: mov 0x0(%r13),%r14 ; r_offsetgdb 상황으로 보면
RAX 0
RBX 0x7ffff7ffe2e0 ◂— 0
RCX 0x1b
RDX 0x4005b8 —▸ 0x602018 (remove@got[plt]) —▸ 0x400726 (remove@plt+6) ◂— pushq $0 /* 'h' */
RDI 0x400478 ◂— addb %ch, 0x62(%rcx, %rbp, 2)
*RSI 0xb00000007
R8 0x4002c8 ◂— addb %al, (%rax)
R9 0
R10 0x7ffff7c140c8 ◂— 0x110012000007e6
R11 0x7ffff7c2a200 (__libc_start_main) ◂— endbr64
R12 9
R13 0x400690 —▸ 0x602060 (setvbuf@got[plt]) —▸ 0x4007b6 (setvbuf@plt+6) ◂— pushq $9 /* 'h\t' */
R14 0
R15 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0
RBP 0x7fffffffd4f0 —▸ 0x7fffffffdf00 —▸ 0x7fffffffe0f0 —▸ 0x7fffffffe190 —▸ 0x7fffffffe1f0 ◂— ...
RSP 0x7fffffffd4b0 ◂— 0x1d67a0
*RIP 0x7ffff7fd7b4d (_dl_fixup+93) ◂— movq (%r13), %r14
───────────────────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────────────────────────────────────────
0x7ffff7fd7b49 <_dl_fixup+89> movq 8(%r13), %rsi RSI, [0x400698] => 0xb00000007
► 0x7ffff7fd7b4d <_dl_fixup+93> movq (%r13), %r14 R14, [0x400690] => 0x602060 (setvbuf@got[plt]) —▸ 0x4007b6 (setvbuf@plt+6) ◂— pushq $9 /* 'h\t' */
0x7ffff7fd7b51 <_dl_fixup+97> movq %rsi, %rdx RDX => 0xb00000007
0x7ffff7fd7b54 <_dl_fixup+100> addq %rax, %r14 R14 => 0x602060 (setvbuf@got[plt]) (0x602060 + 0x0)
0x7ffff7fd7b57 <_dl_fixup+103> shrq $0x20, %rdx
0x7ffff7fd7b5b <_dl_fixup+107> leaq (%rdx, %rdx), %rcx RCX => 0x16
0x400690에 있는 값을 가져오는데 해당영역은 .rela.plt 영역이며 setvbuf 함수에 대한
Elf64_Rela 구조체의 r_offset 필드이다.
따라서 지금 어디 got에 값을 써야하는지도 알고 있고 이제 위에 .dynstr 위치도 알았으니 해당
.dynstr base로 부터 우리가 찾는 재배치 항목의 offset을 구하면 문자열을 얻을 수 있다.
.dynstr base로 부터 우리가 찾는 재배치 항목의 offset은 .dynsym 섹션에 위치해있다.
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;setvbuf 함수에 대한 Elf64_Sym 구조체를 찾고 해당 구조체에 st_name 필드에 재배치 항목의 offset 값이 들어가 있다.
이 값을 찾는 과정은 다음과 같다.
0x00007ffff7fd7b49 <+89>: mov 0x8(%r13),%rsi ; r_info
...
0x00007ffff7fd7b54 <+100>: add %rax,%r14 ; r14 = r_offset + reloc_base
0x00007ffff7fd7b57 <+103>: shr $0x20,%rdx ; r_info 상위 32비트 추출
...
0x00007ffff7fd7b62 <+114>: lea (%r8,%rdx,8),%rdx ; rdx = &.dynsym[symidx]gdb 분석상으로 보면
RAX 0
RBX 0x7ffff7ffe2e0 ◂— 0
*RCX 0x16
*RDX 0x21
RDI 0x400478 ◂— addb %ch, 0x62(%rcx, %rbp, 2)
RSI 0xb00000007
R8 0x4002c8 ◂— addb %al, (%rax)
R9 0
R10 0x7ffff7c140c8 ◂— 0x110012000007e6
R11 0x7ffff7c2a200 (__libc_start_main) ◂— endbr64
R12 9
R13 0x400690 —▸ 0x602060 (setvbuf@got[plt]) —▸ 0x4007b6 (setvbuf@plt+6) ◂— pushq $9 /* 'h\t' */
*R14 0x602060 (setvbuf@got[plt]) —▸ 0x4007b6 (setvbuf@plt+6) ◂— pushq $9 /* 'h\t' */
R15 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0
RBP 0x7fffffffd4f0 —▸ 0x7fffffffdf00 —▸ 0x7fffffffe0f0 —▸ 0x7fffffffe190 —▸ 0x7fffffffe1f0 ◂— ...
RSP 0x7fffffffd4b0 ◂— 0x1d67a0
*RIP 0x7ffff7fd7b62 (_dl_fixup+114) ◂— leaq (%r8, %rdx, 8), %rdx
───────────────────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────────────────────────────────────────
0x7ffff7fd7b51 <_dl_fixup+97> movq %rsi, %rdx
0x7ffff7fd7b54 <_dl_fixup+100> addq %rax, %r14
0x7ffff7fd7b57 <_dl_fixup+103> shrq $0x20, %rdx
0x7ffff7fd7b5b <_dl_fixup+107> leaq (%rdx, %rdx), %rcx
0x7ffff7fd7b5f <_dl_fixup+111> addq %rcx, %rdx
► 0x7ffff7fd7b62 <_dl_fixup+114> leaq (%r8, %rdx, 8), %rdx RDX => 0x4003d0 ◂— addb %al, (%rax) /* 'f' */
0x7ffff7fd7b66 <_dl_fixup+118> movq %rdx, -0x40(%rbp) [0x7fffffffd4b0] <= 0x4003d0 ◂— addb %al, (%rax) /* 'f' */
0x7ffff7fd7b6a <_dl_fixup+122> cmpl $7, %esi 7 - 7 EFLAGS => 0x246 [ cf PF af ZF sf IF df of ]
0x7ffff7fd7b6d <_dl_fixup+125> jne _dl_fixup+651 <_dl_fixup+651>
0x7ffff7fd7b73 <_dl_fixup+131> testb $3, 5(%rdx) 0 & 3 EFLAGS => 0x246 [ cf PF af ZF sf IF df of ]
0x7ffff7fd7b77 <_dl_fixup+135> jne _dl_fixup+608 <_dl_fixup+608>
0x4003d0 값을 얻은 것을 알 수 있는데 이 위치가 이제 rdx = &.dynsym[symidx]
즉, 우리가 찾는 setvbuf 함수에 대한 Elf64_Sym 구조체 이다. 여기서 첫번째 필드인 st_name 값을 확인해 보면
pwndbg> x/4wx 0x4003d0
0x4003d0: 0x00000066 0x00000012 0x00000000 0x00000000
st_name 은 4바이트이므로 0x00000066 값을 가진다.
pwndbg> info files
Symbols from "/home/amemoyoi/1day_2pwn/Notepad".
Native process:
Using the running image of child process 2262447.
While running this, GDB does not access memory from...
Local exec file:
`/home/amemoyoi/1day_2pwn/Notepad', file type elf64-x86-64.
Entry point: 0x400800
0x0000000000400238 - 0x0000000000400254 is .interp
0x0000000000400254 - 0x0000000000400274 is .note.ABI-tag
0x0000000000400274 - 0x0000000000400298 is .note.gnu.build-id
0x0000000000400298 - 0x00000000004002c8 is .gnu.hash
0x00000000004002c8 - 0x0000000000400478 is .dynsym
0x0000000000400478 - 0x0000000000400513 is .dynstr
0x0000000000400514 - 0x0000000000400538 is .gnu.version
0x0000000000400538 - 0x0000000000400558 is .gnu.version_r
0x0000000000400558 - 0x00000000004005b8 is .rela.dyn
0x00000000004005b8 - 0x00000000004006f0 is .rela.plt
0x0000000000400478인 dynstr 에서 0x00000066 을 더한 위치에 값을 확인해보면
pwndbg> x/s 0x00000066 + 0x000000000400478
0x4004de: "setvbuf"setvbuf 문자열이 들어가 있는 것을 알 수 있다. 이제 이 문자열을 가지고 간다.
_dl_lookup_symbol_x
0x00007ffff7fd01f7 <+295>: call 0x7ffff7fcf530 <do_lookup_x>do_lookup_x
0x00007ffff7fcf777 <+583>: call 0x7ffff7fcf390 <check_match>이 문자열을 do_lookup_x _dl_lookup_symbol_x 이 두 함수에서 strcmp 함수로 비교하면서 libc 상에서 같은 이름의 심볼을 찾고 libc 상에서 주소를 받아온다.
check_match
0x00007ffff7fcf500 <+368>: call 0x7ffff7fd1380 <_dl_name_match_p>이 함수에 대한 자세한 정보는 모르나 do_lookup_x _dl_lookup_symbol_x 와 같은 용도로 사용된다.
위와 같은 과정을 거친다. 이후에 함수의 주소를 얻으면,
실제로 got에 저장이 되는 것은 _dl_fixup 함수 내부에서 _dl_lookup_symbol_x 함수 호출 이후에
0x00007ffff7fd7c7d <+397>: mov %rax,(%r14)이 부분이다. 이때 gdb를 보면
*RAX 0x7ffff7c88550 (setvbuf) ◂— endbr64
*RBX 0x7ffff7ffe2e0 ◂— 0
*RCX 0x4003d0 ◂— addb %al, (%rax) /* 'f' */
*RDX 0
*RDI 0x7ffff7c0a0c0 ◂— 0x11002200003bac
*RSI 1
*R8 1
*R9 0
*R10 0x7ffff7c0a0c0 ◂— 0x11002200003bac
*R11 3
*R12 9
*R13 0x400690 —▸ 0x602060 (setvbuf@got[plt]) —▸ 0x4007b6 (setvbuf@plt+6) ◂— pushq $9 /* 'h\t' */
*R14 0x602060 (setvbuf@got[plt]) —▸ 0x4007b6 (setvbuf@plt+6) ◂— pushq $9 /* 'h\t' */
*R15 0x7ffff7fbd160 —▸ 0x7ffff7c00000 ◂— 0x3010102464c457f
*RBP 0x7fffffffd4f0 —▸ 0x7fffffffdf00 —▸ 0x7fffffffe0f0 —▸ 0x7fffffffe190 —▸ 0x7fffffffe1f0 ◂— ...
*RSP 0x7fffffffd4b0 —▸ 0x7ffff7c0a0c0 ◂— 0x11002200003bac
*RIP 0x7ffff7fd7c7d (_dl_fixup+397) ◂— movq %rax, (%r14)
───────────────────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────────────────────────────────────────
► 0x7ffff7fd7c7d <_dl_fixup+397> movq %rax, (%r14) [setvbuf@got[plt]] <= 0x7ffff7c88550 (setvbuf) ◂— endbr64
0x7ffff7fd7c80 <_dl_fixup+400> leaq -0x28(%rbp), %rsp RSP => 0x7fffffffd4c8 —▸ 0x7fffffffdee0 —▸ 0x7fffffffe218 ◂— ...
0x7ffff7fd7c84 <_dl_fixup+404> popq %rbx RBX => 0x7fffffffdee0setbuf got에 실제 함수 주소를 넣는 것을 알 수 있다.
그리고 _dl_runtime_resolve_xsavec 함수 내에서 _dl_fixup 실행후 반환값이 함수의 주소이고 이를
0x00007ffff7fda3aa <+186>: jmp *%r11*RAX 0x7ffff7e038e0 (_IO_2_1_stdin_) ◂— 0xfbad2088
*RBX 0x7fffffffe218 —▸ 0x7fffffffe4c8 ◂— '/home/amemoyoi/1day_2pwn/Notepad'
*RCX 0
*RDX 2
*RDI 0x7ffff7e038e0 (_IO_2_1_stdin_) ◂— 0xfbad2088
*RSI 0
*R8 0x400c80 (__libc_csu_fini) ◂— repz retq
*R9 0x7ffff7fca380 (_dl_fini) ◂— endbr64
R10 0x7ffff7c0a0c0 ◂— 0x11002200003bac
*R11 0x7ffff7c88550 (setvbuf) ◂— endbr64
R12 1
R13 0
R14 0
R15 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0
RBP 0x7fffffffdf00 —▸ 0x7fffffffe0f0 —▸ 0x7fffffffe190 —▸ 0x7fffffffe1f0 ◂— 0
*RSP 0x7fffffffdef8 —▸ 0x400918 (Initalize+34) ◂— movq 0x201781(%rip), %rax
*RIP 0x7ffff7fda3aa (_dl_runtime_resolve_xsavec+186) ◂— jmpq *%r11
───────────────────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────────────────────────────────────────
► 0x7ffff7fda3aa <_dl_runtime_resolve_xsavec+186> jmpq *%r11 <setvbuf>
↓
R11 레지스터에 넣은뒤 jmp로 그냥 바로 실행시키는 것을 볼 수 있다.