Back
Featured image of post All Solves During a Weekend

All Solves During a Weekend

这个周末遇到好多新奇的东西,其实有几道题值得单独写一篇,但是懒了,就统一记录一下吧。包括美团CTF和SECCON的部分题目。

打美团 CTF 的时候才意识到,有一种坐牢叫AK

[MTCTF 4th]wow

先进行脱壳,因为脱壳后用的是绝对地址,而加载的时候是动态加载,所有没有办法调试,那就直接静态看吧

JUMPOUT 那里看一下汇编,发现有一个天堂之门

push 0x33
call $+5
add [esp+84h+var_84], 5
retf

直接把后面的数据全 dump 出来,然后用 ida64 打开

void __fastcall sub_0(unsigned int *a1)
{
  unsigned int v1; // er9
  int v2; // ebp
  unsigned int v3; // esi
  unsigned int *v4; // rdi
  unsigned int *v5; // r10
  unsigned int v6; // ebx
  unsigned int i; // er11
  unsigned int v8; // er8
  int v9; // edx
  unsigned int v10; // eax
  unsigned int v11; // er9
  unsigned int v12; // er8
  int v13; // ebx
  int v14; // ebx
  int v15; // ebx
  int v16; // edx
  unsigned int v17; // eax
  unsigned int v18; // er9

  v1 = a1[8];
  v2 = 12;
  v3 = 0;
  while ( 1 )
  {
    v3 += 1732584193;
    v4 = a1 + 1;
    v5 = a1;
    v6 = v3 >> 2;
    for ( i = 0; i < 8; ++i )
    {
      v8 = *v4;
      if ( (((unsigned __int8)i ^ (unsigned __int8)v6) & 3) != 0 )
      {
        switch ( ((unsigned __int8)i ^ (unsigned __int8)v6) & 3 )
        {
          case 1:
            v9 = (v1 >> 5) ^ (4 * v8);
            v10 = v1;
            v11 = v1 ^ 0x10325476;
            break;
          case 2:
            v9 = (v1 >> 5) ^ (4 * v8);
            v10 = v1;
            v11 = v1 ^ 0x98BADCFE;
            break;
          case 3:
            v9 = (v1 >> 5) ^ (4 * v8);
            v10 = v1;
            v11 = v1 ^ 0xC3D2E1F0;
            break;
          default:
            goto LABEL_12;
        }
      }
      else
      {
        v9 = (v1 >> 5) ^ (4 * v8);
        v10 = v1;
        v11 = v1 ^ 0xEFCDAB89;
      }
      *v5 += (v11 + (v3 ^ v8)) ^ (((16 * v10) ^ (v8 >> 3)) + v9);
      v1 = *v5;
LABEL_12:
      ++v4;
      ++v5;
    }
    v12 = *a1;
    v13 = ((unsigned __int8)i ^ (unsigned __int8)v6) & 3;
    if ( !v13 )
    {
      v16 = (v1 >> 5) ^ (4 * v12);
      v17 = v1;
      v18 = v1 ^ 0xEFCDAB89;
      goto LABEL_21;
    }
    v14 = v13 - 1;
    if ( !v14 )
    {
      v16 = (v1 >> 5) ^ (4 * v12);
      v17 = v1;
      v18 = v1 ^ 0x10325476;
      goto LABEL_21;
    }
    v15 = v14 - 1;
    if ( !v15 )
    {
      v16 = (v1 >> 5) ^ (4 * v12);
      v17 = v1;
      v18 = v1 ^ 0x98BADCFE;
LABEL_21:
      a1[8] += (v18 + (v3 ^ v12)) ^ (((16 * v17) ^ (v12 >> 3)) + v16);
      v1 = a1[8];
      goto LABEL_22;
    }
    if ( v15 == 1 )
    {
      v16 = (v1 >> 5) ^ (4 * v12);
      v17 = v1;
      v18 = v1 ^ 0xC3D2E1F0;
      goto LABEL_21;
    }
LABEL_22:
    if ( !--v2 )
      __asm { retfq }
  }
}

是一个 xxtea,用 switch 来判断使用哪个 key,round 是 12,所以直接用脚本解密就行

#include <stdio.h>  
#include <stdint.h>  
#define DELTA 0x67452301  
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))  
  
