1. 程式人生 > 其它 >CUMTCTF RE專項賽 2021-11-06

CUMTCTF RE專項賽 2021-11-06

伊始

開賽前一天晚上聽說專項賽不打的要被踢掉了。在隊內摸魚混了很久除了偶爾搜點 crypto 出來改改指令碼啥都不會。這時候只剩下RE專項賽了,而可憐的zer0_凌還啥都不會,只能大半夜零時抱佛腳翻開從0到1並且瞎 j2 學了一點,姑且是知道了 IDA 怎麼用之類的程度。然後莫名其妙地感覺挺好玩,於是就有了這個 blog 和這篇題解。(為了和記錄 ACM 內容的 blog 分開)

正文

賽時過的題

0基礎入門題

命令列裡面隨便執行玩了一下,輸入東西會返回 PLEASE, DO NOT GIVE UP! GIVE IT ANOTHER TRY. 的字樣。

扔到 exeinfo 裡看了一下就是普通的 64位 PE 檔案,直接扔到 IDA64 裡,在 string window 裡找到 PLEASE, DO NOT GIVE UP! GIVE IT ANOTHER TRY.

這串話的位置然後 F5 反編譯出來下面這段東西。

int __cdecl main(int argc, const char **argv, const char **envp)
{
    unsigned __int64 v3; // rcx
    __int64 v4; // rax
    unsigned __int64 v5; // rax
    int v6; // eax
    const char *v7; // rcx
    int Buf1[11]; // [rsp+20h] [rbp-19h] BYREF
    char v10; // [rsp+4Ch] [rbp+13h]
    __int128 Buf2[2]; // [rsp+50h] [rbp+17h] BYREF
    __int64 v12; // [rsp+70h] [rbp+37h]
    int v13; // [rsp+78h] [rbp+3Fh]
    __int16 v14; // [rsp+7Ch] [rbp+43h]

    Buf1[0] = -388306234;
    Buf1[1] = -154343226;
    Buf1[2] = -896106278;
    Buf1[3] = -322263874;
    Buf1[4] = 1726391498;
    Buf1[5] = 1927072446;
    Buf1[6] = -1966433182;
    Buf1[7] = -824417628;
    Buf1[8] = 1722060478;
    Buf1[9] = -2135003454;
    Buf1[10] = -597523762;
    v10 = -6;
    Buf2[0] = 0i64;
    v12 = 0i64;
    Buf2[1] = 0i64;
    v13 = 0;
    v14 = 0;
    sub_140001010("%45s");
    v3 = 0i64;
    v4 = -1i64;
    do
        ++v4;
    while ( *((_BYTE *)Buf2 + v4) );
    if ( v4 )
    {
        do
        {
            *((_BYTE *)Buf2 + v3) = __ROL1__(*((_BYTE *)Buf2 + v3), 1);
            ++v3;
            v5 = -1i64;
            do
                ++v5;
            while ( *((_BYTE *)Buf2 + v5) );
        }
        while ( v3 < v5 );
    }
    v6 = memcmp(Buf1, Buf2, 0x2Dui64);
    v7 = "WELCOME TO THE WORLD OF REVERSE ENGINEERING!!!";
    if ( v6 )
        v7 = "PLEASE, DO NOT GIVE UP! GIVE IT ANOTHER TRY.";
    puts(v7);
    return 0;
}

之前並沒有經驗,所以百度了一些東西:

  1. BYTE 代表 8 位長度的型別。
  2. __ROL1__(value, len) 表示將 value 迴圈左移 len 位, len 為負數表示右移 $\left| len \right|$ 位。

發現最後判斷了輸入串 Str2Str1 的等價性,可以推知答案就是由 Str1 產生的。

觀察程式碼發現包含 v5 的迴圈求的是當前 byte 最後非全 $0$ 的位置。並且對所有非 0 的 byte 全部以 byte 為分組進行迴圈左移 $1$ 位的加密。

那麼原來的 Str1 其實是一個字串。

