DEF CON CTF Quals 2015 - catwestern (coding challenge 1) Writeup
This challenge asks us to solve a programming problem. The TCP service is running on port 9999. When you connect to it, it tells you:
****Initial Register State**** rax=0xef91c9bc5e06177b rbx=0x2182d45a3d3e608c rcx=0x6bd55e3cde9d0e55 rdx=0xdd59f3fc1c666a5e rsi=0xbc9ec68be44252db rdi=0x3327008906dea77c r8=0xdca252f662c1eede r9=0xb9b44beb597bc359 r10=0xbd6649285ef50a50 r11=0x37b751981d3582d r12=0xfa34e739634ccb7b r13=0x6824becdc88f4ed r14=0xebd638495d51e14a r15=0x6935dba2deba8f6d ****Send Solution In The Same Format**** About to send 76 bytes:
Then, it sends that many bytes of x86_64 bytecode. It is a backwards challenge! You write the shellcode-running service this time.
The register state changes each time, as does the length of the bytecode. The problem, intuitively enough, is asking us to execute the code it sends us with the initial register state, and print the register state back to it.
This is not very hard, if you know a little assembly. All you have to do is read the register state into memory, put the code somewhere executable, and do some marshalling. An observation of the code indicates that it is basically all ALU instructions, which makes it pretty safe, but I ran it in an EC2 instance anyway so I didn't even have to worry about running the solution as root.
Here is some very simple code that does this.
parse.cpp
1 #include <stdio.h> 2 #include <stdint.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <sys/mman.h> 6 7 uint64_t regstate[14]; //a, b, c, d, si, di, 9-15 8 const char* regnames[] = {"rax", "rbx", "rcx", "rdx", "rsi", "rdi", 9 "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"}; 10 11 extern "C" void shellcall(void* regstate, void* shellcode); 12 13 int main() { 14 char* foo = 0; 15 size_t foo_sz = 0; 16 size_t ri=0; 17 18 getline(&foo, &foo_sz, stdin); //get header 19 fprintf(stderr, "Got header: %s", foo); 20 21 while (getline(&foo, &foo_sz, stdin) > 0 && ri<14) { 22 //fprintf(stderr, "l=%d %s\n", ri, foo); 23 regstate[ri++] = strtoull(strstr(foo, "0x"), 0, 16); 24 fprintf(stderr, "%s=0x%016llx\n", regnames[ri-1], regstate[ri-1]); 25 } 26 27 getline(&foo, &foo_sz, stdin); //get header 28 fprintf(stderr, "Got header: %s", foo); 29 30 size_t n_bytes = 0; 31 sscanf(foo, "About to send %u", &n_bytes); 32 33 fprintf(stderr, "Expecting %u bytes\n", n_bytes); 34 uint8_t* shellcode = (uint8_t*)mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); 35 36 for (int i=0;i<n_bytes;i++) shellcode[i] = fgetc(stdin); 37 38 mprotect(shellcode, 4096, PROT_READ|PROT_EXEC); 39 40 fprintf(stderr, "Exec shellcode: regstate at 0x%016lX shellcode at 0x%016lX\n", (uint64_t)regstate, (uint64_t)shellcode); 41 shellcall(regstate, shellcode); 42 43 for (int i=0;i<14;i++) fprintf(stderr, "%s=0x%016llx\n", regnames[i], regstate[i]); 44 for (int i=0;i<14;i++) printf("%s=0x%016llx\n", regnames[i], regstate[i]); 45 printf("\n"); 46 fflush(stdout); 47 48 while (getline(&foo, &foo_sz, stdin) > 0) fprintf(stderr, "%s", foo); 49 50 return 0; 51 }
shellcall.S
1 .section .text 2 .globl shellcall 3 .type shellcall, @function 4 5 shellcall: 6 // rdi = regstate, rsi = shellcode 7 push %rbp 8 push %rax 9 push %rbx 10 push %rcx 11 push %rdx 12 push %rsi 13 push %rdi 14 push %r8 15 push %r9 16 push %r10 17 push %r11 18 push %r12 19 push %r13 20 push %r14 21 push %r15 22 23 push %rsi 24 movq %rdi,%rbp 25 26 movq 0(%rbp),%rax 27 movq 8(%rbp),%rbx 28 movq 16(%rbp),%rcx 29 movq 24(%rbp),%rdx 30 movq 32(%rbp),%rsi 31 movq 40(%rbp),%rdi 32 movq 48(%rbp),%r8 33 movq 56(%rbp),%r9 34 movq 64(%rbp),%r10 35 movq 72(%rbp),%r11 36 movq 80(%rbp),%r12 37 movq 88(%rbp),%r13 38 movq 96(%rbp),%r14 39 movq 104(%rbp),%r15 40 41 push %rbp 42 call *8(%rsp) 43 pop %rbp 44 45 movq %r15,104(%rbp) 46 movq %r14,96(%rbp) 47 movq %r13,88(%rbp) 48 movq %r12,80(%rbp) 49 movq %r11,72(%rbp) 50 movq %r10,64(%rbp) 51 movq %r9,56(%rbp) 52 movq %r8,48(%rbp) 53 movq %rdi,40(%rbp) 54 movq %rsi,32(%rbp) 55 movq %rdx,24(%rbp) 56 movq %rcx,16(%rbp) 57 movq %rbx,8(%rbp) 58 movq %rax,0(%rbp) 59 60 pop %rax //discard 61 pop %r15 62 pop %r14 63 pop %r13 64 pop %r12 65 pop %r11 66 pop %r10 67 pop %r9 68 pop %r8 69 pop %rdi 70 pop %rsi 71 pop %rdx 72 pop %rcx 73 pop %rbx 74 pop %rax 75 pop %rbp 76 77 ret
The mmap and mprotect in parse.cpp take care of putting the shellcode in an executable place.
As you can see, I am very lazy and did not want to worry about what can be clobbered or not. The only trick to this assembler function is the state marshalling. We need to set a lot of registers for the initial state, but the problem mercifully leaves RBP and RSP alone, and although the shellcode uses the stack, it only ever does push and pop, and never does base pointer dereferences at all. So, RBP is free for our use. I put the location of my register state array in RBP and proceed to set up the registers. The call address is placed on the stack (via push rsi), and we save RBP across the shellcode call even though it isn't likely necessary. Then, we just call the saved shellcode pointer, and run that.
The shellcode that gets sent to us (yes, it isn't strictly shellcode, just code) always ends in a ret. So, we are very safe. Just wait for it to ret, save off state, pop all our saved state off the stack, and return into parse.cpp.
Then, we can print the register state back to the service, in the same format as before, and it will tell us the flag. Here is a transcript of my session:
[root@ip-172-31-23-134 ec2-user]# socat TCP4:catwestern_631d7907670909fc4df2defc13f2057c.quals.shallweplayaga.me:9999 EXEC:./a.out Got header: ****Initial Register State**** rax=0x9f9da7286ac5281e rbx=0x92fbcd1edafe5bb2 rcx=0xf34f594b5cc1e0b2 rdx=0x1b1c24508b7cb519 rsi=0x902a02cae9a0e63f rdi=0xc56beb738c5b1b11 r8=0xd13193869ca5ad2a r9=0x41e6fc66e96f9cd6 r10=0x9741e20d00176ab2 r11=0x906cd1136b84cc21 r12=0xa0bb2b7f459a649d r13=0x54188af1909e1eb7 r14=0x603511b2ade4e546 r15=0x87a16908441ab303 Got header: About to send 87 bytes: Expecting 87 bytes Exec shellcode: regstate at 0x00000000006014A0 shellcode at 0x00007FE0236EA000 rax=0x9f9da7286ac5281e rbx=0xe562c9d8e0e6f14b rcx=0x0cb0a6b4a33e1f4d rdx=0x1b1c24508b7cb519 rsi=0xa0bb2b7f459a649c rdi=0x000000001947faf8 r8=0x2ece6c797ffef2f6 r9=0x41e6fc66e96f9cd6 r10=0xaadb2fb59e405196 r11=0xf143280600000000 r12=0x000000001947804c r13=0xa7571845f5f58250 r14=0x603511b2a44847da r15=0x87a16907fe758657 The flag is: Cats with frickin lazer beamz on top of their heads! [root@ip-172-31-23-134 ec2-user]#