1byte overwrtie in RA & libc_start_main gadget

일반적으로 main() 함수에서 RA는

__libc_start_call_main+122 이런식으로 libc의 영역을 가리키는데 이때 만약 RA에 대해서 1바이트 overwrite가 가능하면, 어떤게 가능해질까?

일단

C
pwndbg> disass __libc_start_call_main
Dump of assembler code for function __libc_start_call_main:
   0x00007ffff7dd5150 <+0>:	push   %rbp
   0x00007ffff7dd5151 <+1>:	mov    %rsp,%rbp
   0x00007ffff7dd5154 <+4>:	sub    $0x90,%rsp
   0x00007ffff7dd515b <+11>:	mov    %rdi,-0x78(%rbp)
   0x00007ffff7dd515f <+15>:	lea    -0x70(%rbp),%rdi
   0x00007ffff7dd5163 <+19>:	mov    %esi,-0x7c(%rbp)
   0x00007ffff7dd5166 <+22>:	mov    %rdx,-0x88(%rbp)
   0x00007ffff7dd516d <+29>:	mov    %fs:0x28,%rax
   0x00007ffff7dd5176 <+38>:	mov    %rax,-0x8(%rbp)
   0x00007ffff7dd517a <+42>:	xor    %eax,%eax
   0x00007ffff7dd517c <+44>:	call   0x7ffff7deffa0 <__GI__setjmp>
   0x00007ffff7dd5181 <+49>:	endbr64
   0x00007ffff7dd5185 <+53>:	test   %eax,%eax
   0x00007ffff7dd5187 <+55>:	jne    0x7ffff7dd51d1 <__libc_start_call_main+129>
   0x00007ffff7dd5189 <+57>:	mov    %fs:0x300,%rax
   0x00007ffff7dd5192 <+66>:	mov    %rax,-0x28(%rbp)
   0x00007ffff7dd5196 <+70>:	mov    %fs:0x2f8,%rax
   0x00007ffff7dd519f <+79>:	mov    %rax,-0x20(%rbp)
   0x00007ffff7dd51a3 <+83>:	lea    -0x70(%rbp),%rax
   0x00007ffff7dd51a7 <+87>:	mov    %rax,%fs:0x300
   0x00007ffff7dd51b0 <+96>:	mov    0x1d8de9(%rip),%rax        # 0x7ffff7fadfa0
   0x00007ffff7dd51b7 <+103>:	mov    -0x88(%rbp),%rsi
   0x00007ffff7dd51be <+110>:	mov    -0x7c(%rbp),%edi
   0x00007ffff7dd51c1 <+113>:	mov    (%rax),%rdx
   0x00007ffff7dd51c4 <+116>:	mov    -0x78(%rbp),%rax
=> 0x00007ffff7dd51c8 <+120>:	call   *%rax
   0x00007ffff7dd51ca <+122>:	mov    %eax,%edi
   0x00007ffff7dd51cc <+124>:	call   0x7ffff7df2ba0 <__GI_exit>
   0x00007ffff7dd51d1 <+129>:	call   0x7ffff7e44290 <__GI___nptl_deallocate_tsd>
   0x00007ffff7dd51d6 <+134>:	lock subl $0x1,0x1d8ef2(%rip)        # 0x7ffff7fae0d0 <__nptl_nthreads>
   0x00007ffff7dd51de <+142>:	je     0x7ffff7dd51f0 <__libc_start_call_main+160>
   0x00007ffff7dd51e0 <+144>:	mov    $0x3c,%edx
   0x00007ffff7dd51e5 <+149>:	nopl   (%rax)
   0x00007ffff7dd51e8 <+152>:	xor    %edi,%edi
   0x00007ffff7dd51ea <+154>:	mov    %edx,%eax
   0x00007ffff7dd51ec <+156>:	syscall
   0x00007ffff7dd51ee <+158>:	jmp    0x7ffff7dd51e8 <__libc_start_call_main+152>
   0x00007ffff7dd51f0 <+160>:	xor    %eax,%eax
   0x00007ffff7dd51f2 <+162>:	jmp    0x7ffff7dd51ca <__libc_start_call_main+122>

__libc_start_call_main은 다음과 같은 구조를 뛰는데 1바이트 overwrite로 이 함수함에 모든 부분을 가젯으로 쓸 수 있다.

우리가 이번에 주목해야 하는 가젯은

C
   0x00007ffff7dd51b0 <+96>:	mov    0x1d8de9(%rip),%rax        # 0x7ffff7fadfa0
   0x00007ffff7dd51b7 <+103>:	mov    -0x88(%rbp),%rsi
   0x00007ffff7dd51be <+110>:	mov    -0x7c(%rbp),%edi
   0x00007ffff7dd51c1 <+113>:	mov    (%rax),%rdx
   0x00007ffff7dd51c4 <+116>:	mov    -0x78(%rbp),%rax
=> 0x00007ffff7dd51c8 <+120>:	call   *%rax

이 부분인데, 바로 실제로 main 함수가 실행이 되는 부분이다. 근데 이 부분을 돌려보면서 놀란게 우리가 스택을 보다보면, main함수의 시작 주소가 스택에 높은 주소에 항상 있던것을 볼 수 있었는데 그 스택에서 가져와서 call *%rax 으로 실행을 시키고 있었다.

즉, main()함수가 시작되기전에 rsp와 동일하다면, 이 가젯을 실행시켰을때

C
   0x00007ffff7dd51b0 <+96>:	mov    0x1d8de9(%rip),%rax        # 0x7ffff7fadfa0

이 부분에서 environ을 참조하고 기본적으로 환경변수들이 스택에 이미 있기 때문에 정상적으로 다시 main을 부를 수 있다. 심지어 스택에 __libc_start_call_main 와 같은 RA를 넣으면서 말이다.

발동조건은 다시한번 말하지만, 본래 main 함수가 실행되기전처럼 스택만 맟추면 된다. main 함수에서 bof로 rbp를 덮을때 이 rbp를 다시 복원을 해준다면, leave에서 정상화를 해주므로 rbp 복원만 하면 정상적으로 이 가젯을 사용할 수 있다.