【2022强网拟态】windows_call

文章描述了一个Windows程序的逆向分析过程,涉及API的加密和解密步骤。首先,程序通过特定计算加载库,然后在sub_401DFE函数中对比加密后的API名称。接着,介绍了对原始flag进行大小写转换和MD5哈希验证的方法。之后,详细解析了一层加密变换,以及AES加密过程,包括轮密钥和异或操作。最后,给出了解密后的数据通过异或操作还原原始信息的步骤。
摘要由CSDN通过智能技术生成

附件:

📎windows_call.zip

解题流程

C:\Users\youngyt\Downloads\20221105强网拟态附件\20221105强网拟态附件\rev\windows_call>windows_call.exe
Please input your flag: sadasd
[-] Try again!

LoadLibrary

  1. 初始化加载库后,通过调试很容易发现,可以将数值转换为字符串,然后再加载相应的库。

  first_calc_dll((int)base, v44, 12);
  sec_calc_dll((int)base, 3u, -559038737);
  ntdll = LoadLibrary(base);
  ntdll_addr = (_DWORD *)GetModuleHandle(ntdll, -1);
  v49 = ntdll_addr;
  first_calc_dll((int)base, v43, 16);
  sec_calc_dll((int)base, 4u, -559038737);
  kernelbase = LoadLibrary(base);
  kernelbase_addr = (_DWORD *)GetModuleHandle(kernelbase, -1);
  v56 = 0;

HOOK APINAME

2. 在函数 sub_401DFE 中,参数 a2 是一个加密后的 API 名称的数值。该函数的功能是将获取到的每个 API 名称加密后与参数 a2 进行比对,如果比对成功,则返回该 API 的地址。不过,该函数不会返回 API 名称,这可以用于隐藏 API 的名称。

      if ( v13 )
      {
        v14 = 0;
        v15 = (_BYTE *)(v6 + *(_DWORD *)(v19 + 4 * v12));  //APIname
        do
        {
          v14 = v13 + __ROL4__(v14, 15);
          v13 = *++v15;             //这里下个断点,每个字节每个字节的比对,字节存放在edi里。
        }
        while ( *v15 );
        v6 = v18;  //这里清空了V6的数据,导致存放APINAME的v15数据也被清空了
        v23 = v14; //计算结果赋值
        v2 = a1;
      }
      if ( a2 == v23 ) //根据函数定义,a2的数据存放在"esp"+0x3c
    {
LABEL_25:
      v2[6] = 0;
      return v6 + *(_DWORD *)(v9 + 4 * v8);
    }
    

由于计算完毕后,APINAME的栈数据会被清空,因此在命中API之后无法直接获取其名称,需要在计算过程中提前进行名称的比对。为了实现这一点,可以在 sub_401DFE 函数中的 v13 = *++v15; 处下一个断点,并设置以下 Python 脚本作为条件:

import idaapi

e = idaapi.get_reg_val("edx") //计算出来的数据
enc = int(idc.get_bytes(idc.get_reg_value("esp")+0x3c, 4)[::-1].hex(),16) //

if e == enc:
    print("该APINAME:",idaapi.get_bytes(idaapi.get_reg_val("edi")-0x20,0x21).decode().split('\x00')[-1])
    return True
else:
    return False
	

PREFLAG VERIFICATION

3. flag{} 内包含 40 个字符, 将其中的小写字母全部转换为大写字母,然后计算 MD5 哈希值, 对原始的 flag 和全部大写的 flag 分别计算 MD5 哈希值,并检查生成的哈希值中是否相同。因为不能大于102也不能大于"F",所以flag中的数值只能为0-9和A-F

0 - 9 48 - 57A - Z 65 - 90a - z 97 - 122

FIRST ENC

4.第一层变换将原字符串中的奇数位置和偶数位置的十六进制数据分别取出,并将它们转换成十六进制的字符。然后将这些数值合并成一个字节,例如 "0x41"(A) 和 "0x42"(B) 合并后成为 "0xAB"。最后将这些字节依次覆盖回原字符串的位置。

这个规律非常容易在动态调试中发现,可以进行验证。需要注意的是,实际的实现涉及到低 8 位和高 8 位的问题,可以通过查看对应的汇编代码进行理解。

现在开始整活了,先拿flag前4个字节进行个循环异或。

下面就是简单的异或。

SECOND ENC

v37这里一进去就是aes_key的轮密特征,对key做初始化, 下面一个进去之后先是和v38做了异或,然后进行AES,没有iv。

sub_40234A((int)v26, (int)v37, 128); (v37是key,看函数有轮密特征)
 

