社团第一次纳新re wp

qiandao

python写的exe

复制代码
python pyinstxtractor.py E:\firefox\qiandao1.exe

pycdc E:\retools\pyinstxtractor-2026.04\qiandao1.exe_extracted\qiandao.pyc -o aaa.py
py 复制代码
# Source Generated with Decompyle++
# File: qiandao.pyc (Python 3.11)

a = '53 44 50 43 53 45 43 7b 72 65 76 65 72 73 65 5f 71 69 61 6e 5f 64 61 6f 7d 0a'
correct_flag = bytes.fromhex(a).decode('utf-8').strip()
user_input = input('please input your flag: ')
if user_input == correct_flag:
    print('great')
    return None
print('wrong')

很简单

py 复制代码
a = '53 44 50 43 53 45 43 7b 72 65 76 65 72 73 65 5f 71 69 61 6e 5f 64 61 6f 7d 0a'
correct_flag = bytes.fromhex(a).decode('utf-8').strip()
print(correct_flag)

SDPCSEC{reverse_qian_dao}

Roses

Sss电脑桌面上的 1.txt 文件被一个 恶意程序(flower22.exe)加密了,加密后的内容在encode.txt中,经初步分析,这个恶意程序存在某种代码混淆,你能帮Sss恢复他的1.txt的内容吗,这里面会有你想要的东西

添加了一种花指令(3处),注意留意ida中变红的地方

前置知识

花指令

扰反汇编器和反编译器的静态分析的无用或混淆指令

特征:

  • 信号 1:call 到诡异的地址

    复制代码
    call near ptr 14177FF06h

    正常调用跳去一个函数地址(比如 14000xxxx),但这个跳到了 14177xxxx------程序一共才 0x12000 字节(140000000 ~ 140012000),这地址在程序范围外,明显是错的。

    这就是最大的标志:E8 被误读成 CALL,算出来的地址巨大无比。

  • 信号 2:jz / jnz 显示奇怪的写法

    复制代码
    jz short near ptr loc_1400015FC+2

    正常的跳转写法是 jz loc_xxxxxx(一个具体的标签)。+2 这种写法暴露了 IDA 的困惑------它知道 jz 跳到了某个地址的"中间",但它没法给那里命名,只能凑合写。

  • 信号 3:反汇编中间有巨大的空白

    复制代码
    1400015FC: call near ptr 14177FF06h    ← 这里 CALL
    14000179A: add rsp, 60h                ← 下一个指令隔了 0x19E 字节

    间隔 400 多字节,中间全是灰色 db → 说明 IDA 放弃了那片区域的翻译。正常的函数体不会断。

  • 信号 4:Hex View 里 E8 后面跟的是正常指令字节

    打开 Hex View 看 call 的位置。比如在 0x1400015FC,E8 后面跟的是 05 E9 77 01 00 00 C7 45 EC。

    • 如果后面真的是一个 CALL 的 4 字节地址 → 应该看起来像一段有规律的数值
    • 如果后面是 E9(JMP 指令)、48(REX 前缀)、C7(MOV 指令)→ 这就是正常代码的开头,而不是地址
  • 信号 5:F5 反编译显示 JUMPOUT

    复制代码
    if (malloc(...))
     JUMPOUT(0x1400015FELL);

    这行不是 C 语法,是 IDA 找不到跳转目标的控制流的投降声明

对于本题的分析

这个二进制里有三处相同的陷阱,都在 0xE8 这个数字上。

什么是 0xE8?

0xE8 是 CPU 的 "CALL" 指令的第一个字节。CPU 看到 0xE8,就知道要执行一个"打电话"操作------先记住下一行地址,然后跳到另一个地方去执行。

关键: 0xE8 就是一个5字节长的指令:

复制代码
E8 XX XX XX XX
│  └───┬────┘
│      └─ 电话要打给谁的"地址"(4字节)
└─ "打电话" 指令

IDA 的默认翻译方式是线性扫描------从前往后逐字节翻译,碰到 E8 就按5字节解读。

陷阱的设计者做了什么?

