how program start
로더, libc.. 정말 많은 정보들이 많다. 그럼 이런 질문을 받을 수 있다.
프로그램이 리눅스에서 정확히 어떤 방식으로 실행되는 거지?
아마 그럼 이렇게 답할 것이다.
_start → _libc_start_main → main 으로 시작된다.
당연히 맞는 소리이다. 하지만, 좀더 자세히 말하고 싶지 않나?
이번에는 그 질문에 대한 답을 자세히 알아가보자
사실 이 글이 쓰여진 원인은 심볼에 대해 공부하다가 시작되었다.
우리가 gdb를 쓸때 심볼이 stripe 되어있으면, main 심볼이 있지 않아서 gdb가 main에서 시작하지 않고 함수를 볼 수 없어서 불편하다. 그런데 프로그램은 어떻게 심볼이 없는데 main함수 위치를 알 수 있지? 라는 생각에서 시작되었다.
처음부터 차근차근 가보자면, ELF 파일에는 entry point라는 개념이 있다.
pwndbg> info files
Symbols from "/home/amemoyoi/1day_2pwn/test".
Local exec file:
`/home/amemoyoi/1day_2pwn/test', file type elf64-x86-64.
Entry point: 0x1060
0x0000000000000318 - 0x0000000000000334 is .interp
0x0000000000000338 - 0x0000000000000368 is .note.gnu.property
0x0000000000000368 - 0x000000000000038c is .note.gnu.build-id
0x000000000000038c - 0x00000000000003ac is .note.ABI-tag
0x00000000000003b0 - 0x00000000000003d4 is .gnu.hash
0x00000000000003d8 - 0x0000000000000480 is .dynsym
0x0000000000000480 - 0x000000000000050d is .dynstr
0x000000000000050e - 0x000000000000051c is .gnu.version
0x0000000000000520 - 0x0000000000000550 is .gnu.version_r
0x0000000000000550 - 0x0000000000000610 is .rela.dyn
0x0000000000000610 - 0x0000000000000628 is .rela.plt
0x0000000000001000 - 0x000000000000101b is .init
0x0000000000001020 - 0x0000000000001040 is .plt
0x0000000000001040 - 0x0000000000001050 is .plt.got
0x0000000000001050 - 0x0000000000001060 is .plt.sec
0x0000000000001060 - 0x0000000000001167 is .text
0x0000000000001168 - 0x0000000000001175 is .fini
0x0000000000002000 - 0x0000000000002011 is .rodata
0x0000000000002014 - 0x0000000000002048 is .eh_frame_hdr
0x0000000000002048 - 0x00000000000020f4 is .eh_frame
0x0000000000003db8 - 0x0000000000003dc0 is .init_array
0x0000000000003dc0 - 0x0000000000003dc8 is .fini_array
0x0000000000003dc8 - 0x0000000000003fb8 is .dynamic
0x0000000000003fb8 - 0x0000000000004000 is .got
0x0000000000004000 - 0x0000000000004010 is .data
0x0000000000004010 - 0x0000000000004018 is .bss
pwndbg>
gdb로 프로그램을 시작하기 전에 Info files 명령어로 한번보면
Entry point: 0x1060 라는 값이 있다. 이값은 프로그램이 시작하면
pwndbg> info files
Symbols from "/home/amemoyoi/1day_2pwn/test".
Native process:
Using the running image of child process 2250082.
While running this, GDB does not access memory from...
Local exec file:
`/home/amemoyoi/1day_2pwn/test', file type elf64-x86-64.
Entry point: 0x555555555060
0x0000555555554318 - 0x0000555555554334 is .interp
0x0000555555554338 - 0x0000555555554368 is .note.gnu.property
0x0000555555554368 - 0x000055555555438c is .note.gnu.build-id
0x000055555555438c - 0x00005555555543ac is .note.ABI-tag
0x00005555555543b0 - 0x00005555555543d4 is .gnu.hash
0x00005555555543d8 - 0x0000555555554480 is .dynsym
0x0000555555554480 - 0x000055555555450d is .dynstr
0x000055555555450e - 0x000055555555451c is .gnu.version0x555555555060 다음의 주소로 바뀌는데 이 주소는
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
Start End Perm Size Offset File
0x555555554000 0x555555555000 r--p 1000 0 /home/amemoyoi/1day_2pwn/test
0x555555555000 0x555555556000 r-xp 1000 1000 /home/amemoyoi/1day_2pwn/test
0x555555556000 0x555555557000 r--p 1000 2000 /home/amemoyoi/1day_2pwn/test
0x555555557000 0x555555558000 r--p 1000 2000 /home/amemoyoi/1day_2pwn/test
0x555555558000 0x555555559000 rw-p 1000 3000 /home/amemoyoi/1day_2pwn/test프로그램의 base 주소 + 원래 entry point 값을 합친값이 된다.
그리고 이 위치는
pwndbg> disass 0x555555555060
Dump of assembler code for function _start:
0x0000555555555060 <+0>: endbr64
0x0000555555555064 <+4>: xor %ebp,%ebp
0x0000555555555066 <+6>: mov %rdx,%r9
0x0000555555555069 <+9>: pop %rsi
0x000055555555506a <+10>: mov %rsp,%rdx
0x000055555555506d <+13>: and $0xfffffffffffffff0,%rsp
0x0000555555555071 <+17>: push %rax
0x0000555555555072 <+18>: push %rsp
0x0000555555555073 <+19>: xor %r8d,%r8d
0x0000555555555076 <+22>: xor %ecx,%ecx
0x0000555555555078 <+24>: lea 0xca(%rip),%rdi # 0x555555555149 <main>
0x000055555555507f <+31>: call *0x2f53(%rip) # 0x555555557fd8
0x0000555555555085 <+37>: hlt
End of assembler dump._start 함수의 위치이다. 즉, ELF 파일에는 헤더의 entry point 값이 존재하며 이 값은 _start 함수의 offset이다.

