首页
社区
课程
招聘
[原创]启明X辰样本分析报告
发表于: 2023-3-5 14:42 28128

[原创]启明X辰样本分析报告

2023-3-5 14:42
28128

这是在面试启明X辰时,公司要求分析的 CreakMe,这里做一份记录。

整理一下各个按钮的 id 与对应的linstener

这个方法起初看起来有点复杂,一直以为是个加密的方法,但仔细观察,这其实就是个十六进制字符串转十进制的方法,将传入的十六进制字符串转换成了对应的十进制字节数组

方法D 由两个方法组成,分别是方法F 和方法E,而方法D只是简单的判断方法E的返回值真假

根据之前分析过的方法G,其实可以直接判断这显然是方法G的逆运算,也就是AES的解密方法,该方法返回了解密后的明文字节数组

按钮 generate2的分析较为困难,主要问题在于 OpenSSL 库函数较为生疏,而OpenSSL的所以这里先补充一下本次分析中遇到的数据结构和函数知识。

这个函数较为简单,通过函数 sub_46660 向 dest 中写入数据,回传到屏幕作为密码

这个函数就是 generate2 按钮的主体逻辑了,首先将该函数分为四个部分依次分析:

这个函数总的来讲就是不断的将求得的hash作为key再次求hash,在分析这个函数时遇到的问题主要在于作者使用了 HMAC_Update 并且还总是在参数中不该传0的传0,无法确定是否对上下文产生了影响,解决方法是花了一些时间在 VS 中搭了一下环境,写了一个简单的 demo 进行简单的测试。

测试demo

这个函数的总的来说就是使用上一层传入的 hash 作为 key,加密用户名得到 encrypt_data,然后将 encrypt_data分段赋值给最终的 res_encrypt_data,分析这个函数的问题也还是在于作者使用了 EVP_EncryptUpdate 这个不熟悉的函数,并且还总是在参数中不该传0的传0,无法确定是否对上下文产生了影响,解决方法是花了一些时间在 VS 中搭了一下环境,测试了各种情况的结果。

测试demo

这一部分主要就是通过 hash 第二次加密 encrypt_data 得到 encrypt_data2

这个函数主要分为三个部分:

初始化数组 v21、v22,需要注意的是 v21 与 v22 在内存中相邻

第一次循环加密

这个循环的赋值有点混乱,利用两个相邻的数组越界赋值,所以这里我们先进行一个初步整理

这样看依然不是很清晰,所以进一步整理:

这时可以看出是 v10 每次加上v21[i],避免v10作为下标时越界又与256取余,随后利用一个中间变量交换v21[v10]和v21[i]的值,实际上这一部分的结果是固定的。

第二次循环加密

整个循环中不好分析的难点在于 v15 = v12 + 1 - ((v12 + ((unsigned int)((v12 + 1) >> 31) >> 24) + 1) & 0xFFFFFF00);

这里做下该语句的简要分析:

(v12+1)>>31 因为v12是有符号类型,也就是说该结果不是0就是0xffffffff, 关键在于v12的最高位判断

而 v12 的值来自于 v15,纵观整个循环 v15 又来自 v12,看似互相套娃但由于v15和v12的初值都为0

所以在循环量较小的时候 v12 的高位必然为0,所以 ((v12 + ((unsigned int)((v12 + 1) >> 31) >> 24) + 1) & 0xFFFFFF00) = 0

那么看似复杂的第一行代码可以直接简化为 v15 = v12 + 1

随后将代码进一步进行化简,执行流程就更为清晰

由于已经详细的分析了 generate2, 简单观察便可知check2只是其流程的逆运算,这里附上一些分析代码片段:

代码片段1

代码片段2

代码片段3

代码片段4

check2 按钮总结

分析完 check2 按钮后,发现相比较 generate2 缺少一个流程,那就是求用户名 hash 作为key,于是回溯了一下这个 key,通过追溯发现,这个 key 已经通过两个异或加密函数加密在了 password 中,只需要将其逆运算取出来即可使用。

在分析按钮 generate3 时很多数据静态分析时并不存在,所以这里还需要分析 _init 函数,该函数地址并没有被 IDA 分析出来,可以通过以下两个办法找到:

使用 readelf 工具查看

使用 IDA 动态调试,在被写入数据的地址下内存写入断点

这个函数就是 _init 函数,其主要分为三个函数,分别是 set_buff1、 set_buff2、 set_buff3

这个函数首先将 19ca9c + image_base 存到 buff1_60[0-3] 中,根据后面的分析结果这里存的是一个函数地址,之后在 buff1_60[28-43] 中填入了一些随机数,随后调用了 sub_48840,并传入了一个字符串

该函数主要是一些范围的判断,主要还是调用函数 sub_488E0,并传入了一个字符串

这里就是 buff1 真正调用的函数,作用是初始化 buff1 空间中的数据

这个函数比较浅,主要就是计算随机数的 hash,然后初始化 buff2 的内存数据,结果是:

基本上和 set_buff2 如出一辙,调用了一个新的函数 get_hash2,不过和之前求hash函数的也是基本一致,这里直接列出 buff3 内存布局:

可以轻易发现,主要是调用了 MainActivity.this.getbt 方法

这里的 fun3 就是 buff3 的前四个字节指向的地址(19ca8c + image_base),整个按钮 generate3 的真正核心逻辑自此真正展开

函数 fun3 的大致逻辑如下:

主要功能为设置 dest 区域的内存数据

主要功能为设置 src 区域的内存数据,并返回一个影响密码长度的值,另外我记着在分析这个函数的时候遇到了阻碍 IDA 分析的一连串 nop,我的解决方法是把 nop 改为 mov eax, eax ,然后依次使用快捷键 u 、c、p、f5,重新识别。

这个函数总的来讲就是不断的修改 fun3 区域内的数据并最后计算出了一个 hash

这个函数主要就是在一个循环中通过一系列的变换,不断的在修改 src 中的数据

利用启动应用时初始化的数据,不断的求 hash、加密、更新数据区的内容,最终计算出 password

可以轻易发现,主要是调用了 MainActivity.this.getck 方法,通过其返回值判断成功失败

可以发现主要就是在调用函数 dword_1A50BC[0] + 4 而这个地址其实就是在 _init 初始化的 [19ca8c + image_base]+4(函数sub_43280)

这个函数是解密的核心函数,这里总结一下流程:

没有找到这个函数对应的加密流程,应该是导致解密失败的主要原因

EVP_DecryptFinal_ex 解密失败,这里我查阅了一下 openssl 源码,结合动态调试返回的地方,对应在 EVP_R_INVALID_OPERATION 处返回,正好源码里有注释,翻译了一下是:防止解密时意外使用加密上下文。 应该是解密的数据不对导致的失败返回 0。

和 generate3 相比,check3 的代码量少了许多,并没有像check2那样完全逆向流程解密,导致验证失败,而且我思考了一下,注册机应该是写不了的,因为最开始在 _init 函数中初始化的数据是随机的。

本次样本分析共历时七天,总的来讲主要集中在 OpenSSL 函数、SSE指令集的用法的细节问题,但最终经过网络搜索、动态调试和搭建实验环境调试 demo 予以解决。

[1] OpenSSL之EVP(一)——数据结构及源码结构介绍

https://blog.csdn.net/scuyxi/article/details/60365001

[2] OpenSSL学习之一:HMAC算法分析

https://blog.csdn.net/KXue0703/article/details/120795546

[3] SSE指令集优化学习:双线性插值

https://blog.csdn.net/djzhao/article/details/78408198

[4] OpenSSL中文手册之EVP库详解

https://blog.csdn.net/liao20081228/article/details/76285896

[5] Intel白皮书SSE2相关指令查询

https://software.intel.com/sites/landingpage/IntrinsicsGuide/#techs=SSE2&text=_mm_cmpeq_epi32&expand=773

最后附上样本
http://gofile.me/6J4EF/mzW5XxJtC

分析报告 分析环境
分析人 刘XX
时间 2023年2月28日
平台 x86 模拟器
样本 分析环境
样本名称 20230221.demo.apk
MD5值 2A006D76B461ECE94596D46B3C9B3072
SHA1值 CF7810FA84DCCFA7F4F21847188E133A4C80CBA7
CRC32 6D81EAF7
text id button linstener
generate1 btn_login this.s = button g
GENERATE2 btn_gen2 this.t = button2 e
generate3 btn_gen3 this.u = button3 f
check1 btn_check this.v = button4 d
check2 btn_check2 this.w = button5 b
check3 btn_check3 this.x = button6 c
class g implements View.OnClickListener {
    g() {
    }
 
    @Override // android.view.View.OnClickListener
    public void onClick(View view) {
        String obj = MainActivity.this.y.getText().toString();
        MainActivity.this.z.getText().toString();
        if (obj.length() != 16) {
            Toast.makeText(MainActivity.this.getApplicationContext(), "用户名长度必须为16字节", 0).show();
            return;
        }
        MainActivity.this.v.setEnabled(true);
        MainActivity.this.w.setEnabled(false);
        MainActivity.this.x.setEnabled(false);
        MainActivity.this.z.setText(com.test.pac.demo.a.a.a(MainActivity.this.G(obj.getBytes())));
    }
}
class g implements View.OnClickListener {
    g() {
    }
 
    @Override // android.view.View.OnClickListener
    public void onClick(View view) {
        String obj = MainActivity.this.y.getText().toString();
        MainActivity.this.z.getText().toString();
        if (obj.length() != 16) {
            Toast.makeText(MainActivity.this.getApplicationContext(), "用户名长度必须为16字节", 0).show();
            return;
        }
        MainActivity.this.v.setEnabled(true);
        MainActivity.this.w.setEnabled(false);
        MainActivity.this.x.setEnabled(false);
        MainActivity.this.z.setText(com.test.pac.demo.a.a.a(MainActivity.this.G(obj.getBytes())));
    }
}
public byte[] G(byte[] bArr) {
    try {
        byte[] bArr2 = new byte[16];
        // 随机生成16个字节
        new Random().nextBytes(bArr2);
        // 实例化一个加密对象
        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        // 传入密钥和随机数
        cipher.init(1, new SecretKeySpec("6SvMO4msTk1OqA8n".getBytes(), "AES"), new IvParameterSpec(bArr2));
        // 传入明文加密并得到返回的密文
        byte[] doFinal = cipher.doFinal(bArr);
        // AES 加密后的数据长度是不变的,这里其实就是创建了一个长度32的字节数组
        byte[] bArr3 = new byte[doFinal.length + 16];
        // 从最开始申请的随机数的下标8位开始拷贝,拷贝8个长度,放到 barr3 的起始位置
        System.arraycopy(bArr2, 8, bArr3, 0, 8);
        // 把 AES 加密后的全部数据拷贝到 barr3,从barr3的下标8开始,其实就是接着上边结束位置
        System.arraycopy(doFinal, 0, bArr3, 8, doFinal.length);
        // 从最开始申请的随机数的下标0位开始拷贝,拷贝8个长度,从barr3的下标 doFinal.length+8 开始,其实就是接着上边结束位置
        System.arraycopy(bArr2, 0, bArr3, doFinal.length + 8, 8);
        // 小结一下,bArr3 的组成如下所示:
        // bArr3 = bArr2[8-15] + aes(明文) + bArr2[0-7]
        return bArr3;
    } catch (Exception e2) {
        e2.printStackTrace();
        return null;
    }
}
public byte[] G(byte[] bArr) {
    try {
        byte[] bArr2 = new byte[16];
        // 随机生成16个字节
        new Random().nextBytes(bArr2);
        // 实例化一个加密对象
        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        // 传入密钥和随机数
        cipher.init(1, new SecretKeySpec("6SvMO4msTk1OqA8n".getBytes(), "AES"), new IvParameterSpec(bArr2));
        // 传入明文加密并得到返回的密文
        byte[] doFinal = cipher.doFinal(bArr);
        // AES 加密后的数据长度是不变的,这里其实就是创建了一个长度32的字节数组
        byte[] bArr3 = new byte[doFinal.length + 16];
        // 从最开始申请的随机数的下标8位开始拷贝,拷贝8个长度,放到 barr3 的起始位置
        System.arraycopy(bArr2, 8, bArr3, 0, 8);
        // 把 AES 加密后的全部数据拷贝到 barr3,从barr3的下标8开始,其实就是接着上边结束位置
        System.arraycopy(doFinal, 0, bArr3, 8, doFinal.length);
        // 从最开始申请的随机数的下标0位开始拷贝,拷贝8个长度,从barr3的下标 doFinal.length+8 开始,其实就是接着上边结束位置
        System.arraycopy(bArr2, 0, bArr3, doFinal.length + 8, 8);
        // 小结一下,bArr3 的组成如下所示:
        // bArr3 = bArr2[8-15] + aes(明文) + bArr2[0-7]
        return bArr3;
    } catch (Exception e2) {
        e2.printStackTrace();
        return null;
    }
}
public static String a(byte[] bArr) {
    StringBuffer stringBuffer = new StringBuffer();
    for (byte b : bArr) {
        // 取绝对值后转成16进制
        String hexString = Integer.toHexString(b & 255);
        if (hexString.length() == 1) {
            hexString = '0' + hexString;
        }
        stringBuffer.append(hexString.toUpperCase());
    }
    return stringBuffer.toString();
}
public static String a(byte[] bArr) {
    StringBuffer stringBuffer = new StringBuffer();
    for (byte b : bArr) {
        // 取绝对值后转成16进制
        String hexString = Integer.toHexString(b & 255);
        if (hexString.length() == 1) {
            hexString = '0' + hexString;
        }
        stringBuffer.append(hexString.toUpperCase());
    }
    return stringBuffer.toString();
}
class d implements View.OnClickListener {
    d() {
    }
 
