본문 바로가기

Secret

dynelf 원리

* libc 모듈 베이스주소 찾기


일단 바이너리가 있다고치면(PIE 인 경우는 PIE 베이스 안다고치면) got.plt 섹션의 주소는 아는셈이고

그 섹션의 처음에 위치한 포인터배열 (rw세그먼트 시작에 있음) 에서 [1] 번째 엔트리가 linkmap 이중연결리스트의 head 포인터임


root@ubuntu:~/tmp/leak# readelf -S leak

There are 30 section headers, starting at offset 0x117c:


Section Headers:

  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al

  [ 0]                   NULL            00000000 000000 000000 00      0   0  0

  [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1

  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4


...............


  [21] .dynamic          DYNAMIC         08049f14 000f14 0000e8 08  WA  6   0  4

  [22] .got              PROGBITS        08049ffc 000ffc 000004 04  WA  0   0  4

  [23] .got.plt          PROGBITS        0804a000 001000 00001c 04  WA  0   0  4


(gdb) x/100wx 0x804a000

0x804a000: 0x08049f14 0xf7ffd938 0xf7ff04f0 0x08048316

0x804a010 <__gmon_start__@got.plt>: 0x08048326 0xf7e2f970 0x08048346 0x00000000


여기서 0xf7ffd938 이 바로 그 주인공.

참고로 linkmap 들은 ld.so 속의 rw 세그먼트 속에 있음.


(gdb) x/30wx 0xf7ffd938

0xf7ffd938: 0x00000000 0xf7ffdc24 0x08049f14 0xf7ffdc28

0xf7ffd948: 0x00000000 0xf7ffd938 0x00000000 0xf7ffdc18

0xf7ffd958: 0x00000000 0x08049f14 0x08049f84 0x08049f7c

0xf7ffd968: 0x00000000 0x08049f54 0x08049f5c 0x00000000


f7fdc000-f7ffc000 r-xp 00000000 08:01 2752527                            /lib32/ld-2.19.so

f7ffc000-f7ffd000 r--p 0001f000 08:01 2752527                            /lib32/ld-2.19.so

f7ffd000-f7ffe000 rw-p 00020000 08:01 2752527                            /lib32/ld-2.19.so


linkmap 의 구조는 다음과 같음


struct link_map{


   /* Base address shared object is loaded at.  */

   ElfW(Addr) l_addr;


   /* Absolute file name object was found in.  */

   char *l_name;


   /* Dynamic section of the shared object.  */

   ElfW(Dyn) *l_ld;


   /* Chain of loaded objects.  */

   struct link_map *l_next, *l_prev; 


   ....... 이후 겁나많지만 생략 ....


이제, l_next 를 따라다니면서 l_name 이 libc 인 놈이 어디있는지 찾으면됨.

linkmap head 를 덤프해서 따라가는것으로 예를들면...


(gdb) x/30wx 0xf7ffd938

0xf7ffd938: 0x00000000 0xf7ffdc24 0x08049f14 0xf7ffdc28

0xf7ffd948: 0x00000000 0xf7ffd938 0x00000000 0xf7ffdc18


0xf7ffdc24 에 무슨 문자열이 있나 보자 -> libc 스트링 없네? next로 이동.


(gdb) x/30wx 0xf7ffdc28

0xf7ffdc28: 0xf7fdd000 0xf7ffde94 0xf7fdb350 0xf7fc1000

0xf7ffdc38: 0xf7ffd938 0xf7ffdc28 0x00000000 0xf7ffde88


0xf7ffde94 에 무슨 문자열이 있나 보자 -> libc 스트링 없네? next로 이동.


(gdb) x/30wx 0xf7fc1000

0xf7fc1000: 0xf7e16000 0xf7fdae10 0xf7fbcda8 0xf7ffd55c

0xf7fc1010: 0xf7ffdc28 0xf7fc1000 0x00000000 0xf7fc1260


0xf7ffde94 에 무슨 문자열이 있나 보자 ->

(gdb) x/s 0xf7fdae10

0xf7fdae10: "/lib32/libc.so.6"

빙고! 이놈이 libc.so 에 대한 link_map 엔트리구나.  베이스주소는 따라서 0xf7e16000!



이렇게 libc.so 의 베이스주소를 찾았으면, 이제 남은일은 libc.so 가 export 하는 함수의 주소를 찾는것.




* libc 모듈 내부에서 특정함수 주소찾기.


모듈이 export 하는 심볼(그중에 우리가 원하는건 함수) 에 대한 정보를 얻기 위해서는

2개의 섹션을 뒤져야함. (아주 정확히 하려면 dynstr 도 봐야하지만 여기선 그정돈 필요없음)


1. gnu_hash 섹션

2. dynsym 섹션


이 2가지 섹션의 주소는 

ELF 의 .dynamic 이라는 섹션을 뒤져보면 알 수 있음.

그렇다면 .dynamic 의 주소는 어떻게 아나? 아까 위에서 link_map 구조체를 확인해보면

특정 모듈의 link_map 엔트리 속에 .dynamic 섹션주소가 있음.


dynamic 섹션은 ELF_Dyn 이라는 구조체의 배열인데, 그 구조체는 다음과 같이 정의됨.


typedef struct {

        Elf32_Sword d_tag;

        union {

                Elf32_Word      d_val;

                Elf32_Addr      d_ptr;

                Elf32_Off       d_off;

        } d_un;

} Elf32_Dyn;


그리고 각각의 tag 번호가 무엇을 의미하는지는 구글링해보면 목록표같은것이 나옴.

아무튼 우리가 찾아야 하는 3개의 섹션에 대한 tag 번호는 각각


gnu_hash: 0x6ffffef5

dynsym: 0x6


이렇게임...

보통 이놈들은 아래처럼 옹기종기 모여있음.


(gdb) x/40wx 0xf7fbcdd0

0xf7fbcdd0: 0x00000004 0xf7fb72a4 0x6ffffef5 0xf7e161b8

0xf7fbcde0: 0x00000005 0xf7e23438 0x00000006 0xf7e19ec8


아무튼 이제 각 섹션의 시작위치를 다 알아냈으면, 특정한 심볼(여기선 함수) 의

주소는 dynsym 테이블에서 해당 심볼의 엔트리를찾고, 그 속에서 해당 심볼에 대한

offset 을 구해서 이것을 libc base 주소에 더하여 구할수 있음.

dynsym 테이블은 아래와 같이 생겼음.


(gdb) x/40wx 0xf7e19ec8

0xf7e19ec8: 0x00000000 0x00000000 0x00000000 0x00000000

0xf7e19ed8: 0x00001da8 0x00000000 0x00000000 0x00000011

0xf7e19ee8: 0x00000bde 0x00000000 0x00000000 0x00000011

0xf7e19ef8: 0x000045d1 0x00000000 0x00000000 0x00000011

0xf7e19f08: 0x000013f4 0x00000000 0x00000000 0x00000020


4개의 엔트리로된 구조체들의 배열인데.  다른건 신경쓸것없고, 중요한건 두번째 엔트리가 해당 심볼의

offset 이라는점(첫번째 엔트리의 값은 dynstr 테이블에서의 문자열오프셋값). 

즉, system 함수의 symbol table 상의 index 번호만 알 수 있으면 됨.


그럼이제 문제는 dynsym 테이블에서 해당 심볼의 index 를 어떻게 찾는가? 인데...

이것은 gnu_hash 섹션에 있는 bucket 이라는 테이블상에서 구할 수 있음.


그럼 bucket 이라는 테이블의 시작위치는 어떻게 찾으며, symbol 의 index 를 담고있는

bucket 테이블상의 index 는 또 어떻게 알수있나...??  일단 bucket 테이블의 시작주소부터

찾아보자...


bucket 테이블은 gnu_hash 섹션의 어딘가에 있는데, 그 위치를 찾으려면 일단 gnu_hash 섹션의 구조를

조금 봐야함.


[섹션헤더][블룸필터][bucket테이블][chain테이블]


이런 구조인데 섹션헤더속에 블룸필터의 사이즈가 있음... 섹션헤더는 다시


[nbucket][symindex][maskwords][shift2] 


이렇게 4개의 멤버로 구성됨. 실제로 gdb 로 보면 아래와 같은식임


(gdb) x/30wx 0xf7e161b8

0xf7e161b8: 0x000003f3 0x0000000a 0x00000200 0x0000000e


여기서 maskwords 가 블룸필터의 갯수인데, 32비트기준으로 4배 해주면 바이트사이즈가됨.

그러면 섹션헤더 길이 자체가 16바이트이니까, 여기에 블룸필터 사이즈를 더하면 bucket 테이블의

시작위치가됨.


즉, gdb 상에서 실제로 본다면 이런식임


(gdb) x/30wx 0xf7e161b8 + 0x10 + 0x200*4

0xf7e169c8: 0x00000000 0x0000000a 0x0000000d 0x0000000e

0xf7e169d8: 0x00000011 0x00000014 0x00000015 0x00000016

0xf7e169e8: 0x00000017 0x00000019 0x0000001a 0x0000001d

0xf7e169f8: 0x00000020 0x00000022 0x00000026 0x00000028


보면 뭔가 index 스러워보이는 값들이 쭈루룩 들어있는걸 볼수있음.  저 하나하나의 index 들이

모두 어떤 symbol 의 인덱스임.


이제 남은건 bucket 테이블에서 system 함수(심볼) 의 index 를 구하는것...  

이것을 구하기 위해서는 심볼에대한 해시가 필요함.


gnu_hash 기준으로 심볼해시 계산하는법은 구글링하면 바로나오므로 생략.

예를들어 "system" 에 대한 gnu_hash 는 0x1ceee48a 이거임.

이건 내가 static 하게 바로 계산할수 있는거니까, 이미 아는 정보인셈이고

중요한건 이 해시를 다시 해싱해서 bucket 의 index 를 구할 수 있음. 


해시값을 다시 해싱한다? 사실 매우 단순함.

그것은 바로 mod nbucket 을 하는것임...  nbucket 은 bucket 테이블의 총 사이즈인데

단지 해시값을 테이블 배열길이로 다시 mod 해서 index 를 얻는것.

지금의 예제에서는 nbucket 이 0x3f3 이니까 0x1ceee48a % 0x3f3 하면 0x272 가 됨.

무튼 이렇게 구한 bucket index 를 이용해서 bucket 테이블에서 symbol index 를 가져올 수 있음.


실제로 해본다면, 여기선 0xf7e169c8 + 4*0x272 니깐...


(gdb) x/10wx 0xf7e169c8 + 4*0x272

0xf7e17390: 0x000005a3 0x000005a5 0x000005aa 0x000005ad

0xf7e173a0: 0x000005af 0x000005b1 0x000005b3 0x000005b7


이렇게됨. 0x5a3 이 있음...

그럼 드디어 symbol index 를 구한것인가?  불행하게도 아직 아님 ㅠㅠ

이렇게 얻은 symbol index 는 쉽게 collision 이 날 수 있는 32비트값을 해싱하여 구한것이기때문에

콜리전의 확률이 상당히큼... 따라서 chain 테이블(체이닝으로 콜리전을 처리한듯) 을 뒤져서 

실제 결과를 확인해야함...


그럼 chain 테이블은 어디에 있나?

다행히 간단하게 bucket 테이블 바로 뒤에 붙어서 시작함.

따라서 bucket 테이블에서 nbucket 만큼 넘어가면 chain 테이블이 있음.


bucket 테이블 시작에서, 테이블 사이즈를 뛰어넘어가보면... gdb 상으로 보면 다음과 같음.


(gdb) x/30wx 0xf7e169c8 + 4*0x3f3

0xf7e17994: 0x1e160e72 0xa6511920 0x8adcad37 0xa6511921

0xf7e179a4: 0x6f6ea83c 0x9bda0c02 0xa6511923 0x12238a74

0xf7e179b4: 0xd95aa80e 0x6b637bd1 0x3456f575 0xa6cc5f5b

0xf7e179c4: 0x230b7fbd 0x1b858b92 0xef7872a5 0x13cb33df

0xf7e179d4: 0x02afac5c 0xf6681994 0x0b626a65 0x88dc1c82

0xf7e179e4: 0x1259595a 0x88dc1c83 0xf072b2e2 0x7348186b


뭔개 hash 들의 테이블같아보임... 이 해시들이 서로 콜리전나는놈들끼리 뭉쳐있는것임.

콜리전이 뭔지 모른다면 컴퓨터자료구조 책의 hashing 에 대해서 공부하면됨.


이제 이 chain 테이블에서 내가 찾은 symbol index 에 대한 collision 정보가 있는 위치를

계산해야 하는데, 그건 다음과 같이 계산할 수 있음.


collision 배열시작 = chain 테이블 시작주소 + (symbol index - symindex)*(WORDSIZE)


gdb 로 본다음 다음과같음.


(gdb) x/30wx 0xf7e169c8 + 4*0x3f3 + 4*(0x5a3 - 0xa)

0xf7e18ff8: 0x1ceee48a 0x96a3051b 0x9bda2e0a 0x0b886fae

0xf7e19008: 0x2bae7822 0xc290441c 0xf7295bdb 0x966b0572

0xf7e19018: 0x966b0572 0xb354f7cb 0x0f6ea9bc 0x5d29a27f


여기서 symindex 는 아까 gnu_hash 섹션헤더속에 들어있었음.

이렇게 해서 collision 배열의 시작위치를 얻게되면.

이제부터 내가 구한 symbol index 가 맞는지 확인할 수 있음.


collision 배열은 hash collision 을 유발하는 hash 들에 대한 배열인데

나의 해시가 collision 배열 몇번째 엔트리에 위치하는지만 확인하면 이젠 진짜 

정확한 symbol index 를 얻을 수 있음 -_-


예를들어 collision 배열[0] 에 있는 해시값이, 이미 나의 해시값 (gnu_hash) 과 동일하다면

이미 빙고, 내가 처음 구한 bucket index 가 올바를 인덱스임.  하지만 그게 일치안하고

그 뒤의 해시 collision[1] 이 일치한다면, 내 symbol index 는 사실 +1 해줘야 올바른 값인것임.


지금 예시에선 다행히 collision[0] 이 나의 원래 해시 0x1ceee48a 랑 동일하니, 최초로구한 symbol index 가 올바른 인덱스인경우지만, 다른 심볼들은 아닐확률이 꾀 높음.


아무튼 이렇게 해서 대망의 symbol index 값을 구해냈으면

이제 dynsym 테이블에서 symbol index 번째의 구조체 엔트리를 확인하고

거기서 첫번째 엔트리인 symbol offset 을 가져와서 libc base 주소랑 더해주면 끝!!!


즉, dynsym 에서 이제 올바르게 구한 오프셋 0x5a3 을 통해서 메모리를 보면

(gdb) x/10wx 0xf7e19ec8 + 16*(0x5a3)

0xf7e1f8f8: 0x0000306a 0x0003fcd0 0x00000038 0x000c0022

0xf7e1f908: 0x0000249f 0x0010f1e0 0x00000055 0x000c0012

0xf7e1f918: 0x00001bde 0x00096d70


사전에 구한 libc 베이스주소에 system 오프셋을 더하면

(gdb) x/10i 0xf7e16000 + 0x3fcd0

   0xf7e55cd0 <system>: push   %ebx

   0xf7e55cd1 <system+1>: sub    $0x8,%esp

   0xf7e55cd4 <system+4>: mov    0x10(%esp),%eax


끝.





'Secret' 카테고리의 다른 글

Heap Overflow Length Extension for GHOST vulnerability  (1) 2015.01.30
QEMU Internal - Interrupt  (0) 2014.03.19
QEMU Internal - TB Chaining  (1) 2014.03.17
QEMU Internal - CPU  (0) 2014.03.14
QEMU Internal - Monitor  (0) 2014.03.14