void btea(uint32_t *v, int n, uint32_t const key[4])  
{  
    uint32_t y, z, sum;  
    unsigned p, rounds, e;  
    if (n > 1)            /* Coding Part */  
    {  
        printf("No use");
    }  
    else if (n < -1)      /* Decoding Part */  
    {  
        n = -n;  
        // rounds = 6 + 52/n; 
        rounds = 12; 
        sum = rounds*DELTA;  
        y = v[0];  
        do  
        {  
            e = (sum >> 2) & 3;  
            for (p=n-1; p>0; p--)  
            {  
                z = v[p-1];  
                y = v[p] -= MX;  
            }  
            z = v[n-1];  
            y = v[0] -= MX;  
            sum -= DELTA;  
        }  
        while (--rounds);  
    }  
}  

int main()  
{  
    unsigned int v[10] = {
        3640088821u,
        1382566363u,
        3805750627u,
        1214181292u,
        1620003782u,
        1482291050u,
        2956289443u,
        1044419009u,
        3554368410u,
        0u
    };
    uint32_t const k[4]= {0xEFCDAB89u, 0x10325476u, 0x98BADCFEu, 0xC3D2E1F0u};  
    int n= -9; //n的绝对值表示v的长度,取正表示加密,取负表示解密  
    // v为要加密的数据是两个32位无符号整数  
    // k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位  
    btea(v, n, k);   
    printf("%s\n", v);
    return 0;  
}  

[MTCTF 4th]Superflat

这个题用了 movfuscator,正好前段时间有人在群里提过,一下就想起来了

但要解混淆需要装一个 demov 的工具,配置环境有点复杂

考虑到这个程序中没有任何的动态链接过程,而且能看到两个函数名 getcharputchar,那大胆猜测是按照每一位来判断的,可以考虑上 pintools

先手动测试了一下,发现对于同样的输入,执行的指令数字完全相同,并且每正确一位会增加 4888 条指令,于是根据 pintools 这个工具自己写个爆破脚本

在爆破前看了一下 output 文件,发现总长度是42位,于是直接猜测 flag 格式为 flag{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} 其中 x 为 [0-9a-f] (小写字母是手动爆破了第一位确定的)

import subprocess

password = "flag{d06f0bcc-xxxxxxxxxxxxxxxxxxxxxxxxxxx}"

cur_pos = 14
last_count = 693225

while cur_pos < len(password) - 1:
    for i in "0123456789abcdef-":
        command = "echo " + password[:cur_pos] + i + password[cur_pos+1:] + " | ./pin-3.18-98332-gaebd7b1e6-gcc-linux/pin -t ./pin-3.18-98332-gaebd7b1e6-gcc-linux/source/tools/ManualExamples/obj-ia32/inscount0.so -- ./superflat; cat inscount.out"
        output = subprocess.check_output(command, shell=True, stderr=subprocess.PIPE)
        count = int(output.split(b'\n')[1].split()[1].decode())
        if count - last_count > 0:
            print (i)
            password = password[:cur_pos] + i + password[cur_pos + 1:]
            cur_pos += 1
            last_count = count
            break
    else:
        print ("\nerror:", cur_pos)
        break
# flag{d06f0bcc-93e0-9c5b-161e-e1464176d395}

第一次在比赛中用上 pintool,纪念一下

[SECCONCTF 2021]dis-me

python 逆向,一上线就被叫来做这题了(属于是把 python 题目包场了)

先自己看了眼字节码,然后判断了一下,删了一些东西,再 pycdc 反编译,同时结合着自己的分析,拿到了反编译结果

import marshal
import base64
import types
f = open(__file__, 'rb')
f.seek(12)
s = marshal.loads(f.read()).co_code[4:]
types.FunctionType(marshal.loads(base64.b64decode(bytes([((y - x * 7 - 45) % 256) for x, y in enumerate((s[2 : 2 + s[0] * 256 + s[1]]))]))), globals())()
del s
del f
del marshal
del base64
del types
return None

读取了自身文件,然后偏移改到了 12,就是跳过了文件头,直接从 E3 00 00 00 开始,是 marshal 的正确格式(但自己写的时候有一些问题,不太懂了)

接下来 .co_code[4:] 猜测是取了代码段,并从下标为 4 的地方开始取

s[2 : 2 + s[0] * 256 + s[1]]:接下来根据取出数据的前两位来判断接下来取出数据的总长度,并且跳过了前两位

于是最终取出的数据为 0x2C 开始的数据,一直取到 64 00 64 01 这个正确程序的开头部分

types 那一行里面的 bytes([((y - x * 7 - 45) % 256) for x, y in enumerate((s[2 : 2 + s[0] * 256 + s[1]]))]) 一开始并没有分析出来,主要是位置不确定,于是自己拿数据试了试可能的几个方案,发现这样可以得到 base64的结果,就说明是这个了

将 base64 的结果解码到文件,然后补全文件头后 uncompyle6 反编译,得到正确的流程

