• 【reverse】新160个CrackMe之154-cpp_crackme1——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);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146

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

    常量串隐藏

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

    import idc
    
    
    def 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_s
    print(mp)  # {'0x4070c0': 'You must enter a name!', '0x407118': 'You must enter a serial!', '0x407178': 'Error!', '0x407190': 'Invalid serial!', '0x4071cc': 'Good serial!', '0x4071fc': 'Well Done!'}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

    判定:

        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 )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    需要关注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]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    所以重点关注对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 );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

    #include 
    #include 
    
    int main() {
        char c1, c2, c3;
        c1 = 0xfe;
        c2 = 0xfc;
        c3 = 0xfa;
        int v1 = c1, v2 = c2, v3 = c3;
        printf ("%d %d %d\n", v1, v2, v3); // -2 -4 -6
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    接下来看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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    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 );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    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 );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    因此,我们要做的,是把生成序列号的算法复现出来,算法参数:你输入的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 buf1
    
    
    def 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 buf2
    
    
    def 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))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    法二
    #include 
    #include 
    #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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118

    参考资料

    1. movsx命令:https://blog.csdn.net/cswangbin/article/details/3955395
    2. https://www.bilibili.com/video/BV16d4y1d72A
  • 相关阅读:
    Java进阶面试题
    SpringBoot第三方bean管理
    分治算法(divide and conquer)
    java虚拟机详解篇十三(本地方法栈)
    学习笔记:机器学习之支持向量机(SVM)(上)
    如何判断一款软件的安全性?
    .NET 中的表达式树
    教你如何搭建一个大学生查题公众号
    【Python机器学习】sklearn.datasets其他通用函数
    把Python写的程序打包成exe应用文件
  • 原文地址:https://blog.csdn.net/hans774882968/article/details/126682443