白云点缀的蓝 发表于 2021-7-28 23:02

对一个文字转语音软件的分析

本帖最后由 白云点缀的蓝 于 2021-11-9 22:25 编辑

我们开od,然后在od里找找相关关键信息od载入后,我们右键中文搜索引擎,选择智能搜索
我们根据前面的开通会员的弹窗信息来进行搜索过滤

扩展:od里搜索出来的东西是什么呢?我们打开visual studio工具来做个分析我们点击文件,然后选择新建,然后选择项目
选择c++,然后选择windows,然后选择空项目,最后点击下一步


设置项目的存放路径,还有项目名称,设置好后,我们点击创建



然后我们右键源文件,选择添加新建项

点击新建项后,我们选择c++文件,把名称的后缀改为c,然后点击添加
我们在文件中写如下代码,
#include<Windows.h>
int main() {

      MessageBoxA(NULL, "这个字符串在od中能看到吗", "吾爱汇编",NULL);
return 0;
}
这个MessageBoxA的功能是弹出一个对话框


#include<Windows.h>//Windows.h类似于一个菜单,你去餐馆点菜的时候,通常都会给你一个菜单然后你就从中选择一个想要的菜,这个MessageBoxA就相当于一个你想要的菜。,也就是说MessageBoxA,他在Windows.h这个文件中。#include就相当于导入一个菜单,固定写法,后面的<>,两个大于小于代表你要包含一个系统的头文件,Windows.h就是一个系统的头文件。int main(){}这个又是什么呢?这个是一个main函数,一个程序的入口点,没有入口点,你的程序里的代码就无法运行,int这个代表是一个整型的返回值,这个int占4个字节。一个字节由八个bit组成,这个比特是由0和1组成的,在计算机的世界中,计算机只能识别0和1,八个bit,一个bit有两种选择,分别为0,和1,比如   一个字节   0000 0000 这个就是八个bit,四个字节也就是32个比特,0000 0000 0000 0000 0000 0000 0000 0000最高位是一个符合位,0为正整数,1为负数。int的表示范围是:-2^31 (-2147483648) ~ 2^31-1 (2147483647),这个int就是一个数字而已,没有小数点,一个基本数据类型下面是其他一些数据类型

unsigned就是无符合的意思,也就是说最高位即使为1他也为正数,我们拿unsigned int举例1111 1111 1111 1111 1111 1111 1111 1111,以下是计算器计算出来的值
在计算前我们需要把计算器选择为程序员模式


下面是一些浮点类型的数据类型,他们都有小数点,相比前面的整型数据类型

return 0;//这个是什么意思呢?return是返回的意思,因为int main这个入口函数前面有个int,所以他的返回值就是int类型的,也就是这个,-2^31 (-2147483648) ~ 2^31-1 (2147483647),我们可以把后面的0随意替换成int类型范围内的数字即可,我们分析一下这个函数,MessageBoxA(NULL, "这个字符串在od中能看到吗", "吾爱汇编",NULL);我们按住ctrl+鼠标右键转到定义,点击MessageBoxA

