伊始 & 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 )

包上 cumtctf{} 的flag是
cumtctf{UWillBeNB}

补题

小结

ranked \(4^{th}\) ,过了两个题。

有点遗憾,感觉还可以再多过一个的。但是确实比较忙,卡住之后就跑路了。不过对于学了三四个小时能够达到的成绩也可以说是足够了,不过,这次真正感受到了这项赛事的魅力,也是第一次认真对待ctf类比赛,我会爱上它的,我想。

image

posted @ 2021-11-06 18:38  zer0_凌  阅读(86)  评论(0)    收藏  举报