写一个PE的壳_Part 6:简单的混淆


系列汇总


处理完Part 4,Part 5会轻松很多;Part 6主要是对Part 4 中的最终结果中加入简单的混淆

1.清理

step 1:移除不必要信息

  • 当前现状

我们已经使用 -nostartfiles-nostdlib编译选项,在最终的二进制文件中移除了C的标准库和运行时库;但是结果PE中可能还有其他不必要的section和符号信息

给cal.exe打包的后的section分布如下

在这里插入图片描述

  • 加入改变

我们将新增2个编译选项:-fno-ident-fno-asynchronous-unwind-tables 进一步清除section

-fno-ident:忽略#ident命令
-fno-asynchronous-unwind-tables:用来不生成CFI指令,禁止生成.eh_frame和.eh_frame_hdr section

同时使用strip.exe移除符号信息和调试信息,进一步减小文件体积

因此,compile_stub函数更改如下

def compile_stub(input_cfile, output_exe_file, more_parameters = []):
    cmd = (["mingw32-gcc.exe", input_cfile, "-o", output_exe_file] # Force the ImageBase of the destination PE
        + more_parameters +
        ["-Wl,--entry=__start", 		# define the entry point
        "-nostartfiles", "-nostdlib", 	# no standard lib
        "-fno-ident",  "-fno-asynchronous-unwind-tables", # Remove unnecessary sections
        "-lkernel32" 					# Add necessary imports
        ])
    print("[+] Compiling stub : "+" ".join(cmd))
    subprocess.run(cmd)
    subprocess.run(["strip.exe", output_exe_file])

step 2:重命名.packed

当前我们有一个非常明确的名字,.packed是我们自己的section;为了通用性,我们可以将它定义为只读(read-only),且名字用通用名字.rodata替换

packed_section = lief.PE.Section(".rodata")
    packed_section.content =  packed_data
    packed_section.size = len(packed_data)
    packed_section.characteristics = (lief.PE.SECTION_CHARACTERISTICS.MEM_READ
                                    | lief.PE.SECTION_CHARACTERISTICS.CNT_INITIALIZED_DATA)

不知你是否还记得,我们在C程序里是要解析.packed名称的section的,且整个section是二进制文件中的最后一个section,因此C代码修正如下:

char* packed_PE = unpacker_VA + sections[p_NT_HDR->FileHeader.NumberOfSections - 1].VirtualAddress;

现在一切操作正常的话,对于支持ASLR的二进制文件(本次使用calc.exe测试)打包后,我们应该有3个section:.text.idata(upacker.exe的import table)和.rodata(要被打包的PE)

在这里插入图片描述

可以看到,.rdata消失了,运行一下,程序依然可以正常运行;可以发现,在.rodata中有一个完整的PE信息,这对于解密者来说没有什么秘密而言

2.简单的混淆

MZ和DOS stub这些在在.rodata中都可以找到,我们试着隐藏一下这些信息

为了简单处理,我们对整个输入文件进行异或处理;简单做法就是给.rdata放入input数据时异或一次,真正执行input数据前再异或一次进行解密

step 1:改变packer(python)

使用硬编码,对输入文件进行异或操作;为了让它更复杂一点,使用每个结果字节作为下一个字节的密钥(CBC加密模式)

def encrypt_data(data) :
    KEY = 0xAA
    result = [0] * len(data)
    for i in range(0, len(data)):
        KEY = data[i] ^ KEY
        result[i] = KEY
    return result

input数据放入.rodata前,调用encrypt_data函数加密input文件

packed_data = encrypt_data(list(input_PE_data)) 	# encrypt the input file data
packed_data = pad_data(packed_data, file_alignment) # pad with 0 to align with file alignment (removes a lief warning)

packed_section = lief.PE.Section(".rodata")
packed_section.content = packed_data

step 2:改变unpacker(C)

执行input文件前,要执行解密的过程,即执行上面pyhton中encrypt_data函数的逆运算

void decrypt_data(char* src, DWORD size) {
    DWORD oldProtect;
    //make sure we can write on the destination
    VirtualProtect(src, size, PAGE_READWRITE, &oldProtect);

    DWORD KEY = 0xAA;
    DWORD new_key = 0;
    for(DWORD i=0; i<size; ++i) {
        new_key = src[i];
        src[i] = src[i] ^ KEY;
        KEY = new_key;
    }
}

加载.rodata数据前,要调用encrypt_data进行解密

encrypt_data(packed_PE, sections[p_NT_HDR->FileHeader.NumberOfSections - 1].SizeOfRawData);

3.结果

被打包测试程序是calc.exe,最后得到的打包好的二进制文件依然可以弹出计算器

现在查看打包好的二进制文件,拥有的section更少,.rodata里面的内容已经被混淆了

在这里插入图片描述

仅仅一个简单的混淆,再想逆向出PE的完整内容就会增加很多难度;静态分析时,杀毒原件查看文件时于不会看到calc.exe的签名,因为calc.exe的所有内容都被异或处理了;动态分析时,某个时刻还是可以找到calc.exe的所有信息的

4.其他可能

  • 输入表包含了5个已知的函数(用来导入其他函数),他们是恶意代码常用的函数;我们可以使用LIEF添加许多其他看起来不那么可疑的内容,即使我们没有使用它们
  • 异或非常容易被破解;知道PE文件的头2个bytes是MZ,很容易Crack出关键字KEY和算法;这里可以使用压缩算法或者自己写的复杂算法
  • 避免添加一个完整的section,可以通过其他方式找到被打包的二进制数据

5.参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值