飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 5220|回复: 9

[原创] 逆向分析商业软件 010 Editor 及注册机编写

[复制链接]
  • TA的每日心情
    无聊
    2022-6-21 14:02
  • 签到天数: 96 天

    [LV.6]常住居民II

    发表于 2019-3-21 20:36:53 | 显示全部楼层 |阅读模式
    本帖最后由 一夜抱妇 于 2019-3-21 20:50 编辑

    0x00 010 Editor 简单介绍
    010 Editor 是一款非常强大的文本、十六进制编辑器,除了文本、十六进制编辑外,还包括文件解析、计算器、文件比较等功能,但它真正的强大之处还在于文件的解析功能。我们可以使用 010Editor 官方网站提供的解析脚本对 avi、bmp、png、exe 等简单格式的文件进行解析,当然也可以根据需求来自己编写文件解析脚本。但是 010 Editor 是收费的,不过官方给出了 30 天的免费使用期限供用户体验。本文将为大家分析如何去除使用限制和编写注册机。目前最新版本为 v9.0.1,也是本文将分析的版本。010 Editor 官网:http://www.sweetscape.com/010editor/ 。

    _QIQ9Q47~GHWQ%B$CNL{Y}X.png 0x01 暴力破解分析
    在软件破解中,暴力破解可谓是一个最基本,也是最常见的一种手段,无非就是改跳转指令(例如 JNZ 改 JZ、JZ 改JNZ、把跳转指令 NOP 掉等),让它跳的时候不跳,不该跳的时候跳,从而避开检测以获取它的永久使用权限。下面就列下 010 Editor 暴力破解的分析步骤:

    • 找到注册窗口
    • 测试注册窗口的反应
    • 根据反应做出下一步分析的打算(猜测 API、API 下断动态调试、挑出敏感字符串,在程序中搜索)
    • 动态分析,定位关键跳转,修改代码
    • 动态分析,定位关键 CALL,修改代码
    在官网下载最新版本并在你的计算机上安装好后,双击让它运行起来,并点击关于,会看到 Free 30-Day Trial 字样,另外,注册窗口在 Tools --> Register ,如下:
    3~@PMU`YCHGF_BXSKZJJ.png
    点击后弹出注册的窗口,这里需要你填写用户名和序列号,如下:
    `N06BE_ST{6{HUQFW7O`AA9.png
    点击 Check License 按钮,弹出如下提示:
    L4V})Y3I57RF0X2OY)0WK6J.png
    上述意思是你输入了一个无效的用户名或者是密码,也就是说,你点击那个按钮会弹出这个提示窗口,那么我们可以从这个提示窗口入手,
    比如对创建这个窗口的 API 下断,或者是寻找提示信息的这段字符串。废话不多说,上 OD,附加进程调试,如下:
    }86G8WRT`7$A6@(534F2ROX.png
    附加后,我们需要来到主模块,点击 OD 上方的 e 按钮,点击第一个,这里需要重复操作两次才到主模块,完成上述操作后,我们先来看下程序主模块导入了哪些函数,右键 -> 查找 -> 当前模块中的名称(标签),或者按下快捷键 Ctrl + N,如下:
    )Q0{~`0F@)XZ4L887LAI(PP.png
    F9HC(WFEN}QR0ZC9Y4~WD.png
    在上面图中可以看出 010 Editor 调用了大量的 Qt 库函数,由此可猜测它是由 Qt 编写的,对于 Qt 中创建窗口函数,个人觉得,不管是何种高级语言,在底层中还是得调用诸如 CreateWindowMessageBox 以及一些 Dialog 相关的函数,这些函数位于 User32.dll 中,同样点击 OD 上方的小 e 按钮,在里面找到 user32.dll,双击进去,在这里要说一点就是,创建窗口的 API 函数不止一个,而且也不存在 CreateWindowMessageBox 这类函数,其实它们只是个宏,在深入一点都是调用 Ex 的版本,那么问题又来了,是调用 A 还是调用 W 的,学过 windows 编程的都知道,A 的最终还是会去调用 W 的, 所以我们不妨先对 CreateWindowExW 函数下断,按 Ctrl + G,输入 CreateWindowExW,如下:
    {W{}RWB~LB~J$RLVKUT9(GH.png
    转到这里后,对该地址下断,回到 010 Editor 注册界面,点击 Check Lincense,之后你会发现程序断下来了,这说明断点下对了,程序停在了 0x74892480 地址处。断下来之后,我们接下来要想的是哪里调用了这个函数,我们可以上方的 k 小按钮来查看,如下

    7%6TCFZNQ45HEXKKEY~_JWV.png
    通过栈回溯分析,可以看出,在地址 0x00EFCD24 上的函数过程有个很显眼的字 show,貌似跟显示窗口有关,我们双击进去看下里面有什么可以利用的信息,结果没发现有价值的东西,不要慌,我们继续点击上面这个地址的下一个地址,双击进行查看,结果也没有,再跟进去下一个地址,也就是 0x00EFCDFC处,双击进去,会发现如下这种情况:
    GX(C{N@H87N~FHJQTNNQFTB.png

    通过上图可以看出,在这里面出现了大量可疑的字符串,其中 Invalid name or password...... 这个字符串就是我们随意输入用户名和密码时弹出的信息提示,该处为一个 push 指令,我们向上看会发现该处是由 0x1DE8489 处跳转过来的,如下:
    1.png

    不妨在 0x1DE8489 处下断点,把之前的断点禁用或者删除,回到注册界面,点击 Check License,会发现在该处断下来了,F8 一下就跳到无效密码处,在该 JNZ 指令上有三组 CMP 指令,并且第一组 CMP 指令是从地址 0x01DE8336 处跳转过来的,如下:
    2.png
    我们来到第一组 CMP 指令源跳转处,如下:
    3.png

    0x1DE8330 地址处的 CMP 指令,有两条源跳转,分别为 0x01DE8237 0x01DE8241,如下:

    4.png

    我们不妨在上面处下个断,重新回到注册界面,点击 Check License,会发现它会断在刚下的这个断点处,按 F8 下去,你会发现有两个关键的地方,一个是 EDI 的值,一个是 EBX 的值,前者需要和 0xDB 这个值作比较,后者则需要跟 0xE7 这个值作比较,如下:
    5.png
    6.png
    这些值都是来自于 EAX 的赋值,而我们知道,EAX 寄存器存放的是函数的返回值,所以我们需要跟进函数里头去看下返回值是如何返回的,但现不急着跟进去,因为现在是暴力破解它,所以我们只需将地址 0x019D8336 处的 JNZ 指令给 NOP 掉即可,如下:
    7.png
    最后保存文件即可,如下:
    8.png

    双击运行 Dump 出来的文件,点击 Check License 会发现弹出如下提示:
    9.png
    点击 OK 就可以进入到主界面了,这样就达到了暴力破解的目的了,但是这种方法对于这个软件来说有一点不好的地方的就是每次运行都会弹出这个注册界面,需再次点击 Check License,这样才能够使用。我们能不能双击直接进入呢?答案肯定能,我们重新理一下整个过程,如下:
    如果 EBX 为 0xE7 则跳,跳过之后,再对 EDI 比较,此时 EDI 为0x177,0x177 不等于 0xDB
    又跳走,接着有两次对 EDI 进行比较,值分别为 0xED 和0x20C,我们知道,EDI 值为 0x177,所以两处的 JE 指令不成立,最后对比 EBX 的值,用它跟 0x93 对比,很明显 0xE7 不等于 0x93,所以JNZ 指令成立,将跳过密码已被接受过程。
    所以为了一开始不跳,EBX 的值要不为 0xE7EDI 的值要为 0xDB,在上面谈到的两个重要的 CALL 里,第二个 CALL 是根据第一个 CALL 的最终 EAX 的来进行对 EDI 进行赋值,所以,为了让 EDI 的值为 0xDBEBX 的值要为 0x2D,也就是说第一个CALL 的最终 EAX 值要为 0x2D,我们可以跟进第一个CALL 内,在最开头部分写下:
    mov eax,0x2D
    如下:
    10.png
    修改完后,会发现下一条指令的 OPCODE 下有一根下划线,这说明需要重定位,下次再运行时可能就不是这个地址了,所以我们需要做的就是把软件的重定位标志置为 0 保存即可,如下:
    11.png
    重新运行 OD,将其载入,在最开头部分写下:

    mov eax,0x2D
    retn 0x8
    如下所示:
    12.png
    最后 Dump 出来,双击运行下看是否成功,如下:
    13.png
    由此可见,我们已成功完美破解了 010 Editor 了,尽情享用吧!
    0x02 算法分析
    在这小节当中,会对 010 Editor 注册算法进行详细分析。回顾上一小节,我们只是修改了一个跳转,但关键点还是在于那两个重要的 CALL,尤其是第一个 CALL,必须让它返回 0x2D 才是正确的,所以本小节将会跟进第一个关键 CALL 内去分析注册算法。在跟进之前,我们先来看下这个 CALL 有哪些参数,如下:
    14.png

    我们知道,ECX 传参为 this 指针,后面两个分别为 0x4596 0xA,传的参肯定和用户名和密码有关系,我们不妨跟进 ECX 地址所在处数据窗口,ECX 的值为 0330B300,如下:
    15.png
    从上图可知,这个 this 指针应该是指向一个字符串数组,接着我们需进入到地址 0330B304 处,如下:
    16.png
    同样地,注意地址的对应,在这一段为你输入的密码,也可叫做序列号。知道这些后,对后面分析有一定帮助。我们跟进去第一个重要 CALL 内,以下作了一个简单的分析:
    17.png

    由上面分析知,ECX 存放的是 this 指针,而 this 指针指向的是一个字符串数组,这个数组存放着用户名和密码字符串,我已将说明标注在上图中。所以为什么说之前我们需要跟进 ECX 中的数据窗口去查看下这个地址里存放的是什么东西,那么在上图分析过程中起到一个辅助作用。
    18.png
    在检测用户名和密码是否为空后,紧接着将一个局部变量的值压和栈中,右键栈中的地址数据窗口跟随,会发现这个数据是你输入的密码字符串,而且为 16 进制,所以说 PUSH 指令下的这个 CALL,是将密码字符串转为 16 进制字节的数据。如果你不放心,可以多测试几下,这里把密码换成 1234-5678-9009-8765-4321,如下:
    19.png
    我们接着分析,在接下来,程序会将字符串 999 压入栈中进行一番不知什么操作,如下:
    20.png
    这一段并没有对用户名和密码字符串进行操作,所以不去深究它也无太大影响,在逆向分析中,并不是每条都分析一遍你才能破解它,而是找到关键部分进行分析,这一小段为一个小循环,这个 CALL,根据提示,应该 Qt 中运算符重载,由于对 Qt 不是很熟悉,而且这小段并未对用户名和密码进行操作,所以跳过它往下继续分析。从循环开始,那就是真正的到达算法处,算法并不是很难,但是有一点绕,如下:
    21.png
    在上图中,有几个关键点地方,第一个圈起来的,它会拿你输入的密码的第 4 组数据去和 0x9C0xAC0xFC 这三个数进行比较,如果不是这三个数中的其中一个,那么程序会将 0xE7 赋值给 EAX,而在前面一小节当中,EAX 的值不能为 0XE7,否则失败,所以我们令 JNZ 下方这条指令为新的 EIP,否则 JNZ 条件成立跳过去了,就不好分析了,最后我们可以肯定,密码有三个版本,这里只分析下 0x9C 这个版本。第二个关键点是第二个圈起来的地方,跟进这个 CALL 里,如下:
    22.png
    还好这个 CALL 里的代码并不是太复杂,主要处理的是 k[0] k[6],也就是 12 87 这两个数。第三个关键点是第三个圈起来的地方,我们跟进这个CALL 内,如下:
    23.png
    同样,代码也不复杂,主要是判断余数是否为 0,如果为 0,那么就返回商,如果不为 0,那么就返回 0。所以这么一整段下来,在对 k[0]k[1]k[2]k[3]k[4]k[5]k[6]k[7] 在进行操作,而我们输入的密码有 10 组,OD 中并没看到对 k[8]k[9] 进行处理,其实这里有个关子,那就是当你密码中第 4 组数据为 0x9C,那么生成的密码并没有 10 组,而是8 组,如下:
    24.png
    但不管怎么说,在 0x9C 情况下对每组数据进行异或、相加、与等操作,我们不妨先建立下注册机模型,我这里用的是MFC 进行编写,如下:
    25.png
    Generate 生成按钮添加单击事件,相关代码如下:
    // 生成密码
    void CMy010EditorRegisterDlg::OnBnClickedButtonGenerate()
    {
                 // TODO: 在此添加控件通知处理程序代码
                 m_edit_password.SetWindowText(L"");
                 CStringstr;
                 srand(time(NULL));
                 BYTEk[8] = { 0x12,0x34,0x56,0x9C,0x90,0x09,0x87,0x65 };
                 while (TRUE)
                 {
                              BYTEk0 = rand() % 0xFF;
                              BYTEk6 = rand() % 0xFF;
                              // AL = (k[0]^k[6]^0x18 + 0x3D)^0xA7
                              BYTEAL = (k0 ^ k6 ^ 0x18 + 0x3D) ^ 0xA7;
                              if (AL > 0)
                              {
                                          k[0] = k0;
                                          k[6] = k6;
                                          break;
                              }
                 }

                 // ESI = (0x100*(k[1]^k[7] & 0xFF) + k[2]^k[5] & 0xFF)&0xFFFF
                 // EAX = ((ESI^0x7892+0x4D30)^0x3421)&0xFFFF / 0xB
                 // 判断余数是否为0,为0返回商,不为0返回0
                 while (TRUE)
                 {
                              BYTEk1 = rand() % 0xFF;
                              BYTEk7 = rand() % 0xFF;
                              BYTEk2 = rand() % 0xFF;
                              BYTEk5 = rand() % 0xFF;
                              
                              DWORDESI = (0x100 * (k1 ^ k7 & 0xFF) + k2 ^ k5 & 0xFF) & 0xFFFF;
                              DWORDEAX = (((ESI ^ 0x7892) + 0x4D30) ^ 0x3421) & 0xFFFF;
                              if (EAX % 0xB == 0 && EAX / 0xB <= 0x3E8)
                              {
                                          k[1] = k1;
                                          k[7] = k7;
                                          k[2] = k2;
                                          k[5] = k5;
                                          break;
                              }
                 }
                 str.Format(L"%02x%02x-%02x%02x-%02x%02x-%02x%02x", k[0], k[1], k[2], k[3], k[4], k[5], k[6], k[7]);
                 m_edit_password.SetWindowText(str.MakeUpper());
    }

    以上代码只是让你遇到那两个 JE 和一个 JA 跳转指令可以使它不成立,这个雏形生成的密码并没有与用户名进行关联,在下一小节当中会分析密码与用户名的之间的联系。
    0x03 深入分析算法
    在上一小节当中,通过穷举法来找出符合要求的密码。下面将会为大家分析下用户名和密码之间的关系。
    26.png
    咱继续,接下来这一小段将 ECX 0x2 进行对比,其实好像也没什么用,我们看 JMP 跳转后的指令,如下:
    27.png
    这一段主要是将用户名的字符串转为 ASCII 版,在接下来的一个调用 CALL 后,EAX 中为用户名字符串地址,如下:
    28.png
    29.png
    在上图中的 ds:[edi+0x1C] 处其实是前面处理 k[0] k[6] 那个 CALL 后的返回值(即 AL 的值),所以我们需要将代码中的



    if (AL > 0)
    {
          k[0] = k0;
          k[6] = k6;
          break;
    }
    改为如下:
      
    1
      2
      3
      4
      5
      6
      
      
    if (AL > 0xA)
      {
                   k[0] = k0;
                   k[6] = k6;
                   break;
      }
      
    接下来就是要做的就是那个对用户名处理的 CALL,通过分析知道,这个 CALL 的返回值类似于哈希值,我们需要得到这个值,回为这个值需要和 k[4]k[5]k[6]k[7] 发生关系,我们不妨声明这样一个函数,用来处理 ASCII 码版的用户名字符串,这里有个取巧的办法,那就是利用 IDA,将 010 Editor 载入 IDA 中,我们需要先获取处理这个用户名字符串的 CALL 的地址,这个地址可以通过 OD 找到,找到之后,切换到 IDA,按 g 输入地址回车,再按下 F5 进行翻译,如下:
    30.png
    大致浏览一下,并不是太复杂,其中用到了一个数组,也就是上图圈出来的,双击进去,如下
    31.png
    这里显示的比较乱,我们可把地址 02EDD840 转到 OD 里查看,如下:
    41.png
    利用 OD 的插件将这一段进行数据转换并以 C++ 形式复制出来,并将其命名为 dwEncodeArray,如下:
    42.png
    并将原来的代码做下调整,如下:
      
    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
      
      
    // 生成密码
      void CMy010EditorRegisterDlg::OnBnClickedButtonGenerate()
      {
                   using namespace std;
                   // 先判断用户名输入是否为空
                   if(m_edit_name.GetWindowTextLength())
                   {
                                m_edit_password.SetWindowText(L"");
                                CString  str;
                                srand(time(NULL));
                                int nRet = 0x3E8;
                                BYTE  k[8] = { 0x12,0x34,0x56,0x9C,0x90,0x09,0x87,0x65 };
                                m_edit_name.GetWindowText(str);
      
                                CStringA stra(str.GetBuffer(0));
                                str.ReleaseBuffer();
                                string st(stra.GetBuffer(0));
                                const char* cs = st.c_str();
      
                                // 用户名进行加密
                                DWORD  dwKey = EncodeUserName(cs, 1, 0, nRet);
                                // CMP k[4],RetValue&0xFF
                                // CMP k[5],RetValue>>8&0xFF
                                // CMP k[6],RetValue>>16&0xFF
                                // CMP k[7],RetValue>>24&0xFF
                                k[4] = dwKey & 0xFF;
                                k[5] = dwKey >> 8 & 0xFF;
                                k[6] = dwKey >> 16 & 0xFF;
                                k[7] = dwKey >> 24 & 0xFF;
      
                                while (TRUE)
                                {
                                            BYTE  k0 = rand() % 0xFF;
                                            BYTE  k6 = k[6];
                                            // AL = (k[0]^k[6]^0x18 + 0x3D)^0xA7
                                            BYTE  AL = (k0 ^ k6 ^ 0x18 + 0x3D) ^ 0xA7;
                                            if (AL > 0xA)
                                            {
                                                         k[0] = k0;
                                                         k[6] = k6;
                                                         break;
                                            }
                                }
      
                                // ESI = (0x100*(k[1]^k[7] & 0xFF) + k[2]^k[5] &  0xFF)&0xFFFF
                                // EAX = ((ESI^0x7892+0x4D30)^0x3421)&0xFFFF / 0xB
                                // 判断余数是否为0,为0返回商,不为0返回0
                                while (TRUE)
                                {
                                            BYTE  k1 = rand() % 0xFF;
                                            BYTE  k7 = k[7];
                                            BYTE  k2 = rand() % 0xFF;
                                            BYTE  k5 = k[5];
      
                                            DWORD  ESI = (0x100 * (k1 ^ k7 & 0xFF) + k2 ^ k5 & 0xFF) & 0xFFFF;
                                            DWORD  EAX = (((ESI ^ 0x7892) + 0x4D30) ^ 0x3421) & 0xFFFF;
      
                                            if (EAX % 0xB == 0 && EAX / 0xB == 0x3E8)
                                            {
                                                         k[1] = k1;
                                                         k[7] = k7;
                                                         k[2] = k2;
                                                         k[5] = k5;
                                                         break;
                                            }
                                }
      
                                str.Format(L"%02x%02x-%02x%02x-%02x%02x-%02x%02x", k[0], k[1], k[2], k[3], k[4], k[5], k[6], k[7]);
                                m_edit_password.SetWindowText(str.MakeUpper());
                   }
                   else
                   {
                                MessageBox(L"Please enter name", L"warning", MB_OK);
                   }
      }
      
    EncodeUserName 函数如下:
    int __cdecl CMy010EditorRegisterDlg::EncodeUserName(const char *a1, int a2, char a3, unsigned __int16 a4)
    {
                
    const char *v4; // edx
                
    signed int v5; // esi
                
    signed int v6; // edi
                
    unsigned __int8 v7; // bl
                
    int v8; // eax
                
    int v9; // ecx
                
    int v10; // ecx
                
    int result; // eax
                
    unsigned __int8 v12; // [esp+8h] [ebp-10h]
                
    unsigned __int8 v13; // [esp+Ch] [ebp-Ch]
                
    unsigned __int8 v14; // [esp+10h] [ebp-8h]
                
    int v15; // [esp+14h] [ebp-4h]

                 v4 = a1;
                 v15 =
    0;
                 v5 =
    strlen(a1);
                 v6 =
    0;
                
    if (v5 <= 0)
                              
    return 0;
                 v12 =
    0;
                 v13 =
    0;
                 v7 =
    15 * a4;
                 v14 =
    17 * a3;
                
    do
                 {
                              v8 =
    toupper((unsigned __int8)v4[v6]);
                              v9 = v15 + dwEcodeArray[v8];
                              
    if (a2)
                                          v10 = dwEcodeArray[v13]
                                          + dwEcodeArray[v7]
                                          + dwEcodeArray[v14]
                                          + dwEcodeArray[(
    unsigned __int8)(v8 + 47)] * (dwEcodeArray[(unsigned __int8)(v8 + 13)] ^ v9);
                              
    else
                                          v10 = dwEcodeArray[v12]
                                          + dwEcodeArray[v7]
                                          + dwEcodeArray[v14]
                                          + dwEcodeArray[(
    unsigned __int8)(v8 + 23)] * (dwEcodeArray[(unsigned __int8)(v8 + 63)] ^ v9);
                              result = v10;
                              v15 = v10;
                              v13 +=
    19;
                              ++v6;
                              v14 +=
    9;
                              v7 +=
    13;
                              v12 +=
    7;
                              v4 = a1;
                 }
    while (v6 < v5);
                
    return result;

    }

    编译运行后如下:
    44.png
    45.png
    这样就达到了任意用户名注册!


    (本文完)









    010EditorRegister.rar

    135.85 KB, 下载次数: 33, 下载积分: 飘云币 -2 枚

    评分

    参与人数 2威望 +2 飘云币 +2 收起 理由
    adime + 1 + 1 原创精品 感谢分享!
    黑的思想 + 1 + 1 赞一个,这个帖子很给力!

    查看全部评分

    PYG19周年生日快乐!
  • TA的每日心情

    2024-3-17 14:20
  • 签到天数: 21 天

    [LV.4]偶尔看看III

    发表于 2019-3-29 21:57:39 | 显示全部楼层
    大牛厉害,小菜向你学习
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2020-9-9 09:24
  • 签到天数: 37 天

    [LV.5]常住居民I

    发表于 2019-4-18 13:02:13 | 显示全部楼层
    好像用不了,楼猪,,
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    6 天前
  • 签到天数: 484 天

    [LV.9]以坛为家II

    发表于 2019-10-2 19:53:05 | 显示全部楼层
    感谢大神分享,新手正好好好学习一下。
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    2022-1-16 19:10
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2019-11-27 17:40:09 | 显示全部楼层
    感谢大神分享,估计还要对主程序禁止联网吧,没准会联网验证
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    2022-1-16 19:10
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2020-2-19 17:28:25 | 显示全部楼层
    感谢您的分享,这个还需要【】屏蔽网络验证的吧
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    3 天前
  • 签到天数: 560 天

    [LV.9]以坛为家II

    发表于 2020-2-27 22:15:44 | 显示全部楼层
    感谢您的分享~!留个脚印慢慢消化~。
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2020-4-10 17:18
  • 签到天数: 4 天

    [LV.2]偶尔看看I

    发表于 2020-3-5 09:40:50 | 显示全部楼层
    太详细了,学习!!
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2023-11-8 16:50
  • 签到天数: 63 天

    [LV.6]常住居民II

    发表于 2020-3-9 07:11:37 | 显示全部楼层
    厉害,小白学习了
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表