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 복원만 하면 정상적으로 이 가젯을 사용할 수 있다.