한국어(Korean): Link


Accessing the server, the values of registers and weird string are given.

When we encode the string in hex, they all end with 'c3', which is opcode for "retq" in x64 assembly. Thus we can infer that we have to execute the assembly code and return the values of registers in the same format.

It's hard to run opcode in python so we'll use python as wrapper that uses socket and call C-compiled binary using subprocess module.

We can access the rax register in C by

register unsigned long long rax asm("rax");

Let's put the assembly code in a character array "code" and run it.


#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    void (*f)();
    register unsigned long long rax asm("rax");
    register unsigned long long rbx asm("rbx");
    register unsigned long long rcx asm("rcx");
    register unsigned long long rdx asm("rdx");
    register unsigned long long rsi asm("rsi");
    register unsigned long long rdi asm("rdi");
    register unsigned long long r8 asm("r8");
    register unsigned long long r9 asm("r9");
    register unsigned long long r10 asm("r10");
    register unsigned long long r11 asm("r11");
    register unsigned long long r12 asm("r12");
    register unsigned long long r13 asm("r13");
    register unsigned long long r14 asm("r14");
    register unsigned long long r15 asm("r15");

    unsigned long long _rax=0,_rbx=0,_rcx=0,_rdx=0,_rsi=0,_rdi=0,_r8=0,_r9=0,_r10=0,_r11=0,_r12=0,_r13=0,_r14=0,_r15=0;
    unsigned char code[1024]={0,};
    unsigned char tmp[3]={0,};
    int tmp2=0;

    if(argc<17) { printf("nope\n"); return 0; }

    int i;
    // put hex-encoded assembly in char[] code
    for(i=0; i<atoi(argv[1]); i++) {
        memcpy(tmp, argv[16]+i*2, 2);
        tmp2=strtol(tmp, &tmp, 16);
        code[i]=tmp2;
    }
    _rax =strtoll(argv[2], 0, 16);
    _rbx =strtoll(argv[3], 0, 16);
    _rcx =strtoll(argv[4], 0, 16);
    _rdx =strtoll(argv[5], 0, 16);
    _rsi =strtoll(argv[6], 0, 16);
    _rdi =strtoll(argv[7], 0, 16);
    _r8  =strtoll(argv[8], 0, 16);
    _r9  =strtoll(argv[9], 0, 16);
    _r10=strtoll(argv[10], 0, 16);
    _r11=strtoll(argv[11], 0, 16);
    _r12=strtoll(argv[12], 0, 16);
    _r13=strtoll(argv[13], 0, 16);
    _r14=strtoll(argv[14], 0, 16);
    _r15=strtoll(argv[15], 0, 16);

    f=(void *)code;

    rax=_rax;rbx=_rbx;rcx=_rcx;rdx=_rdx;
    rsi=_rsi;rdi=_rdi;r8=_r8;r9=_r9;
    r10=_r10;r11=_r11;r12=_r12;r13=_r13;
    r14=_r14;r15=_r15;

// Location of f: rbp-0xb8
    asm volatile(
        "call -0xb8(%rbp)\n\t"
    );
    _rax=rax;_rbx=rbx;_rcx=rcx;_rdx=rdx;
    _rsi=rsi;_rdi=rdi;_r8=r8;_r9=r9;
    _r10=r10;_r11=r11;_r12=r12;_r13=r13;
    _r14=r14;_r15=r15;
    printf("rax=0x%llx\n", _rax);
    printf("rbx=0x%llx\n", _rbx);
    printf("rcx=0x%llx\n", _rcx);
    printf("rdx=0x%llx\n", _rdx);
    printf("rsi=0x%llx\n", _rsi);
    printf("rdi=0x%llx\n", _rdi);
    printf("r8=0x%llx\n",  _r8);
    printf("r9=0x%llx\n",  _r9);
    printf("r10=0x%llx\n", _r10);
    printf("r11=0x%llx\n", _r11);
    printf("r12=0x%llx\n", _r12);
    printf("r13=0x%llx\n", _r13);
    printf("r14=0x%llx\n", _r14);
    printf("r15=0x%llx", _r15);
    return 0;
}


여기서 f()로 호출하면 컴파일러가 rax에 f의 주소를 넣고 call *%rax의 형태로 바뀌어서 rax값을 보존하기 위해 call 어셈블리를 inline으로 직접 넣었습니다. rbp-0xb8은 한번 컴파일을 해본 뒤 f가 어디에있는지 직접 찾아서 넣은 것입니다. f를 직접 호출하려고했는데 inline assembly와 C언어 변수간의 호환이 어렵더군요...

The reason I put "call rbp-0xb8" is because if we just call f() then the compiler put the address of f in rax and call *%rax when the value of rax must remain intact. I compiled the code first then figured out f is at rbp-0xb8. I wanted to call f directly but using C variable in inline assembly is harder than I thought...

We need to run code in stack so make it executable by giving "-z execstack" option in gcc:

gcc -o catwestern catwestern.c -z execstack -fno-stack-protector -g

Finally we just have to call the binary appropriately in python wrapper


from socket import *

s = socket(2,1)
s.connect(('catwestern_631d7907670909fc4df2defc13f2057c.quals.shallweplayaga.me',9999))
reg = s.recv(2048)

print reg
reg = reg.split('\n')[1:]
for i in reg:
    exec i

a = s.recv(2048)
print a
a = a.split('\n')
a = '\n'.join(a[2:])
print 'LENGTH:', len(a)

regs = [rax,rbx,rcx,rdx,rsi,rdi,r8,r9,r10,r11,r12,r13,r14,r15]
for n,i in enumerate(regs):
    if i>0x7fffffffffffffff:
        regs[n] = -(0xffffffffffffffff-i+1)
cmd=('./catwestern %d ' +'%x '*14+ '%s')%(len(a),
    regs[0],regs[1],regs[2],regs[3],regs[4],
    regs[5],regs[6],regs[7],regs[8],regs[9],
    regs[10],regs[11],regs[12],regs[13],a.encode('hex'))

import subprocess
print cmd
t = subprocess.check_output(cmd, shell=True)

for i in t.split('\n'):
    exec i

ans = '''rax=0x%x
rbx=0x%x
rcx=0x%x
rdx=0x%x
rsi=0x%x
rdi=0x%x
r8=0x%x
r9=0x%x
r10=0x%x
r11=0x%x
r12=0x%x
r13=0x%x
r14=0x%x
r15=0x%x
''' % (rax,rbx,rcx,rdx,rsi,rdi,r8,r9,r10,r11,r12,r13,r14,r15)
print ans
s.send(ans)
print s.recv(2048)



Flag: Cats with frickin lazer beamz on top of their heads!

+ Recent posts