f = lambda n: n if n <= 1 else (f(n - 1) + f(n - 2)) % 10
flag = input('Input the flag > ')
s = 1
if flag.startswith('SECCON{'):
    if flag.endswith('}'):
        if len(flag) == 40:
            s = sum(abs(ord(c) - ord(str(f(i)))) for i, c in enumerate(flag[7:-1]))
    s or print('Correct! The flag is', flag)
else:
    print('Wrong :(')

直接把 str(f(i)) 跑一遍就拿到 flag 了(斐波那契)

[SECCONCTF 2021]flag

这道题是个 wasm 逆向,人生第一次做出来,值得纪念

先从网站上把 wasm 文件下载下来

第一步肯定是 wasm2c 一下

./wasm2c 0001d242.wasm -o out.c

接下来进行编译

g++ -c out.c -o out.o

用 ida 打开,发现一个叫 check 的函数,到网站的 js 里面动调到 check,可以获取加载的数据

check(input, "NekoPunch", "6dbf84f73cf6a112268b09525ea550a665e21cb2e3e13af7e3ea0ecb52f5b9cda5b6522b1e978734553f1d7956d4af94bfc3f4d68c8fba9eeecf4035550b9106f70d57d1a6cdaf3211eaaa78d71a9038b71be621241e8b608a43b107f8860f543ab0189aa063800de4bae7d0b11045b8")

