System Hacking

main 함수가 호출, 종료되는 과정(.init_array&.fini_array)

jir4vvit 2021. 6. 3. 15:10
참고 : https://wogh8732.tistory.com/228
        https://dreamhack.io/learn/11#8
        https://rninche01.tistory.com/entry/Linux-Binary-Execution-Flow
분석 대상 바이너리 : pwnable.xyz의 Dirty Turtle문제 바이너리

fini_array 가 뭔지 알아보려고 위 블로그를 토대로 main함수가 호출, 종료되는 과정을 살펴보았다. 


1. ELF 헤더 확인

Entry point(EP) : 코드가 시작될 때 실행이 시작되는 파일, 프로그램의 시작점 또는 그 영역

Entry point 주소를 IDA에서 찾아보면 _start 함수임을 알 수 있다.

 

프로그램을 실행하면, _start 함수가 맨 처음에 호출이 된다.

 

 

2. _start 함수

_start 함수에서는 _libc_start_main함수를 호출한다.

이 함수는 바이너리 실행 과정에 필요한 여러 요소들을 초기화하기 위해 호출이 된다.

이 함수는 libc 안에 존재하는 함수이기 때문에, plt를 뒤져 got table에 등록을 한 다음, 호출이 된다.

 

3. _libc_start_main 함수

_libc_start_main 함수 내부에서 __libc_csu_init 을 호출한다.

 

3.1 __libc_csu_init 함수

size는 8 (init_array 크기)

size만큼 반복문을 돌면서 init_array에 저장된 함수 포인터들을 호출한다.

 

gdb로 보면 frame_dummy란 값으로 들어있다.

바로 이부분이 호출하는 과정..

그런데 이 바이너리는 하나만 호출하고 끝이 나더라.

 

 

그러고 다시 __libc_start_main으로 돌아간다.

여기서 이제 main이 호출이 된다!!

 

 

 

4. main이 종료되는 과정

main함수에서 ret를 하게 되면 __libc_start_main+231 로 간다.

 

그리고 __libc_start_main에서 exit 함수를 호출하게 된다. (드림핵에서는 __GI_exit 함수라고 한다.)

여기 인자로 0x0가 되어 있는 것을 확인할 수 있는데,

우리가 흔히 .c파일을 작성할 때 main() 마지막에 return 0; 으로 작성하는데 여기서 return값 0이 exit()의 파라미터로 들어가게 되는 것이라고 한다.

 

 

exit 함수 내부에서는 __run_exit_handlers를 호출한다.

 

__run_exit_handlers 함수 내부에서는 _dl_fini를 호출한다.

(__run_exit_handlers 함수는 exit_function 구조체 멤버 변수인 flavor 값에 따라서 함수를 호출하게 되는데, 기본 상태에서는 로더 라이브러리 내부에 존재하는 _dl_fini 함수를 호출한다고 한다.

 

_dl_fini 함수 내부에서 .fini_array 섹션을 호출한다.

(.fini_array배열에 들어있는 __do_global_dtors_aux()를 호출하는 것을 확인할 수 있다.)

(내가 .fini_array 주소에 0x41414141를 넣어놔서 0x41414141이 호출되는 것을 확인할 수 있다.)

 

 

.fini_array배열에 있는 함수들을 호출 한 후

 

다시 _dl_fini()로 돌아와 _fini()를 호출한다. (여기서는 딱히 하는 일이 없다.)

_dl_fini()가 리턴되고 다시 __run_exit_handlers()루틴으로 돌아오면 __GI__exit()를 호출하는데, 이는 앞서 이야기했던(드림핵에서는 _GI_exit()를 호출한다고 설명했던..) 것과는 다른 함수이다.

 

__GI__exit()에서는 rax값을 0xe7, rdi를 0x0으로 세팅하고

syscall하면서 이제 완전히 프로세스가 종료된다고 한다.

 

 

5. 정리

  1.  elf파일의 entry point가 가리키는 _start()부터 호출하여 시작하게 된다.
  2.  _start루틴(crt(C run time))에서는 커널로부터 받은 argc, argv인자를 저장하고 스택을 초기화한 후 glibc내에 정의된 __libc_start_main()를 호출한다.
  3.  __libc_start_main()에서는 .init / .fini섹션 작업과 관련된 함수들을 호출하고 메인함수를 호출한다.(.init → main() → exit())

.fini_array는 프로그램이 종료된 후 참조되는 섹션이다.

따라서 RELRO 보호기법이 설정되어 있지 않고 임의 주소 쓰기가 가능하다면, .fini_array 섹션을 덮어 실행 흐름을 조작하는 것이 가능하다.

 

 

'System Hacking' 카테고리의 다른 글

type confusion  (0) 2021.06.28
Double Staged FSB  (0) 2021.05.30
integer overflow 2  (0) 2021.05.19
Sigreturn-oriented programming (SROP)  (0) 2021.05.10
Stack Pivoting (스택 피봇팅)  (0) 2021.05.03