    @Override // android.view.View.OnClickListener
    public void onClick(View view) {
        Context applicationContext;
        String str;
        String obj = MainActivity.this.y.getText().toString(); // username
        String obj2 = MainActivity.this.z.getText().toString(); // password
        if (obj.length() != 16) {
            Toast.makeText(MainActivity.this.getApplicationContext(), "用户名长度必须为16字节", 0).show();
            return;
        }
        if (MainActivity.this.D(com.test.pac.demo.a.a.b(obj2), obj.getBytes()) > 0) {
            applicationContext = MainActivity.this.getApplicationContext();
            str = "verify success!";
        } else {
            applicationContext = MainActivity.this.getApplicationContext();
            str = "verify failed!";
        }
        Toast.makeText(applicationContext, str, 0).show();
    }
}
class d implements View.OnClickListener {
    d() {
    }
 
    @Override // android.view.View.OnClickListener
    public void onClick(View view) {
        Context applicationContext;
        String str;
        String obj = MainActivity.this.y.getText().toString(); // username
        String obj2 = MainActivity.this.z.getText().toString(); // password
        if (obj.length() != 16) {
            Toast.makeText(MainActivity.this.getApplicationContext(), "用户名长度必须为16字节", 0).show();
            return;
        }
        if (MainActivity.this.D(com.test.pac.demo.a.a.b(obj2), obj.getBytes()) > 0) {
            applicationContext = MainActivity.this.getApplicationContext();
            str = "verify success!";
        } else {
            applicationContext = MainActivity.this.getApplicationContext();
            str = "verify failed!";
        }
        Toast.makeText(applicationContext, str, 0).show();
    }
}
public static byte[] b(String str) {
    if (str.length() < 1) {
        return null;
    }
    byte[] bArr = new byte[str.length() / 2];
    for (int i = 0; i < str.length() / 2; i++) {
        int i2 = i * 2;     // i2 = 0 2 4 6 8 10 ...
        int i3 = i2 + 1;    // i3 = 1 3 5 7 9 11 ...
        // 一个16位的十六进制数由两个字符表示,高位字符转成十进制后乘以16 + 低位字符转成十进制
        bArr[i] = (byte) ((Integer.parseInt(str.substring(i2, i3), 16) * 16) + Integer.parseInt(str.substring(i3, i2 + 2), 16));
    }
    return bArr;
}
public static byte[] b(String str) {
    if (str.length() < 1) {
        return null;
    }
    byte[] bArr = new byte[str.length() / 2];
    for (int i = 0; i < str.length() / 2; i++) {
        int i2 = i * 2;     // i2 = 0 2 4 6 8 10 ...
        int i3 = i2 + 1;    // i3 = 1 3 5 7 9 11 ...
        // 一个16位的十六进制数由两个字符表示,高位字符转成十进制后乘以16 + 低位字符转成十进制
        bArr[i] = (byte) ((Integer.parseInt(str.substring(i2, i3), 16) * 16) + Integer.parseInt(str.substring(i3, i2 + 2), 16));
    }
    return bArr;
}
public int D(byte[] bArr, byte[] bArr2) {
    return E(F(bArr), bArr2) ? 1 : 0;
}
public int D(byte[] bArr, byte[] bArr2) {
    return E(F(bArr), bArr2) ? 1 : 0;
}
private byte[] F(byte[] bArr) {
    try {
        byte[] bArr2 = new byte[16];
        // 把密文的0-7位拷贝到bArr2的8-15位
        System.arraycopy(bArr, 0, bArr2, 8, 8);
        // 把密文的末端8位拷贝到bArr2的0-7位
        System.arraycopy(bArr, bArr.length - 8, bArr2, 0, 8);
        // 实例化一个加密对象
        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        // 设置模式为解密,同时传入密钥和随机数的密文
        cipher.init(2, new SecretKeySpec("6SvMO4msTk1OqA8n".getBytes(), "AES"), new IvParameterSpec(bArr2));
        // 申请16个字节的空间
        byte[] bArr3 = new byte[bArr.length - 16];
        // 密文的下标8开始拷贝,拷贝16个,也就是将密文的[7-23]位拷贝到bArr3
        System.arraycopy(bArr, 8, bArr3, 0, bArr.length - 16);
        // 返回解密后的明文
        return cipher.doFinal(bArr3);
    } catch (Exception e2) {
        e2.printStackTrace();
        return null;
    }
}
private byte[] F(byte[] bArr) {
    try {
        byte[] bArr2 = new byte[16];
        // 把密文的0-7位拷贝到bArr2的8-15位
        System.arraycopy(bArr, 0, bArr2, 8, 8);
        // 把密文的末端8位拷贝到bArr2的0-7位
        System.arraycopy(bArr, bArr.length - 8, bArr2, 0, 8);
        // 实例化一个加密对象
        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        // 设置模式为解密,同时传入密钥和随机数的密文
        cipher.init(2, new SecretKeySpec("6SvMO4msTk1OqA8n".getBytes(), "AES"), new IvParameterSpec(bArr2));
        // 申请16个字节的空间
        byte[] bArr3 = new byte[bArr.length - 16];
        // 密文的下标8开始拷贝,拷贝16个,也就是将密文的[7-23]位拷贝到bArr3
        System.arraycopy(bArr, 8, bArr3, 0, bArr.length - 16);
        // 返回解密后的明文
        return cipher.doFinal(bArr3);
    } catch (Exception e2) {
        e2.printStackTrace();
        return null;
    }
}
private boolean E(byte[] bArr, byte[] bArr2) {
    if (bArr.length == 0 || bArr2.length == 0 || bArr.length != bArr2.length) {
        return false;
    }
    for (int i = 0; i < bArr.length && i < bArr2.length; i++) {
        if (bArr[i] != bArr2[i]) {
            System.out.println("different");
            return false;
        }
    }
    return true;
}
private boolean E(byte[] bArr, byte[] bArr2) {
    if (bArr.length == 0 || bArr2.length == 0 || bArr.length != bArr2.length) {
        return false;
    }
    for (int i = 0; i < bArr.length && i < bArr2.length; i++) {
        if (bArr[i] != bArr2[i]) {
            System.out.println("different");
            return false;
        }
    }
    return true;
}
数据结构与函数 功能说明
HMAC_CTX_init 初始化一个 HMAC_CTX
HMAC_CTX 是一个上下文,保存状态数据和中间计算
EVP_sha256 返回 sha256 的 EVP_MD
EVP_MD 用来存放摘要算法信息以及各种计算函数。
HMAC_Init_ex 初始化HAMC_CTX上下文结构,key为秘钥,len为秘钥长度,md为计算hash
HMAC_Update 向HMAC上下文输入字节流
HMAC_Final 生成最终的HMAC串,成功时len更新为HMAC的长度
jbyteArray __cdecl Java_com_test_pac_demo_MainActivity_M1(JNIEnv *env, jobject a2, BYTE *bytes_)
{
  jbyteArray v4; // esi
  int v6; // [esp-14h] [ebp-1030h]
  unsigned int user_name_length; // [esp+0h] [ebp-101Ch]
  jbyte *byte_user_name; // [esp+4h] [ebp-1018h]
  char dest[4096]; // [esp+8h] [ebp-1014h] BYREF
  unsigned int v10; // [esp+1008h] [ebp-14h]
 
  v10 = __readgsdword(0x14u);
  user_name_length = (*env)->GetArrayLength(env, bytes_);
  byte_user_name = (*env)->GetByteArrayElements(env, bytes_, 0);
  memset(dest, 0, sizeof(dest));
  v6 = sub_46660((int)byte_user_name, user_name_length, dest);
  v4 = (*env)->NewByteArray(env, v6);
  (*env)->SetByteArrayRegion(env, v4, 0, v6, dest);
  (*env)->ReleaseByteArrayElements(env, bytes_, byte_user_name, 0);
  return v4;
}
jbyteArray __cdecl Java_com_test_pac_demo_MainActivity_M1(JNIEnv *env, jobject a2, BYTE *bytes_)
{
  jbyteArray v4; // esi
  int v6; // [esp-14h] [ebp-1030h]
  unsigned int user_name_length; // [esp+0h] [ebp-101Ch]
  jbyte *byte_user_name; // [esp+4h] [ebp-1018h]
  char dest[4096]; // [esp+8h] [ebp-1014h] BYREF
  unsigned int v10; // [esp+1008h] [ebp-14h]
 
  v10 = __readgsdword(0x14u);
  user_name_length = (*env)->GetArrayLength(env, bytes_);
  byte_user_name = (*env)->GetByteArrayElements(env, bytes_, 0);
  memset(dest, 0, sizeof(dest));
  v6 = sub_46660((int)byte_user_name, user_name_length, dest);
  v4 = (*env)->NewByteArray(env, v6);
  (*env)->SetByteArrayRegion(env, v4, 0, v6, dest);
  (*env)->ReleaseByteArrayElements(env, bytes_, byte_user_name, 0);
  return v4;
}
int __cdecl sub_46660(BYTE *byte_user_name, unsigned int user_name_length, char *dest)
{
  ......
   
  canary = __readgsdword(0x14u);                // 开启canary保护
  memset(&key_data[32], 0, 32);
  v3 = -32;
  do
  {
    v4 = lrand48();                             // 产生一个正的长整型随机数
    key_data[v3++ + 64] = v4 + v4 / 255;        // 为 key_data[32-63] 位赋值随机数
  }
  while ( v3 );
  memset(key_data, 0, 32);
  v5 = -32;
  do
  {
    v_eax = lrand48();
    key_data[v5++ + 32] = v_eax + v_eax / 255;  // 为 key_data[0-31] 位赋值随机数
  }
  while ( v5 );
  memset(hash, 0, sizeof(hash));
  hmac_ctx_hash(&key_data[32], key_data, hash); // 参数1:data 参数2:key 参数3:dest
  strcpy((char *)const_key, "123456awxzcdfqwqt2wetbwerw");
  memset(encrypt_data, 0, sizeof(encrypt_data));
  evp_encrypt((int)hash, (int)&hash[32], 12, const_key, 27, (int)byte_user_name, user_name_length, encrypt_data);// 加密用户名
  memset(encrypt_data2, 0, sizeof(encrypt_data2));
  *(__m128i *)hash_temp = _mm_load_si128((const __m128i *)&hash[48]);// 从hash的第48个字节开始加载128bits,也就是加载16个字节
  __memcpy_chk((int)encrypt_data2, (int)encrypt_data, user_name_length + 16, 0x2800);
 
  if ( !(((int)(user_name_length + 16) < 0) ^ __OFADD__(16, user_name_length) | (user_name_length == -16)) )// 用户名长度为16,所以 1 ^ 0 | 0 = 1
                                                // __OFADD__  测试两数相加后是否溢出
  {
    v_esi = 0;
    v8 = user_name_length + 16;                 // v8 = 32
    if ( user_name_length >= 4294967280 )       // //检查用户名长度是否大于4294967280
      goto LABEL_15;
    if ( user_name_length + 15 > 15 )           // 检查用户名长度是否大于 0,此处跳转至LABEL15,感谢手下留情
      goto LABEL_15;
    v_esi = v8 & 0xFFFFFFF0;
    si128 = _mm_load_si128((const __m128i *)(&(&off_1A25DC)[-11535] + 1));
    v10 = _mm_load_si128((const __m128i *)(&off_1A25DC - 92275));
    v11 = _mm_load_si128((const __m128i *)(&(&off_1A25DC)[-11534] + 1));
    v12 = _mm_load_si128((const __m128i *)(&off_1A25DC - 92267));
    v13 = 0;
    v34 = *(__m128i *)hash_temp;
    v31 = *(__m128i *)(&(&off_1A25DC)[-11533] + 1);
    v33 = *(__m128i *)(&off_1A25DC - 92259);
    v32 = _mm_load_si128((const __m128i *)(&(&off_1A25DC)[-11532] + 1));
       
    ......
       
    while ( v_esi != v13 );
    if ( v8 != v_esi )
    {
LABEL_15:
      do
      {
        v28 = (v_esi + 1) ^ hash_temp[v_esi & 0xF];// hash48[0-F] 分别与 1 进行异或,也就是说src每一位如果是
                                                // 奇数:减一
                                                // 偶数:不变
        encrypt_data2[v_esi] = v28 ^ (((unsigned __int8)(encrypt_data[v_esi] + v28) >> 4) | (16
                                                                                           * (encrypt_data[v_esi] + v28)));//
                                                // encrypt_data[i] + v28 看成一个整体结果记为 res
                                                // res转成了无符号类型所以是逻辑右移,左侧用零补齐
                                                // res乘以16等价于逻辑左移4位
                                                // 其实就是把res的高四位和低四位颠倒了一下
                                                // 最后再和v28异或
      }
      while ( v8 != ++v_esi );                  // 循环32次
    }
  }
  memset(s, 0, 960u);
  *(_OWORD *)hash_temp = *(_OWORD *)hash;
  *(_OWORD *)&hash_temp[16] = *(_OWORD *)&hash[16];
  *(_OWORD *)&hash_temp[32] = *(_OWORD *)&hash[32];
  *(_OWORD *)&hash_temp[48] = *(_OWORD *)&hash[48];// hash_temp[0-63] = hash[0-63]
  __memcpy_chk((int)s, (int)encrypt_data2, user_name_length + 16, 960);//这里把密文进行了一次拷贝,但看起来后续没有用到
  *(_OWORD *)v39 = 0LL;
  return encrypt_4(hash_temp, user_name_length + 80, (int)v39, 16, dest);// 第三次加密得到最终的dest
}
int __cdecl sub_46660(BYTE *byte_user_name, unsigned int user_name_length, char *dest)
{
  ......
   
  canary = __readgsdword(0x14u);                // 开启canary保护
  memset(&key_data[32], 0, 32);
  v3 = -32;
  do
  {
    v4 = lrand48();                             // 产生一个正的长整型随机数
    key_data[v3++ + 64] = v4 + v4 / 255;        // 为 key_data[32-63] 位赋值随机数
  }
  while ( v3 );
  memset(key_data, 0, 32);
  v5 = -32;
  do
  {
    v_eax = lrand48();
    key_data[v5++ + 32] = v_eax + v_eax / 255;  // 为 key_data[0-31] 位赋值随机数
  }
  while ( v5 );
  memset(hash, 0, sizeof(hash));
  hmac_ctx_hash(&key_data[32], key_data, hash); // 参数1:data 参数2:key 参数3:dest
  strcpy((char *)const_key, "123456awxzcdfqwqt2wetbwerw");
  memset(encrypt_data, 0, sizeof(encrypt_data));
  evp_encrypt((int)hash, (int)&hash[32], 12, const_key, 27, (int)byte_user_name, user_name_length, encrypt_data);// 加密用户名
  memset(encrypt_data2, 0, sizeof(encrypt_data2));
  *(__m128i *)hash_temp = _mm_load_si128((const __m128i *)&hash[48]);// 从hash的第48个字节开始加载128bits,也就是加载16个字节
  __memcpy_chk((int)encrypt_data2, (int)encrypt_data, user_name_length + 16, 0x2800);
 
  if ( !(((int)(user_name_length + 16) < 0) ^ __OFADD__(16, user_name_length) | (user_name_length == -16)) )// 用户名长度为16,所以 1 ^ 0 | 0 = 1
                                                // __OFADD__  测试两数相加后是否溢出
  {
    v_esi = 0;
    v8 = user_name_length + 16;                 // v8 = 32
    if ( user_name_length >= 4294967280 )       // //检查用户名长度是否大于4294967280
      goto LABEL_15;
    if ( user_name_length + 15 > 15 )           // 检查用户名长度是否大于 0,此处跳转至LABEL15,感谢手下留情
      goto LABEL_15;
    v_esi = v8 & 0xFFFFFFF0;
    si128 = _mm_load_si128((const __m128i *)(&(&off_1A25DC)[-11535] + 1));
    v10 = _mm_load_si128((const __m128i *)(&off_1A25DC - 92275));
    v11 = _mm_load_si128((const __m128i *)(&(&off_1A25DC)[-11534] + 1));
    v12 = _mm_load_si128((const __m128i *)(&off_1A25DC - 92267));
    v13 = 0;
    v34 = *(__m128i *)hash_temp;
    v31 = *(__m128i *)(&(&off_1A25DC)[-11533] + 1);
    v33 = *(__m128i *)(&off_1A25DC - 92259);
    v32 = _mm_load_si128((const __m128i *)(&(&off_1A25DC)[-11532] + 1));
       
    ......
       
    while ( v_esi != v13 );
    if ( v8 != v_esi )
    {
LABEL_15:
      do
      {
        v28 = (v_esi + 1) ^ hash_temp[v_esi & 0xF];// hash48[0-F] 分别与 1 进行异或,也就是说src每一位如果是
                                                // 奇数:减一
                                                // 偶数:不变
        encrypt_data2[v_esi] = v28 ^ (((unsigned __int8)(encrypt_data[v_esi] + v28) >> 4) | (16
                                                                                           * (encrypt_data[v_esi] + v28)));//
                                                // encrypt_data[i] + v28 看成一个整体结果记为 res
                                                // res转成了无符号类型所以是逻辑右移,左侧用零补齐
                                                // res乘以16等价于逻辑左移4位
                                                // 其实就是把res的高四位和低四位颠倒了一下
                                                // 最后再和v28异或
      }
      while ( v8 != ++v_esi );                  // 循环32次
    }
  }
  memset(s, 0, 960u);
  *(_OWORD *)hash_temp = *(_OWORD *)hash;
  *(_OWORD *)&hash_temp[16] = *(_OWORD *)&hash[16];
  *(_OWORD *)&hash_temp[32] = *(_OWORD *)&hash[32];
  *(_OWORD *)&hash_temp[48] = *(_OWORD *)&hash[48];// hash_temp[0-63] = hash[0-63]
  __memcpy_chk((int)s, (int)encrypt_data2, user_name_length + 16, 960);//这里把密文进行了一次拷贝,但看起来后续没有用到
  *(_OWORD *)v39 = 0LL;
  return encrypt_4(hash_temp, user_name_length + 80, (int)v39, 16, dest);// 第三次加密得到最终的dest
}
unsigned int __cdecl hmac_ctx_hash(BYTE *data, BYTE *key, BYTE *res)
{
  int md; // esi
  __int64 v4; // xmm1_8
  __int64 v5; // xmm0_8
  int len; // [esp+8h] [ebp-154h] BYREF
  char v8; // [esp+Eh] [ebp-14Eh] BYREF
  char v9; // [esp+Fh] [ebp-14Dh] BYREF
  BYTE hash3[32]; // [esp+10h] [ebp-14Ch] BYREF
  BYTE hash2[32]; // [esp+30h] [ebp-12Ch] BYREF
  int ctx[52]; // [esp+50h] [ebp-10Ch] BYREF
  BYTE hash1[40]; // [esp+120h] [ebp-3Ch] BYREF
  unsigned int canary; // [esp+148h] [ebp-14h]
 
  canary = __readgsdword(0x14u);
  memset(hash1, 0, 32);
  HMAC_CTX_init((int)ctx);                      // 初始化一个 HMAC_CTX
  md = EVP_sha256();                            // 返回 sha256 的 EVP_MD
  HMAC_Init_ex((int)ctx, (int)key, 32, md, 0);  // 初始化HAMC_CTX上下文结构,key为秘钥,len为秘钥长度,md为计算hash的函数集合
  HMAC_Update((int)ctx, (int)data, 0);          // 向HMAC上下文输入字节流,加密并输出,传入长度为0
                                                // 经过 demo 验证,该条指令不会对上下文产生影响
  len = 0;
  HMAC_Final((int)ctx, (int)hash1, (int)&len);  // 生成最终的HMAC串,成功时len更新为HMAC的长度。
  memset(hash2, 0, sizeof(hash2));
  v9 = 1;
  HMAC_CTX_init((int)ctx);
  HMAC_Init_ex((int)ctx, (int)hash1, 32, md, 0);// 把 hash1 作为 key
  HMAC_Update((int)ctx, (int)data, 0);
  HMAC_Update((int)ctx, (int)&v9, 1);           // 输入字节流 1,长度为1
  len = 0;
  HMAC_Final((int)ctx, (int)hash2, (int)&len);  // 得到第二次的 hash
  memset(hash3, 0, sizeof(hash3));
  v8 = 2;
  HMAC_CTX_init((int)ctx);
  HMAC_Init_ex((int)ctx, (int)hash1, 32, md, 0);// 把第一次的 hash 作为 key 再次加密
  HMAC_Update((int)ctx, (int)hash2, 32);        // 输入字节流 hash2,长度为32
  HMAC_Update((int)ctx, (int)&v8, 1);           // 输入字节流 2,长度为1
  len = 0;
  HMAC_Final((int)ctx, (int)hash3, (int)&len);  // 得到第三次的 hash
  v4 = *(_QWORD *)&hash2[8];                    // v4 = hash2[8-15]
  *(_QWORD *)res = *(_QWORD *)hash2;            // res[0-7] = hash2[0-7]
  *((_QWORD *)res + 1) = v4;                    // res[8-15] = hash2[8-15]
  *((_QWORD *)res + 2) = *(_QWORD *)&hash2[16]; // res[16-23] = hash2[16-23]
  *((_QWORD *)res + 3) = *(_QWORD *)&hash2[24]; // res[24-31] = hash2[24-31]
  *((_QWORD *)res + 7) = *(_QWORD *)&hash3[24]; // res[56-63] = hash3[24-31]
  *((_QWORD *)res + 6) = *(_QWORD *)&hash3[16]; // res[48-55] = hash3[16-31]
  v5 = *(_QWORD *)hash3;
  *((_QWORD *)res + 5) = *(_QWORD *)&hash3[8];  // res[40-47] = hash3[8-15]
  *((_QWORD *)res + 4) = v5;                    // res[32-39] = hash3[0-7]
  return __readgsdword(0x14u);
}
unsigned int __cdecl hmac_ctx_hash(BYTE *data, BYTE *key, BYTE *res)
{
  int md; // esi
  __int64 v4; // xmm1_8
  __int64 v5; // xmm0_8
  int len; // [esp+8h] [ebp-154h] BYREF
  char v8; // [esp+Eh] [ebp-14Eh] BYREF
  char v9; // [esp+Fh] [ebp-14Dh] BYREF
  BYTE hash3[32]; // [esp+10h] [ebp-14Ch] BYREF
  BYTE hash2[32]; // [esp+30h] [ebp-12Ch] BYREF
  int ctx[52]; // [esp+50h] [ebp-10Ch] BYREF
  BYTE hash1[40]; // [esp+120h] [ebp-3Ch] BYREF
  unsigned int canary; // [esp+148h] [ebp-14h]
 
  canary = __readgsdword(0x14u);
  memset(hash1, 0, 32);
  HMAC_CTX_init((int)ctx);                      // 初始化一个 HMAC_CTX
  md = EVP_sha256();                            // 返回 sha256 的 EVP_MD
  HMAC_Init_ex((int)ctx, (int)key, 32, md, 0);  // 初始化HAMC_CTX上下文结构,key为秘钥,len为秘钥长度,md为计算hash的函数集合
  HMAC_Update((int)ctx, (int)data, 0);          // 向HMAC上下文输入字节流,加密并输出,传入长度为0
                                                // 经过 demo 验证,该条指令不会对上下文产生影响
  len = 0;
  HMAC_Final((int)ctx, (int)hash1, (int)&len);  // 生成最终的HMAC串,成功时len更新为HMAC的长度。
  memset(hash2, 0, sizeof(hash2));
  v9 = 1;
  HMAC_CTX_init((int)ctx);
  HMAC_Init_ex((int)ctx, (int)hash1, 32, md, 0);// 把 hash1 作为 key
  HMAC_Update((int)ctx, (int)data, 0);
  HMAC_Update((int)ctx, (int)&v9, 1);           // 输入字节流 1,长度为1
  len = 0;
  HMAC_Final((int)ctx, (int)hash2, (int)&len);  // 得到第二次的 hash
  memset(hash3, 0, sizeof(hash3));
  v8 = 2;
  HMAC_CTX_init((int)ctx);
  HMAC_Init_ex((int)ctx, (int)hash1, 32, md, 0);// 把第一次的 hash 作为 key 再次加密
  HMAC_Update((int)ctx, (int)hash2, 32);        // 输入字节流 hash2,长度为32
  HMAC_Update((int)ctx, (int)&v8, 1);           // 输入字节流 2,长度为1
  len = 0;
  HMAC_Final((int)ctx, (int)hash3, (int)&len);  // 得到第三次的 hash
  v4 = *(_QWORD *)&hash2[8];                    // v4 = hash2[8-15]
  *(_QWORD *)res = *(_QWORD *)hash2;            // res[0-7] = hash2[0-7]
  *((_QWORD *)res + 1) = v4;                    // res[8-15] = hash2[8-15]
  *((_QWORD *)res + 2) = *(_QWORD *)&hash2[16]; // res[16-23] = hash2[16-23]
  *((_QWORD *)res + 3) = *(_QWORD *)&hash2[24]; // res[24-31] = hash2[24-31]
  *((_QWORD *)res + 7) = *(_QWORD *)&hash3[24]; // res[56-63] = hash3[24-31]
  *((_QWORD *)res + 6) = *(_QWORD *)&hash3[16]; // res[48-55] = hash3[16-31]
  v5 = *(_QWORD *)hash3;
  *((_QWORD *)res + 5) = *(_QWORD *)&hash3[8];  // res[40-47] = hash3[8-15]
  *((_QWORD *)res + 4) = v5;                    // res[32-39] = hash3[0-7]
  return __readgsdword(0x14u);
}
#include "openssl/hmac.h"
#include "openssl/evp.h"
#include "openssl/aes.h"
#pragma comment(lib, "libcrypto.lib")
 