intWINAPIMessageBoxA(    _In_opt_ HWND hWnd,    _In_opt_ LPCSTR lpText,    _In_opt_ LPCSTR lpCaption,_In_ UINT uType);
我们可以知道,这个函数的返回值是int类型的,我们分析一下这个WINAPI我们同样Ctrl+鼠标左键转到定义
我们可以看到如下代码,
#define WINAPI      __stdcall这句代码的意思是#define 固定写法,他的意思是定义一个全局常量,什么是常量呢?就是不能在程序运行时改变的一个值WINAPI这个就是常量的名称,__stdcall这个就是这个常量的值这个__stdcall是一个关键字,就是你不能拿他起名字,全局代码中你都不能拿这个来定义变量。且你写上他后就有特殊的功能什么是变量呢?int a = 10;这个就是定义一个变量,int这个是变量的数据类型,a就是这个变量的名字,=10就是给a这个地址里面赋值。变量名相当于一个地址,然后地址里面存了10这个值,地址是什么呢?地址就是一个16进制的数,相当于一个编号,对一个数据存放的地方取一个编号,比如0x6666666,这个就是一个地址,地址里面可以放数据,也可以再放一个地址,然后放的地址里面又有数据,我们从中延申出指针,指针就是地址,一级指针就是在地址中单纯放一个数据,二级指针就是地址里面放地址,然后最里面那个地址放数据,int a = 10; //定义一个变量      int* p = &a;//取存放变量a的地址      printf("p= %x\n", p); //打印变量      printf("*p=%d\n", *p);//打印指针里面存放的数据我们对上面的代码进行分析,int* p = &a;//这个就是定义一个指针变量,什么是指针变量?就是存放指针的变量,也就是说存放地址的变量,一级指针定义格式 ,这个int就是你这个地址里面要存放什么样的数据类型,int的话,那就是存放-2^31 (-2147483648) ~ 2^31-1 (2147483647) 这个范围的数据,其他的数据类型比如double,char ,float等都可以,但是地址是固定的,也就是说地址在0x00000000-0xFFFFFFFF这个范围内,正好是4个字节,这里我们说的是32位的地址,无论你定义什么样的指针,地址都是4个字节的,但是地址里面存放的数据可能不一样,因此我们延伸出数据类型,这个数据类型我在前面有将,大家可以翻到前面去看看,这个&a中的&是取地址的意思,对变量a进行取地址,然后赋值给指针p,printf("p= %x\n", p);,这个就是打印一个变量,在使用前,我们需要导入一个系统的头文件,相当于一个菜单,有了菜单,我们才可以点菜,这个头文件是stdio.h,因为是系统的头文件,所以我们要加<>,如果是自己定义的头文件,我们就改成双引号就行“”,也就是这样#include <stdio.h>printf("p= %x\n", p),这个函数中第一个参数是格式,第二个参数是你要打印的变量,%x是打印一个16进制数,因为这个p是地址,所以我们要用%x,这个\n就是换行的意思,这个p=可以随便写,好识别就行,%d就是打印一个整型数据。*p就是取p地址里面的值,因为p的地址是从a那里获取的,所以*p就是10;

当我们看见类似xxxxxx();那这个就是函数了,函数名xxxxxx,()这个括号里面放参数即可,下面我们演示一个二级指针int** pp = &p;      printf("pp= %x\n", pp);      printf("*pp= %x\n", *pp);      printf("**pp= %x\n", **pp);**代表这是一个二级指针,有多少个*就代表是几级指针&p代表对指针p取地址,也就是存放指针p的地址,printf("pp= %x\n", pp);这个就是打印pp的地址,*pp就是取pp里面值,因为二级指针pp是一级指针p的地址,所以在pp前面加个*就是取的指针p的地址,指针p的地址又是变量a的地址,所以**p就是取a地址里面的值,也就是10;


我们回到__stdcall这个关键字上面,被__stdcall关键字修饰的函数,他的参数是从右向左通过堆栈传递的我们把我们的写好的程序放入od看一下,我们先生成一下exe文件,我们右键中文搜索引擎,然后选择智能搜索

我们双击吾爱汇编,然后在如下位置下断点。扩展一下基础知识,push就是往堆栈里面放数据下面这块红色的位置就是堆栈00121773    6A 00         PUSH 0x000121775    68 307B1200   PUSH 0x127B30                            ; 吾爱汇编0012177A    68 387B1200   PUSH 0x127B38                            ; 这个字符串在od中能看到吗0012177F    6A 00         PUSH 0x0 PUSH 0X0就是把NULL放入堆栈,这个0x0也是一个地址,只不过他是空地址,PUSH 0x127B30 这个就是把 0x127B30这个地址放入堆栈,这个地址里面存放了吾爱汇编这个字符串,也就是说存放吾爱汇编这个字符串的地址是一个一级指针,PUSH 0x127B38 这个就是把 0x127B38 这个地址放入堆栈,这个地址里面存放了这个字符串在od中能看到吗这个字符串,这个地址是一个一级指针PUSH 0x0 这个就是把NULL放入堆栈,这个0x0也是一个地址,只不过他是空地址,在c语言中NULL就是空的意思,也就是空指针,他是一串0x00000000的地址,简写位0x0

