C - 라이브러리

markyang92·2024년 3월 13일
0

C

목록 보기
7/10

라이브러리

  • '함수들'을 기계어로 변환 후, 파일 하나로 저장해 놓은 것(마치 바이너리 처럼..)
  • 실행파일이 아니기 때문에, main 함수가 필요 없다.
  • .c 파일에서 이 기능이 필요할 때 같이 링크해서 쓸 수 있다.

libc

  • /lib/x86_64-linux-gnu/libc.so.6에 존재

표준 라이브러리 경로

$ ld --verbose | grep SEARCH_DIR | tr -s ' ;' '\n'
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu")
SEARCH_DIR("=/lib/x86_64-linux-gnu")
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu")
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64")
SEARCH_DIR("=/usr/local/lib64")
SEARCH_DIR("=/lib64")
SEARCH_DIR("=/usr/lib64")
SEARCH_DIR("=/usr/local/lib")
SEARCH_DIR("=/lib")
SEARCH_DIR("=/usr/lib")
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64")
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib")

static 라이브러리

  • static 라이브러리와 링크하는 것을 정적 링킹
  • 라이브러리 안에 있는 '기계어'를 최종 실행 파일에 가져다 복사한다.
  • dynamic 링킹에 비해
    • 실행 파일의 크기가 커진다.
    • 메모리를 더 잡아먹을 수 있다.
    • 실행 속도가 빠르다.
  • 컴파일 옵션에 따라, include 한 헤더의 함수가 모두 포함될 수도 있고, 아닐 수도 있다.

dynamic 라이브러리

  • dynamic라이브러리와 링크하는 것을 동적 링킹
  • 실행 파일에 여전히 구멍이 있음
    • 실행 파일'실제로 실행할 때' 링킹이 일어난다.
  • static 링킹에 비해
    • 실행파일 크기가 작다.
    • 여러 실행파일이 동일한 라이브러리를 공유할 수 있다. = 메모리 절약
    • 여러 실행파일이 각각 다른 dynamic 라이브러리를 사용하면 dll 지옥

static, dynamic 간단비교

간단비교 1.

#include <stdio.h>

int main() {
  puts("Hello, world!");
  return 0;
}
  • 정적링크
$ gcc -o static hello-world.c -static
  • 동적링크
$ gcc -o dynamic hello-world.c -no-pie

  • 정적링크
 main:
  push   rbp
  mov    rbp,rsp
  lea    rax,[rip+0x96880] # 0x498004
  mov    rdi,rax
  call   0x40c140 <puts>   # puts를 0x40c140에서 직접 호출
  mov    eax,0x0
  pop    rbp
  ret

  • 동적링크
main: 
 push   rbp
 mov    rbp,rsp
 lea    rdi,[rip+0xebf] # 0x402004
 mov    rdi,rax
 call   0x401040 <puts@plt> # puts의 plt주소인 0x401040 을 호출한다.
 mov    eax,0x0
 pop    rbp
 ret

PLT, GOT

  • PLT, GOT: 라이브러리에서 동적 링크된 심볼의 주소를 찾을 때 사용하는 테이블
    • 바이너리가 실행시, ASLR에 의해 라이브러리가 임의의 주소에 매핑된다.
      • 이 상태에서, 라이브러리 함수를 호출하면, 함수의 이름을 바탕으로 라이브러리에서 심볼들을 탐색
        해당 함수의 정의를 발견하면 그 주소로 실행 흐름을 옮긴다.
      • 이 과정을 runtime resolve
      • 만약 반복적으로 호출되는 함수의 정의를 매번 탐색해야한다면 비효율적이다.
        그래서 ELFGOT라는 테이블을 두고, resolve된 함수의 주소를 해당 테이블에 저장한다.
        그리고, 나중에 다시 해당 함수를 호출하면 저장된 주소를 꺼내서 사용한다.
// Name: got.c
// Compile: gcc -o got got.c -no-pie

#include <stdio.h>

int main() {
    puts("Resolving address of 'puts'.");
    puts("Get address from GOT");
}
$ gdb ./got
pwndbg> entry
pwndbg> got
  • got을 실행 직후, GOT의 상태를 보여주는 명령인 got을 사용
    puts의 GOT 엔트리인 0x404018에는 아직 puts의 주소를 찾기 전이므로, 함수 주소 대신 .plt 섹션
    어딘가의 주소인 0x401030이 적혀있다.

  • 이제 main()에서 puts@plt를 호출하는 지점에 중단점 설정