他在正常的代码中间插了 6 个精心挑选的字节: 31 C0 74 02 E8 05

我们一步步看这 6 个字节干了什么:

第 1 步:31 C0 → xor eax, eax

  • 作用 :异或相同为0,把eax寄存器清零
  • 附带效果:零标志位(ZF)被设置为1

第 2 步:74 02 → jz +2

  • jz = Jump if Zero = "如果ZF=1就跳"
  • +2 跳 2两字节指令

现在 CPU 的跳转目标是:这条指令的结尾地址 + 2。我们来算一下从哪里跳到哪里。

用本题里实际的位置来算:

  • 31 C0 在地址 0x1400015F8 ~ 0x1400015F9(2字节)
  • 74 02 在地址 0x1400015FA ~ 0x1400015FB(2字节)
  • 这个 jz 指令结束在 0x1400015FB,再加上偏移量 02
  • 跳转目标 0x1400015FC0x1400015FD,跳到0x1400015FE

第 3 步:E8 05 → 垃圾字节

  • 这两字节在 0x1400015FC ~ 0x1400015FD
  • CPU 永远不会执行它们 ,因为上一步的 jz 直接跳过去了
  • 但是! 0xE8 这个数字在 IDA 看来就是 CALL 指令的开头

于是,IDA 误以为这里有一个函数调用,开始尝试反汇编后面的 4 个字节作为目标地址,从而产生误导性的伪代码或错误的控制流图,而实际上这部分代码在运行时根本不会被 CPU 执行。

题解

先看String

题目说这是一个加密程序

看到有Encrypted Result:,跟过去看看

点击x看引用

复制代码
sub_1400017A0+181	lea     rax, aEncryptedResul; "Encrypted Result:\n%s\n"

1507处有jz 2跳转,指令本身是两个字节,占据1507和1508,跳两个,应该从150B开始执行

但是ida错误的把应该跳过的1509识别成call 5

修复方法:点击1509行,按u(Undefine),点击150B行,按c(Code),再按F5即可查看反汇编

修改前

pseudocode 复制代码
__int64 __fastcall sub_1400014E7(__int64 a1, __int64 a2, __int64 a3)
{
  __int64 result; // rax

  result = 0;
  if ( a3 )
    JUMPOUT(0x14000150BLL);
  return result;
}

修改后

pseudocode 复制代码
unsigned __int64 __fastcall sub_1400014E7(__int64 a1, __int64 a2, unsigned __int64 a3)
{
  unsigned __int64 result; // rax
  unsigned __int64 i; // [rsp+8h] [rbp-8h]

  for ( i = 0; ; ++i )
  {
    result = i;
    if ( i >= a3 )
      break;
    *(_BYTE *)(a2 + i) = aRoses[i % 5] ^ *(_BYTE *)(a1 + i);
  }
  return result;
}

同理把剩下两个花指令修复

注意不要无脑看到jz后面有call就修复,算一些跳转的位数到底有没有跳过call

如果修复后f5还是不可读,按alt+P重新分析函数

跟着start内容跳转到sub_140001180

看到关键逻辑

复制代码
  Code = sub_1400017A0();
  ::Code = Code;
  if ( !dword_14000D00C )
    exit(Code);

查看sub_1400017A0的伪代码

pseudocode 复制代码
__int64 sub_1400017A0()
{
  __int64 v0; // rax
  char *Block; // [rsp+20h] [rbp-30h]
  void *v3; // [rsp+28h] [rbp-28h]
  void *Buffer; // [rsp+30h] [rbp-20h]
  size_t Size; // [rsp+38h] [rbp-18h]
  FILE *Stream; // [rsp+40h] [rbp-10h]

  sub_140001A20();
  Stream = fopen("C:\\Users\\Lenovo\\Desktop\\1.txt", "rb");
  if ( Stream )
  {
    fseek(Stream, 0, 2);
    Size = ftell(Stream);
    fseek(Stream, 0, 0);
    if ( Size )
    {
      Buffer = malloc(Size);
      v3 = malloc(Size);
      if ( Buffer && v3 )
      {
        fread(Buffer, 1u, Size, Stream);
        fclose(Stream);
        sub_1400014E7((__int64)Buffer, (__int64)v3, Size);
        Block = (char *)sub_140001578(v3, Size);
        if ( Block )
        {
          sub_140001493("Encrypted Result:\n%s\n", Block);
          free(Block);
        }
        else
        {
          sub_140001493("Encoding failed.\n");
        }
        free(Buffer);
        free(v3);
        return 0;
      }
      else
      {
        perror("Memory allocation failed");
        fclose(Stream);
        return 1;
      }
    }
    else
    {
      fclose(Stream);
      return 0;
    }
  }
  else
  {
    v0 = psub_140007A50();
    sub_140001450(v0, "Error: Cannot open file %s\n", "C:\\Users\\Lenovo\\Desktop\\1.txt");
    return 1;
  }
}

