65.9K
CodeProject 正在变化。 阅读更多。
Home

ASM.Net - x86 仿真

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (31投票s)

2011年12月26日

CPOL

3分钟阅读

viewsIcon

53736

downloadIcon

839

ASM.Net 一个 x86 汇编仿真器,以托管方式仿真语言

Asm.Net 是一个使用 C# 开发的汇编仿真器,它还可以帮助您在 .NET Framework 中使用汇编语言开发程序。您还可以使用 Asm.Net 来仿真运行程序。

介绍 

ASM.Net 是一个 x86 汇编仿真器,可以仿真汇编指令。虽然目前并非全部指令都支持。但已有大量指令/操作码正在工作。这可能已足够您在其中进行开发。

ASM.Net 还能够将您编写的所有代码转换为一个字节数组,ASM.Net 可以再次读取该字节数组以获取所有指令/变量/API。因此,您在 ASM.Net 中编写的代码是完全可移植的,可以在另一台计算机上运行,或者进行处理,无论您想去哪里,都不会出现任何问题。但这还不是全部,ASM.Net 能够生成的字节数组在每个部分都被加密,并进行压缩,以防止代码被盗。

使用代码

使用 ASM.Net 中的代码很容易,但您确实需要具备一些汇编知识才能理解其中的任何内容,例如 JMP、JNZ、CALL、XOR 等等。使用 OpcodeWriter,我们可以编写代码,创建变量,以及做更多的事情。最后,当我们编写了程序所需的一切之后,我们还可以使用 ASM.Net 调试器进行调试,看看一切是否如预期般工作。

这是一个编写 MessageBox 的示例代码

OpcodeWriter writer = new OpcodeWriter();

//Call MessageBox
writer.codeSection.PUSH_VALUE(0); //push a value to stack
//ASM.Net creates a new variable and pushes the address of it to stack
writer.codeSection.PUSH_STRING("Title here");
writer.codeSection.PUSH_STRING("Hello CodeProject!");
writer.codeSection.PUSH_VALUE(0); //push a value to stack
writer.codeSection.CALL(Functions.User32_MessageBoxA); //call our MessageBox
AsmNet asmNet = new AsmNet(writer.Generate(true)); //initialize ASM.Net
Processor Cpu = asmNet.InitializeCPU(); //initialize the CPU to be able to execute our code
Cpu.RunLoop(); //execute the code we wrote

如您所见,显示 MessageBox 的代码量并不多。您可能已经注意到,ASM.Net 只需要一个参数,就是我之前告诉您的字节数组。OpcodeWriter 使用 Generate 函数可以转换为字节数组。您看到的 Generate 函数的参数 True 是为了在运行时检查每个 JUMP 地址是否存在错误。这可以在您将代码分享给他人之前,在运行时防止错误。

但显示一个简单的 MessageBox 并不是 ASM.Net 的全部功能,它还可以做更多的事情。这是一个简单的 TCP 服务器

OpcodeWriter writer = new OpcodeWriter();

WSAData wsaData = new WSAData();
sockaddr_in sockaddr = new sockaddr_in();
sockaddr_in Clientsockaddr = new sockaddr_in();
VirtualAddress wsaDataAddr = writer.dataSection.CreateVariable(wsaData);
VirtualAddress SockinAddress = writer.dataSection.CreateVariable(sockaddr);
VirtualAddress ClientSockinAddress = writer.dataSection.CreateVariable(Clientsockaddr);
VirtualAddress ArrayAddress = 
  writer.dataSection.CreateVariable(ASCIIEncoding.ASCII.GetBytes(":)"));
//the data we want to send when a client connects

//socket initialization
//set the WSADATA settings
writer.codeSection.MOV_VARIABLE_VALUE(wsaDataAddr, "HighVersion", (ushort)2);
writer.codeSection.MOV_VARIABLE_VALUE(wsaDataAddr, "Version", (ushort)2);

//set the sockaddr_in settings, setting the family IPv4
writer.codeSection.MOV_VARIABLE_VALUE(SockinAddress, 
  "sin_family", (short)ValueCodes.InterNetworkv4);
//setting port, we need to encode it first...
writer.codeSection.PUSH_VALUE(1337); //1337=listen port
writer.codeSection.CALL(Functions.ws2_32_htons);
writer.codeSection.MOV_VARIABLE_REGISTER(SockinAddress, "sin_port", Register.EAX);

writer.codeSection.PUSH_VARIABLE(wsaDataAddr);
writer.codeSection.PUSH_VALUE(36);
writer.codeSection.CALL(Functions.ws2_32_WSAStartup);

//started successfully ?
writer.codeSection.MOV_ECX(0);
writer.codeSection.CMP(CmpRegisterOpcodes.CMP_ECX_EAX);
writer.codeSection.JNE("failed");