于是看一下 check 函数就行

  v6 = w2c_g0;
  i32_store(&w2c_memory, (unsigned int)w2c_g0 + 40LL, a1);
  // 省略
  v7 = i32_load(&w2c_memory, v6 + 40LL);
  v8 = i32_load(&w2c_memory, v6 + 40LL);
  v9 = w2c_f25(v8) - 1;
  v52 = w2c_f21(v7, v9);
  if ( (char)i32_load8_u(&w2c_memory, v52) == '}'

wasm 是基于堆栈的,这里明显能看到栈的痕迹,上来先把第一个参数(输入字符串的地址)存到了 v6+40 的地方(猜测v6是栈顶)

通过第一条 if 比较,猜测 v52 是输入的最后一位,这样就可以知道 w2c_f25 是计算长度,w2c_f21 相当于 a1[a2]

接下来注意到中间有一个非常整齐的循环

      while ( (int)i32_load(&w2c_memory, v6 + 12LL) < 128 )
      {
        v22 = i32_load(&w2c_memory, v6 + 28LL);
        w2c_f23(v22, 0, 4u, 8u, 0xCu);
        v23 = i32_load(&w2c_memory, v6 + 28LL);
        w2c_f23(v23, 5u, 9u, 0xDu, 1u);
        v24 = i32_load(&w2c_memory, v6 + 28LL);
        w2c_f23(v24, 0xAu, 0xEu, 2u, 6u);
        v25 = i32_load(&w2c_memory, v6 + 28LL);
        w2c_f23(v25, 0xFu, 3u, 7u, 0xBu);
        v26 = i32_load(&w2c_memory, v6 + 28LL);
        w2c_f23(v26, 0, 1u, 2u, 3u);
        v27 = i32_load(&w2c_memory, v6 + 28LL);
        w2c_f23(v27, 5u, 6u, 7u, 4u);
        v28 = i32_load(&w2c_memory, v6 + 28LL);
        w2c_f23(v28, 0xAu, 0xBu, 8u, 9u);
        v29 = i32_load(&w2c_memory, v6 + 28LL);
        w2c_f23(v29, 0xFu, 0xCu, 0xDu, 0xEu);
        v57 = i32_load(&w2c_memory, v6 + 12LL) + 1;
        i32_store(&w2c_memory, v6 + 12LL, v57);
      }

显然 v6+12 的地方存的是循环次数,这个格式和前段时间学的 salsa20 很像

查看 w2c_f23 函数

  i32_store(&w2c_memory, (unsigned int)(w2c_g0 - 32) + 28LL, a1);
  i32_store(&w2c_memory, v9 + 24LL, a2);
  i32_store(&w2c_memory, v9 + 20LL, a3);
  i32_store(&w2c_memory, v9 + 16LL, a4);
  i32_store(&w2c_memory, v9 + 12LL, a5);
  v10 = i32_load(&w2c_memory, v9 + 28LL);
  v11 = i32_load(&w2c_memory, v9 + 24LL) + v10;
  v12 = i32_load8_u(&w2c_memory, v11);
  v13 = i32_load(&w2c_memory, v9 + 28LL);
  v14 = i32_load(&w2c_memory, v9 + 12LL) + v13;
  v15 = i32_load8_u(&w2c_memory, v14);
  v16 = i32_load(&w2c_memory, v9 + 28LL);
  v17 = i32_load(&w2c_memory, v9 + 24LL) + v16;
  v18 = i32_load8_u(&w2c_memory, v17);
  v19 = i32_load(&w2c_memory, v9 + 28LL);
  v20 = i32_load(&w2c_memory, v9 + 12LL) + v19;
  v21 = i32_load8_u(&w2c_memory, v20);
  v22 = i32_load(&w2c_memory, v9 + 28LL);
  v23 = i32_load(&w2c_memory, v9 + 20LL) + v22;
  v24 = (((int)(unsigned __int8)(v21 + v18) >> 7) | (2 * (unsigned __int8)(v15 + v12))) ^ (unsigned __int8)i32_load8_u(&w2c_memory, v23);
  i32_store8(&w2c_memory, v23, v24);            // x[b] ^= ROL((x[d] + x[a]), 1)
  v25 = i32_load(&w2c_memory, v9 + 28LL);
  v26 = i32_load(&w2c_memory, v9 + 20LL) + v25;
  v27 = i32_load8_u(&w2c_memory, v26);
  v28 = i32_load(&w2c_memory, v9 + 28LL);
  v29 = i32_load(&w2c_memory, v9 + 24LL) + v28;
  v30 = i32_load8_u(&w2c_memory, v29);
  v31 = i32_load(&w2c_memory, v9 + 28LL);
  v32 = i32_load(&w2c_memory, v9 + 20LL) + v31;
  v33 = i32_load8_u(&w2c_memory, v32);
  v34 = i32_load(&w2c_memory, v9 + 28LL);
  v35 = i32_load(&w2c_memory, v9 + 24LL) + v34;
  v36 = i32_load8_u(&w2c_memory, v35);
  v37 = i32_load(&w2c_memory, v9 + 28LL);
  v38 = i32_load(&w2c_memory, v9 + 16LL) + v37;
  v39 = (((int)(unsigned __int8)(v36 + v33) >> 6) | (4 * (unsigned __int8)(v30 + v27))) ^ (unsigned __int8)i32_load8_u(&w2c_memory, v38);
  i32_store8(&w2c_memory, v38, v39);            // x[c] ^= ROL((x[a] + x[b]), 2)
  v40 = i32_load(&w2c_memory, v9 + 28LL);
  v41 = i32_load(&w2c_memory, v9 + 16LL) + v40;
  v42 = i32_load8_u(&w2c_memory, v41);
  v43 = i32_load(&w2c_memory, v9 + 28LL);
  v44 = i32_load(&w2c_memory, v9 + 20LL) + v43;
  v45 = i32_load8_u(&w2c_memory, v44);
  v46 = i32_load(&w2c_memory, v9 + 28LL);
  v47 = i32_load(&w2c_memory, v9 + 16LL) + v46;
  v48 = i32_load8_u(&w2c_memory, v47);
  v49 = i32_load(&w2c_memory, v9 + 28LL);
  v50 = i32_load(&w2c_memory, v9 + 20LL) + v49;
  v51 = i32_load8_u(&w2c_memory, v50);
  v52 = i32_load(&w2c_memory, v9 + 28LL);
  v53 = i32_load(&w2c_memory, v9 + 12LL) + v52;
  v54 = (((int)(unsigned __int8)(v51 + v48) >> 5) | (8 * (unsigned __int8)(v45 + v42))) ^ (unsigned __int8)i32_load8_u(&w2c_memory, v53);
  i32_store8(&w2c_memory, v53, v54);            // x[d] ^= ROL((x[b] + x[c]), 3)
  v55 = i32_load(&w2c_memory, v9 + 28LL);
  v56 = i32_load(&w2c_memory, v9 + 12LL) + v55;
  v57 = i32_load8_u(&w2c_memory, v56);
  v58 = i32_load(&w2c_memory, v9 + 28LL);
  v59 = i32_load(&w2c_memory, v9 + 16LL) + v58;
  v60 = i32_load8_u(&w2c_memory, v59);
  v61 = i32_load(&w2c_memory, v9 + 28LL);
  v62 = i32_load(&w2c_memory, v9 + 12LL) + v61;
  v63 = i32_load8_u(&w2c_memory, v62);
  v64 = i32_load(&w2c_memory, v9 + 28LL);
  v65 = i32_load(&w2c_memory, v9 + 16LL) + v64;
  v66 = i32_load8_u(&w2c_memory, v65);
  v67 = i32_load(&w2c_memory, v9 + 28LL);
  v68 = i32_load(&w2c_memory, v9 + 24LL) + v67;
  v69 = (((int)(unsigned __int8)(v66 + v63) >> 4) | (16 * (unsigned __int8)(v60 + v57))) ^ (unsigned __int8)i32_load8_u(&w2c_memory, v68);
  i32_store8(&w2c_memory, v68, v69);            // x[a] ^= ROL((x[c] + x[d]), 4)

这个东西套到 salsa20 的模板里就长这个样子

#define QUARTERROUND(x, a, b, c, d) \
    x[b] ^= ROL(x[a] + x[d], 1); \
    x[c] ^= ROL(x[b] + x[a], 2); \
    x[d] ^= ROL(x[c] + x[b], 3); \
    x[a] ^= ROL(x[d] + x[c], 4);

于是那个 while 循环就是这个

for(int i = 0; i < 128; i++){
    QUARTERROUND(v6+28LL,  0,  4,  8, 12);
    QUARTERROUND(v6+28LL,  5,  9, 13,  1);
    QUARTERROUND(v6+28LL, 10, 14,  2,  6);
    QUARTERROUND(v6+28LL, 15,  3,  7, 11);
    QUARTERROUND(v6+28LL,  0,  1,  2,  3);
    QUARTERROUND(v6+28LL,  5,  6,  7,  4);
    QUARTERROUND(v6+28LL, 10, 11,  8,  9);
    QUARTERROUND(v6+28LL, 15, 12, 13, 14);
}

接下来看一下验证部分

      i32_store(&w2c_memory, v6 + 8LL, 0LL);
      while ( (int)i32_load(&w2c_memory, v6 + 8LL) < 16 )
      {
        v30 = i32_load(&w2c_memory, v6 + 32LL);
        v31 = 4 * i32_load(&w2c_memory, v6 + 20LL);
        v32 = 2 * i32_load(&w2c_memory, v6 + 8LL) + v31;
        v33 = w2c_f21(v30, v32);
        v34 = i32_load8_u(&w2c_memory, v33);
        v35 = i32_load(&w2c_memory, v6 + 28LL);
        v36 = i32_load(&w2c_memory, v6 + 8LL) + v35;
        v55 = (unsigned __int8)i32_load8_u(&w2c_memory, v36);
        v37 = i32_load8_u(&w2c_memory, v55 / 16 + 3504LL);
        v38 = (v34 != v37) | (unsigned int)i32_load(&w2c_memory, v6 + 24LL);
        i32_store(&w2c_memory, v6 + 24LL, v38);
        v39 = i32_load(&w2c_memory, v6 + 32LL);
        v40 = 4 * i32_load(&w2c_memory, v6 + 20LL);
        v41 = 2 * i32_load(&w2c_memory, v6 + 8LL) + v40;
        v42 = w2c_f21(v39, v41 + 1);
        v43 = i32_load8_u(&w2c_memory, v42);
        v44 = i32_load(&w2c_memory, v6 + 28LL);
        v45 = i32_load(&w2c_memory, v6 + 8LL) + v44;
        v3 = (unsigned __int8)i32_load8_u(&w2c_memory, v45) % 16;
        v46 = i32_load8_u(&w2c_memory, v3 + 3504LL);
        v47 = (v43 != v46) | (unsigned int)i32_load(&w2c_memory, v6 + 24LL);
        i32_store(&w2c_memory, v6 + 24LL, v47);
        v58 = i32_load(&w2c_memory, v6 + 8LL) + 1;
        i32_store(&w2c_memory, v6 + 8LL, v58);
      }
      v59 = i32_load(&w2c_memory, v6 + 20LL) + 8;
      i32_store(&w2c_memory, v6 + 20LL, v59);
    }
    v48 = i32_load(&w2c_memory, v6 + 28LL);
    w2c_free(v48);
    v60 = i32_load(&w2c_memory, v6 + 24LL);
    i32_store(&w2c_memory, v6 + 44LL, v60);
  }
  else
  {
LABEL_8:
    i32_store(&w2c_memory, v6 + 44LL, 0xFFFFFFFFLL);
  }
  v49 = i32_load(&w2c_memory, v6 + 44LL);
  w2c_g0 = v6 + 48;
  --wasm_rt_call_stack_depth;
  return v49;
}

