BeginCTF2024逆向部分wp

提交人:太刀

superguesser | 动态调试

静态分析发现函数全部被混淆 在入口下断点步过函数直到进入被还原的函数 找到核心加密逻辑:

// positive sp value has been detected, the output may be wrong!
__int64 __fastcall sub_401530()
{
  __int64 stack; // rbp
  char v1; // zf
  __int64 (*v2)(void); // rax
  void *v3; // rsp
  __int64 v4; // rcx
  char v5; // of
​
  (loc_46EB33)();
  _disable();
  (loc_46EB77)();
  (loc_46EBB4)();
  if ( v1 )
    JUMPOUT(0x401548i64);
  (loc_46EBF7)();
  *(stack - 96) = 81;
  *(stack - 95) = 81;
  *(stack - 94) = 82;
  *(stack - 93) = 95;
  *(stack - 92) = 89;
  *(stack - 91) = 67;
  *(stack - 90) = 93;
  *(stack - 89) = 95;
  *(stack - 88) = 89;
  *(stack - 87) = 73;
  *(stack - 86) = 90;
  *(stack - 85) = 89;
  *(stack - 84) = 86;
  *(stack - 83) = 46;
  *(stack - 82) = 38;
  *(stack - 81) = 29;
  *(stack - 80) = 42;
  *(stack - 79) = 55;
  *(stack - 78) = 26;
  *(stack - 77) = 39;
  *(stack - 76) = 41;
  *(stack - 75) = 23;
  *(stack - 74) = 40;
  *(stack - 73) = 36;
  *(stack - 72) = 42;
  *(stack - 71) = 56;
  *(stack - 70) = 37;
  *(stack - 69) = 33;
  *(stack - 68) = 61;
  *(stack - 67) = 15;
  *(stack - 66) = 50;
  *(stack - 65) = 58;
  *(stack - 64) = 60;
  *(stack - 63) = 61;
  *(stack - 62) = 54;
  *(stack - 61) = 51;
  *(stack - 60) = 42;
  *(stack - 59) = 0;
  *(stack - 24) = (loc_46EC76)(stack - 96);
  *(stack - 25) = 51;
  v2 = (loc_46ECB3)();
  __outbyte(0x2Fu, v2);
  *(stack - 26) = v2();
  *(stack - 40) = *(stack - 24) - 1i64;
  (loc_46ECF6)();
  (loc_46ED38)();
  v3 = alloca((loc_46ED75)());
  *(stack - 48) = alloc();
  (scanf_0)(v4, *(stack - 48));
  (loc_46EE32)();
  (loc_46EEB1)();
  if ( !v5 )
    JUMPOUT(0x401651i64);
  while ( *(stack - 20) < *(stack - 24) )
    *(*(stack - 48) + (*(stack - 20))++) ^= *(stack - 20) + *(stack - 25) + 17 * *(stack - 26);// i + ? + 17 * ??
  if ( !(cmp)(*(stack - 48), stack - 96, *(stack - 24)) )
  {
    (loc_46F030)();
    JUMPOUT(0x4016E8i64);
  }
  (loc_46EF73)();
  return (loc_46EFF3)();
}

动调发现stack-48处存放的就是输入的flag 而只有这一处加密 由于不确定*(stack - 26)的值是否因为反调试手段被修改 故用爆破方法解flag:

enc = [81, 81, 82, 95, 89, 67, 93, 95, 89, 73, 90, 89, 86, 46, 38, 29, 42, 55, 26, 39, 41, 23, 40, 36, 42, 56, 37, 33, 61, 15, 50, 58, 60, 61, 54, 51, 42]
for ran in range(128):
    flag = ""
    for i in range(len(enc)):
        flag += chr((enc[i] ^ (i + 0x33 + 17 * ran)) & 0xff)
    if flag[:5] == "begin":
        print(flag)
        # begin{debugging_is_an_anathor_choice}

ezpython | 注入解法

用pyinstxtractor解包出的key和enc无法通过sm4解密得到flag 故使用原程序的环境进行解密 原程序反编译后主要逻辑:

