r0ops

r0ops: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xb16f0af4069e5f9972c2bbb85360187144163125, stripped

首先程式會在 listen on 13337 port 接著執行位於 0xDEAD3AF 上的函數:

int main() {
    int fd = socket(2, 1, 0);
    if ( fd == 3 ) {
        addr.sa_family = 2;
        addr.sa_data[2] = htonl(0);
        addr.sa_data[0] = htons(13337);
        bind(3, &addr, 16u);
        listen(fd, 10);
        sub_DEAD3AF();
    }
}
int sub_DEAD3AF() {
    int fd = accept(3, 0, 0);
    recv(fd, buffer, 4096, 0);
    close(fd);
    memcpy(0xE0AF0A0, 0xE0B00A0, 4096);
    __asm { 
        mov eax, offset buffer 
        mov rdi, rax 
        mov eax, 0x0B20C0B8 
        mov rsi, rax
        mov eax, 0xE0AF8A0
        mov rsp, raxret
    }
}

sub_DEAD3AF() 會 recv 4096 個 byte,接著 呼叫 memcpy() 複製一些資料 最後把 rsp 修改為 0xE0AF8A0 後執行 ret
如同題目名稱, rsp 指向的資料區塊是個 rop chain... 透過肉眼辨識配合 ida pro 重新命名可以很快的把這些 function address 整理成對應的指令:

接者轉換成類 asm 格式:

pop rcx #8
pop r9  #1337DEADBEEF0095h
mov rax, [rdi]
add rsi, 8
mov [rsi], rax
mov r8, [rsi]
sub rsi, 8
add rdi, 8
add rsi, 8
mov [rsi], r9
( 中間省略 )
cmp rax, rbx
jne +rdx 
pop rdx # 0FFFFFFFFFFFFFC38h
add rap, rdx
PrintFlag sub_DEAD3AF

不難發現 register 間的資料轉移都是夾在 add rsi, 8sub rsi, 8 之間

再進一步簡化翻譯出 c code,其中 flag[8] 為我們送出的內容:

#include <stdio.h>
unsigned long long flag[8] = {1, 1, 1, 1, 1, 1, 1, 1};
void PrintFlag() {
    puts("YOU WIN!\n");
    printf("FLAG IS: 0ctf{");
    for ( int i = 0; i < 8; ++i )
        printf("%08llx", flag[i]);
    puts("}\n");
}
int main( int argc, char *argv[] ) {
    unsigned long long r9 = 0x1337DEADBEEF0095;
    for ( int i = 0; i < 8; ++i ) {
        unsigned long long r8 = flag[i];
        unsigned long long r12 = 1;
        unsigned long long r10 = 13337;
        r9 *= 0xCAFE; r9 += 0xBEEF;
        while ( r10 ) {
            if ( r10 & 1 )
            r12 *= r8; r8 *= r8;
            r10 >>= 1;
        }
        if ( r12 != r9 ) printf("Failed.\n");
    }
    PrintFlag();
}

整理一下整個流程,簡單來說程式會從 port 13337 讀取 8 個 64bits 整數,計算出每個數的 13337 次方 mod 2 ^ 64 並與特定值比較,最後用這符合條件的 8 個數拼出 flag

在這裡可以發現程式組 flag 時其實是用 %08llx 做 format, 也就是說我們只需要知道目標數的最後 4 bytes,可以直接用暴力破解的方式爆出 crack.c:

crack.c
#include <stdio.h>
void fuck(unsigned long long r8) {
    unsigned long long tmp = r8;
    unsigned long long r9 = 0x1337DEADBEEF0095;
    unsigned long long r12 = 1;
    unsigned long long r10 = 13337;
    r9 *= 0xCAFE; r9 += 0xBEEF;
    while ( r10 ) {
        if ( r10 & 1 ) r12 *= r8;
        r8 *= r8;
        r10 >>= 1;
    }
    if ( ( r12 & 0xFFFFFFFF ) == 0x0798e4c5 ) printf("Flag1 Found! %llx\n", tmp);
    else if ( ( r12 & 0xFFFFFFFF ) == 0x2e372c65 ) printf("Flag2 Found! %llx\n", tmp);
    else if ( ( r12 & 0xFFFFFFFF ) == 0x63c67d25 ) printf("Flag3 Found! %llx\n", tmp);
    else if ( ( r12 & 0xFFFFFFFF ) == 0x9db01ba5 ) printf("Flag4 Found! %llx\n", tmp);
    else if ( ( r12 & 0xFFFFFFFF ) == 0x6a8c5ea5 ) printf("Flag5 Found! %llx\n", tmp);
    else if ( ( r12 & 0xFFFFFFFF ) == 0x79f4d8a5 ) printf("Flag6 Found! %llx\n", tmp);
    else if ( ( r12 & 0xFFFFFFFF ) == 0x33e1e4a5 ) printf("Flag7 Found! %llx\n", tmp);
    else if ( ( r12 & 0xFFFFFFFF ) == 0xb88bcca5 )
}
printf("Flag8 Found! %llx\n", tmp);
int main( int argc, char *argv[] ) {
    for ( unsigned int i = 0; i < 0xFFFFFFFF; ++i ) fuck(i);
}

最後手動組出 flag
Flag: 0ctf{c97155a5e288fa45f926b1058e4e0385d6ccde8513002885dc67948524bcbc85}