Published on

0CTF 2015 Quals - r0ops

allways r0ops !

只值 150 分的逆向題,卻在上面浪費了不少時間,覺得很挫敗...

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 上的函數

要注意的是由於程式會直接判斷 fd 是否等於 3,如果用 debugger load 的話 fd 通常會大於 3, 需要手動 patch 一下

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, rax
        ret
    }
}

sub_DEAD3AF() 會 recv 4096 bytes,最後把 rsp 修改為 0xE0AF8A0 後執行 ret

如同題目名稱,rsp 指向的資料區塊是個 rop chain, 透過肉眼辨識配合 IDA Pro 重新命名可以很快的把這些 function address 整理成對應的指令

rop

接著轉成類 asm 格式:

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
mov rax, [rsi]
sub rsi, 8
pop rbx
0CAFEh
imul rax, rbx
add rsi, 8
mov [rsi], rax
mov r9, [rsi]
sub rsi, 8
add rsi, 8
mov [rsi], r9
mov rax, [rsi]
sub rsi, 8
pop rbx
0BEEFh
add_rax_rbx
add rsi, 
mov [rsi], rax
mov r9, [rsi]
sub rsi, 8
pop r12
1
pop r10
3419h
pop rax
0
pop rbx
0
pop rdx
1D8h
cmp rax, rbx | jz +rdx
add rsi, 8
mov [rsi], r10
mov r11, [rsi]
sub rsi, 8
add rsi, 8
mov [rsi], r11
mov rax, [rsi]
sub rsi, 8
pop rbx
1
and rax, rbx
add rsi, 8
mov [rsi], rax
mov r11, [rsi]
sub rsi, 8
add rsi, 8
mov [rsi], r11
mov rax, [rsi]
sub rsi, 8
pop rbx
1
pop rdx
68h
cmp rax, rbx | jne +rdx
add rsi, 8
mov [rsi], r12
mov rax, [rsi]
sub rsi, 8
add rsi, 8
mov [rsi], r8
mov rbx, [rsi]
sub rsi, 8
imul rax, rbx
add rsi, 8
mov [rsi], rax
mov r12, [rsi]
sub rsi, 8
add rsi, 8
mov [rsi], r8
mov rax, [rsi]
sub rsi, 8
add rsi, 8
mov [rsi], r8
mov rbx, [rsi]
sub rsi, 8
imul rax, rbx
add rsi, 8
mov [rsi], rax
mov r8, [rsi]
sub rsi, 8
add rsi, 8
mov [rsi], r10
mov rax, [rsi]
sub rsi, 8
shr rax, 1
add rsi, 8
mov [rsi], rax
mov r10, [rsi]
sub rsi, 8
add rsi, 8
mov [rsi], r10
mov rax, [rsi]
sub rsi, 8
pop rbx
0
pop rdx
0FFFFFFFFFFFFFDE0h
cmp rax, rbx | jne +rdx
add rsi, 8
mov [rsi], r12
mov rax, [rsi]
sub rsi, 8
add rsi, 8
mov [rsi], r9
mov rbx, [rsi]
sub rsi, 8
pop rdx
20h
cmp rax, rbx | jne +rdx
pop rdx
0FFFFFFFFFFFFFC38h
add rap, rdx
call PrintFlag
call sub_DEAD3AF

不難發現 register 之間的資料轉移都是位於 add rsi, 8sub rsi, 8 之間, 可以再進一步簡化翻譯出 C code:

#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();
}

// r9 in 8 rounds:
// 0x2724090c0798e4c5
// 0x44e477ee2e372c65
// 0xa150eec963c67d25
// 0xeab7d48b9db01ba5
// 0xf01b0cf36a8c5ea5
// 0x930eeb9679f4d8a5
// 0xaeb27b8833e1e4a5
// 0x2a900a13b88bcca5

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

由於程式最後組 flag 時是用 "%08llx" 做 format,因此只需要知道目標數的最後 4 bytes, 可以直接用暴力破解的方式得到符合條件的值:

#include <stdio.h>

void f(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 )
        f(i);
}

Flag: 0ctf{c97155a5e288fa45f926b1058e4e0385d6ccde8513002885dc67948524bcbc85}