很显然 v6+44v6+24 就是判断是否通过验证的,只要每次 (v43 != v46)(v34 != v37) 不成立就行

根据之前的经验,v6+32 是第三个参数(密文字符串地址),v6+20 是最外层循环的索引(每次 +8),v6+8 是这层循环的次数,v6+28 是上一个循环加密后的结果

于是把这个循环简化一下

for (int i = 0; i < len(input); i++){
    // ...
    for (int j = 0; j < 16; j++) {
        v30 = a3;
        v31 = 4 * i;
        v32 = 2 * j + v31;
        v33 = &v30[v32];
        v34 = *v33;
        v35 = cipher;
        v36 = &cipher[i];
        v55 = *v36;
        v37 = loc_3504[v55 / 16]; // 3504 中存的是 0-9a-f
        res |= (v34 != v37);
        // 后半部分相似
    }
}

再简化一下就是

for (int i = 0; i < strlen(input); i++) {
    // ...
    for (int j = 0; j < 16; j++) {
        v34 = a3[4 * i + 2 * j];
        v37 = loc_3504[cipher[i] / 16];
        v43 = a3[4 * i + 2 * j + 1];
        v46 = loc_3504[cipher[i] % 16];
        res |= (v34 != v37) | (v43 != v46);
    }
}

所以只需要让加密结果转 hex 后等于密文即可