pwndbg> c
pwndbg> si
  • 좀있다 _dl_runtime_resolve_xsavec 함수가 실행되는데, 이 함수에서 puts의 주소가 구해지고, GOT 엔트리에 주소를 쓴다.
    실제로 ni 명령어를 반복적으로 수행해서 _dl_runtime_resolve_xsave 안으로 진입한 후, finish 명령어로 함수를 빠져나오면, puts의 GOT 엔트리에 libc 영역내 실제 puts주소인 0x7ffff7e02ed0가 쓰여 있는 모습을 확인할 수 있다.
pwndbg> ni
...
pwndbg> ni
_dl_runtime_resolve_fxsave () at ../sysdeps/x86_64/dl-trampoline.h:67
67  ../sysdeps/x86_64/dl-trampoline.h: No such file or directory.
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
   0x401030                                          endbr64
   0x401034                                          push   0
   0x401039                                          bnd jmp 0x401020                     <0x401020>
    ↓
   0x401020                                          push   qword ptr [rip + 0x2fe2]      <_GLOBAL_OFFSET_TABLE_+8>
   0x401026                                          bnd jmp qword ptr [rip + 0x2fe3]     <_dl_runtime_resolve_fxsave>
    ↓
 ► 0x7ffff7fd8be0 <_dl_runtime_resolve_fxsave>       endbr64
   0x7ffff7fd8be4 <_dl_runtime_resolve_fxsave+4>     push   rbx
   0x7ffff7fd8be5 <_dl_runtime_resolve_fxsave+5>     mov    rbx, rsp
   0x7ffff7fd8be8 <_dl_runtime_resolve_fxsave+8>     and    rsp, 0xfffffffffffffff0
   0x7ffff7fd8bec <_dl_runtime_resolve_fxsave+12>    sub    rsp, 0x240
   0x7ffff7fd8bf3 <_dl_runtime_resolve_fxsave+19>    mov    qword ptr [rsp], rax
...
pwndbg> finish
Run till exit from #0  _dl_runtime_resolve_fxsave () at ../sysdeps/x86_64/dl-trampoline.h:67
Resolving address of 'puts'.
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
   0x401148 <main+18>    call   puts@plt                      <puts@plt>

 ► 0x40114d <main+23>    lea    rax, [rip + 0xecd]
   0x401154 <main+30>    mov    rdi, rax
   0x401157 <main+33>    call   puts@plt                      <puts@plt>

   0x40115c <main+38>    mov    eax, 0
   0x401161 <main+43>    pop    rbp
   0x401162 <main+44>    ret

   0x401163              add    bl, dh
...
pwndbg> got
GOT protection: Partial RELRO | GOT functions: 1
[0x404018] puts@GLIBC_2.2.5 -> 0x7ffff7e02ed0 (puts) ◂— endbr64
pwndbg> vmmap 0x7ffff7e02ed0
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
             Start                End Perm     Size Offset File
    0x7ffff7daa000     0x7ffff7f3f000 r-xp   195000  28000 /usr/lib/x86_64-linux-gnu/libc.so.6 +0x58ed0

  • resolve된 후
    puts@plt를 두 번째로 호출할 때는 puts의 GOT엔트리에 실제 puts의 주소인 0x7ffff7e02ed0가 쓰여있어서 바로 puts가 실행된다.

해킹의 관점에서 본 PLT와 GOT

  • PLT, GOT은 동적 링크된 바이너리에서 라이브러리 함수의 주소를 찾고,
    기록할 때 사용되는 중요한 테이블
    • 그런데, PLT에서 GOT을 참조하여 실행흐름을 옮길 때, GOT의 값을 검증하지 않는 보안 약점있음
    • 위의 예에서, puts의 GOT 엔트리에 저장된 값을 공격자가 임의로 변경할 수 있다면,
      puts가 호출될 때 공격자가 원하는 코드가 실행되게 할 수 있다.
  • GOT 엔트리에 저장된 값을 임의로 변조할 수 있는 수단이 있음을 가정하고, 이 공격 기법이 가능한지
    gdb로 간단하게 실험해 볼 수 있다.
    got 바이너리에서 main()내 두 번째 puts() 호출 직전에 puts의 GOT 엔트리를 "AAAAAAAA"로 변경 한 후,
    실행 시키면, 실제로 "AAAAAAAA"로 실행 흐름이 옮겨지는 것을 확인할 수 있다.
  • 이런 기법을 GOT Overwrite
profile
pllpokko@alumni.kaist.ac.kr

0개의 댓글