Return to csu

2022. 9. 5. 18:20·Tips & theory
728x90
반응형

최근에 꽤나 재미있는 기법을 알게되어 그 기록을 남긴다.

느낀점은 결국 ret address 변조가 가능하면 니 컴퓨터 = 내 컴퓨터

 

1. 서론

만일 rop 시 적절한 gadget이 구해지지 않는 경우에는 어떻게 하면 좋을까?

예를 들면 read 함수에서 rsi 값은 입력 받는 크기를 가지고 있는데, 이 값이 항상 0이고 pop rsi gadget이 없다면 어떻게 해야할까.

이런 경우에 return to csu 기법을 사용할 수 있다.

 

2. 원리

2.1. 기본 원리

사실 프로그램을 실행하면 main 함수부터 실행되는 것 같지만, 그 전에 실행되는 함수들이 있다.

이는 프로그램 실행 전, 실행을 위한 일련의 동작인데 예를 들자면 코드를 메모리에 넘겨주거나 인자를 넘겨주거나 하는 역할을 한다.

이에 대부분의 프로그램 (전부 다는 아니다)은 기본적으로

  • _start
  • __libc_start_main
    • __libc_csu_init
    • __ main
  • _fini

의 흐름으로 실행된다.

즉, __libc_csu_init 함수는 거의 무조건 포함된다는 것이다.

문제는 이 함수 내에서 레지스터를 변조할 수 있는 어셈블러 코드가 있고, 이를 활용해 특정 함수 실행이 가능해진다.

 

2.2. 상세 구조

해당 함수의 코드는 아래와 같다.

__libc_csu_init (int argc, char **argv, char **envp)
{
  /* For dynamically linked executables the preinit array is executed by
     the dynamic linker (before initializing any shared object).  */
#ifndef LIBC_NONSHARED
  /* For static executables, preinit happens right before init.  */
  {
    const size_t size = __preinit_array_end - __preinit_array_start;
    size_t i;
    for (i = 0; i < size; i++)
      (*__preinit_array_start [i]) (argc, argv, envp);
  }
#endif
#ifndef NO_INITFINI
  _init ();
#endif
  const size_t size = __init_array_end - __init_array_start;
  for (size_t i = 0; i < size; i++)
      (*__init_array_start [i]) (argc, argv, envp);
}

사실 이건 그냥 보여주기 위한 것이고, 실질적으로 중요한 부분은 아래이다.

아무 파일이나 gdb로 열어 function을 보았다.

gef➤  info functions 
All defined functions:

Non-debugging symbols:
0x0000000000400548  _init
0x0000000000400570  puts@plt
0x0000000000400580  __stack_chk_fail@plt
0x0000000000400590  printf@plt
0x00000000004005a0  read@plt
0x00000000004005b0  setvbuf@plt
0x00000000004005c0  _start
0x00000000004005f0  _dl_relocate_static_pie
0x0000000000400600  deregister_tm_clones
0x0000000000400630  register_tm_clones
0x0000000000400670  __do_global_dtors_aux
0x00000000004006a0  frame_dummy
0x00000000004006a7  main
0x0000000000400790  __libc_csu_init
0x0000000000400800  __libc_csu_fini
0x0000000000400804  _fini

이와 같이 __libc_csu_init 함수가 포함된 것을 볼 수 있다.

해당 위치의 어셈블러 코드를 보면 아래와 같다.