找关键逻辑

c 复制代码
sub_1400014E7((__int64)Buffer, (__int64)v3, Size);
Block = sub_140001578((__int64)v3, Size);
if ( Block )
        {
          sub_140001493("Encrypted Result:\n%s\n", Block);
          free(Block);
        }

sub_1400014E7

c 复制代码
unsigned __int64 __fastcall sub_1400014E7(__int64 Buffer, __int64 a2, unsigned __int64 Size)
{
  unsigned __int64 Size_2; // rax
  unsigned __int64 Size_1; // [rsp+8h] [rbp-8h]

  for ( Size_1 = 0; ; ++Size_1 )
  {
    Size_2 = Size_1;
    if ( Size_1 >= Size )
      break;
    *(_BYTE *)(a2 + Size_1) = aRoses[Size_1 % 5] ^ *(_BYTE *)(Buffer + Size_1);
  }
  return Size_2;
}

aRoses的值是'Roses',与Roses异或加密

sub_140001578

c 复制代码
_BYTE *__fastcall sub_140001578(__int64 a1, unsigned __int64 Size)
{
  __int64 v3; // rax
  __int64 v4; // rax
  unsigned __int64 n2; // [rsp+30h] [rbp-30h]
  _BYTE *v6; // [rsp+38h] [rbp-28h]
  unsigned int v7; // [rsp+4Ch] [rbp-14h]
  __int64 v8; // [rsp+50h] [rbp-10h]
  __int64 v9; // [rsp+50h] [rbp-10h]
  unsigned __int64 Size_1; // [rsp+58h] [rbp-8h]

  Size_1 = 0;
  v8 = 0;
  if ( !a1 || !Size )
    return 0;
  v6 = malloc(4 * ((Size + 2) / 3) + 1);
  if ( !v6 )
    return 0;
  while ( Size_1 < Size )
  {
    n2 = Size - Size_1;
    v7 = *(unsigned __int8 *)(a1 + Size_1) << 16;
    if ( Size - Size_1 > 1 )
      v7 |= *(unsigned __int8 *)(Size_1 + 1 + a1) << 8;
    if ( n2 > 2 )
      v7 |= *(unsigned __int8 *)(Size_1 + 2 + a1);
    v6[v8] = aVxkw7qtsmd5bri[(v7 >> 18) & 0x3F];
    v6[v8 + 1] = aVxkw7qtsmd5bri[(v7 >> 12) & 0x3F];
    v3 = v8 + 2;
    v9 = v8 + 3;
    if ( n2 <= 1 )
      v6[v3] = 61;
    else
      v6[v3] = aVxkw7qtsmd5bri[(v7 >> 6) & 0x3F];
    v4 = v9;
    v8 = v9 + 1;
    if ( n2 <= 2 )
      v6[v4] = 61;
    else
      v6[v4] = aVxkw7qtsmd5bri[v7 & 0x3F];
    Size_1 += 3LL;
  }
  v6[v8] = 0;
  return v6;
}

看起来是使用了自定义表的base64编码