ida로도 헤더 부분에 Entry point 값이 있는 것을 알 수 있다.
그럼, 이제 _start 함수를 보자면
pwndbg> disass 0x555555555060
Dump of assembler code for function _start:
0x0000555555555060 <+0>: endbr64
0x0000555555555064 <+4>: xor %ebp,%ebp
0x0000555555555066 <+6>: mov %rdx,%r9
0x0000555555555069 <+9>: pop %rsi
0x000055555555506a <+10>: mov %rsp,%rdx
0x000055555555506d <+13>: and $0xfffffffffffffff0,%rsp
0x0000555555555071 <+17>: push %rax
0x0000555555555072 <+18>: push %rsp
0x0000555555555073 <+19>: xor %r8d,%r8d
0x0000555555555076 <+22>: xor %ecx,%ecx
0x0000555555555078 <+24>: lea 0xca(%rip),%rdi # 0x555555555149 <main>
0x000055555555507f <+31>: call *0x2f53(%rip) # 0x555555557fd8
0x0000555555555085 <+37>: hlt
End of assembler dump.다음과 같다.
이거의 ida코드는
void __fastcall __noreturn start(__int64 a1, __int64 a2, void (*a3)(void))
{
__int64 v3; // rax
int v4; // esi
__int64 v5; // [rsp-8h] [rbp-8h] BYREF
char *retaddr; // [rsp+0h] [rbp+0h] BYREF
v4 = v5;
v5 = v3;
_libc_start_main((int (*)(int, char **, char **))main, v4, &retaddr, 0LL, 0LL, a3, &v5);
__halt();
}다음과 같다. 일단 우리는 위에 부분은 다른 곳에서 다루고 아래에 있는
_libc_start_main 에 집중해 보자
_libc_start_main 함수의 첫번째 인자로 main 함수의 위치가 들어간다.
그렇다면 $rdi로 들어간다는 것인데
0x0000555555555078 <+24>: lea 0xca(%rip),%rdi 다음과 같이 이미 컴파일러 입장에서는 _start 함수내에 있을 현재 rip와 offset을 통해 main 함수를 넣는다.
.init_array에 대해서도 정리 해야할듯?
_start
└──→ __libc_start_main(main, ...)
├──→ 실행 환경 초기화 (libc 내부 처리)
├──→ .init_array에 등록된 함수들 호출 👈
└──→ main() 호출