我们下断点后,点击运行执行到下面这段代码,这个call就对应着代码里的MessageBoxA函数,这个在代码段下断点原理是什么呢?断点的原理是在断点处重写代码,插入一个int3中断指令,当CPU执行到int3指令的时候,OD就可以获得控制权。也就是说,当你下断点的时候,od会把你下断点的位置改为int 3指令,这个int 3指令与push类似,也是一条汇编指令,我们常常把这个int 3断点称为CC断点,原因是,int 3的汇编代码在od中的十六进制就是CC,

为什么我们看不见在断点出的int 3呢?因为这个od处理过了,因此我们看不到这个int 3汇编代码这个int 3,也就是CC 断点,常常用来检测软件是否被od调试了,00121781    FF15 98B01200   CALL DWORD PTR DS:             ; user32.MessageBoxA我们拿一段简单的反调试代码来分析
#include <iostream>
#include <windows.h>
using namespace std;
int main() {
      FARPROC Uaddr;
      BYTE Mark = 0;
      Uaddr = GetProcAddress(LoadLibrary(L"user32.dll"), "MessageBoxA");
      Mark = *((BYTE*)Uaddr);//取Messagebox函数的第一字节

      if (Mark == 0xCC)
      {
                MessageBoxA(NULL, "bad guy ", "", MB_OK);
                return TRUE;//发现断点
      }
      MessageBoxA(NULL, "good boy", "", MB_OK);
      
      cout << "hello" << endl;
      system("pause");
      return 0;
}
#include <iostream>包含一个头文件iostream,在c++中有些头文件后面的后缀.h默认省略因为这是一个系统头文件,所以需要加<>#include <windows.h> 包含一个windows的系统头文件
using namespace std;这个是使用一个命名空间,using namespace这个是固定的写法,std可以变换,这个std也可以类比成一个菜单,std里面也有很多函数,我们使用这个std命名空间后,里面的函数,我们在全局范围内都可以使用,比如cout << "hello" << endl;这个就是在c++中的打印hello,这个cout需要在使用std这个命名空间后才可以使用,endl是换行的意思,相当于\n我们看看这个FARPROC Uaddr;这个是定义一个函数指针变量,名字叫Uaddr我们Ctrl+左键,转到定义typedef int (FAR WINAPI *FARPROC)();这个就是定义一个函数指针,用来存放函数的地址的,typedef就是定义返回值为int类型,FAR代表他是远程地址调用,因为MessageBox这个函数地址相比自己的写的程序是很远的,WINAPI这个我前面说了,这个是__stdcall 代表函数他的参数是从右向左通过堆栈传递的,*代表这是一个指针,FARPROC代码这是一个函数指针的名字 我们再看一下这个变量,BYTE Mark = 0;变量类型为BYTE ,变量名为Mark;我们同样转到定义,可以看到他是一个无符号整型的一个数据类型。这个typedef相当于给 unsigned char 取了一个别名,叫BYTEtypedef unsigned char       BYTE;我们分析一下GetProcAddress,我们转到定义,可以看到他的返回值是FARPROC也就是说我们要用 int (FAR WINAPI *FARPROC)();这个来接收他,所以我们要定义一个变量来接收他FARPROC Uaddr,就是这个,WINAPI就不用多说了吧前面讲了两遍了__stdcall,这个是一个调用规范,规范一个参数的传递方式,也就是在堆栈中的存入顺序GetProcAddress这个是函数名hModule这个是函数的第一个参数lpProcName这个是函数的第二个参数FARPROCWINAPIGetProcAddress(    _In_ HMODULE hModule,    _In_ LPCSTR lpProcName);我们转到定义,分析一下下面这两个参数是什么LPCSTR :CHAR *LPCSTR, *PCSTR这个我们可以知道,他是一个指针,存放的数据类型为CHAR类型的HMODULE :这个是一个句柄,只有拿到句柄后,我们才可能有权限操作,
我们看一下第一个参数,这个是加载库文件,因为MessageBoxA函数就在这个user32.dll里面,所以我们要调用这个函数,这个函数他会返回一个句柄,也就是HMODULE ,我们可以把这个LoadLibrary(L"user32.dll")就理解成HMODULE ,因为他的返回值就是HMODULE ,
下面这个是LoadLibrary函数的定义,为什么是LoadLibraryW呢?因为下面这句代码,他把LoadLibraryW改名为LoadLibrary,因此LoadLibrary的原型为LoadLibraryW#define LoadLibraryLoadLibraryW
HMODULE   这个是返回值,是一个句柄WINAPI这个是调用规范,也就是参数在堆栈中的传递顺序LoadLibraryW 这个是函数名LPCWSTR lpLibFileName 这个是函数的参数,也就是库的文件名LPCWSTR WCHAR *LPCWSTR, *PCWSTR; 可以知道他是一个指针,我们看看WCHAR 是什么, 这个是定义,typedef wchar_t WCHAR; 把wchar_t定义为WHAR,也就是改名typedef unsigned short wchar_t;把unsigned short改名为wchar_t也就是说unsigned short是LPCWSTR 的实际数据类型HMODULE   WINAPILoadLibraryW(    _In_ LPCWSTR lpLibFileName    );
LoadLibrary(L"user32.dll")为什么前面要加个L呢?这个L告诉我们的c编译器把user32.dll字符串按宽字符保存-即每个字符占用2个字节 GetProcAddress(LoadLibrary(L"user32.dll"), "MessageBoxA");我们看看最后的参数,"MessageBoxA",就是从user32.dll里找MessageA这个函数,实际上,代码变成可执行文件后,每个函数都会有一个地址,不仅仅是变量数据,获得返回值后,给前面定义好的函数指针变量下面我们分析下面这个,将Uaddr转为BYTE *类型,在前面我们已经说了,这个BYTE的实际类型是char类,也就是一个字节,也就是说,把Uaddr转为存放一个字节数据的地址,因为我们的CC断点,也就是int 3汇编代码他的十六进制只要一个字节就可以存下,所以char就够了