int main()
{
    unsigned char key[32] = { 0 };
    unsigned char data[32] = { 0 };
    unsigned char hash[32] = { 0 };
    unsigned int len = 0;
    for (int i = 0; i < 32; i++)
    {
        key[i] = i;
        data[i] = i*2;
    }
 
    HMAC_CTX *ctx = HMAC_CTX_new();
    const EVP_MD *md = EVP_sha256();
    HMAC_Init_ex(ctx, key, 32, md, 0);
    //HMAC_Update(ctx, data, 0); // 该条语句注释未产生任何影响
    HMAC_Final(ctx, hash, &len);
}
#include "openssl/hmac.h"
#include "openssl/evp.h"
#include "openssl/aes.h"
#pragma comment(lib, "libcrypto.lib")
 
int main()
{
    unsigned char key[32] = { 0 };
    unsigned char data[32] = { 0 };
    unsigned char hash[32] = { 0 };
    unsigned int len = 0;
    for (int i = 0; i < 32; i++)
    {
        key[i] = i;
        data[i] = i*2;
    }
 
    HMAC_CTX *ctx = HMAC_CTX_new();
    const EVP_MD *md = EVP_sha256();
    HMAC_Init_ex(ctx, key, 32, md, 0);
    //HMAC_Update(ctx, data, 0); // 该条语句注释未产生任何影响
    HMAC_Final(ctx, hash, &len);
}
int __cdecl evp_encrypt(
        int hash,
        int iv,
        int iv_len,
        BYTE *const_key,
        int const_key_length,
        int byte_user_name,
        int user_name_length,
        char *res_encrypt_data)
{
  int ctx; // esi
  int cipher; // eax
  size_t v10; // eax
  __int64 v11; // xmm1_8
  int v13; // [esp+0h] [ebp-41Ch] BYREF
  size_t encrypt_data_len; // [esp+4h] [ebp-418h] BYREF
  BYTE encrypt_data[1024]; // [esp+8h] [ebp-414h] BYREF
  unsigned int v16; // [esp+408h] [ebp-14h]
 
  v16 = __readgsdword(0x14u);
  ctx = EVP_CIPHER_CTX_new();                   // 创建加密上下文
  cipher = EVP_aes_256_gcm();                   // 选择一种加密算法
  EVP_EncryptInit_ex(ctx, cipher, 0, 0, 0);     // 初始化密码上下文ctx
  EVP_CIPHER_CTX_ctrl(ctx, 9, iv_len, 0);       // # define EVP_CTRL_AEAD_SET_IVLEN 0x9
                                                // 设置向量IV的长度
  EVP_EncryptInit_ex(ctx, 0, 0, hash, iv);      // 使用hash作为key初始化ctx
  EVP_EncryptUpdate(ctx, 0, (int)&encrypt_data_len, (int)const_key, const_key_length);// 加密 const_key,让人困惑的是输出地址为0,
                                                // 无法确定是否会对上下文产生影响,经过 demo 测试最终确定加密结果被丢弃
  EVP_EncryptUpdate(ctx, (int)encrypt_data, (int)&encrypt_data_len, byte_user_name, user_name_length);// 加密用户名
  memcpy(res_encrypt_data, encrypt_data, encrypt_data_len);// dest[0-15] = encrypt_data[0-15]
                                                // 将第二次EncryptUpdate的结果拷贝到dest中,而且经过动态调试得知,encrypt_data_len=16
  EVP_EncryptFinal_ex(ctx, (int)encrypt_data, (int)&v13);// 块对齐
  EVP_CIPHER_CTX_ctrl(ctx, 16, 16, (int)encrypt_data);// # define EVP_CTRL_AEAD_GET_TAG 0x10   获取标签
  v10 = encrypt_data_len;
  v11 = *(_QWORD *)&encrypt_data[8];            // v11 = encrypt_data[8-15]
  *(_QWORD *)&res_encrypt_data[encrypt_data_len] = *(_QWORD *)encrypt_data;// dest[16-23] = encrypt_data[0-7]
  *(_QWORD *)&res_encrypt_data[v10 + 8] = v11;  // dest[24-31] = v11 = encrypt_data[8-15]
  EVP_CIPHER_CTX_free(ctx);                     // 释放上下文
  return encrypt_data_len + 16;                 // 返回加密结果的长度+16
}
int __cdecl evp_encrypt(
        int hash,
        int iv,
        int iv_len,
        BYTE *const_key,
        int const_key_length,
        int byte_user_name,
        int user_name_length,
        char *res_encrypt_data)
{
  int ctx; // esi
  int cipher; // eax
  size_t v10; // eax
  __int64 v11; // xmm1_8
  int v13; // [esp+0h] [ebp-41Ch] BYREF
  size_t encrypt_data_len; // [esp+4h] [ebp-418h] BYREF
  BYTE encrypt_data[1024]; // [esp+8h] [ebp-414h] BYREF
  unsigned int v16; // [esp+408h] [ebp-14h]
 
  v16 = __readgsdword(0x14u);
  ctx = EVP_CIPHER_CTX_new();                   // 创建加密上下文
  cipher = EVP_aes_256_gcm();                   // 选择一种加密算法
  EVP_EncryptInit_ex(ctx, cipher, 0, 0, 0);     // 初始化密码上下文ctx
  EVP_CIPHER_CTX_ctrl(ctx, 9, iv_len, 0);       // # define EVP_CTRL_AEAD_SET_IVLEN 0x9
                                                // 设置向量IV的长度
  EVP_EncryptInit_ex(ctx, 0, 0, hash, iv);      // 使用hash作为key初始化ctx
  EVP_EncryptUpdate(ctx, 0, (int)&encrypt_data_len, (int)const_key, const_key_length);// 加密 const_key,让人困惑的是输出地址为0,
                                                // 无法确定是否会对上下文产生影响,经过 demo 测试最终确定加密结果被丢弃
  EVP_EncryptUpdate(ctx, (int)encrypt_data, (int)&encrypt_data_len, byte_user_name, user_name_length);// 加密用户名
  memcpy(res_encrypt_data, encrypt_data, encrypt_data_len);// dest[0-15] = encrypt_data[0-15]
                                                // 将第二次EncryptUpdate的结果拷贝到dest中,而且经过动态调试得知,encrypt_data_len=16
  EVP_EncryptFinal_ex(ctx, (int)encrypt_data, (int)&v13);// 块对齐
  EVP_CIPHER_CTX_ctrl(ctx, 16, 16, (int)encrypt_data);// # define EVP_CTRL_AEAD_GET_TAG 0x10   获取标签
  v10 = encrypt_data_len;
  v11 = *(_QWORD *)&encrypt_data[8];            // v11 = encrypt_data[8-15]
  *(_QWORD *)&res_encrypt_data[encrypt_data_len] = *(_QWORD *)encrypt_data;// dest[16-23] = encrypt_data[0-7]
  *(_QWORD *)&res_encrypt_data[v10 + 8] = v11;  // dest[24-31] = v11 = encrypt_data[8-15]
  EVP_CIPHER_CTX_free(ctx);                     // 释放上下文
  return encrypt_data_len + 16;                 // 返回加密结果的长度+16
}
#include "openssl/hmac.h"
#include "openssl/evp.h"
#include "openssl/aes.h"
#pragma comment(lib, "libcrypto.lib")
 