gef➤  x/40i 0x0000000000400790
   0x400790 <__libc_csu_init>:  push   %r15
   0x400792 <__libc_csu_init+2>:        push   %r14
   0x400794 <__libc_csu_init+4>:        mov    %rdx,%r15
   0x400797 <__libc_csu_init+7>:        push   %r13
   0x400799 <__libc_csu_init+9>:        push   %r12
   0x40079b <__libc_csu_init+11>:       lea    0x20066e(%rip),%r12        # 0x600e10
   0x4007a2 <__libc_csu_init+18>:       push   %rbp
   0x4007a3 <__libc_csu_init+19>:       lea    0x20066e(%rip),%rbp        # 0x600e18
   0x4007aa <__libc_csu_init+26>:       push   %rbx
   0x4007ab <__libc_csu_init+27>:       mov    %edi,%r13d
   0x4007ae <__libc_csu_init+30>:       mov    %rsi,%r14
   0x4007b1 <__libc_csu_init+33>:       sub    %r12,%rbp
   0x4007b4 <__libc_csu_init+36>:       sub    $0x8,%rsp
   0x4007b8 <__libc_csu_init+40>:       sar    $0x3,%rbp
   0x4007bc <__libc_csu_init+44>:       call   0x400548 <_init>
   0x4007c1 <__libc_csu_init+49>:       test   %rbp,%rbp
   0x4007c4 <__libc_csu_init+52>:       je     0x4007e6 <__libc_csu_init+86>
   0x4007c6 <__libc_csu_init+54>:       xor    %ebx,%ebx
   0x4007c8 <__libc_csu_init+56>:       nopl   0x0(%rax,%rax,1)
   0x4007d0 <__libc_csu_init+64>:       mov    %r15,%rdx
   0x4007d3 <__libc_csu_init+67>:       mov    %r14,%rsi
   0x4007d6 <__libc_csu_init+70>:       mov    %r13d,%edi
   0x4007d9 <__libc_csu_init+73>:       call   *(%r12,%rbx,8)
   0x4007dd <__libc_csu_init+77>:       add    $0x1,%rbx
   0x4007e1 <__libc_csu_init+81>:       cmp    %rbx,%rbp
   0x4007e4 <__libc_csu_init+84>:       jne    0x4007d0 <__libc_csu_init+64>
   0x4007e6 <__libc_csu_init+86>:       add    $0x8,%rsp
   0x4007ea <__libc_csu_init+90>:       pop    %rbx
   0x4007eb <__libc_csu_init+91>:       pop    %rbp
   0x4007ec <__libc_csu_init+92>:       pop    %r12
   0x4007ee <__libc_csu_init+94>:       pop    %r13
   0x4007f0 <__libc_csu_init+96>:       pop    %r14
   0x4007f2 <__libc_csu_init+98>:       pop    %r15
   0x4007f4 <__libc_csu_init+100>:      ret

 

2.3. 분석

우리가 관심을 가져야 하는 부분은 아래 부분이고, 조금 더 세밀하게 나누면 두개의 파트로 나눌 수 있다.

...
   0x4007d0 <__libc_csu_init+64>:       mov    %r15,%rdx
   0x4007d3 <__libc_csu_init+67>:       mov    %r14,%rsi
   0x4007d6 <__libc_csu_init+70>:       mov    %r13d,%edi
   0x4007d9 <__libc_csu_init+73>:       call   *(%r12,%rbx,8)
   0x4007dd <__libc_csu_init+77>:       add    $0x1,%rbx
   0x4007e1 <__libc_csu_init+81>:       cmp    %rbx,%rbp
   0x4007e4 <__libc_csu_init+84>:       jne    0x4007d0 <__libc_csu_init+64>
   -------------------------------------------------------------------------
   0x4007e6 <__libc_csu_init+86>:       add    $0x8,%rsp
   0x4007ea <__libc_csu_init+90>:       pop    %rbx
   0x4007eb <__libc_csu_init+91>:       pop    %rbp
   0x4007ec <__libc_csu_init+92>:       pop    %r12
   0x4007ee <__libc_csu_init+94>:       pop    %r13
   0x4007f0 <__libc_csu_init+96>:       pop    %r14
   0x4007f2 <__libc_csu_init+98>:       pop    %r15
   0x4007f4 <__libc_csu_init+100>:      ret
...

 

위쪽 파트를 보통 stage 2, 아래쪽 파트를 stage 1이라 말한다.

 

2.3.1. stage 1

이 부분은 스택의 값을 레지스터로 삽입하는 역할을 한다.

   0x4007e6 <__libc_csu_init+86>:       add    $0x8,%rsp
   0x4007ea <__libc_csu_init+90>:       pop    %rbx
   0x4007eb <__libc_csu_init+91>:       pop    %rbp
   0x4007ec <__libc_csu_init+92>:       pop    %r12
   0x4007ee <__libc_csu_init+94>:       pop    %r13
   0x4007f0 <__libc_csu_init+96>:       pop    %r14
   0x4007f2 <__libc_csu_init+98>:       pop    %r15
   0x4007f4 <__libc_csu_init+100>:      ret

만일 스택에 아래와 같이 값이 삽입되어있다고 가정하자.

낮은 주소
AAAAAAAA
BBBBBBBB
CCCCCCCC
DDDDDDDD
EEEEEEEE
FFFFFFFF
GGGGGGGG
높은 주소

이는 위의 코드가 실행되면서 아래와 같이 레지스터에 값이 삽입되게 된다.

낮은 주소 stage 1
XXXXXXXX add $0x8, %rsp로 인해 무시됨.
AAAAAAAA rbx
BBBBBBBB rbp
CCCCCCCC r12
DDDDDDDD r13
EEEEEEEE r14
FFFFFFFF r15
GGGGGGGG ret
높은 주소 -

이후 GGGGGGGG의 위치로 return 하게 되는데, 만일 GGGGGGGG의 값이 0x4007d0라면 stage 2로 진입하게 될 것이다.

 

2.3.2. stage 2

