MFC+纯算法逆向

admin 2022年9月5日08:06:16评论17 views字数 11194阅读37分18秒阅读模式

MFC+纯算法逆向

依赖

IDA版本为7.7。

PETools查看概况

32位程序,入口点Section: [.text], EP: 0x00001DB9故无壳。

正文

作者:hans774882968以及hans774882968以及hans774882968

本文juejin:https://juejin.cn/post/7139136489585115173/

本文csdn:https://blog.csdn.net/hans774882968/article/details/126682443

本文52pojie:https://www.52pojie.cn/thread-1683826-1-1.html

如何定位关键函数

  1. 因为函数很少,所以一个一个点开来看,即可定位到关键函数sub_401660

  2. 打开x64dbg,正常地触发一次输入序列号错误,点击暂停键,查看调用堆栈,即可定位。

分析

sub_401660

 复制代码 隐藏代码
int sub_401660(){
  int iii; // ebx  int *v2; // edi  int v3; // eax  int i; // ecx  int v5; // ecx  char *v6; // edi  int v7; // edx  int v8; // esi  char *v9; // eax  int v10; // ecx  int v11; // ecx  int *v12; // edi  int WindowTextA; // eax  char v14[120]; // [esp+8h] [ebp-328h] BYREF  CHAR Caption[256]; // [esp+80h] [ebp-2B0h] BYREF  CHAR Text[256]; // [esp+180h] [ebp-1B0h] BYREF  char String[28]; // [esp+280h] [ebp-B0h] BYREF  __int16 v18; // [esp+29Ch] [ebp-94h]  char Buf1[28]; // [esp+2A0h] [ebp-90h] BYREF  __int16 v20; // [esp+2BCh] [ebp-74h]  char v21; // [esp+2BEh] [ebp-72h]  char Buf2[28]; // [esp+2C0h] [ebp-70h] BYREF  __int16 v23; // [esp+2DCh] [ebp-54h]  char v24; // [esp+2DEh] [ebp-52h]  int v25; // [esp+2E0h] [ebp-50h]  int v26; // [esp+2E4h] [ebp-4Ch]  CHAR Str1[28]; // [esp+2E8h] [ebp-48h] BYREF  char Source[43]; // [esp+304h] [ebp-2Ch] BYREF  char v29; // [esp+32Fh] [ebp-1h]  memset(String, 0, sizeof(String));
  v18 = 0;
  memset(Buf1, 0, sizeof(Buf1));
  v20 = 0;
  memset(Buf2, 0, sizeof(Buf2));
  v23 = 0;
  memset(v14, 0, sizeof(v14));
  memset(Source, 0, 30);
  if ( GetWindowTextA(dword_40974C, String, 30) >= 1 )
  {
    iii = 0;
    v2 = &dword_407030;
    do    {
      if ( iii >= lstrlenA(String) )
        Buf1[iii] = (iii + (*v2 ^ 0x5A)) % 57 + 65;
      else        Buf1[iii] = (iii + 1) ^ String[iii];
      ++v2;
      ++iii;
    }
    while ( (int)v2 < (int)&unk_4070A8 );
    v21 = 0;
    _swab(Buf1, Buf2, 30);
    v24 = 0;
    v3 = 0;
    for ( i = 0; i < 30; ++i )
    {
      v29 = Buf2[i];
      LOBYTE(v3) = v29;
      v3 = __ROR4__(v3, 1);
      v29 = v3;
      Buf2[i] = v3;
    }
    v5 = 0;
    v6 = v14;
    do    {
      v7 = Buf2[v5];
      v8 = Buf1[v5];
      v6 += 4;
      ++v5;
      *((_DWORD *)v6 - 1) = v7 * v8 + (v7 ^ v8);
    }
    while ( v5 < 30 );
    v9 = v14;
    v10 = 15;
    do    {
      *(_DWORD *)v9 += *((_DWORD *)v9 + 15);
      v9 += 4;
      --v10;
    }
    while ( v10 );
    v11 = 0;
    v12 = (int *)v14;
    do    {
      v25 = *v12;
      LOBYTE(v26) = v25;
      ++v12;
      Source[v11++] = (unsigned __int8)v25 % 9 + 48;
    }
    while ( v11 < 15 );
    memset(&Source[16], 0, 25);
    Source[16] = toupper(dword_407030 ^ 0x63);
    Source[17] = toupper(dword_407034 ^ 0x63);
    Source[18] = toupper(dword_407038 ^ 0x63);
    Source[19] = '-';
    strncat(&Source[16], Source, 0xFu);
    qmemcpy(&Source[35], "-2413", 5);
    WindowTextA = GetWindowTextA(dword_409750, Str1, 25);
    if ( WindowTextA >= 1 )
    {
      if ( WindowTextA >= 24        && !strncmp(Str1, &Source[16], 0x14u)
        && (Str1[20] ^ 0x63) == 0x53        && (Str1[21] ^ 0x63) == 0x52        && (Str1[22] ^ 0x63) == 0x57        && (Str1[23] ^ 0x63) == 0x52 )
      {
        sub_401B20(byte_4071CC, 12);
        qmemcpy(Caption, byte_409754, 0xFFu);
        sub_401B20(byte_4071FC, 10);
        qmemcpy(Text, byte_409754, 0xFFu);
        return MessageBoxA(hWnd, Caption, Text, 0x40u);
      }
      else      {
        sub_401B20(byte_407190, 15);
        qmemcpy(Caption, byte_409754, 0xFFu);
        sub_401B20(byte_407178, 6);
        qmemcpy(Text, byte_409754, 0xFFu);
        return MessageBoxA(hWnd, Caption, Text, 0x10u);
      }
    }
    else    {
      sub_401B20(byte_407118, 24);
      qmemcpy(Caption, byte_409754, 0xFFu);
      sub_401B20(byte_407178, 6);
      qmemcpy(Text, byte_409754, 0xFFu);
      return MessageBoxA(hWnd, Caption, Text, 0x10u);
    }
  }
  else  {
    sub_401B20(byte_4070C0, 22);
    qmemcpy(Text, byte_409754, 0xFFu);
    sub_401B20(byte_407178, 6);
    qmemcpy(Caption, byte_409754, 0xFFu);
    return MessageBoxA(hWnd, Text, Caption, 0x10u);
  }
}