int main()
{
    unsigned char key[32] = { 0 };
    unsigned char iv[32] = { 0 };
    unsigned char encrypt_data[32] = { 0 };
    unsigned char user_name[16] = { 'a','b','c','d', 'a','b','c','d','a','b','c','d', 'a','b','c','d' };
    const unsigned char *const_key = (const unsigned char *)"123456awxzcdfqwqt2wetbwerw";
    int len = 0;
    for (int i = 0; i < 32; i++)
    {
        key[i] = i;
        iv[i] = i;
    }
 
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    const EVP_CIPHER *cipher = EVP_aes_256_gcm();
    EVP_EncryptInit_ex(ctx, cipher, 0, key, iv);
    EVP_CIPHER_CTX_ctrl(ctx, 9, 12, 0);
    //EVP_EncryptUpdate(ctx, 0, &len, const_key, 27);// 该条语句注释未产生任何影响
 
    len = 0;
    EVP_EncryptUpdate(ctx, encrypt_data, &len, user_name, 16);
    len = 0;
    EVP_EncryptFinal_ex(ctx, encrypt_data, &len);
}
#include "openssl/hmac.h"
#include "openssl/evp.h"
#include "openssl/aes.h"
#pragma comment(lib, "libcrypto.lib")
 
int main()
{
    unsigned char key[32] = { 0 };
    unsigned char iv[32] = { 0 };
    unsigned char encrypt_data[32] = { 0 };
    unsigned char user_name[16] = { 'a','b','c','d', 'a','b','c','d','a','b','c','d', 'a','b','c','d' };
    const unsigned char *const_key = (const unsigned char *)"123456awxzcdfqwqt2wetbwerw";
    int len = 0;
    for (int i = 0; i < 32; i++)
    {
        key[i] = i;
        iv[i] = i;
    }
 
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    const EVP_CIPHER *cipher = EVP_aes_256_gcm();
    EVP_EncryptInit_ex(ctx, cipher, 0, key, iv);
    EVP_CIPHER_CTX_ctrl(ctx, 9, 12, 0);
    //EVP_EncryptUpdate(ctx, 0, &len, const_key, 27);// 该条语句注释未产生任何影响
 
    len = 0;
    EVP_EncryptUpdate(ctx, encrypt_data, &len, user_name, 16);
    len = 0;
    EVP_EncryptFinal_ex(ctx, encrypt_data, &len);
}
*(__m128i *)hash_temp = _mm_load_si128((const __m128i *)&hash[48]);// 从hash的第48个字节开始加载128bits,也就是加载16个字节
 