复制代码
'VxKw7QTsMd5Bri83NZe9Ut6pChXzD4IAYqmLuakbHofRWycvjGPnS2JE/+l01OFg'
py 复制代码
import base64
CUSTOM_ALPHABET = "VxKw7QTsMd5Bri83NZe9Ut6pChXzD4IAYqmLuakbHofRWycvjGPnS2JE/+l01OFg"
STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
def decode(text):
    trans = str.maketrans(CUSTOM_ALPHABET, STD_ALPHABET)
    b64_std = text.translate(trans)
    raw = base64.b64decode(b64_std)
    
    key = b"Roses"
    return bytes(raw[i] ^ key[i % 5] for i in range(len(raw)))

data="QY/sxQrVKqD77KCTsVy97GHpwV4PMGjKAaYR7q76X7OxtUQbNudppkxAAk11MYHxxVDOsUaQMZDRrmCbQPyFznMFKxjpTmCssaO9DLD1i2i+9nVNVKCVsmDeMNoQUtrupt26AaYksZUTdu1ExVDnttrJijMWMKVj5edKU7CipqD7BKCs7YWCwZhw7KjSpj7lVwSwxtN4i9VzVV7iTj75xLxCQqqF6wjsxVDbs7aQrxSmMPuJxmoFznSOTGhAUGrwsSULTPh9wZMq9G7VQLG3VNVprjjsVx4PsGCpUGURMn497GSseSdaCbu="
result = decode(data)
print(result)
复制代码
SDPCSEC{M155_1da_thank_y0u_f0r_solv1ng_her_troub7e}

Gift

Some parts of the base64 encoding process seem different from the standard encoding, and it looks like it's not just in one place.

魔改base64,魔改了两处位置

ida打开

c 复制代码
__int64 __fastcall main(int a1, char **a2, char **a3)
{
  size_t v3; // rax
  const char *s1; // rax
  char *ptr; // rbx
  char s_[4104]; // [rsp+2h] [rbp-1008h] BYREF

  puts("Input:");
  if ( !fgets(s_, 4096, stdin) )
    goto LABEL_10;
  v3 = strlen(s_);
  if ( v3 && s_[v3 - 1] == 10 )
    s_[v3 - 1] = 0;
  s1 = (const char *)sub_1260(s_);
  ptr = (char *)s1;
  if ( s1 )
  {
    if ( !strcmp(s1, "YeBCCdVNCf4eMTNjXjFjXeNHJjNwFNlwWPxHLwZfXdxtFwZkGgZkHC==") )
      puts("Correct");
    else
      puts("Wrong");
    free(ptr);
    return 0;
  }
  else
  {
LABEL_10:
    fwrite("Wrong\n", 1u, 6u, stderr);
    return 1;
  }
}

看看sub_1260如何处理输入

c 复制代码
_BYTE *__fastcall sub_1260(const char *s)
{
  size_t n2_3; // rax
  size_t n2_1; // rbp
  _BYTE *v4; // rsi
  size_t n2; // rdi
  __int64 n4; // rdx
  size_t n2_2; // rcx
  unsigned int v8; // eax
  char n61; // al
  unsigned int v10; // eax
  char v11; // r8
  _BYTE *v13; // rax

  n2_3 = strlen(s);
  if ( n2_3 )
  {
    n2_1 = n2_3;
    v4 = malloc(4 * ((n2_3 + 2) / 3) + 1);
    if ( v4 )
    {
      n2 = n2_1;
      n4 = 4;
      n2_2 = 0;
      while ( 1 )
      {
        v10 = (unsigned __int8)s[n2_2] << 16;
        if ( n2 == 1 )
        {
          v4[n4 - 2] = 61;
          v11 = aZyxabcdefghijk[(v10 >> 18) ^ 0x15];
          v4[n4 - 3] = aZyxabcdefghijk[(v10 >> 12) & 0x30 ^ 0x15];
          v4[n4 - 4] = v11;
        }
        else
        {
          v8 = ((unsigned __int8)s[n2_2 + 1] << 8) | v10;
          if ( n2 != 2 )
            v8 |= (unsigned __int8)s[n2_2 + 2];
          v4[n4 - 4] = aZyxabcdefghijk[(v8 >> 18) ^ 0x15];
          v4[n4 - 3] = aZyxabcdefghijk[(v8 >> 12) & 0x3F ^ 0x15LL];
          v4[n4 - 2] = aZyxabcdefghijk[(v8 >> 6) & 0x3F ^ 0x15LL];
          if ( n2 != 2 )
          {
            n61 = aZyxabcdefghijk[v8 & 0x3F ^ 0x15LL];
            goto LABEL_8;
          }
        }
        n61 = 61;
LABEL_8:
        n2_2 += 3LL;
        v4[n4 - 1] = n61;
        n2 -= 3LL;
        if ( n2_2 >= n2_1 )
        {
          v4[n4] = 0;
          return v4;
        }
        n4 += 4;
      }
    }
  }
  else
  {
    v13 = malloc(1u);
    v4 = v13;
    if ( v13 )
      *v13 = 0;
  }
  return v4;
}