//create a socket
writer.codeSection.PUSH_VALUE(ValueCodes.Tcp, (int)0);
writer.codeSection.PUSH_VALUE(ValueCodes.Stream, (int)0);
writer.codeSection.PUSH_VALUE(ValueCodes.InterNetworkv4, (int)0);
writer.codeSection.CALL(Functions.ws2_32_socket);
                
//is socket > 0 ?
writer.codeSection.MOV_ECX((int)ValueCodes.INVALID_SOCKET);
writer.codeSection.CMP(CmpRegisterOpcodes.CMP_ECX_EAX);
writer.codeSection.JE("failed");

//lets move our socket handle to EBX
writer.codeSection.MOV(MovRegisterOpcodes.MOV_EBX_EAX);

//lets bind our socket
writer.codeSection.PUSH_VALUE(Marshal.SizeOf(sockaddr));
writer.codeSection.PUSH_VARIABLE(SockinAddress); //our sockaddr_in
writer.codeSection.PUSH_EBX(); //socket handle
writer.codeSection.CALL(Functions.ws2_32_bind);

//ok lets listen at a port
writer.codeSection.PUSH_VALUE((int)100);
writer.codeSection.PUSH_EBX(); //socket
writer.codeSection.CALL(Functions.ws2_32_listen);


//now a infinite loop for accept our connections but lets setup our console
writer.codeSection.PUSH_VALUE(-11); //STD_OUTPUT_HANDLE
writer.codeSection.CALL(Functions.Kernel32_GetStdHandle);
writer.codeSection.MOV(MovRegisterOpcodes.MOV_EDX_EAX);

writer.codeSection.CreateLabel("loop");
//lets accept connections
writer.codeSection.PUSH_VALUE(Marshal.SizeOf(Clientsockaddr));
writer.codeSection.PUSH_VARIABLE(ClientSockinAddress);
writer.codeSection.PUSH_EBX(); //server socket
writer.codeSection.CALL(Functions.ws2_32_accept);
writer.codeSection.MOV(MovRegisterOpcodes.MOV_EDI_EAX); //set client socket to EDI


writer.codeSection.PUSH_VALUE(0);
writer.codeSection.PUSH_VALUE(0);
writer.codeSection.PUSH_VALUE(20);//char length
writer.codeSection.PUSH_STRING("new client accepted\r\n");
writer.codeSection.PUSH_EDX();
writer.codeSection.CALL(Functions.Kernel32_WriteConsoleA);

//lets send a packet
writer.codeSection.PUSH_VALUE(0);
writer.codeSection.PUSH_VALUE(2);
writer.codeSection.PUSH_VARIABLE(ArrayAddress);
writer.codeSection.PUSH_EDI(); //client socket
writer.codeSection.CALL(Functions.ws2_32_send);

//close our connection with the client...
writer.codeSection.PUSH_EDI();
writer.codeSection.CALL(Functions.ws2_32_closesocket);

writer.codeSection.JMP("loop");

writer.codeSection.PUSH_EBX();
writer.codeSection.CALL(Functions.ws2_32_closesocket);

writer.codeSection.CreateLabel("failed");
writer.codeSection.XOR(XorRegisterOpcodes.XOR_ECX_ECX); 

当您阅读代码时,会发现一些 JUMPs、Calls 等等。但这个示例是用于启动一个简单的 TCP 服务器,该服务器接受连接,并向连接者发送一个简单的笑脸 "Smile | <img src= " align="top" src="https://codeproject.org.cn/script/Forums/Images/smiley_smile.gif" /> "。正如您在此图像中看到的,它正在等待新连接。顺便说一句,不要以为这是 Linux,这只是一个带有 Ubuntu 主题的 Windows 7。

好的,例如,我们在 Windows 的命令提示符下输入 "telnet 127.0.0.1 1337"。

正如您所见,当我按下回车键后,命令提示符显示了一个笑脸。

您可以看到 "Smile | <img src= " align="top" src="https://codeproject.org.cn/script/Forums/Images/smiley_smile.gif" /> " 出现了,并且工作得很好,并且在另一个控制台中写了一些内容,表示已接受新连接(请参阅上面 TCP 服务器中的代码)。为了使调试稍微容易一些,让我们看看我们的 JUMPs 会去哪里。

正如您所见,红线立即显示了 JUMP 的去向。如果您有任何问题,我很乐意回答。但您也应该花点时间深入研究代码。还有事件等,供您使用。

兴趣点 

在制作 ASM.Net 的过程中,我需要笑一笑,因为我现在可以编写可移植的代码,而不是使用 CodeDom,它可能会因为缺少某个 .dll 文件而导致错误,并将代码转储到临时目录,以便人们可以访问和窃取代码。

ASM.Net 可以随处运行,但它仍然需要 ASM.Net 库,并且如果不需要,甚至不需要转储到硬盘。

历史

您可以查看我们的 SVN:

© . All rights reserved.