LEET MORE CTF JAILBREAK WRITEUP by ea Ok, you have a service running at ctf.ifmo.ru on port 4004. It's in chroot. The directory contains only one file named key which is owned by alice,is about 9kb and is readable. Now, lets check the service: $ file 9e005d843a72e02cd1bb1b4ea40ca9c9 9e005d843a72e02cd1bb1b4ea40ca9c9: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.8, not stripped so it's an 32bit elf... [ea@foundation jailbreak] $ strings 9e005d843a72e02cd1bb1b4ea40ca9c9 /lib/ld-linux.so.2 __gmon_start__ libc.so.6 _IO_stdin_used setuid chroot socket htons sprintf __stack_chk_fail listen bind chdir read setgroups shutdown setsockopt system atoi close accept setgid __libc_start_main write GLIBC_2.4 GLIBC_2.0 PTRh QVh; [^_] Give me your fucking shellcode... ./private_data/ "%s" %d & [ea@foundation jailbreak] $ strings reveals a bit. From here we can conclude that it chroots to ./private_data and that that is the directory containing key file. let's see what objdump has to say : [ea@foundation jailbreak] $ objdump -d 9e005d843a72e02cd1bb1b4ea40ca9c9 | less --too many uninteresting parts-- 0804883b
: 804883b: 8d 4c 24 04 lea 0x4(%esp),%ecx 804883f: 83 e4 f0 and $0xfffffff0,%esp 8048842: ff 71 fc pushl -0x4(%ecx) 8048845: 55 push %ebp 8048846: 89 e5 mov %esp,%ebp 8048848: 51 push %ecx 8048849: 81 ec 84 00 00 00 sub $0x84,%esp 804884f: 8b 41 04 mov 0x4(%ecx),%eax 8048852: 89 45 98 mov %eax,-0x68(%ebp) 8048855: 65 a1 14 00 00 00 mov %gs:0x14,%eax 804885b: 89 45 f8 mov %eax,-0x8(%ebp) 804885e: 31 c0 xor %eax,%eax 8048860: 83 39 01 cmpl $0x1,(%ecx) <----[1] 8048863: 0f 8e 9a 00 00 00 jle 8048903 8048869: c7 04 24 e3 8a 04 08 movl $0x8048ae3,(%esp) 8048870: e8 37 fe ff ff call 80486ac 8048875: c7 04 24 f3 8a 04 08 movl $0x8048af3,(%esp) 804887c: e8 1b fe ff ff call 804869c 8048881: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp) 8048888: 00 8048889: c7 04 24 00 00 00 00 movl $0x0,(%esp) 8048890: e8 47 fd ff ff call 80485dc 8048895: c7 04 24 fe ff 00 00 movl $0xfffe,(%esp) 804889c: e8 6b fe ff ff call 804870c 80488a1: c7 04 24 fe ff 00 00 movl $0xfffe,(%esp) 80488a8: e8 df fd ff ff call 804868c 80488ad: 8b 45 98 mov -0x68(%ebp),%eax 80488b0: 83 c0 04 add $0x4,%eax 80488b3: 8b 00 mov (%eax),%eax 80488b5: 89 04 24 mov %eax,(%esp) 80488b8: e8 0f fe ff ff call 80486cc 80488bd: 89 45 b0 mov %eax,-0x50(%ebp) 80488c0: 8b 45 b0 mov -0x50(%ebp),%eax 80488c3: 89 04 24 mov %eax,(%esp) 80488c6: e8 09 ff ff ff call 80487d4 --too many uninteresting parts-- here we see that it first checks how many arguments was it executed with ([1] in the code above), if it's less than or equal to 1 it jumps to setting up sockets and accepting connections after it accepts the connection it executes it's self with system, with socket number as argument. Now the new process fails the check at [1] and continues to do chdir and chroot and the rest until the call to ucnoJlH9IeM_LLIeJlJlKog function, now lets check that one: 080487d4 : 80487d4: 55 push %ebp 80487d5: 89 e5 mov %esp,%ebp 80487d7: 81 ec a8 00 00 00 sub $0xa8,%esp 80487dd: 65 a1 14 00 00 00 mov %gs:0x14,%eax 80487e3: 89 45 fc mov %eax,-0x4(%ebp) 80487e6: 31 c0 xor %eax,%eax 80487e8: c7 44 24 08 22 00 00 movl $0x22,0x8(%esp) 80487ef: 00 80487f0: c7 44 24 04 c0 8a 04 movl $0x8048ac0,0x4(%esp) 80487f7: 08 80487f8: 8b 45 08 mov 0x8(%ebp),%eax 80487fb: 89 04 24 mov %eax,(%esp) 80487fe: e8 19 fe ff ff call 804861c 8048803: c7 44 24 08 80 00 00 movl $0x80,0x8(%esp) 804880a: 00 804880b: 8d 85 7c ff ff ff lea -0x84(%ebp),%eax 8048811: 89 44 24 04 mov %eax,0x4(%esp) 8048815: 8b 45 08 mov 0x8(%ebp),%eax 8048818: 89 04 24 mov %eax,(%esp) 804881b: e8 3c fe ff ff call 804865c 8048820: 8d 85 7c ff ff ff lea -0x84(%ebp),%eax 8048826: ff d0 call *%eax <---------- [2] 8048828: 8b 45 fc mov -0x4(%ebp),%eax 804882b: 65 33 05 14 00 00 00 xor %gs:0x14,%eax 8048832: 74 05 je 8048839 8048834: e8 b3 fe ff ff call 80486ec <__stack_chk_fail@plt> 8048839: c9 leave 804883a: c3 ret what this does actually is writes "Give me your fucking shellcode..." to a socket reads up to 128 bytes from it into a buffer and jumps to it [2]. It's that simple. Now lets see, what do we need our shellcode to do: 1. open() a key file 2. read() from it into a buffer 3. write() buffer content to a connection socket 4. loop read/write until EOF my shellcode is ugly and i won't put it here , but it's pretty simple. I had some problems with with read/write loop, so i decided to dump that and use lseek to get the rest of the file. Wrote a simple python script that connects to a service, sends shellcode , recives bytes, incremets the lseek offset in the shellcode, sends the shellcode... until the whole file is retrived. now after we retrived the file , let's check it out: $ file f1 f1: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.8, not stripped [ea@foundation ex] $ $ strings f1 /lib/ld-linux.so.2 __gmon_start__ libc.so.6 _IO_stdin_used __stack_chk_fail printf memcmp __libc_start_main GLIBC_2.4 GLIBC_2.0 PTRh` QVhT e6=e 6)u3 3!&)f [^_] JAILKEY= [ea@foundation ex] $ ok , so strings doesn't reveal much , but there is one interesting bit. The string JAILKEY= , that actually looks like evironment variable if we run the bin, it just exist: $ ./f1 [ea@foundation ex] $ echo $? 0 [ea@foundation ex] $ but if we run it with JAILKEY set to something : $ JAILKEY=asdawaw ./f1 wWLVWJeC^_ wn0}g~v`+[ea@foundation ex] $ echo $? 0 [ea@foundation ex] $ so, it check the JAILKEY env var and decrypts something. Now, I was a little lucky on this part. While I was testing my shellcode I made an error at write size argument, and after writing to socket what it read from a file , it continued writing content of the stack, and along with it the content of env variables. There was one interesting env var: JAILKEY=ALICE_NEEDS_YOUR_HELP So, lets run a bin with JAILKEY set to ALICE_NEEDS_YOUR_HELP : [ea@foundation ex] $ JAILKEY=ALICE_NEEDS_YOUR_HELP ./f1 What is UID of alice?[ea@foundation ex] $ What is UID of alice, it asks. Could that be the flag? Time for another shellcode. I just substituted read() syscall in previous shellcode with lstat(), wrote info to socket, and then just copied the UID from that. I didn't write down the UID , just checked it on the game server, and it gave me points. Note that you can't do getuid since it is changed by the process. Nice challenge, took me a while to make my shellcode to work, but once I got the key file, the rest was easy. Altho the whole challenge could have been done in a more elegant way but this worked... Too bad the game was in the middle of the week,so we basically had very limited time to play due to work/school. cheers, ea