......
 
do
{
    v28 = (v_esi + 1) ^ hash_temp[v_esi & 0xF];// hash48[0-F] 分别与 1 进行异或,也就是说src每一位如果是
                                                // 奇数:减一
                                                // 偶数:不变
    encrypt_data2[v_esi] =
        v28 ^ (((unsigned __int8)(encrypt_data[v_esi] + v28) >> 4) | (16* (encrypt_data[v_esi] + v28)));
                                                // encrypt_data[i] + v28 看成一个整体结果记为 res
                                                // res转成了无符号类型所以是逻辑右移,左侧用零补齐
                                                // res乘以16等价于逻辑左移4位
                                                // 其实就是把res的高四位和低四位颠倒了一下
                                                // 最后再和v28异或
}while ( v8 != ++v_esi );                       // 循环32次
*(__m128i *)hash_temp = _mm_load_si128((const __m128i *)&hash[48]);// 从hash的第48个字节开始加载128bits,也就是加载16个字节
 
......
 
do
{
    v28 = (v_esi + 1) ^ hash_temp[v_esi & 0xF];// hash48[0-F] 分别与 1 进行异或,也就是说src每一位如果是
                                                // 奇数:减一
                                                // 偶数:不变
    encrypt_data2[v_esi] =
        v28 ^ (((unsigned __int8)(encrypt_data[v_esi] + v28) >> 4) | (16* (encrypt_data[v_esi] + v28)));
                                                // encrypt_data[i] + v28 看成一个整体结果记为 res
                                                // res转成了无符号类型所以是逻辑右移,左侧用零补齐
                                                // res乘以16等价于逻辑左移4位
                                                // 其实就是把res的高四位和低四位颠倒了一下
                                                // 最后再和v28异或
}while ( v8 != ++v_esi );                       // 循环32次
_BYTE v21[256]; // [esp+10h] [ebp-21Ch] BYREF
_BYTE v22[264]; // [esp+110h] [ebp-11Ch] BYREF
unsigned int v23; // [esp+218h] [ebp-14h]
 
v23 = __readgsdword(0x14u);
memcpy(dest, hash_temp, user_name_length_add_80);//注意此时dest被赋值为hash_temp.最后一步的化简会用到
memset(v22, 0, 256);
memset(v21, 0, sizeof(v21));
for ( i = 0; i != 256; ++i )
{
  v21[i] = i;                                 // v21 依次赋值 1 2 3 4 5 6 ...
  v22[i] = v39[i % c_16];                     // v22 依次填充 0,因为v39全是0
}
v_dest = dest;
_BYTE v21[256]; // [esp+10h] [ebp-21Ch] BYREF
_BYTE v22[264]; // [esp+110h] [ebp-11Ch] BYREF
unsigned int v23; // [esp+218h] [ebp-14h]
 
v23 = __readgsdword(0x14u);
memcpy(dest, hash_temp, user_name_length_add_80);//注意此时dest被赋值为hash_temp.最后一步的化简会用到
memset(v22, 0, 256);
memset(v21, 0, sizeof(v21));
for ( i = 0; i != 256; ++i )
{
  v21[i] = i;                                 // v21 依次赋值 1 2 3 4 5 6 ...
  v22[i] = v39[i % c_16];                     // v22 依次填充 0,因为v39全是0
}
v_dest = dest;
v6 = dest;
v7 = 0;
v8 = -256;
do
{
  v9 = (unsigned __int8)v22[v8];
  v10 = v9 + v7 + (unsigned __int8)v22[v8 + 256];
  v10 %= 256;
  v11 = v21[v10];
  v21[v10] = v9;
  v22[v8++] = v11;
  v7 = v10;
}
while ( v8 );
v6 = dest;
v7 = 0;
v8 = -256;
do
{
  v9 = (unsigned __int8)v22[v8];
  v10 = v9 + v7 + (unsigned __int8)v22[v8 + 256];
  v10 %= 256;
  v11 = v21[v10];
  v21[v10] = v9;
  v22[v8++] = v11;
  v7 = v10;
}
while ( v8 );
unsigned char v21[256] = {0};
 
for (int i = 0; i < 256; i++)
    v21[i] = i;
 
int v7 = 0;
int v8 = 0;
int v9 = 0;
int v10 = 0;
char v11 = 0;
 
for (int i = 0; i < 256; i++)
{
    v9 = v21[i];
    v10 = v9 + v7;
    v10 %= 256;
    v11 = v21[v10];
    v21[v10] = v9;
    v21[i] = v11;
    v7 = v10;
}
unsigned char v21[256] = {0};
 
for (int i = 0; i < 256; i++)
    v21[i] = i;
 
int v7 = 0;
int v8 = 0;
int v9 = 0;
int v10 = 0;
char v11 = 0;
 
for (int i = 0; i < 256; i++)
{
    v9 = v21[i];
    v10 = v9 + v7;
    v10 %= 256;
    v11 = v21[v10];
    v21[v10] = v9;
    v21[i] = v11;
    v7 = v10;
}
int v_tmp = 0;
for (int i = 0; i < 256; i++)
{
    v10 += v21[i];
    v10 %= 256;
    v_tmp = v21[v10];
    v21[v10] = v21[i];
    v21[i] = v_tmp;
}
int v_tmp = 0;
for (int i = 0; i < 256; i++)
{
    v10 += v21[i];
    v10 %= 256;
    v_tmp = v21[v10];
    v21[v10] = v21[i];
    v21[i] = v_tmp;
}
v12 = 0;
v13 = 0;
v14 = user_name_length_add_80;
do
{
  v15 = v12 + 1 - ((v12 + ((unsigned int)((v12 + 1) >> 31) >> 24) + 1) & 0xFFFFFF00);
  v16 = (unsigned __int8)v21[v15];
  v13 = (v16 + v13) % 256;
  v17 = v14;
  v18 = v21[v13];
  v21[v13] = v16;
  v21[v15] = v18;
  *v_dest++ ^= v21[(unsigned __int8)(v21[v13] + v18)];
  v14 = v17 - 1;
  v19 = v17 == 1;
  v12 = v15;
}
v12 = 0;
v13 = 0;
v14 = user_name_length_add_80;
do
{
  v15 = v12 + 1 - ((v12 + ((unsigned int)((v12 + 1) >> 31) >> 24) + 1) & 0xFFFFFF00);
  v16 = (unsigned __int8)v21[v15];
  v13 = (v16 + v13) % 256;
  v17 = v14;
  v18 = v21[v13];
  v21[v13] = v16;
  v21[v15] = v18;
  *v_dest++ ^= v21[(unsigned __int8)(v21[v13] + v18)];
  v14 = v17 - 1;
  v19 = v17 == 1;
  v12 = v15;
}
char tmp = 0;
do
{
    //取出 v13 和 v15,v13 += v21[v15],为避免越界v13作为下标越界,模256取余数
    v15++;
    v13 = (v21[v15] + v13) % 256;
 
    // 交换 v21[13] 和 v21[15]
    tmp = v21[v13];
    v21[v13] = v21[v15];
    v21[v15] = tmp;
 
    // v21[13]+v21[15] 作为下标id,为避免越界,类型强转为unsigned char使其不超过255
    // *hash_temp ^= v21[id]; hash_temp++;
    *hash_temp++ ^= v21[(unsigned char)(v21[v13] + v21[v15])];
 
    //v14是 user_name_length_add_80 = 0x60
    v14--;
} while (v14!=0);
char tmp = 0;
do
{
    //取出 v13 和 v15,v13 += v21[v15],为避免越界v13作为下标越界,模256取余数
    v15++;
    v13 = (v21[v15] + v13) % 256;
 
    // 交换 v21[13] 和 v21[15]
    tmp = v21[v13];
    v21[v13] = v21[v15];
    v21[v15] = tmp;
 
    // v21[13]+v21[15] 作为下标id,为避免越界,类型强转为unsigned char使其不超过255
    // *hash_temp ^= v21[id]; hash_temp++;
    *hash_temp++ ^= v21[(unsigned char)(v21[v13] + v21[v15])];
 
    //v14是 user_name_length_add_80 = 0x60
    v14--;
} while (v14!=0);
v21 = encrypt_4(pass_word, pass_word_len, (BYTE *)&v32, 16, v33);//
                                            // encrypt_4 是已经分析过的,但在解密时重新调用了一遍,这时再分析一遍该函数可以发现
                                            // 核心算法是和一串固定的字节数组异或,这里第二次异或可以还原之前的值,作为解密
v23 = v19;
*(_QWORD *)v34 = *(_QWORD *)v33;            // 这里可以看到将 encrypt_4 的结果 v33 赋值给 v34,显然这就是对应加密时的逆运算
*(_QWORD *)&v34[8] = *(_QWORD *)&v33[8];
*(_QWORD *)&v34[16] = *(_QWORD *)&v33[16];
*(_QWORD *)&v34[24] = *(_QWORD *)&v33[24];
*(_QWORD *)&v34[32] = *(_QWORD *)&v33[32];
*(_QWORD *)&v34[40] = *(_QWORD *)&v33[40];
v21 = encrypt_4(pass_word, pass_word_len, (BYTE *)&v32, 16, v33);//
                                            // encrypt_4 是已经分析过的,但在解密时重新调用了一遍,这时再分析一遍该函数可以发现
                                            // 核心算法是和一串固定的字节数组异或,这里第二次异或可以还原之前的值,作为解密