sub_402613((int)v26, 1, 16, (int)v38, INPUT + 1028, v42); (v38向量)

sub_402613:
do
  {
    v12[v15] = *v12 ^ v12[v14];       //这里在把v38和在和前面加密后和没加密的flag进行异或
    ++v12;
    --v13;
  }
  while ( v13 );
  result = sub_4025F0(a1, a2, v9, v9); //这个地方执行加密,没有iv,考虑ecb(cbc要iv),而且这里a1是加密句柄,看了一下这个地址,然后用findcrypt再看一下,AES特征值就在该段,很明显在进行aes

SOLVE FIRST ENC

5. 验证给了flag加密后的前两个字节的验证相关信息,用z3求解。

from z3 import *
s = Solver()
# 创建变量
x = BitVec('x', 16)
y = BitVec('y', 16)
# x = 0xc9e8
# y = 0xcca0
# flag{E8C9A0CC....}
# 添加限制条件

s.add(x & 0xff00 < 0xca00)
s.add(x + 0x3800 - 0x10000   <= 0x800)         
s.add(x + 0x3800   >=  0x10000 )
s.add(y + 13568 - 0x10000 <= 0x500 )
s.add(y + 13568  >= 0x10000 )
s.add(y & 0xff == 0xa0)
s.add(y - x == 696)

MD5Ini = x ^ y
v25 = MD5Ini & 0xFF
MD5Fina = MD5Ini >> 8


v38 = [0] * 16
# 模拟循环计算v38
for v23 in range(16):
    v38[v23] = (MD5Fina + v23) ^ (v25 + v23)
# print(','.join([hex(i) for i in v38]))
v29 = 0
v37 = [0] * 16
# 模拟循环计算v37
for v27 in range(16):
    v28 = v38[v27] ^ (v27-64+0x100) 
    v37[v27] = v28
    v29 += v28
# print(','.join([hex(i) for i in v37]))
s.add(v29==0x8a8)

# 检查是否有解
if s.check() == sat:
    m = s.model()
    print(f"x = {hex(m[x].as_long())}")
    print(f"y = {hex(m[y].as_long())}")
else:
    print("No solution found.")

# x = 0xc9e8
# y = 0xcca0
# flag{E8C9A0CC....}

部分解释:

  1. 根据伪代码直接x + 0x3800 <= 0x800显然在python中不适用,会进位得出结果进行验证,那么这个时候减去进位即可。

s.add(x + 0x3800 - 0x10000   <= 0x800)         
s.add(x + 0x3800   >=  0x10000 )
  1. 例如 [ecx-40h] 为 eax = 3 - 40h = FFFFFFC3 m = 0x08, xor al,m 结果为0x00000CB, python中有符号 3 - 40h = -0x3d
    首先将十进制数3转换为十六进制数,得到0x03。然后将十六进制数0x40(也就是64)取反并加1得到-64,因为在补码表示中,-64的二进制表示为1111 1111 1100 0000,对每一位取反并加1可以得到0xC0(也就是192)。因此,赋值操作可以写成3 - 64 = -61,即eax的值为-61。最终将eax的值以十六进制形式表示为FFFFFFC3。

 v28 = v38[v27] ^ (v27-64+0x100) 

SOLVE SECOND ENC

6. 解出之后用AES解一下,然后再配合v38异或一下, 用v37作为key解ecb

v38 : 0x4d,0x4f,0x4d,0x43,0x45,0x47,0x45,0x43,0x5d,0x5f,0x5d,0x43,0x45,0x47,0x45,0x43
v37 : 0x8d,0x8e,0x8f,0x80,0x81,0x82,0x83,0x84,0x95,0x96,0x97,0x88,0x89,0x8a,0x8b,0x8c

7. 最后异或回去

hex_data = "0xc6d7198e95eb775824509a0dbf154aff"
-
# 将十六进制数据转换为字节数组
byte_data = bytes.fromhex(hex_data[2:])

# 对每个字节进行异或操作,并将结果存储到列表中
result = [byte ^ key for byte, key in zip(byte_data, [0x4d,0x4f,0x4d,0x43,0x45,0x47,0x45,0x43,0x5d,0x5f,0x5d,0x43,0x45,0x47,0x45,0x43])]

# 将结果转换为十六进制字符串
hex_result = ''.join([hex(byte)[2:].zfill(2) for byte in result])

print(hex_result.upper())

和上面拼接得到flag

flag{E8C9A0CC8B9854CDD0AC321B790FC74EFA520FBC}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值