이 부분에서는 각 레지스터의 값을 참조로 함수를 실행하는 역할을 한다.

   0x4007d0 <__libc_csu_init+64>:       mov    %r15,%rdx
   0x4007d3 <__libc_csu_init+67>:       mov    %r14,%rsi
   0x4007d6 <__libc_csu_init+70>:       mov    %r13d,%edi
   0x4007d9 <__libc_csu_init+73>:       call   *(%r12,%rbx,8)
   0x4007dd <__libc_csu_init+77>:       add    $0x1,%rbx
   0x4007e1 <__libc_csu_init+81>:       cmp    %rbx,%rbp
   0x4007e4 <__libc_csu_init+84>:       jne    0x4007d0 <__libc_csu_init+64>

위의 값을 다시 가져오고, 0x4007d6 위치까지 실행되면 아래와 같은 모양이 될 것이다.

낮은 주소 stage 1 stage 2
XXXXXXXX add $0x8, %rsp로 인해 무시됨.  
AAAAAAAA rbx  
BBBBBBBB rbp  
CCCCCCCC r12  
DDDDDDDD r13 edi (4 byte만 삽입됨)
EEEEEEEE r14 rsi
FFFFFFFF r15 rdx
 0x4007d0 ret  
높은 주소 -  

이후  0x4007dd에서 r12 + rbx x 8에 담긴 주소를 call 하게된다.

이 때 만일 rbx인 AAAAAAAA가 0이고 r12인 CCCCCCCC가 put 함수의 got 이라면 *put_got 위치의 값을 call하게 될 것이고 put 함수가 실행될 것이다.

낮은 주소 stage 1 stage 2
XXXXXXXX add $0x8, %rsp로 인해 무시됨.  
0 rbx  
BBBBBBBB rbp  
puts_got r12  
DDDDDDDD r13 edi (4 byte만 삽입됨)
EEEEEEEE r14 rsi
FFFFFFFF r15 rdx
 0x4007d0 ret  
높은 주소 -  

더불어 함수 호출 규약에 따라 rdi, rsi, rdx 레지스터의 값은 puts 함수의 인자로 사용되며,

함수 호출 규약. — think storage (tistory.com)

 

함수 호출 규약.

레지스터에 대한 기초를 이해하고 난 뒤에 봐야하는 글. https://wyv3rn.tistory.com/144 system hacking을 register 기초 1. register란 간단히 요약하자면, 프로그램이 실행되면 기본적으로 ram에 필요한 데이터..

wyv3rn.tistory.com

이 때 만일 rdi가 read_got 이라면 이를 출력하려 할 것이다.

낮은 주소 stage 1 stage 2
XXXXXXXX add $0x8, %rsp로 인해 무시됨.  
0 rbx call *(%r12, %rbx, 8)의 연산에 사용됨.
BBBBBBBB rbp  
puts_got r12 call *(%r12, %rbx, 8)에 의해 호출됨.
read_got r13 edi (4 byte만 삽입됨)
EEEEEEEE r14 rsi
FFFFFFFF r15 rdx
 0x4007d0 ret  
높은 주소 -  

다시 남은 어셈블러 코드를 보면 아래와 같다.

   0x4007dd <__libc_csu_init+77>:       add    $0x1,%rbx
   0x4007e1 <__libc_csu_init+81>:       cmp    %rbx,%rbp
   0x4007e4 <__libc_csu_init+84>:       jne    0x4007d0 <__libc_csu_init+64>

이제 rbx에 1을 넣고, rbp와 비교해서 다르면 0x4007d0 즉 stage2로 돌아가고 그게 아니라면 이후에 이어지는 stag1을 계속 실행할 것이다.

즉 최종적으로 아래와 같은 모양이 된다.

낮은 주소 stage 1 stage 2
XXXXXXXX add $0x8, %rsp로 인해 무시됨.  
0 rbx call *(%r12, %rbx, 8)의 연산에 사용됨.
1 rbp cmp %rbx, %bp 연산에 사용됨.
puts_got r12 call *(%r12, %rbx, 8)에 의해 호출됨.
read_got r13 edi (4 byte만 삽입됨)
EEEEEEEE r14 rsi
FFFFFFFF r15 rdx
 0x4007d0 ret  
높은 주소 -  

 

2.3.3. 전체 작동 원리

앞서 stage 1에서는 각 레지스터에 값을, stage 2에서는 그 레지스터의 값을 참조로 함수를 실행할 수 있음을 확인하였다.

즉, stage 1에서 삽입된 값을 테이블화 하면 아래와 같이 사용됨을 알 수 있다.