同样的方法,翻译一下第一个 while 循环

for (int j = 0; j < 8; j++) {
    cipher[8 + j] = input[i + j];
}

于是想要找一下前八位是什么,发现

    v12 = i32_load(&w2c_memory, v6 + 36LL);
    v13 = w2c_f20(v12);
    i32_store(&w2c_memory, v6 + 28LL, v13);

于是猜是把第二个参数的前 8 字节存到了 cipher 的前 8 字节

这样就分析完了整个加密流程,每次取 8 字节明文,使用类似 salsa 加密的核心算法,生成的结果与 hexstring 进行比较

#include <stdio.h>
#include <stdint.h>

#define ROL(a,b) (((a) << (b)) | ((a) >> (8 - (b))))
#define REQUARTERROUND(x, a, b, c, d) \
    x[a] ^= ROL((x[d] + x[c]) & 0xff, 4); \
    x[d] ^= ROL((x[c] + x[b]) & 0xff, 3); \
    x[c] ^= ROL((x[b] + x[a]) & 0xff, 2); \
    x[b] ^= ROL((x[a] + x[d]) & 0xff, 1); 

using namespace std;

void decrypt(unsigned char * in){
    for(int i = 0; i < 128; i++){
        REQUARTERROUND(in, 15, 12, 13, 14);
        REQUARTERROUND(in, 10, 11,  8,  9);
        REQUARTERROUND(in,  5,  6,  7,  4);
        REQUARTERROUND(in,  0,  1,  2,  3);
        REQUARTERROUND(in, 15,  3,  7, 11);
        REQUARTERROUND(in, 10, 14,  2,  6);
        REQUARTERROUND(in,  5,  9, 13,  1);
        REQUARTERROUND(in,  0,  4,  8, 12);
    }
}

int main(){
    unsigned char enc[] = {
        109u, 191u, 132u, 247u,  60u, 246u, 161u,  18u, 
         38u, 139u,   9u,  82u,  94u, 165u,  80u, 166u, 
        101u, 226u,  28u, 178u, 227u, 225u,  58u, 247u, 
        227u, 234u,  14u, 203u,  82u, 245u, 185u, 205u, 
        165u, 182u,  82u,  43u,  30u, 151u, 135u,  52u, 
         85u,  63u,  29u, 121u,  86u, 212u, 175u, 148u, 
        191u, 195u, 244u, 214u, 140u, 143u, 186u, 158u, 
        238u, 207u,  64u,  53u,  85u,  11u, 145u,   6u, 
        247u,  13u,  87u, 209u, 166u, 205u, 175u,  50u, 
         17u, 234u, 170u, 120u, 215u,  26u, 144u,  56u, 
        183u,  27u, 230u,  33u,  36u,  30u, 139u,  96u, 
        138u,  67u, 177u,   7u, 248u, 134u,  15u,  84u, 
         58u, 176u,  24u, 154u, 160u,  99u, 128u,  13u, 
        228u, 186u, 231u, 208u, 177u,  16u,  69u, 184u
    }; // 先用 python 把 hexstring 转换回来

    for (int i = 0; i < 7; i++)
        decrypt(enc + i * 16);
    for(int i = 0; i < 7; i++)
        for (int j = 8; j < 16; j++)
            printf("%c", enc[16 * i + j]);
    printf("\n");
    return 0;
}

[SECCONCTF 2021]qchecker

这道题是 ruby 混淆

程序的空格会拼成 SECCON 的字样

测试后发现错误的输入会让程序拼成 WRONG. 的字样

整理一下格式,理解程序