v23 = v19;
*(_QWORD *)v34 = *(_QWORD *)v33;            // 这里可以看到将 encrypt_4 的结果 v33 赋值给 v34,显然这就是对应加密时的逆运算
*(_QWORD *)&v34[8] = *(_QWORD *)&v33[8];
*(_QWORD *)&v34[16] = *(_QWORD *)&v33[16];
*(_QWORD *)&v34[24] = *(_QWORD *)&v33[24];
*(_QWORD *)&v34[32] = *(_QWORD *)&v33[32];
*(_QWORD *)&v34[40] = *(_QWORD *)&v33[40];
do
{
  v15 = v7 ^ v24[((_BYTE)v7 - 1) & 0xF];// 这里也是,重新审视一下这个循环算法,就是异或,这里第二次调用作为解密算法
  v5[v7 - 1] = __ROL1__(v15 ^ v33[v7 + 63], 4) - v15;
  --v7;
}
while ( v7 > 0 );
do
{
  v15 = v7 ^ v24[((_BYTE)v7 - 1) & 0xF];// 这里也是,重新审视一下这个循环算法,就是异或,这里第二次调用作为解密算法
  v5[v7 - 1] = __ROL1__(v15 ^ v33[v7 + 63], 4) - v15;
  --v7;
}
while ( v7 > 0 );
v21 = v6 - 80;
v16 = Decrypt((int)v34, (int)&v34[32], 12, (int)v31, 27, (int)&v5[v20 - 16], 16, (int)v5, v6 - 80, v24);
                                                                            // 解密数据,解密后的数据放在v24中
v4 = 0;
if ( v16 && v21 == user_name_len && !memcmp(v24, user_name, user_name_len) )// 校验用户名和解密后的数据,一致返回1
  return 1;
v21 = v6 - 80;
v16 = Decrypt((int)v34, (int)&v34[32], 12, (int)v31, 27, (int)&v5[v20 - 16], 16, (int)v5, v6 - 80, v24);
                                                                            // 解密数据,解密后的数据放在v24中
v4 = 0;
if ( v16 && v21 == user_name_len && !memcmp(v24, user_name, user_name_len) )// 校验用户名和解密后的数据,一致返回1
  return 1;