from gmssl import sm4
from secrets import key, enc
import base64
​
def pad_pkcs7(data):
    """PKCS#7填充"""
    padding_len = 16 - len(data) % 16
    padding = bytes([padding_len] * padding_len)
    return data + padding
​
​
def unpad_pkcs7(padded_data):
    """PKCS#7去填充"""
    padding_len = padded_data[-1]
    return padded_data[:-padding_len]
​
​
class SM4:
    def __init__(self):
        self.gmsm4 = sm4.CryptSM4()
​
    def encryptSM4(self, encrypt_key, value):
        gmsm4 = self.gmsm4
        gmsm4.set_key(encrypt_key.encode(), sm4.SM4_ENCRYPT)
        padded_value = pad_pkcs7(value.encode())
        encrypt_value = gmsm4.crypt_ecb(padded_value)
        return base64.b64encode(encrypt_value)
​
​
if __name__ == "__main__":
    flag = input("...")
    sm4_instance = SM4()
    flag_1 = sm4_instance.encryptSM4(key, flag)
    if flag_1 == enc:
        print("Success")
    else:
        print("Failed")

在powershell启动题目程序(用cmd或者直接启动貌似不行) 找到该程序的pid 使用de4py的PyShell功能将以下解密代码注入到程序:

def decryptSM4(self, encrypt_key, enc):
    gmsm4 = self.gmsm4
    gmsm4.set_key(encrypt_key.encode(), sm4.SM4_DECRYPT)
    encrypt_value = base64.b64decode(enc)
    decrypt_value = gmsm4.crypt_ecb(encrypt_value)
    return unpad_pkcs7(decrypt_value).decode()
​
SM4.decrypt = decryptSM4
sm4_new = SM4()
print(sm4_new.decrypt(key, enc))

效果:

goforfun | go语言逆向

第一个加密的核心特征:

  for ( i = 255; i >= 0; --i )
  {
    if ( i >= 0x100 )
      runtime_panicIndex();
    *(box + i) = -1 - i;
  }
  count = 0;
  v7 = 0;
  while ( count < 256 )
  {
    v8 = *(box + count);
    v9 = count;
    v10 = count - 12 * (((2863311531LL * count) >> 32) >> 3);
    if ( v10 >= 0xC )
      runtime_panicIndex();
    v7 += v35[v10] + v8;
    *(box + v9) = *(box + v7);
    *(box + v7) = v8;
    count = v9 + 1;
  }
  sub_462C00(&PRGA_result, box);
  flag_context = v4;
  lenth_of_context = v34;
  v26 = v11;
  main_PRGA(PRGA_result);

Sbox初始化 PRGA 但是不是普通的RC4加密 进入PRGA函数:

int __usercall main_PRGA@<eax>(char table)
{
  int result; // eax
  signed int v2; // ecx
  unsigned int v3; // edx
  signed int v4; // ebx
  char v5; // bp
  char v6; // si
  char v7; // cl
  char v8; // [esp+12h] [ebp-2h]
  void *retaddr; // [esp+14h] [ebp+0h] BYREF
​
  if ( &retaddr <= *(*__readfsdword(runtime_tls_g) + 8) )
    runtime_morestack_noctxt();
  result = runtime_makeslice(&uint8, STACK[0x11C], STACK[0x11C]);
  v2 = STACK[0x11C];
  v3 = STACK[0x118];
  v4 = 0;
  v5 = 0;
  v6 = 0;
  while ( v2 > v4 )
  {
    v7 = *(&table + (v5 + 1));
    v8 = v7 + v6;
    *(&table + (v5 + 1)) = *(&table + (v7 + v6));
    *(&table + (v7 + v6)) = v7;
    *(result + v4) = *(&table + (v7 + *(&table + (v5 + 1)))) ^ *(v3 + v4) ^ 0x2F;
    ++v4;
    v2 = STACK[0x11C];
    ++v5;
    v6 = v8;
  }
  STACK[0x124] = result;
  STACK[0x128] = v2;
  STACK[0x12C] = v2;
  return result;
}