eval$uate=%w(
    a=%(eval$uate=%w(#{$uate})*"");
    Bftjarzs=b=->a{a.split(?+).map{|b|b.to_i(36)}};
    c=b["awyiv4fjfkuu2pkv+awyiv4fvut716g3j+axce5e4pxrogszr3+5i0omfd5dm9xf9q7+axce5e4khrz21ypr+5htqqi9iasvmjri7+axcc76i03zrn7gu7+cbt4m8xybr3cb27+1ge6snjex10w3si9+1k8vdb4fzcys2yo0"];
    d,e,f,g,h,i=b["0+0+zeexaxq012eg+k2htkr1olaj6+3cbp5mnkzllt3+2qpvamo605t7j"];
    (j=eval(?A<<82<<71<<86)[0])
    && d==0 
    && (
        e+=1;k=2**64;l=->(a,b){(a-j.ord)*256.pow(b-2,b)%b};
        f=l[f,k+13];
        g=l[g,k+37];
        h=l[h,k+51];
        i=l[i,k+81];
        j==?}&&(d=e==32&&f+g+h+i==0?2:1);
        a.sub!(/"0.*?"/,'"0'+[d,e,f,g,h,i].map{|x|x.to_s(36)}*?+<<34)
    );
    srand(f);
    k=b["7acw+jsjm+46d84"];
    l=d==2?7:6;
    m=[?#*(l*20)<<10]*11*"";
    l.times{
        |a|b=d==0&&e!=0?rand(4):0;
        9.times{
            |e|9.times{
                |f|(c[k[d]/10**a%10]>>(e*9+f)&1)!=0
                &&(
                    g=f;
                    h=e;
                    b.times{
                        g,h=h,8-g
                    };
                    t=(h*l+l+a)*20+h+g*2+2;
                    m[t]=m[t+1]=""<<32
                )
            }
        }
    };
    a.sub!(/B.*?=/,"B=");
    n=m.count(?#)-a.length;
    a.sub!("B=","B#{(1..n).map{(rand(26)+97).chr}*""}=");
    o=0;
    m.length.times{|b|m[b]==?#&&o<a.length&&(m[b]=a[o];o+=1)};
    puts(m)
) * ""

先运行了几份代码做对比,发现区别仅在于 d,e,f,g,h,i=b[]; 这一行,而且只有拼成 WRONG 的时候,d 才是 1,e 则不断自增,其余部分的程序完全一样,根据 d==2?7:6; 猜测,当 d 为 2 的时候正确,因为 SECCON 和 WRONG. 都是 6 个字符,完全符合 d 不为 2 的情况

根据这个猜测,srand(f); 以下的代码是负责控制空格样式的,和验证没有关系,不许管

我们需要关注的只有如下函数

    d,e,f,g,h,i=b["0+0+zeexaxq012eg+k2htkr1olaj6+3cbp5mnkzllt3+2qpvamo605t7j"];
    (j=eval(?A<<82<<71<<86)[0])
    && d==0 
    && (
        e+=1;k=2**64;l=->(a,b){(a-j.ord)*256.pow(b-2,b)%b};
        f=l[f,k+13];
        g=l[g,k+37];
        h=l[h,k+51];
        i=l[i,k+81];
        j==?}&&(d=e==32&&f+g+h+i==0?2:1);
        a.sub!(/"0.*?"/,'"0'+[d,e,f,g,h,i].map{|x|x.to_s(36)}*?+<<34)
    );

根据测试 j=eval(?A<<82<<71<<86)[0] 是获取命令行参数,也就是当前传入的字符

l 函数就是 (a - ord(j)) * invmod(256) % b

把这个程序倒过来就是 (a * 256 + ord(j)) % b

如果不考虑 % b,最后得到的就是类似 n2s(flag[::-1]) 的效果

所以 f, g, h, i 的初值就是 n2s(flag[::-1]) % (k+?),标准的中国剩余定理

a = [4659461645708163688, 2641556351334323346, 15837377083725718695, 12993509283917003551]

m = [(2 ** 64 + 13) , (2 ** 64 + 37), (2 ** 64 + 51), (2 ** 64 + 81) ]

def gcd(x, y):
    while y != 0:
        x, y = y, x % y
    return abs(x)

def ex_gcd(x, y):
    list_x = [x, 1, 0]
    list_y = [y, 0, 1]
    while list_y[0] != 0:
        r = list_x[0] // list_y[0]
        for i in range(3):
            list_x[i] -= r * list_y[i]
        list_x, list_y = list_y, list_x
    if list_x[0] < 0:
        list_x[0], list_x[1], list_x[2] = -list_x[0], -list_x[1], -list_x[2]
    return list_x

def inverse(a, n):
    if n < 2:
        raise ValueError("n < 2, error")
    g, x, y = ex_gcd(a, n)
    if g != 1:
        print ("gcd(a, n) != 1, no inverse modular")
    return x % n

def ex_crt(a, m, k):
    for i in range(1, k):
        t = gcd(m[i], m[i - 1])
        if (a[i] - a[i - 1]) % t != 0:
            return -1
        a[i] = (inverse(m[i - 1] // t, m[i] // t) * (a[i] - a[i - 1]) // t) % (m[i] // t) * m[i - 1] + a[i - 1]
        m[i] = m[i] // t * m[i - 1]
        a[i] = (a[i] % m[i] + m[i]) % m[i]
    return a[-1]

from libnum import *

n2s(ex_crt(a, m, 4))[::-1]

# b'SECCON{L3t5_wr1t3_y0ur_Qu1n3!!!}'

[idek]reverseme

源程序是

#include <stdio.h>
#include <string.h>
#include <math.h>

int main(int argc, char** argv) {
    if(argc != 2){
        printf("Usage: ./reverseme password\n");
        return 1;
    }
    if(strlen(argv[1])!=14){
        printf("Incorrect Length\n");
        return 1;
    }
    if(*argv[1] != 112){//Not enough precision
        printf("Password Incorrect\n");
        return 1;
    }
    double magic_numbers[7] ={
        -68822144.50341525673866271972656250000000000000000000000000, 
        56777293.39031631499528884887695312500000000000000000000000, 
        -3274524.75536667229607701301574707031250000000000000000000, 
        -85761.51255339206545613706111907958984375000000000000000, 
        8443.33244327564352715853601694107055664062500000000000, 
        -166.67369627952575683593750000000000000000000000000000, 
        1.00000000000000000000000000000000000000000000000000, 
    };
    for(int i = 0; i < 6;i++){
        double foo=1.0,bar=0.0;
        for(int j=0;j<7;j++){
            bar += magic_numbers[j] * foo;
            foo *= (float)log(*(float*)((unsigned long)argv[1]+2*i));
        }
        if((int)(100000*bar) != 0){
            printf("Password Incorrect\n");
            return 0;
        }
    }
    printf("Password Correct\n");
    return 0;
}

相同的六次方程,分别带入 flag[0:4], flag[2:6], flag[4:8]...,知道第一位,于是考虑直接爆破

先修改一下源代码

#include <stdio.h>
#include <string.h>
#include <math.h>

int main(int argc, char** argv) {
    if(argc != 2){
        printf("Usage: ./reverseme password\n");
        return 1;
    }
    // if(strlen(argv[1])!=14){
    //     printf("Incorrect Length\n");
    //     return 1;
    // }
    // if(*argv[1] != 112){//Not enough precision
    //     printf("Password Incorrect\n");
    //     return 1;
    // }
    double magic_numbers[7] ={
        -68822144.50341525673866271972656250000000000000000000000000, 
        56777293.39031631499528884887695312500000000000000000000000, 
        -3274524.75536667229607701301574707031250000000000000000000, 
        -85761.51255339206545613706111907958984375000000000000000, 
        8443.33244327564352715853601694107055664062500000000000, 
        -166.67369627952575683593750000000000000000000000000000, 
        1.00000000000000000000000000000000000000000000000000, 
    };
    // for(int i = 0; i < 6;i++){
        int i = 0;
        double foo=1.0,bar=0.0;
        for(int j=0;j<7;j++){
            bar += magic_numbers[j] * foo;
            foo *= (float)log(*(float*)((unsigned long)argv[1]+2*i));
        }
        if((int)(100000*bar) != 0){
            printf("Password Incorrect\n");
            return 0;
        }
    // }
    printf("Password Correct\n");
    return 0;
}

然后 python 脚本进行爆破

import string
import subprocess

charset = string.ascii_letters + string.digits + "!@#$%^_"

# pre = 'p'

# for a in charset:
#     for b in charset:
#         for c in charset:
#             command = "./reverseme_1 p" + a + b + c
#             output = subprocess.check_output(command, shell=True, stderr=subprocess.PIPE)
#             if b"Correct" in output:
#                 print ("p" + a + b + c)


pre = "zf"  # 每次修改一下 pre 就可以进行爆破了
# pfTw
# pLzf zfTw
# p0%Y %Yn@ n@M1 M1aL aLzf zfTw
# p@M1

for a in charset:
    for b in charset:
        command = "./reverseme_1 " + pre + a + b
        output = subprocess.check_output(command, shell=True, stderr=subprocess.PIPE)
        if b"Correct" in output:
            print (pre + a + b)

上面用来爆破 p 开头的,共有四种可能,依次爆破就行

最后得到 flag: p0%Yn@M1aLzfTw

Built with Hugo
Theme Stack designed by Jimmy