int __cdecl Decrypt(int key, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, void *dest)
{
  int ctx; // esi
  int v11; // eax
  int v12; // edi
  size_t v14; // [esp+4h] [ebp-418h] BYREF
  char src[1024]; // [esp+8h] [ebp-414h] BYREF
  unsigned int v16; // [esp+408h] [ebp-14h]
 
  v16 = __readgsdword(0x14u);
  ctx = EVP_CIPHER_CTX_new();
  v11 = EVP_aes_256_gcm();
  EVP_DecryptInit_ex(ctx, v11, 0, 0, 0);        // 大致浏览一下参数与和之前的基本一致,只是调用的是解密函数
  EVP_CIPHER_CTX_ctrl(ctx, 9, a3, 0);
  EVP_DecryptInit_ex(ctx, 0, 0, key, a2);
  if ( a5 )
    EVP_DecryptUpdate(ctx, 0, &v14, a4, a5);
  EVP_DecryptUpdate(ctx, src, &v14, a8, a9);
  memcpy(dest, src, v14);                       // 这里也是一样,解密后的数据放到最后一个参数地址中
  EVP_CIPHER_CTX_ctrl(ctx, 17, a7, a6);
  v12 = EVP_DecryptFinal_ex(ctx, src, &v14);
  EVP_CIPHER_CTX_free(ctx);
  return v12;
}
int __cdecl Decrypt(int key, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, void *dest)
{
  int ctx; // esi
  int v11; // eax
  int v12; // edi
  size_t v14; // [esp+4h] [ebp-418h] BYREF
  char src[1024]; // [esp+8h] [ebp-414h] BYREF
  unsigned int v16; // [esp+408h] [ebp-14h]
 
  v16 = __readgsdword(0x14u);
  ctx = EVP_CIPHER_CTX_new();
  v11 = EVP_aes_256_gcm();
  EVP_DecryptInit_ex(ctx, v11, 0, 0, 0);        // 大致浏览一下参数与和之前的基本一致,只是调用的是解密函数
  EVP_CIPHER_CTX_ctrl(ctx, 9, a3, 0);
  EVP_DecryptInit_ex(ctx, 0, 0, key, a2);
  if ( a5 )
    EVP_DecryptUpdate(ctx, 0, &v14, a4, a5);
  EVP_DecryptUpdate(ctx, src, &v14, a8, a9);
  memcpy(dest, src, v14);                       // 这里也是一样,解密后的数据放到最后一个参数地址中
  EVP_CIPHER_CTX_ctrl(ctx, 17, a7, a6);
  v12 = EVP_DecryptFinal_ex(ctx, src, &v14);
  EVP_CIPHER_CTX_free(ctx);
  return v12;
}
BYTE *__cdecl set_buff1(BYTE *buff1)
{
  *(_DWORD *)buff1 = &off_19CA9C;               // buff1_60[0-3] = 19ca9c + image_base
  *((_DWORD *)buff1 + 5) = 0;                   // buff1_60[16-27] = 0
  *((_DWORD *)buff1 + 4) = 0;
  *((_DWORD *)buff1 + 6) = 0;
  *((_DWORD *)buff1 + 12) = 0;                  // buff1_60[44-59] = 0
  *((_DWORD *)buff1 + 11) = 0;
  *((_DWORD *)buff1 + 14) = 0;
  *((_DWORD *)buff1 + 13) = 0;
  v1 = lrand48();                               // buff1_60[28-43] = rand()
  buff1[28] = v1 + v1 / 255;
  v2 = lrand48();
    ......
  buff1[42] = v15 + v15 / 255;
  v16 = lrand48();
  buff1[43] = v16 + v16 / -16777216;
  return sub_48840(buff1 + 16, "3390fd362dfdda0030d5737632d3d213", 32u);//
                                                // buff1[0-4] 19ca9c + image_base
                                                // buff1[16-19] 存放着申请堆空间大小+1,也就是49
                                                // buff1[20-23] 存放着字符串"3390fd362dfdda0030d5737632d3d213"的长度,也就是32
                                                // buff1[24-27] 存放着字符串"3390fd362dfdda0030d5737632d3d213"的指针
                                                // buff1[28-43] 存放着随机数
}
BYTE *__cdecl set_buff1(BYTE *buff1)
{
  *(_DWORD *)buff1 = &off_19CA9C;               // buff1_60[0-3] = 19ca9c + image_base
  *((_DWORD *)buff1 + 5) = 0;                   // buff1_60[16-27] = 0
  *((_DWORD *)buff1 + 4) = 0;
  *((_DWORD *)buff1 + 6) = 0;
  *((_DWORD *)buff1 + 12) = 0;                  // buff1_60[44-59] = 0
  *((_DWORD *)buff1 + 11) = 0;
  *((_DWORD *)buff1 + 14) = 0;
  *((_DWORD *)buff1 + 13) = 0;
  v1 = lrand48();                               // buff1_60[28-43] = rand()
  buff1[28] = v1 + v1 / 255;
  v2 = lrand48();
    ......
  buff1[42] = v15 + v15 / 255;
  v16 = lrand48();
  buff1[43] = v16 + v16 / -16777216;
  return sub_48840(buff1 + 16, "3390fd362dfdda0030d5737632d3d213", 32u);//
                                                // buff1[0-4] 19ca9c + image_base
                                                // buff1[16-19] 存放着申请堆空间大小+1,也就是49
                                                // buff1[20-23] 存放着字符串"3390fd362dfdda0030d5737632d3d213"的长度,也就是32
                                                // buff1[24-27] 存放着字符串"3390fd362dfdda0030d5737632d3d213"的指针
                                                // buff1[28-43] 存放着随机数
}
BYTE *__cdecl sub_48840(BYTE *buff1_16, void *src, size_t len_32)
{
  v3 = *buff1_16;
  v4 = 10;
  if ( (v3 & 1) != 0 )                          // 0 & 1 != 0 判断失败
    v4 = (*(_DWORD *)buff1_16 & 0xFFFFFFFE) - 1;
  if ( v4 >= len_32 )                           // 10 >= 32 判断失败
  {
    ......
  }
  else
  {
    if ( (v3 & 1) != 0 )                        // 判断失败
      v5 = *((_DWORD *)buff1_16 + 1);
    else
      v5 = v3 >> 1;                             // v5 = 0
    sub_488E0(buff1_16, v4, len_32 - v4, v5, 0, v5, len_32, src);//
                                                // sub_488e0(buff1_16,10,22,0,0,0,32,"3390fd362dfdda0030d5737632d3d213")
                                                // buff1_16[0-3] 存放着申请堆空间大小+1,也就是49
                                                // buff1_16[4-7] 存放着字符串"3390fd362dfdda0030d5737632d3d213"的长度,也就是32
                                                // buff1_16[8-11] 存放着字符串"3390fd362dfdda0030d5737632d3d213"的指针
  }
  return buff1_16;
}
BYTE *__cdecl sub_48840(BYTE *buff1_16, void *src, size_t len_32)
{
  v3 = *buff1_16;
  v4 = 10;
  if ( (v3 & 1) != 0 )                          // 0 & 1 != 0 判断失败
    v4 = (*(_DWORD *)buff1_16 & 0xFFFFFFFE) - 1;
  if ( v4 >= len_32 )                           // 10 >= 32 判断失败
  {
    ......
  }
  else
  {
    if ( (v3 & 1) != 0 )                        // 判断失败
      v5 = *((_DWORD *)buff1_16 + 1);
    else
      v5 = v3 >> 1;                             // v5 = 0
    sub_488E0(buff1_16, v4, len_32 - v4, v5, 0, v5, len_32, src);//
                                                // sub_488e0(buff1_16,10,22,0,0,0,32,"3390fd362dfdda0030d5737632d3d213")
                                                // buff1_16[0-3] 存放着申请堆空间大小+1,也就是49
                                                // buff1_16[4-7] 存放着字符串"3390fd362dfdda0030d5737632d3d213"的长度,也就是32
                                                // buff1_16[8-11] 存放着字符串"3390fd362dfdda0030d5737632d3d213"的指针
  }
  return buff1_16;
}
// sub_488e0(buff1_60_16,10,22,0,0,0,32,"3390fd362dfdda0030d5737632d3d213")
BYTE *__cdecl sub_488E0(BYTE *buff1_60_16,unsigned int a2,unsigned int a3,int a4,size_t n,int a6,size_t a7,void *src)
{
  if ( 0xFFFFFFEE - a2 < a3 )                   // 这里注意a2的类型是无符号类型,-18会转换成一个非常大的正数,所以判断失败
    sub_48A20();
  if ( (*buff1_16 & 1) != 0 )                   // buff1_16 == 0,判断失败
    v8 = (BYTE *)*((_DWORD *)buff1_16 + 2);
  else
    v8 = buff1_16 + 1;                          // v8 = buff1_17
  v18 = v8;                                     // v18 = buff1_17
  v9 = -17;                                     // v9 = -17
  if ( a2 <= 0x7FFFFFE6 )                       // 10 <= 0x7ffffff6,判断成功
  {
    v10 = a2 + a3;                              // v10 = 32; a2 = 10; a3 = 22
    if ( a2 + a3 < 2 * a2 )                     // 10 + 22 < 10 * 2 = false, 判断失败
      v10 = 2 * a2;
    v9 = 11;                                    // v9 = 11
    if ( v10 >= 0xB )                           // 32 >= 0xb,判断成功
      v9 = (v10 + 16) & 0xFFFFFFF0;             // v9 = 48
  }
  v20 = v9;                                     // v20 = 48
  buff_48 = (char *)operator new(v9);           // 申请了48个字节的空间
  buff_48_ = buff_48;
  if ( n )
    memcpy(buff_48, v18, n);
  buff_48__ = buff_48_;
  v13 = a6;                                     // v13 = 0
  v14 = a7;                                     // v14 = a7 = 32
  if ( a7 )
  {
    memcpy(&buff_48_[n], src, a7);              // buff_48[0-31] = "3390fd362dfdda0030d5737632d3d213"
    v13 = a6;                                   // v13= 0
    v14 = a7;                                   // v14 = 32
  }
  v15 = a4 - v13;                               // v15 = 0 - 0 = 0
  if ( a4 - v13 != n )                          // 0 - 0 != 0 该判断失败
  {
    memcpy(&buff_48__[n + v14], &v18[n + a6], v15 - n);
    v14 = a7;
  }
  if ( a2 != 10 )                               // 10 != 10 判断失败
  {
    operator delete(v18);
    v14 = a7;
  }
  result = buff1_16;
  *((_DWORD *)buff1_16 + 2) = buff_48__;        // buff1_16[8-11] = buff_48;注意这里是把申请空间的地址放在了这里
                                                // buff1_16[8-11] 存放了一个堆空间的指针,该指针指向字符串"3390fd362dfdda0030d5737632d3d213"
  *(_DWORD *)buff1_16 = v20 | 1;                // buff1_16[0-3] = 0x00000031
                                                // 小端存放 buff1_16[0-3] = {0x31,00,00,00}
  v17 = v14 + v15;                              // v17 = 32
  *((_DWORD *)buff1_16 + 1) = v17;              // buff1_16[4-7] = 0x00000032
                                                // buff1_16[4-7] = {0x20,0,0,0}
  buff_48__[v17] = 0;                           // 字符串"3390fd362dfdda0030d5737632d3d213"后添加0确保截断
  return result;                                // 总结一下:
                                                // buff1_16[0-3] 存放着申请堆空间大小+1,也就是49
                                                // buff1_16[4-7] 存放着字符串"3390fd362dfdda0030d5737632d3d213"的长度,也就是32
                                                // buff1_16[8-11] 存放着字符串"3390fd362dfdda0030d5737632d3d213"的指针
}
// sub_488e0(buff1_60_16,10,22,0,0,0,32,"3390fd362dfdda0030d5737632d3d213")
BYTE *__cdecl sub_488E0(BYTE *buff1_60_16,unsigned int a2,unsigned int a3,int a4,size_t n,int a6,size_t a7,void *src)
{
  if ( 0xFFFFFFEE - a2 < a3 )                   // 这里注意a2的类型是无符号类型,-18会转换成一个非常大的正数,所以判断失败
    sub_48A20();
  if ( (*buff1_16 & 1) != 0 )                   // buff1_16 == 0,判断失败
    v8 = (BYTE *)*((_DWORD *)buff1_16 + 2);
  else
    v8 = buff1_16 + 1;                          // v8 = buff1_17
  v18 = v8;                                     // v18 = buff1_17
  v9 = -17;                                     // v9 = -17
  if ( a2 <= 0x7FFFFFE6 )                       // 10 <= 0x7ffffff6,判断成功
  {
    v10 = a2 + a3;                              // v10 = 32; a2 = 10; a3 = 22
    if ( a2 + a3 < 2 * a2 )                     // 10 + 22 < 10 * 2 = false, 判断失败
      v10 = 2 * a2;
    v9 = 11;                                    // v9 = 11
    if ( v10 >= 0xB )                           // 32 >= 0xb,判断成功
      v9 = (v10 + 16) & 0xFFFFFFF0;             // v9 = 48
  }
  v20 = v9;                                     // v20 = 48
  buff_48 = (char *)operator new(v9);           // 申请了48个字节的空间
  buff_48_ = buff_48;
  if ( n )
    memcpy(buff_48, v18, n);
  buff_48__ = buff_48_;
  v13 = a6;                                     // v13 = 0
  v14 = a7;                                     // v14 = a7 = 32
  if ( a7 )
  {
    memcpy(&buff_48_[n], src, a7);              // buff_48[0-31] = "3390fd362dfdda0030d5737632d3d213"
    v13 = a6;                                   // v13= 0
    v14 = a7;                                   // v14 = 32
  }
  v15 = a4 - v13;                               // v15 = 0 - 0 = 0
  if ( a4 - v13 != n )                          // 0 - 0 != 0 该判断失败
  {
    memcpy(&buff_48__[n + v14], &v18[n + a6], v15 - n);
    v14 = a7;
  }
  if ( a2 != 10 )                               // 10 != 10 判断失败
  {
    operator delete(v18);
    v14 = a7;
  }
  result = buff1_16;
  *((_DWORD *)buff1_16 + 2) = buff_48__;        // buff1_16[8-11] = buff_48;注意这里是把申请空间的地址放在了这里
                                                // buff1_16[8-11] 存放了一个堆空间的指针,该指针指向字符串"3390fd362dfdda0030d5737632d3d213"
  *(_DWORD *)buff1_16 = v20 | 1;                // buff1_16[0-3] = 0x00000031
                                                // 小端存放 buff1_16[0-3] = {0x31,00,00,00}
  v17 = v14 + v15;                              // v17 = 32
  *((_DWORD *)buff1_16 + 1) = v17;              // buff1_16[4-7] = 0x00000032
                                                // buff1_16[4-7] = {0x20,0,0,0}
  buff_48__[v17] = 0;                           // 字符串"3390fd362dfdda0030d5737632d3d213"后添加0确保截断
  return result;                                // 总结一下:
                                                // buff1_16[0-3] 存放着申请堆空间大小+1,也就是49
                                                // buff1_16[4-7] 存放着字符串"3390fd362dfdda0030d5737632d3d213"的长度,也就是32
                                                // buff1_16[8-11] 存放着字符串"3390fd362dfdda0030d5737632d3d213"的指针
}
unsigned int __cdecl set_buff2(BYTE *buff2)
{
  v31 = __readgsdword(0x14u);
  *(_DWORD *)buff2 = &off_19CAAC;               // buff2[0-3] = 19aac+imagebase
  *((_DWORD *)buff2 + 2) = 0;                   // buff2[4-19] = 0
  *((_DWORD *)buff2 + 1) = 0;
  *((_DWORD *)buff2 + 4) = 0;
  *((_DWORD *)buff2 + 3) = 0;
  memcpy(dest, byte_151F3A, sizeof(dest));      // 把固定地址的数据拷贝至dest,这里暂时将这串数据记作const_data
  memcpy(buff2 + 20, dest, 512u);               // buff2[20-531] = const_data
  RAND_bytes(v29, 32);                          // 生成32字节大小的随机数
  v1 = v29[3];                                  // v1 = v29[3]
  v28[3] = v29[3];
  v2 = v29[2];                                  // v2 = v29[2]
  v28[2] = v29[2];
  v3 = v29[1];                                  // v3 = v29[1]
  v28[1] = v29[1];
  v28[0] = v29[0];                              // v28[0-3] = v29[0-3]
  *(_QWORD *)(buff2 + 532) = v29[0];            // buff2[532-539] = v29[0]
  *(_QWORD *)(buff2 + 540) = v3;                // buff2[540-547] = v3
  *(_QWORD *)(buff2 + 548) = v2;                // buff2[548-555] = v2
  *(_QWORD *)(buff2 + 556) = v1;                // buff2[556-563] = v1
  *(_QWORD *)(buff2 + 588) = v28[3];            // buff2[588-595] = v1
  v4 = v28[1];
  *(_QWORD *)(buff2 + 564) = v28[0];            // buff2[564-571] = v29[0]
  *(_QWORD *)(buff2 + 580) = v28[2];            // buff2[580-587] = v2
  *(_QWORD *)(buff2 + 572) = v4;                // buff2[572-579] = v3
                                                // 小结一下:
                                                // buff2[532-563] = buff2[564-595] 是两组相同的随机数
  v5 = -64;
  v6 = 0;
  do
    v6 = *(_WORD *)&buff2[2 * (buff2[v5++ + 596] ^ HIBYTE(v6)) + 20] ^ (v6 << 8);
  while ( v5 );                                 // 观察这个公式,buff2[v5++ + 596]指定就是最后的64个随机字节,v5++依次取出,这里将这个值记作rand[i]
                                                // 观察buff2[2 * (rand[i] ^ HIBYTE(v6)) + 20],对照当前 buff2 的内存布局,这指的就是之前的 const_data
                                                // 也明白了buff2的空间为什么是596这个奇怪的大小,它的组成是20 + 256*2 + 64
                                                // 需要注意的是最后使用了 *(WORD*)&,也就是 const_data 被当做WORD取出
                                                //
                                                // 小结一下:
                                                // 随机字节和v6异或也是随机的,就记为随机
                                                // 循环将 const_data 以 WORD 类型随机取出,再和上一次结果的高位八位异或
                                                // 总的来讲就是得到了一个随机数 v6
  v27 = v6;
  *(_WORD *)((char *)v28 + 5) = v6;             // 这里修改了v28的第5-6字节为v6
  hmac_ctx_hash((BYTE *)v28, (BYTE *)v29, buff2 + 532);// 这是一个已经分析过的函数,hash和data没有关系
                                                // 把随机数 v29 作为key,hash结果放在buff2[532-595]中
  v7 = -32;
  v8 = 0;
  do
    v8 = *(_WORD *)&buff2[2 * (*((unsigned __int8 *)v29 + v7++) ^ HIBYTE(v8)) + 20] ^ (v8 << 8);
  while ( v7 );                                 // 这个循环和上边差不多,无非是随机数在v29这个随机字节数组中取,最终得到一个随机数 v8
  v9 = lrand48() % 12;                          // v9 是个0-11范围内的随机数
  *(_WORD *)&buff2[v9 + 532] = v8;              // 随机数 v8 被随机的写在 buff2[532-544]中,注意 v8 是 WODD 类型
  *(_WORD *)&buff2[v9 + 534] = v27;             // 一个 0 被随机的写在 buff2[534-546]中,注意 0 是 WODD 类型
  v10 = _mm_xor_ps(*(__m128 *)(buff2 + 36), (__m128)xmmword_148490);
  *(__m128 *)(buff2 + 20) = _mm_xor_ps(*(__m128 *)(buff2 + 20), (__m128)xmmword_148490);// buff2[20-35] ^= 0x00360036003600360036003600360036
  *(__m128 *)(buff2 + 36) = v10;                // buff2[36-51] ^= 0x00360036003600360036003600360036
  v11 = _mm_xor_ps(*(__m128 *)(buff2 + 68), (__m128)xmmword_148490);
  *(__m128 *)(buff2 + 52) = _mm_xor_ps(*(__m128 *)(buff2 + 52), (__m128)xmmword_148490);//
                                                // 发现了规律,其实就是,buff2[20-532] 2字节一组与 0x0036 异或,并更新buff2中的值
                                                // 在这里总结一下:
                                                // buff2[0-3] = 19aac+imagebase
                                                // buff2[4-19] = 0
                                                // buff2[20-531] = const_data ^ 0x0036
                                                // buff2[532-595] = hash
    ......
     
  v25 = _mm_xor_ps(*(__m128 *)(buff2 + 516), (__m128)xmmword_148490);
  *(__m128 *)(buff2 + 500) = _mm_xor_ps(*(__m128 *)(buff2 + 500), (__m128)xmmword_148490);
  *(__m128 *)(buff2 + 516) = v25;
  return __readgsdword(0x14u);
}
unsigned int __cdecl set_buff2(BYTE *buff2)
{
  v31 = __readgsdword(0x14u);
  *(_DWORD *)buff2 = &off_19CAAC;               // buff2[0-3] = 19aac+imagebase
  *((_DWORD *)buff2 + 2) = 0;                   // buff2[4-19] = 0
  *((_DWORD *)buff2 + 1) = 0;
  *((_DWORD *)buff2 + 4) = 0;
  *((_DWORD *)buff2 + 3) = 0;
  memcpy(dest, byte_151F3A, sizeof(dest));      // 把固定地址的数据拷贝至dest,这里暂时将这串数据记作const_data
  memcpy(buff2 + 20, dest, 512u);               // buff2[20-531] = const_data
  RAND_bytes(v29, 32);                          // 生成32字节大小的随机数
  v1 = v29[3];                                  // v1 = v29[3]
  v28[3] = v29[3];
  v2 = v29[2];                                  // v2 = v29[2]
  v28[2] = v29[2];
  v3 = v29[1];                                  // v3 = v29[1]
  v28[1] = v29[1];
  v28[0] = v29[0];                              // v28[0-3] = v29[0-3]
  *(_QWORD *)(buff2 + 532) = v29[0];            // buff2[532-539] = v29[0]
  *(_QWORD *)(buff2 + 540) = v3;                // buff2[540-547] = v3
  *(_QWORD *)(buff2 + 548) = v2;                // buff2[548-555] = v2
  *(_QWORD *)(buff2 + 556) = v1;                // buff2[556-563] = v1
  *(_QWORD *)(buff2 + 588) = v28[3];            // buff2[588-595] = v1
  v4 = v28[1];
  *(_QWORD *)(buff2 + 564) = v28[0];            // buff2[564-571] = v29[0]
  *(_QWORD *)(buff2 + 580) = v28[2];            // buff2[580-587] = v2
  *(_QWORD *)(buff2 + 572) = v4;                // buff2[572-579] = v3
                                                // 小结一下:
                                                // buff2[532-563] = buff2[564-595] 是两组相同的随机数
  v5 = -64;
  v6 = 0;
  do
    v6 = *(_WORD *)&buff2[2 * (buff2[v5++ + 596] ^ HIBYTE(v6)) + 20] ^ (v6 << 8);
  while ( v5 );                                 // 观察这个公式,buff2[v5++ + 596]指定就是最后的64个随机字节,v5++依次取出,这里将这个值记作rand[i]
                                                // 观察buff2[2 * (rand[i] ^ HIBYTE(v6)) + 20],对照当前 buff2 的内存布局,这指的就是之前的 const_data
                                                // 也明白了buff2的空间为什么是596这个奇怪的大小,它的组成是20 + 256*2 + 64
                                                // 需要注意的是最后使用了 *(WORD*)&,也就是 const_data 被当做WORD取出
                                                //
                                                // 小结一下:
                                                // 随机字节和v6异或也是随机的,就记为随机
                                                // 循环将 const_data 以 WORD 类型随机取出,再和上一次结果的高位八位异或
                                                // 总的来讲就是得到了一个随机数 v6
  v27 = v6;
  *(_WORD *)((char *)v28 + 5) = v6;             // 这里修改了v28的第5-6字节为v6
  hmac_ctx_hash((BYTE *)v28, (BYTE *)v29, buff2 + 532);// 这是一个已经分析过的函数,hash和data没有关系
                                                // 把随机数 v29 作为key,hash结果放在buff2[532-595]中
  v7 = -32;
  v8 = 0;
  do
    v8 = *(_WORD *)&buff2[2 * (*((unsigned __int8 *)v29 + v7++) ^ HIBYTE(v8)) + 20] ^ (v8 << 8);
  while ( v7 );                                 // 这个循环和上边差不多,无非是随机数在v29这个随机字节数组中取,最终得到一个随机数 v8
  v9 = lrand48() % 12;                          // v9 是个0-11范围内的随机数
  *(_WORD *)&buff2[v9 + 532] = v8;              // 随机数 v8 被随机的写在 buff2[532-544]中,注意 v8 是 WODD 类型
  *(_WORD *)&buff2[v9 + 534] = v27;             // 一个 0 被随机的写在 buff2[534-546]中,注意 0 是 WODD 类型
  v10 = _mm_xor_ps(*(__m128 *)(buff2 + 36), (__m128)xmmword_148490);
  *(__m128 *)(buff2 + 20) = _mm_xor_ps(*(__m128 *)(buff2 + 20), (__m128)xmmword_148490);// buff2[20-35] ^= 0x00360036003600360036003600360036
  *(__m128 *)(buff2 + 36) = v10;                // buff2[36-51] ^= 0x00360036003600360036003600360036
  v11 = _mm_xor_ps(*(__m128 *)(buff2 + 68), (__m128)xmmword_148490);
  *(__m128 *)(buff2 + 52) = _mm_xor_ps(*(__m128 *)(buff2 + 52), (__m128)xmmword_148490);//
                                                // 发现了规律,其实就是,buff2[20-532] 2字节一组与 0x0036 异或,并更新buff2中的值
                                                // 在这里总结一下:
                                                // buff2[0-3] = 19aac+imagebase
                                                // buff2[4-19] = 0
                                                // buff2[20-531] = const_data ^ 0x0036
                                                // buff2[532-595] = hash
    ......
     
  v25 = _mm_xor_ps(*(__m128 *)(buff2 + 516), (__m128)xmmword_148490);
  *(__m128 *)(buff2 + 500) = _mm_xor_ps(*(__m128 *)(buff2 + 500), (__m128)xmmword_148490);
  *(__m128 *)(buff2 + 516) = v25;
  return __readgsdword(0x14u);
}
unsigned int __cdecl set_buff3(BYTE *buff3)
{
  v32 = __readgsdword(0x14u);
  *(_DWORD *)buff3 = &off_19CA8C;               // buff3[0-3] = 19ca8c + image_base
  *((_DWORD *)buff3 + 18) = 0;                  // buff3[68-83] = 0
  *((_DWORD *)buff3 + 17) = 0;
  *((_DWORD *)buff3 + 20) = 0;
  *((_DWORD *)buff3 + 19) = 0;
  memcpy(dest, &unk_1485A0, sizeof(dest));      // 把固定地址的数据拷贝至dest,这里暂时将这串数据记作const_data
  memcpy(buff3 + 84, dest, 512u);               // buff3[84-595] = const_data
  RAND_bytes(key, 32);                          // 生成32字节大小的随机数
  v1 = *(_QWORD *)&key[24];                     // v1 = key[24-31]
  *(_QWORD *)&data[24] = *(_QWORD *)&key[24];
  v2 = *(_QWORD *)&key[16];                     // v2=v32[16-23]
  *(_QWORD *)&data[16] = *(_QWORD *)&key[16];
  v3 = *(_QWORD *)&key[8];                      // v3=key[8-15]
  *(_QWORD *)&data[8] = *(_QWORD *)&key[8];
  *(_QWORD *)data = *(_QWORD *)key;             // data[0-31] = key[0-31]
  *(_QWORD *)(buff3 + 4) = *(_QWORD *)key;      // buff3[4-11] = key[0-7]
  *(_QWORD *)(buff3 + 12) = v3;                 // buff3[12-19] = v3
  *(_QWORD *)(buff3 + 20) = v2;                 // buff3[20-27] = v2
  *(_QWORD *)(buff3 + 28) = v1;                 // buff3[28-35] = v1
  *(_QWORD *)(buff3 + 60) = *(_QWORD *)&data[24];// buff3[60-67] = v1
  v4 = *(_QWORD *)&data[8];
  *(_QWORD *)(buff3 + 36) = *(_QWORD *)data;    // buff3[36-43] = key[0-7]
  *(_QWORD *)(buff3 + 52) = *(_QWORD *)&data[16];// buff3[52-59] = v2
  *(_QWORD *)(buff3 + 44) = v4;                 // buff3[44-51] = v3
                                                // 小结:
                                                // buff3[4-35] = buff3[36-67] 是两组相同的随机数
 
  v5 = -64;
  v6 = 0;
  do
    v6 = *(_WORD *)&buff3[2 * (buff3[v5++ + 68] ^ HIBYTE(v6)) + 84] ^ (v6 << 8);// 这里和 setbuff2 基本一致,总的来讲就是得到了一个随机数 v6
  while ( v5 );
  v28 = v6;
  *(_WORD *)&data[5] = v6;                      // data 的第5-6字节被赋值为v6
  get_hash2(v27, (int)data, (int)key, buff3 + 4);// buff3[4-67] = hash
  v7 = -32;
  v8 = 0;
  do
    v8 = *(_WORD *)&buff3[2 * (key[v7++] ^ HIBYTE(v8)) + 84] ^ (v8 << 8);// 通过 key 得到一个随机的 v8
  while ( v7 );
  v9 = lrand48() % 30;                          // v9 一个范围在 0-29 的随机数
  *(_WORD *)&buff3[v9 + 4] = v8;                // buff3[4-31] 随机位置被赋值 v8
  *(_WORD *)&buff3[v9 + 6] = v28;               // buff3[6-35]随机位置被赋值v6
  v10 = _mm_xor_ps(*(__m128 *)(buff3 + 100), (__m128)xmmword_1483D0);// buff3[84-595] 2字节一组与 0x0042 异或,并更新buff3中的值
                                                // 总结:
                                                // buff3[0-3] = 19ca8c + image_base
                                                // buff3[4-67] = hash
                                                // buff3[68-83] = 0
                                                // buff3[84-595] = const_data ^ 0x0042
    ......
  v25 = _mm_xor_ps(*(__m128 *)(buff3 + 580), (__m128)xmmword_1483D0);
  *(__m128 *)(buff3 + 564) = _mm_xor_ps(*(__m128 *)(buff3 + 564), (__m128)xmmword_1483D0);
  *(__m128 *)(buff3 + 580) = v25;
  return __readgsdword(0x14u);
}
unsigned int __cdecl set_buff3(BYTE *buff3)
{
  v32 = __readgsdword(0x14u);
  *(_DWORD *)buff3 = &off_19CA8C;               // buff3[0-3] = 19ca8c + image_base
  *((_DWORD *)buff3 + 18) = 0;                  // buff3[68-83] = 0
  *((_DWORD *)buff3 + 17) = 0;
  *((_DWORD *)buff3 + 20) = 0;
  *((_DWORD *)buff3 + 19) = 0;

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2024-1-12 22:27 被简单的简单编辑 ,原因:
收藏
免费 14
支持
分享
最新回复 (11)
雪    币: 4
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
分析的详细
2023-3-5 19:17
0
雪    币: 477
活跃值: (1412)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
考的密码学和数学
2023-3-6 09:40
0
雪    币: 4776
活跃值: (4474)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
一般他们要你逆向这个用多久 需要当天在公司逆完吗?
2023-3-6 10:18
0
雪    币: 4134
活跃值: (5847)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
5
考验的就是耐心啊
2023-3-6 10:45
0
雪    币: 0
活跃值: (247)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6

附上,第三题验证失败原因,以及打印补丁位置。


armv8文件在0x6ACB0处修改为EB 04 00 54=>27 00 00 14


改成无条件跳转,不进行字节位移,完成修复,可以正常效验了。


上传的附件:
2023-3-7 16:37
0
雪    币: 861
活跃值: (69)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
楼主能分享APP吗,我也想分析一下。
2023-3-8 13:07
0
雪    币: 6501
活跃值: (4912)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
8
wx_威郑天 楼主能分享APP吗,我也想分析一下。
帖子的最后有附带样本
2023-3-8 21:48
0
雪    币: 6501
活跃值: (4912)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
9
木志本柯 一般他们要你逆向这个用多久 需要当天在公司逆完吗?
给了七天时间,让拿回去分析,我也用了七天时间
2023-3-8 22:01
0
雪    币: 6501
活跃值: (4912)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
10
badboyl 考验的就是耐心啊
样本分析就是比耐心
2023-3-8 22:01
0
雪    币: 6501
活跃值: (4912)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
11
老弟来了 附上,第三题验证失败原因,以及打印补丁位置。armv8文件在0x6ACB0处修改为EB 04 00 54=&gt;27 00 00 14。改成无条件跳转,不进行字节位移,完成修复,可以正常效验 ...

跪拜大佬

最后于 2023-3-20 14:45 被简单的简单编辑 ,原因:
2023-3-8 22:02
0
雪    币: 5649
活跃值: (3767)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
建议样本上传附件,避免丢失
2023-3-20 03:58
0
游客
登录 | 注册 方可回帖
返回
//