stage 1 stage 2 삽입 되어야할 값 비고
add $0x8, %rsp - padding 8 byte. dummy 값임.
rbx rbx 0 항상 0이어야 r12 주소의 값을 실행할 수 있음.
rbp rbp 1 항상 1이어야 chainning이 가능해짐.
r12 call 실행시킬 함수 주소  
r13 edi argv0 4 byte만 사용할 수 있음. 유의할 것.
r14 rsi argv1  
r15 rdx argv2  
ret ret return address  

 

2.4. 유의할 점 및 단점.

2.4.1. 유의할 점

edi 값은 4 byte만 사용 가능한 경우가 대부분이다.

그러므로 인자로 사용 가능한 값이 제한된다.

이를 우회하기 위해 첫 rtc 시 read 함수를 통해 bss 영역에 값을 쓰고, 해당 값을 edi로 읽어들이는 방법이 있다.

 

2.4.2. 단점

csu의 작동 구조 상 ret address overwrite 후 8 x 8 byte 값을 넣어야 다시 한번 csu 함수 내의 ret address에 도달할 수 있기에 main 함수에서 할당된 buffer의 크기가 커야 한다는 것이다.

하지만 한번이라도 도달할 수 있다면

  • csu 에서 return 시 main 으로 다시 돌아가던지,
  • stack address leak 후 main 함수의 입력 받는 값의 크기를 키워버리거나
  • bss 영역에 shellcode를 삽입하여 해당 위치로 돌아가거나

하는 방법으로 사용 가능하다.

또한 그 구조 상 3개 이상의 인자를 삽입할 수 없다.
(사실 3개 이상 인자를 사용할 함수가 없기에 단점 아닌 단점이다.)

 

가장 좋은 방법은 csu로 libc base만 구하고, main 함수로 돌아가 return 시 onegadget 주소로 return 해버리면 된다.

 

3. 결론

여러 보호 기법으로 인해 rop을 해야할 것 같은데 적절한 gadget이 찾아지지 않는 경우 차선책으로 rtc를 고려해볼 수 있다.

다만 프로그램마다 __libc_csu_init 함수의 어셈블러 코드는 조금씩 다를 수 있기에 직접 확인해봐야한다.

더불어 제약 조건도 꽤나 까다롭다.

그래도 __libc_csu_init 함수는 프로그램의 코드 영역에 존재하기에 언제든 사용할 수 있다.

728x90
반응형
저작자표시 비영리 변경금지 (새창열림)

'Tips & theory' 카테고리의 다른 글

docker <-> host 파일 전송  (0) 2022.09.06
system hacking을 위한 docker 설치 및 사용법  (0) 2022.09.05
one gadget 사용법  (0) 2022.09.05
함수 호출 규약.  (0) 2022.09.05
system hacking을 register 기초  (0) 2022.09.05
'Tips & theory' 카테고리의 다른 글
  • docker <-> host 파일 전송
  • system hacking을 위한 docker 설치 및 사용법
  • one gadget 사용법
  • 함수 호출 규약.
wyv3rn
wyv3rn
아저씨의 흔한 취미. wyv3rn#1249
  • wyv3rn
    think storage
    wyv3rn
  • 전체
    오늘
    어제
    • 분류 전체보기 (500) N
      • To do list (7) N
        • Doing (1) N
        • Complete (6)
      • Diary (35)
      • Tips & theory (77)
      • Kernel Exploit (27) N
        • Theory (15)
        • Exercise (5) N
      • Wargame (313)
        • pwn.college (34)
        • Dreamhack (148)
        • pwnable.kr (15)
        • Lord of Sqlinjection (3)
        • Cryptohack (20)
        • Root me (27)
        • CodeEngn (4)
        • Exploit Education (22)
        • ROP Emporium (8)
        • H4C (10)
        • Hackerchool (22)
      • CTF (41) N
        • Solved (39) N
        • Unsolved (2)
      • Script (0)
  • 블로그 메뉴

    • 홈
    • 방명록
  • 링크

  • 공지사항

    • PWN wargame 모음 (및 느낀점)
    • 비공개 글들에 대해.
    • 뭐라도 하나 얻어가시길...
  • 인기 글

  • 태그

    ROOT ME
    exploit education
    rop
    dreamhack
    hackerschool
    cryptohack
    _IO_FILE
    RTL
    heap
    vtable
    x64
    libc
    64bit
    CANARY
    pwntools
    Me
    pwnable.kr
    root
    phoenix
    lob
    la ctf
    x86
    root-me
    tcache
    Buffer Overflow
    docker
    FSB
    BOF
    32bit
    Format String Bug
  • 최근 댓글

  • 최근 글

  • 250x250
    반응형
  • hELLO· Designed By정상우.v4.10.3
wyv3rn
Return to csu
상단으로

티스토리툴바