转为BYTE *类型后,对这个BYTE *类型的地址加个*取里面的值,也就可以取出是否被下断点了Mark = *((BYTE*)Uaddr);我们看看下面这个代码,下面这个是条件判断语句格式为if(条件){条件成立执行的代码;
}在c语言中非0即真,也就是说除了0之外都是真的,不管你是负数也好,随意一个数都好,只要不是0就是真的。Mark == 0xCC这个就是进行比较,Mark是前面我们定义的一个变量判断Mark是否等于0xCC 这个0xCC就是int 3的十六进制代码,如果等于说明被下断点了,如果下了断点就会弹窗提示为bad guy,我们回到MessageBoxA函数,看看官方说明hWnd
类型:HWND
要创建的消息框的所有者窗口的句柄。如果此参数为NULL,则消息框没有所有者窗口。
lpText
类型:LPCTSTR
要显示的消息。如果字符串由多行组成,您可以在每行之间使用回车符和/或换行符分隔各行。
lpCaption
类型:LPCTSTR
对话框标题。如果此参数为NULL,则默认标题为Error。
uType
类型:UINT
对话框的内容和行为。此参数可以是来自以下标志组的标志的组合。第一个参数一般为NULL,我们并不需要用到句柄,不需要进行操控,第二个参数和第三参数都为CHAR *LPCSTR, *PCSTR;也就是字符指针类型,存放字符的指针,第二个参数为要提示的消息,第三个为标题,第四个为要显示什么类型的对话框具体什么类型,可以上官网查看,复制拿来用就行,https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxa当然我们也分析一下第四个参数是什么这个是MB_OK的定义#define MB_OK                     0x00000000L
第四个参数是下面这个typedef unsigned int      UINT;他是一个无符号整型
在c++中引入的TRUE和False,也就是真和假,实际上就是整型数字取了别名而已如果发现了的话,就直接返回了,也就是说弹出提示框,bad guy后就退出了,因为一个函数遇到return时,他就代表着这个函数要结束了。当我们未发现下断点时则会执行这条代码system("pause");这个是暂停控制台,防止程序一运行窗口就没了。这个system函数的定义如下返回值为int ,调用惯例为 __cdecl,函数名为system, __cdecl惯例的所有参数从右到左依次入栈,这些参数由调用者清除参数为char指针类型,且这个值无法被修改,因为加了const,* command就是取值,加了const就是这个值不能被修改int __cdecl system(      _In_opt_z_ char const* _Command      );return 0;程序正常返回,返回值为0,这个return是固定的,后面的值随便MessageBoxA(NULL, "good boy", "", MB_OK);BYTE Mark = 0;if (Mark == 0xCC)      {                MessageBoxA(NULL, "bad guy ", "", MB_OK);                return TRUE;//发现断点      }我们放入od,测试一下我们的反调试,我们需要在MessageBoxA上下断

点运行后可以发现,检测到了
我们聊聊反反调试吧,因为他检测的段首,也就是检测了一个字节,也就是说我们可以往下进行下断了,但是如果他检测整个代码怎么办呢?我们可以尝试在GetProcAddress函数执行的时候进行拦截,让他获取不了MessageBoxA函数的修改数据,从而达到过反调试。我们回到前面的话题,调用惯例,。


堆栈是一个先进后出的存储顺序先push的在最底下,最后push的在最上面先push的是Style = MB_OK|MB_APPLMODAL是这个,这个是第四个参数第二个push的参数Title = "吾爱汇编"是这个第三个push的参数Text = "这个字符串在od中能看到吗"是这个第四个push的参数hOwner = NULL,003CF888   00000000|hOwner = NULL003CF88C   00917E48|Text = "这个字符串在od中能看到吗"003CF890   00917BF4|Title = "吾爱汇编"003CF894   00000000\Style = MB_OK|MB_APPLMODAL003CF898   00911023OFFSET Project1.<ModuleEntryPoint>003CF89C   00911023OFFSET Project1.<ModuleEntryPoint>
由此证明了,__stdcall 代表函数他的参数是从右向左通过堆栈传递的,因为我们的参数与传入堆栈的顺序完全相反,倒着传递的
我们回到之前的问题,od中搜索出来的东西在代码中是怎样的呢?通过下面的图片,我们可以知道,字符串是会显示在od的搜索工具中那如果是数字,还会显示吗?我们试一试

#include<Windows.h>
#include <stdio.h>
int main() {



      int a = 666666;
      int b = 8888888;
      int c = 2222222;
      MessageBoxA(NULL, "这个字符串在od中能看到吗", "吾爱汇编",NULL);
      //int a = 10;
      //int* p = &a;
      //printf("p= %x\n", p);
      //printf("*p=%d\n", *p);
      //int** pp = &p;
      //printf("pp= %x\n", pp);
      //printf("*pp= %x\n", *pp);
      //printf("**pp= %d\n", **pp);
      return 0;
}
我们定义了三个整数,我们点击生成,然后载入od
可以看到,数字并不会显示在od的搜索字符串中
好的,我们回到那个文字语音的转换软件中,定位到那个提示vip会员的位置

可以看到push了字符串的地址,然后调用了函数我们到上面去看看,看看有没有跳过这个提示的汇编代码
可以看到有个JNZ大跳,跳过了提示开通会员的代码

我们分析一下下面这段代码0011114E    FFD0            CALL EAX00111150    0FB6C8          MOVZX ECX,AL00111153    85C9            TEST ECX,ECX00111155    0F85 F3020000   JNZ 0011144E                           ; SDTextVo.0011144ECALL EAX,这个是调用一个函数 eax在代码中,可以理解成一个变量我们分析一下eax ,eax有8位0x00000000-0xFFFFFFFFax就是四位0x0000-0xffffal就是两位0x00-0xFF也就是说当我们给ax,赋值为8位的是不行的,但是ax给8位寄存器的赋值是可以的小的可以给大的赋值,大的不能给小的赋值下面这个一大块就是寄存器,
当我们用eax,给al赋值时会提示错误
当我们用eax,给ax赋值时也会提示错误

下面的这些都是32位的寄存器EAX、ECX、EDX、EBX为数据寄存器;
ESP、EBP为指针寄存器;
ESI、EDI变址寄存器。
把E去除后,比如ax,就变成16位寄存器了,ax又可以分为al,于ah,举例子当我们执行mov ah,66,时可以看到eax的值为11116633因此6633中的66就是ah

al也一样,我们看图当我们执行下面的汇编代码的时候00111168    B0 88         MOV AL,0x88Eax变为了,11116688

也就是说al就是6688中的88我们分析一下下面这个汇编代码0011116A    0FB6C0          MOVZX EAX,AL
执行前eax 为11116688

执行后变为了00000088也就是说他把111166全部变为了0,只有al变为88

下面我们分析一下test指令Test指令用于判断一个寄存器值是否为0,如果不为零就把Z标志位变为0,从而影响JNZ的跳转,JNZ汇编代码只要Z标志为0就会跳转,为1就继续向下执行代码看图eax并不为零,执行test eax,eax后,JNZ变为0,然后JNZ就跳转了,


我们回到前面的代码
0011114E    FFD0            CALL EAX00111150    0FB6C8          MOVZX ECX,AL00111153    85C9            TEST ECX,ECX00111155    0F85 F3020000   JNZ 0011144E                           ; SDTextVo.0011144E
来个扩展,函数的返回值是存放在eax寄存器的,我们写个代码验证一下
#include<Windows.h>
#include <stdio.h>
int test() {

      return 6666;

}
int main() {



      int a = 666666;
      int b = 8888888;
      int c = 2222222;
      MessageBoxA(NULL, "这个字符串在od中能看到吗", "吾爱汇编",NULL);
      test();
      //int a = 10;
      //int* p = &a;
      //printf("p= %x\n", p);
      //printf("*p=%d\n", *p);
      //int** pp = &p;
      //printf("pp= %x\n", pp);
      //printf("*pp= %x\n", *pp);
      //printf("**pp= %d\n", **pp);
      return 0;
}
我们自己写一个函数,函数名为test,返回值为int类型,我们返回6666这个值;int test() {
      return 6666;
}记得要调用,如果没调用的话,写了也没用我们上od分析

我们执行完这个test的函数后,他返回了一个十六进制的值,经过转换,可以知道他就是我们程序里写的6666,因此函数的返回值是放在eax的,给eax赋值为1或者其他,只要不是0就行,然后retn,返回即可在od中实际跟代码也相差不多,return 6666;只不过这个6666用eax寄存器保存了而已,我们进入call eax这个汇编代码,然后进行赋值操作

530A3700    B8 66660000   MOV EAX,0x6666530A3705    C3            RET530A3706    90            NOP
在汇编中,我们写return是无法识别的,我们需要写成retn,或者ret都行


选择我们修改好的汇编代码,右键复制到可执行文件,然后选择所有修改,进行覆盖即可




白云点缀的蓝 发表于 2021-7-28 23:43

本帖最后由 白云点缀的蓝 于 2021-11-9 22:25 编辑

支持一下~~~

白云点缀的蓝 发表于 2021-7-28 23:05

@Shark恒 有些代码没有排版好,可以帮忙排版一下吗?谢谢

Shark恒 发表于 2021-7-29 10:46

好家伙,直接好家伙!讲解是够深入的,把CC断点的检测逻辑都讲到了,把指针也讲到了,确实很细致。赞一下,得给一个精华

大彩笔 发表于 2021-7-29 11:23

starry、星空 发表于 2021-7-28 23:43
DOC文档版:
https://starrysp.lanzoui.com/iODZnryy87a这个就很好!论坛图片加载 不出来!

白云点缀的蓝 发表于 2021-7-29 12:50

大彩笔 发表于 2021-7-29 11:23
这个就很好!论坛图片加载 不出来!

恒大说在解决这个了,应该很快就能处理好吧

POP 发表于 2021-7-29 17:52

本帖最后由 192939 于 2021-7-29 18:58 编辑

辛苦楼主耐心讲了一大圈

POP 发表于 2021-7-29 18:49

试了下可以免登陆VIP,就是转换出来的语音没情感太机械

tingwei3 发表于 2021-7-29 21:17

好文,点赞了

lies 发表于 2021-7-31 00:29

谢谢教程,收藏
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 对一个文字转语音软件的分析