看一眼魔改base64表

复制代码
ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/

第二处魔改应该就是异或

见识少不敢直接确定只是加了个异或

第一次看这么长的伪代码,脑子快炸了

稍微整理下

c 复制代码
if ( n2 == 1 )
        {
          v4[n4 - 2] = 61;
          v4[n4 - 3] = aZyxabcdefghijk[(v10 >> 12) & 0x30 ^ 0x15];
          v4[n4 - 4] = aZyxabcdefghijk[(v10 >> 18) ^ 0x15];
        }
if ( n2 == 2 ){
	v8 = ((unsigned __int8)s[n2_2 + 1] << 8) | v10;
	v4[n4 - 4] = aZyxabcdefghijk[(v8 >> 18) ^ 0x15];
    v4[n4 - 3] = aZyxabcdefghijk[(v8 >> 12) & 0x3F ^ 0x15LL];
    v4[n4 - 2] = aZyxabcdefghijk[(v8 >> 6) & 0x3F ^ 0x15LL];
    n61 = 61;
    LABEL_8:
}
if ( n2 == 3 ){
	 v10 = (unsigned __int8)s[n2_2] << 16;
	 v8 = ((unsigned __int8)s[n2_2 + 1] << 8) | v10;
	 v8 |= (unsigned __int8)s[n2_2 + 2];
	 v4[n4 - 4] = aZyxabcdefghijk[(v8 >> 18) ^ 0x15];
	 v4[n4 - 3] = aZyxabcdefghijk[(v8 >> 12) & 0x3F ^ 0x15LL];
	 v4[n4 - 2] = aZyxabcdefghijk[(v8 >> 6) & 0x3F ^ 0x15LL];
	 n61 = aZyxabcdefghijk[v8 & 0x3F ^ 0x15LL];
	 goto LABEL_8;
}

每次循环都会把n61重新赋值为61
LABEL_8中v4[n4 - 1] = n61;将每组的最后一位赋值为n61
前面的几组循环都是3位,执行了
n61 = aZyxabcdefghijk[v8 & 0x3F ^ 0x15LL];
goto LABEL_8;
把n61替换为真实结尾,跳过n61=61直接进入LABEL_8
这里不要被缩进骗了,其实LABEL_8:也在循环里
n2=2的时候没有把n61改为"真实值",而是带着上一次的重置值61进入LABEL_8:

可算清楚点了,好难

先把之前写的decode脚本拿出来,改一下

py 复制代码
STANDARD_ALPHABET = "ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/"

def decode(data: str, alphabet: str = STANDARD_ALPHABET) -> str:
    padding = data.count("=")
    data = data.rstrip("=")
    result = []
    for i in range(0, len(data), 4):
        chunk = data[i : i + 4]
        value = 0
        for j, c in enumerate(chunk):
            value |= (alphabet.index(c)  ^ 0x15) << (18 - j * 6)
        n = 4 - padding if i >= len(data) - 4 else 4 
        for j in range(n - 1):
            result.append(chr((value >> (16 - j * 8)) & 0xFF))
    return "".join(result)

a=decode("YeBCCdVNCf4eMTNjXjFjXeNHJjNwFNlwWPxHLwZfXdxtFwZkGgZkHC==")
print(a)
复制代码
SDPCSEC{th1s_7s_A_g1ft_f0r_new_Reverser}