直接把 Buf[0...10] 的內容迴圈位移回去,跑出來是個這樣的東西:
cumtctf{m@Ke_ReveRs3_en91NeER1ng_GR3a7_@gA1n

把後括號補上交上去就過了。問了神仙舍友,大概本質是這樣的:

觀察地址發現 v10 是和 Buf1 連在一起的,Buf1 所在的那個 Char[] 最後賦值上去的一個位置是隻有前 $8$ 位是有數字的,那也就是 v10 那個變數,它沒有被識別成 Buf1[11] 因為後面的 $3$ 個 byte 是全 $0$ 的。

那麼指令碼換一下就行了。

不怎麼會用 python,用 C++ 控制就是用 unsigned char* 去指向 int 陣列的位置然後迴圈位移。
C++ 預設的位移是不處理溢位部分的,迴圈位移的方式是普通位移然後把被移出去的位置補回去。
記得位移在不用 unsigned 的時候有問題(處理方式不大一樣),所以要用 unsigned 。

#include <bits/stdc++.h>

using namespace std;

typedef unsigned char UC;

int Buf1[ 20 ];

int main(  ) {
    Buf1[0] = -388306234;
    Buf1[1] = -154343226;
    Buf1[2] = -896106278;
    Buf1[3] = -322263874;
    Buf1[4] = 1726391498;
    Buf1[5] = 1927072446;
    Buf1[6] = -1966433182;
    Buf1[7] = -824417628;
    Buf1[8] = 1722060478;
    Buf1[9] = -2135003454;
    Buf1[10] = -597523762;
    *(char*)(&Buf1[11]) = -6;

    UC* bt = ( UC* ) Buf1;
    while( *bt ) {
        *bt = ( *bt >> 1 ) | ( ( *bt & 1 ) << 7 );
        ++ bt;
    }
    UC *str = ( UC* ) Buf1;
    cout << str;
    return 0;
}

來自位元組碼的鼓勵

下發檔案1是一個文字,2是一個圖片,3是一個看起來像彙編的東西。
查了一下發現是位元組碼。

一些學到的東西:

  1. 每個塊對應著最右邊行號的那一行修出來的東西。
  2. 中間一列是對應的位元組碼本體和位元組碼的行號,最右邊一列是名字(函式名、變數名、字串等等)。
  3. python 一部分位元組碼的意思。
    LOAD_CONST 載入常量
    LOAD_GLOBAL - STORE_GLOBAL 載入 - 寫入全域性變數,或載入函式。
    LOAD_FAST - STORE_FAST 載入 - 寫入區域性變數。
    CALL_FUNCTION 呼叫向上看最近一個被LOAD沒被CALL的函式,最後一列括號前面的數字表示引數個數,並讀取前面 LOAD 的變數作為引數。
    LOAD_METHOD 呼叫方法。
    BINARY_SUBSCR 表示切片操作(s[i]之類的)。
    迴圈:
    SETUP_LOOP (to linenumber) 開始迴圈。
    JUMP_ABSOLUTE 跳出。
    一段 while - loop 的例子:
    原始碼
i = 0
while i < 10:
    i += 1

位元組碼

 1           0 LOAD_CONST               0 (0)
              2 STORE_NAME               0 (i)
 
  2           4 SETUP_LOOP              20 (to 26)   // 迴圈開始處,26表示迴圈結束點
        >>    6 LOAD_NAME                0 (i)       // “>>" 表示迴圈切入點
              8 LOAD_CONST               1 (10)
             10 COMPARE_OP               0 (<)
             12 POP_JUMP_IF_FALSE       24
 
  3          14 LOAD_NAME                0 (i)
             16 LOAD_CONST               2 (1)
             18 INPLACE_ADD
             20 STORE_NAME               0 (i)
             22 JUMP_ABSOLUTE            6          // 邏輯上,迴圈在此處結束
        >>   24 POP_BLOCK                          
        >>   26 LOAD_CONST               3 (None)
             28 RETURN_VALUE

for in loop 則是將判斷改成了讀取迭代器。

            126 GET_ITER
        >>  128 FOR_ITER                48 (to 178)
            130 STORE_FAST               6 (i)

if 條件句由這個樣子的東西來實現:

 18     >>  180 LOAD_FAST                2 (c)
            182 LOAD_FAST                1 (ff)
            184 COMPARE_OP               2 (==)
            186 POP_JUMP_IF_FALSE      196

這篇blog 寫的很詳細。
這裡 能查一些位元組碼。

按照翻譯出來的東西輸出 c 的值即為答案 :

n = [ -83, -96, -78, -21, -3, -17, 58, 31, 58 ]

f = None
b = None

with open( '1', 'r', encoding = 'utf_8' ) as file1:
    s = file1.read(  )
    file1.close(  )

with open( '2', 'rb' ) as file2:
    b = file2.read(  )
    file2.close(  )

c = ''

for i in range( len( s ) ):
    tmp = ord( s[ i ] ) ^ b[ i ]
    tmp += n[ i ]
    c += chr( tmp )

print( c )

補題