动调发现v3存放的就是输入的flag去除begin{}包裹的内容 所以加密只关心和其异或的东西 在最后异或步骤下状况断点 输出存放异或的数字的寄存器:

image-20240206140321081

得到异或内容:

xor_key = [i ^ 0x2f for i in [132, 14, 121, 193, 41, 61, 231, 134, 147, 244, 180, 102, 100, 175, 25, 151, 133, 7, 230, 74, 200, 3, 55]]

最后一处加密的核心特征:

image-20240206140631616

从64长度的表中按第二处加密得到的结果为下标取元素 最后和密文比较 猜测是换表base64

第二处加密:

v20 = main_byteArrayToBigInt(result, lenth_2);
v22 = main_bigIntModToArray(str_head);

动调发现第一个函数的作用就是将第一处加密得到的结果按小端序储存为Go中的一种大整型数据(没有位数上限 能储存多大取决于计算机)

第二个函数:

__int128 __golang main_bigIntModToArray(int *a1)
{
  int *mod_result_instanc; // eax
  int v2; // ecx
  int v3; // edx
  unsigned int i; // ebx
  int v5; // ebp
  int now_result; // ecx
  int origin_data; // eax
  unsigned int count_1; // ecx
  int add_; // edx
  int *final_procees; // ebx
  int *result_; // [esp+Ch] [ebp-6Ch]
  int *v12; // [esp+14h] [ebp-64h]
  unsigned int v13; // [esp+18h] [ebp-60h]
  int v14; // [esp+1Ch] [ebp-5Ch]
  int v15; // [esp+20h] [ebp-58h]
  unsigned int v16; // [esp+24h] [ebp-54h]
  unsigned int v17; // [esp+28h] [ebp-50h]
  int v18; // [esp+28h] [ebp-50h]
  int mod_num_2; // [esp+2Ch] [ebp-4Ch] BYREF
  int mod_num; // [esp+30h] [ebp-48h] BYREF
  int v21; // [esp+44h] [ebp-34h]
  int v22[4]; // [esp+48h] [ebp-30h] BYREF
  int mod_n[4]; // [esp+58h] [ebp-20h] BYREF
  int be_moded_instanc[4]; // [esp+68h] [ebp-10h] BYREF
  void *retaddr; // [esp+78h] [ebp+0h] BYREF
  __int128 result; // [esp+80h] [ebp+8h]
​
  if ( &retaddr <= *(*__readfsdword(runtime_tls_g) + 8) )
    runtime_morestack_noctxt();
  mod_result_instanc = a1;
  v2 = 0;
  v3 = 0;
  for ( i = 0; ; i = v16 )
  {
    if ( mod_result_instanc[2] )
      v5 = *mod_result_instanc ? -1 : 1;
    else
      v5 = 0;
    if ( v5 <= 0 )
      break;
    v17 = v2;
    v21 = v3;
    LOBYTE(be_moded_instanc[0]) = 0;
    memset(&be_moded_instanc[1], 0, 12);
    mod_num = 64;
    LOBYTE(mod_n[0]) = 0;
    mod_n[1] = &mod_num;
    mod_n[2] = 1;
    mod_n[3] = 1;
    result_ = math_big__Int_Mod(be_moded_instanc, mod_result_instanc, mod_n);
    if ( result_[2] )
      now_result = *result_[1];
    else
      now_result = 0;
    if ( *result_ )
      origin_data = -now_result;
    else
      origin_data = now_result;
    count_1 = i + 1;
    add_ = v17;
    if ( v17 < i + 1 )
    {
      v15 = origin_data;
      v12 = runtime_growslice(v21, i + 1, v17, 1, &int);
      count_1 = v13;
      final_procees = v12;
      add_ = v14;
      origin_data = v15;
    }
    else
    {
      final_procees = v21;
    }
    v21 = final_procees;
    v16 = count_1;
    v18 = add_;
    final_procees[count_1 - 1] = origin_data;
    mod_num_2 = 0x40;
    LOBYTE(v22[0]) = 0;
    v22[1] = &mod_num_2;
    v22[2] = 1;
    v22[3] = 1;
    math_big__Int_Div(a1, a1, v22);
    mod_result_instanc = a1;
    v3 = v21;
    v2 = v18;
  }
  *&result = __PAIR64__(i, v3);
  DWORD2(result) = v2;
  return result;
}