这里有两个全局变量,dword_40974Cdword_409750分别对应你的Name和你输入的序列号。

常量串隐藏

为什么不能通过常量串定位关键函数?因为做了常量串隐藏。看到sub_401B20就是一个字符串解密操作,我们写个idapython脚本看看各个常量串:

 复制代码 隐藏代码
import idcdef get_dec_str(enc):    ans = ''    for i in range(0, len(enc), 4):
        ans += chr(enc[i] ^ 0x63)
    return ans

a = [
    [0x4070c0, 88], [0x407118, 96], [0x407178, 24],
    [0x407190, 60], [0x4071cc, 48], [0x4071fc, 40]
]
mp = {}for addr, c in a:
    enc_s = get_bytes(addr, c)
    dec_s = get_dec_str(enc_s)
    mp[hex(addr)] = dec_sprint(mp)  # {'0x4070c0': 'You must enter a name!', '0x407118': 'You must enter a serial!', '0x407178': 'Error!', '0x407190': 'Invalid serial!', '0x4071cc': 'Good serial!', '0x4071fc': 'Well Done!'}

接下来我们从下到上分析每一个小模块。

判定:

 复制代码 隐藏代码
    WindowTextA = GetWindowTextA(dword_409750, Str1, 25);
    if ( WindowTextA >= 24        && !strncmp(Str1, &Source[16], 0x14u) // Str1是你输入的序列号        && (Str1[20] ^ 0x63) == 0x53        && (Str1[21] ^ 0x63) == 0x52        && (Str1[22] ^ 0x63) == 0x57        && (Str1[23] ^ 0x63) == 0x52 )

需要关注Source + 16字符串怎么来:

 复制代码 隐藏代码
    v12 = (int *)v14;
    do    {
      v25 = *v12;
      LOBYTE(v26) = v25;
      ++v12;
      Source[j++] = (unsigned __int8)v25 % 9 + 48;
    }
    while ( j < 15 );

    memset(&Source[16], 0, 25);
    // 期望的序列号的前4个字符:'ULT-'    Source[16] = toupper(dword_407030 ^ 0x63);
    Source[17] = toupper(dword_407034 ^ 0x63);
    Source[18] = toupper(dword_407038 ^ 0x63);
    Source[19] = '-';
    strncat(&Source[16], Source, 0xFu); // 所以Source[:15]有用    qmemcpy(&Source[35], "-2413", 5); // 迷惑你的,但用到了"-2413"[0]

所以重点关注对v14所在内存空间的修改:

 复制代码 隐藏代码
    v6 = v14;
    do    {
      v7 = Buf2[ii]; // 注意这里是有符号扩展      v8 = Buf1[ii];
      v6 += 4;
      ++ii;
      *((_DWORD *)v6 - 1) = v7 * v8 + (v7 ^ v8); // v6+4-1*4    }
    while ( ii < 30 );
    // 相当于 for i in range(15): v14[i] += v14[i + 15]    v9 = v14;
    v10 = 15;
    do    {
      *(_DWORD *)v9 += *((_DWORD *)v9 + 15);
      v9 += 4;
      --v10;
    }
    while ( v10 );

值得注意的是,v7是int,Buf2[ii]是char,cpp里把char赋值给int的隐式类型转换是“有符号扩展”,即把Buf2[ii]解释为8位有符号整数。对应的汇编指令为:movsx。验证demo:

 复制代码 隐藏代码
#include <windows.h>#include <stdio.h>int main() {
    char c1, c2, c3;
    c1 = 0xfe;
    c2 = 0xfc;
    c3 = 0xfa;
    int v1 = c1, v2 = c2, v3 = c3;
    printf ("%d %d %dn", v1, v2, v3); // -2 -4 -6    return 0;
}

接下来看Buf1Buf2怎么求得。

Buf2

 复制代码 隐藏代码
    _swab(Buf1, Buf2, 30);   
    v3 = 0;
    for ( i = 0; i < 30; ++i )
    {
      v29 = Buf2[i];
      LOBYTE(v3) = v29;
      v3 = __ROR4__(v3, 1);
      v29 = v3;
      Buf2[i] = v3;
    }
  1. _swab即相邻字符串为一组,交换位置。

  2. 易错点:v3 = __ROR4__(v3, 1)不能理解成右移1位,因为v3是int类型,在操作24次后将不等价于右移1位。

_swab

 复制代码 隐藏代码
void __cdecl _swab(char *Buf1, char *Buf2, int SizeInBytes)
{
  unsigned int v4; // esi  char v6; // dl  char *v7; // ecx  if ( SizeInBytes > 1 )
  {
    v4 = (unsigned int)SizeInBytes >> 1;
    do    {
      v6 = *Buf1;
      *Buf2 = Buf1[1];
      Buf1 += 2;
      v7 = Buf2 + 1;
      *v7 = v6;
      Buf2 = v7 + 1;
      --v4;
      // Buf2[i] = Buf1[i+1], Buf2[i+1] = Buf1[i], Buf1 += 2, Buf2 += 2    }
    while ( v4 );
  }
}

Buf1

 复制代码 隐藏代码
    iii = 0;
    v2 = &dword_407030; // GetWindowTextA(dword_40974C, String, 30),所以String就是你的Name    do    {
      if ( iii >= lstrlenA(String) )
        Buf1[iii] = (iii + (*v2 ^ 0x5A)) % 57 + 65;
      else        Buf1[iii] = (iii + 1) ^ String[iii];
      ++v2;
      ++iii;
    }
    while ( (int)v2 < (int)&unk_4070A8 );

因此,我们要做的,是把生成序列号的算法复现出来,算法参数:你输入的Name

踩坑总结

  1. 把char赋值给int的隐式类型转换是“有符号扩展”。

  2. 不要想当然地把__ROR4__简化为右移一位。python实现时不要怕麻烦。

代码

这里我们有两个看上去不错的选择:

  1. 用python来复现。

  2. 用cpp来复现,并用IDA的defs.h来减少代码的改动。defs.h在IDA安装目录下可搜索到。

我把两种方法都做了,法二的心智负担明显更小。

法一

 复制代码 隐藏代码
def get_buf1(inp_name):    dword_407030 = [
        0x16, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x17, 0x00,
        0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
        0x10, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x16, 0x00,
        0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
        0x0E, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x52, 0x00,
        0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
        0x17, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00,
        0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
        0x16, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x07, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00    ]
    buf1 = []
    for i in range(30):
        buf1.append(
            ord(inp_name[i]) ^ (i + 1) if i < len(inp_name) else            ((i + (dword_407030[4 * i] ^ 0x5A)) % 57 + 65)
        )
    return buf1def get_buf2(buf1):    buf2 = []
    for i in range(0, 30, 2):
        buf2.append(buf1[i + 1])
        buf2.append(buf1[i])

    def ror4_1(v): return ((v & 1) << 31) | (v >> 1)
    v3 = 0    for i, v in enumerate(buf2):
        v3 = (v3 & 0xffffff00) | v
        v3 = ror4_1(v3)
        buf2[i] = v3 & 0xff    return buf2def solve(inp_name: str):    ans16_19 = ''.join([chr(i ^ 0x63).upper()
                       for i in [0x16, 0x0f, 0x17]]) + '-'    print(ans16_19)  # ULT-    ans_tail = '-2413'    serial_tail = ''.join([chr(i ^ 0x63) for i in [0x53, 0x52, 0x57, 0x52]])
    print(serial_tail)  # 0141    buf1 = get_buf1(inp_name)
    buf2 = get_buf2(buf1)
    v14 = []

    def as_signed8(v): return v if v < 0x7f else (v - 0x100)
    for x, y in zip(buf1, buf2):
        x = as_signed8(x)
        y = as_signed8(y)
        v14.append(y * x + (y ^ x))
    for i in range(15):
        v14[i] += v14[i + 15]
    ans_main = ''    for i in range(15):
        ans_main += chr((v14[i] & 0xff) % 9 + 48)
    ans = ans16_19 + ans_main + '-' + serial_tail
    return ans

inp_name = "hans"ans = solve(inp_name)print(inp_name, ans, len(ans))
inp_name = "acmer"ans = solve(inp_name)print(inp_name, ans, len(ans))
inp_name = "Hans774882968"ans = solve(inp_name)print(inp_name, ans, len(ans))

法二

 复制代码 隐藏代码
#include <bits/stdc++.h>#include <windows.h>#include "defs.h"using namespace std;#define rep(i,a,b) for(int i = (a);i <= (b);++i)#define re_(i,a,b) for(int i = (a);i < (b);++i)#define dwn(i,a,b) for(int i = (a);i >= (b);--i)void dbg() {
    puts ("");
}template<typename T, typename... R>void dbg (const T &f, const R &... r) {
    cout << f << " ";
    dbg (r...);
}template<typename Type>inline void read (Type &xx) {
    Type f = 1;
    char ch;
    xx = 0;
    for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
    xx *= f;
}void read() {}template<typename T, typename ...R>void read (T &x, R &...r) {
    read (x);
    read (r...);
}string solve (string inpName) {
    int v3; // eax    int v5; // ecx    char *v6; // edi    int v7; // edx    int v8; // esi    char *v9; // eax    int *v12; // edi    char v14[120]; // [esp+8h] [ebp-328h] BYREF    char Buf1[38]; // [esp+2A0h] [ebp-90h] BYREF    char v21; // [esp+2BEh] [ebp-72h]    char Buf2[38]; // [esp+2C0h] [ebp-70h] BYREF    char v24; // [esp+2DEh] [ebp-52h]    int v26; // [esp+2E4h] [ebp-4Ch]    char v29; // [esp+32Fh] [ebp-1h]    int dword_407030[120] = {
        0x16, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x17, 0x00,
        0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
        0x10, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x16, 0x00,
        0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
        0x0E, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x52, 0x00,
        0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
        0x17, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00,
        0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
        0x16, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x07, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00    };
    memset (Buf1, 0, sizeof (Buf1) );
    memset (Buf2, 0, sizeof (Buf2) );
    memset (v14, 0, sizeof (v14) );
    re_ (i, 0, 30) {
        if (i >= inpName.size() )
            Buf1[i] = (i + (dword_407030[4 * i] ^ 0x5A) ) % 57 + 65;
        else            Buf1[i] = (i + 1) ^ inpName[i];
    }
    v21 = 0;
    for (int i = 0; i < 30; i += 2) {
        Buf2[i] = Buf1[i + 1];
        Buf2[i + 1] = Buf1[i];
    }
    v24 = 0;
    v3 = 0;
    re_ (i, 0, 30) {
        v29 = Buf2[i];
        LOBYTE (v3) = v29;
        v3 = __ROR4__ (v3, 1);
        v29 = v3;
        Buf2[i] = v3;
    }
    v5 = 0;
    v6 = v14;
    do {
        v7 = Buf2[v5];
        v8 = Buf1[v5];
        v6 += 4;
        ++v5;
        * ( (_DWORD *) v6 - 1) = v7 * v8 + (v7 ^ v8);
    } while ( v5 < 30 );
    v9 = v14;
    re_ (_, 0, 15) {
        * (_DWORD *) v9 += * ( (_DWORD *) v9 + 15);
        v9 += 4;
    }
    v12 = (int *) v14;
    string ansMain;
    re_ (_, 0, 15) {
        int v25 = *v12;
        ++v12;
        ansMain += (unsigned __int8) v25 % 9 + 48;
    }
    return "ULT-" + ansMain + "-0141";
}int main() {
    string inpName, ans;
    inpName = "hans acmer";
    ans = solve (inpName);
    dbg (inpName, ans, ans.size() );
    inpName = "Hans774882968";
    ans = solve (inpName);
    dbg (inpName, ans, ans.size() );
    inpName = "scuctf";
    ans = solve (inpName);
    dbg (inpName, ans, ans.size() );
    return 0;
}

参考资料

  1. movsx命令:https://blog.csdn.net/cswangbin/article/details/3955395

  2. 暂时无法播放,可回源网站播放

该内容转载自网络,更多内容请点击“阅读原文”

MFC+纯算法逆向

原文始发于微信公众号(web安全工具库):MFC+纯算法逆向

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年9月5日08:06:16
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   MFC+纯算法逆向http://cn-sec.com/archives/1276168.html

发表评论

匿名网友 填写信息