核心是其中的大整型取模和大整型除法 动调发现取模和除法都是对第一个函数得到的大整型进行 另一个操作数是0x40也就是result = big_int & 0xFFFFFF, big_int >> 6符合之前base64的猜测(但是直接用base64解出来不对 所以自己写解密脚本):

xor_key = [i ^ 0x2f for i in [132, 14, 121, 193, 41, 61, 231, 134, 147, 244, 180, 102, 100, 175, 25, 151, 133, 7, 230, 74, 200, 3, 55]]
enc = ["8G+cazk2jqb7w01CtoKH4FsrgR3vVmQ9pPhXLAleOd/nB6DfIxMWYiUZ5SEJyNuT".index(i) for i in"HZ0sMJXqxHgUb2b9RNg+1xw"]
enc = [bin(i).replace("0b", "").zfill(6) for i in enc][::-1]
big_int = int("".join(enc), 2)
flag = []
while big_int > 0:
    flag.append(big_int & 0xff)
    big_int >>= 8
flag = flag[::-1]
for i in range(len(flag)):
    print(chr(flag[i] ^ xor_key[i]), end="")
# go_a_nice_journey

not main | 反调试

IDA识别出的主函数:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // ecx
  __int32 *pt_input1; // edi
  unsigned __int32 now; // edx
  int sum; // ebx
  unsigned __int32 next; // esi
  int v8; // edi
  __int32 *pt_input3; // ecx
  __int32 *v10; // edx
  unsigned int v11; // esi
  bool v12; // cf
  unsigned int v13; // edx
  int v14; // eax
  int v16; // [esp-4h] [ebp-2Ch]
  __int32 *pt_input2; // [esp+Ch] [ebp-1Ch]
  __int128 v18; // [esp+10h] [ebp-18h] BYREF
  int v19; // [esp+20h] [ebp-8h]
​
  sub_141930(std::cin);
  v3 = strlen(enc);
  if ( v3 == 0x1A )
  {
    pt_input1 = enc;
    pt_input2 = enc;
    do
    {
      now = *pt_input1;
      sum = 0;                                  // TEA
      next = pt_input1[1];
      v8 = 32;
      do
      {
        sum -= 0x61C88647;
        now += ((next >> 5) + 97) ^ (16 * next + 102) ^ (sum + next);
        next += ((now >> 5) + 101) ^ (16 * now + 107) ^ (sum + now);
        --v8;
      }
      while ( v8 );
      *pt_input2 = now;
      pt_input2[1] = next;
      pt_input1 = pt_input2 + 2;
      pt_input2 = pt_input1;
    }
    while ( pt_input1 < &debug_flag_1 );
    pt_input3 = enc;
    v10 = to_cmp;
    v11 = 28;
    while ( *pt_input3 == *v10 )
    {
      ++pt_input3;
      ++v10;
      v12 = v11 < 4;
      v11 -= 4;
      if ( v12 )
      {
        v18 = xmmword_143220;
        LOBYTE(v19) = 19;
        v13 = 0;
        v3 = strlen(&v18);
        if ( v3 )
        {
          do
            *(&v18 + v13++) ^= 0x13u;
          while ( v13 < &v18 + strlen(&v18) + 1 - (&v18 + 1) );
        }
        goto LABEL_11;
      }
    }
  }
  else
  {
LABEL_11:                                       // fail
    v14 = sub_141600(v3, sub_141830);
    std::ostream::operator<<(v14, v16);
  }
  return 0;
}

包含一个TEA 但是解出来是假flag while ( pt_input1 < &debug_flag_1 );这一行对flag交叉引用到达一个异常处理函数:

int sub_141010()
{
  uint8_t BeingDebugged; // al
​
  BeingDebugged = NtCurrentPeb()->BeingDebugged;
  debug_flag_1 = BeingDebugged != 0;
  if ( BeingDebugged )
    AddVectoredExceptionHandler(1u, Handler);
  return atexit(sub_142AE0);
}

Handler应该就是程序正常运行过程中主函数触发异常后执行的处理函数:

LONG __userpurge Handler@<eax>(int a1@<edi>, struct _EXCEPTION_POINTERS *ExceptionInfo)
{
  DWORD ExceptionCode; // eax
  PCONTEXT ContextRecord; // eax
  int sum; // ecx
  unsigned __int32 temp; // eax
  unsigned int v7; // ebx
  unsigned int i; // edi
  bool v9; // zf
  _DWORD *real_enc; // edx
  __int32 *v11; // ecx
  unsigned int v12; // esi
  bool v13; // cf
  unsigned int j; // edx
  int v15; // eax
  int v16; // [esp-10h] [ebp-44h]
  unsigned int addr_13503C; // [esp+4h] [ebp-30h]
  int v19; // [esp+8h] [ebp-2Ch]
  int v20; // [esp+Ch] [ebp-28h]
  int temp_2; // [esp+10h] [ebp-24h]
  int key[4]; // [esp+14h] [ebp-20h]
  int v23[3]; // [esp+24h] [ebp-10h] BYREF
​
  ExceptionCode = ExceptionInfo->ExceptionRecord->ExceptionCode;
  if ( ExceptionCode == -2147483645 )
  {
    ContextRecord = ExceptionInfo->ContextRecord;
    dword_145038 = 0;
    ++ContextRecord->Eip;
    return -1;
  }
  else if ( ExceptionCode == 0xC0000094 )
  {
    return 0;
  }
  else
  {
    key[0] = 116;
    addr_13503C = to_cmp ^ ::addr_13503C;
    sum = 0;
    ::addr_13503C ^= to_cmp;
    temp = enc_7_;
    key[1] = 114;
    key[2] = 117;
    key[3] = 101;
    v19 = 12;
    temp_2 = enc_7_;
    do
    {
      v20 = sum - 0x61C88647;
      v7 = ((sum - 0x61C88647) >> 2) & 3;
      for ( i = 0; i < 7; ++i )
      {
        enc[i] += ((v20 ^ enc_1_[i]) + (temp_2 ^ key[v7 ^ i & 3])) ^ (((16 * te
### BUUCTF 逆向工程 Writeup 解题报告 #### 选择合适的题目并分析目标程序 在BUUCTF平台上,逆向工程项目通常涉及对二进制文件或脚本的反编译和静态/动态分析。为了找到解题思路,建议从简单的题目入手,逐步提升难度。每道题目都会给出一个可执行文件或其他类型的程序作为挑战对象。 #### 使用工具辅助分析 常用的逆向工程工具有IDA Pro、Ghidra以及Radare2等[^1]。这些工具可以帮助加载待分析的目标程序,并提供汇编代码视图、函数调用图表等功能支持。对于初学者来说,Ghidra是一个不错的选择因为它免费开源而且功能强大。 #### 寻找关键逻辑与特征字符串 通过对程序进行初步浏览后,应该重点关注那些看起来像是验证输入或者处理核心业务的地方。很多时候,在二进制内部能找到一些提示性的文本串(如错误消息),它们往往指向了我们需要破解的关键部分。 #### 尝试修改行为或绕过保护机制 一旦明确了要攻击的方向,则可以通过打补丁的方式改变原有流程达到目的;或者是利用某些已知漏洞来规避安全措施。例如,在面对加壳软件时,可以尝试脱壳后再做进一步的研究工作。 #### 编写自动化脚本来加速测试过程 当遇到重复性高的操作任务时,不妨编写Python脚本配合调试器使用以提高效率。比如自动发送不同参数组合给被测应用直至触发预期效果为止。 ```python import subprocess for i in range(100): result = subprocess.run(['./target_binary', str(i)], capture_output=True, text=True) if "success" in result.stdout.lower(): print(f"Found solution at input {i}") break ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值