首页
社区
课程
招聘
[原创]分享下wow PE section保护技术
2021-6-11 23:06 16170

[原创]分享下wow PE section保护技术

2021-6-11 23:06
16170

节保护技术

1
WOW 使用了PE节保护技术,重新映射PE内存,致使PE节不可修改,这个技术比较有意思,可以参考    [self-remapping](https://github.com/changeofpace/Self-Remapping-Code)

知道了技术原理,怎么解决,直接上代码

1
2
只喜欢用lua凑合看吧,都是调用windowsapi领会意思就行 
注意先SuspendProcess,重新映射后ResumeProcess
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
local STATUS_SUCCESS = 0
local ViewShare = 1
local ViewUnmap = 2
local SEC_NO_CHANGE = 0x00400000;
local SEC_COMMIT = 0x08000000
local MEM_RELEASE = 0x00008000
local NULL = ffi.cast("void *", 0)
 
function _RemapViewOfSection(ProcessHandle, BaseAddress, RegionSize, NewProtection, CopyBuffer)
 
    local numberOfBytesRead = ffi.new("SIZE_T[1]")
    if ffi.C.ReadProcessMemory(ProcessHandle, ffi.cast("void *", BaseAddress), ffi.cast("void *", CopyBuffer), RegionSize, numberOfBytesRead) == 0 then
        return false;
    end
 
 
 
    local hSection = ffi.new("HANDLE[1]");
    local sectionMaxSize = ffi.new("LARGE_INTEGER[1]")
    sectionMaxSize[0].QuadPart = RegionSize;
 
    local status = win.ntdll.NtCreateSection(hSection, win.SECTION_ALL_ACCESS, NULL, sectionMaxSize, win.PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);
    if (status ~= STATUS_SUCCESS) then
        return false;
    end
 
    --// Unmap the current view.
    status = win.ntdll.NtUnmapViewOfSection(ProcessHandle, ffi.cast("void *", BaseAddress));
    if status ~= STATUS_SUCCESS then
        return false;
    end
 
 
    local viewBase = ffi.new("uintptr_t[1]", BaseAddress)
    local sectionOffset = ffi.new("LARGE_INTEGER[1]")
    local viewSize = ffi.new("SIZE_T[1]");
    status = win.ntdll.NtMapViewOfSection(
            hSection[0],
            ProcessHandle,
            ffi.cast("void **", viewBase),
            0,
            RegionSize,
            sectionOffset,
            viewSize,
            ViewUnmap,
            0,
            NewProtection);
    if status ~= STATUS_SUCCESS then
        return false;
    end
 
    local numberOfBytesWritten = ffi.new("SIZE_T[1]")
    if ffi.C.WriteProcessMemory(ProcessHandle, ffi.cast("void *", viewBase[0]), ffi.cast("void *", CopyBuffer), viewSize[0], numberOfBytesWritten) == 0 then
        return false;
    end
    return true;
end
 
function RemapViewOfSection(ProcessHandle, BaseAddress, RegionSize, NewProtection)
    local copybuf = ffi.C.VirtualAlloc(ffi.cast("void *", 0), RegionSize, bit.bor(win.MEM_COMMIT, win.MEM_RESERVE), win.PAGE_EXECUTE_READWRITE)
    if not copybuf then
        return false
    end
    local result = _RemapViewOfSection(ProcessHandle, BaseAddress, RegionSize, NewProtection, copybuf);
    ffi.C.VirtualFree(copybuf, 0, MEM_RELEASE);
    return result;
end
 
 
local PROCESS_ALL_ACCESS = 0x1fffff
local BaseAddress = 0x0000000140000000
local RegionSize = 0x0000000002330000
local NewProtection = 0x40
 
local 进程名 = "wowclassic.exe"
local tbl = enum.EnumSystemProcessorInformation()
for _, process in pairs(tbl) do
   local processname = process.Name:lower()
   if processname == 进程名 then
       local hProcess = ffi.C.OpenProcess(PROCESS_ALL_ACCESS, 0, process.Pid);
       if hProcess then
           win.ntdll.NtSuspendProcess(hProcess)
           print(RemapViewOfSection(hProcess, BaseAddress, RegionSize, NewProtection))
           win.ntdll.NtResumeProcess(hProcess)
           ffi.C.CloseHandle(hProcess)
           break
       end
   end
end

调用游戏lua那些事

游戏的关键lua函数加了检测,主要是检测返回地址,而且不是检测返回地址是模块PE范围内就行,返回地址必须是游戏真实调用lua函数的返回地址,而且检测字节码...
例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.text:00000001403AE6E0 48 89 5C 24 08                                mov     [rsp+8], rbx
.text:00000001403AE6E5 48 89 74 24 10                                mov     [rsp-18h+arg_8], rsi
.text:00000001403AE6EA 48 89 7C 24 18                                mov     [rsp-18h+arg_10], rdi
.text:00000001403AE6EF 55                                            push    rbp
.text:00000001403AE6F0 41 56                                         push    r14
.text:00000001403AE6F2 41 57                                         push    r15
.text:00000001403AE6F4 48 8B EC                                      mov     rbp, rsp
.text:00000001403AE6F7 48 83 EC 50                                   sub     rsp, 50h
.text:00000001403AE6FB 4C 8B 35 C6 CF 2A 02                          mov     r14, cs:qword_14265B6C8
.text:00000001403AE702 49 8B F0                                      mov     rsi, r8
.text:00000001403AE705 44 8B 05 C4 C9 FA 01                          mov     r8d, cs:dword_14235B0D0
.text:00000001403AE70C 48 8B D9                                      mov     rbx, rcx
.text:00000001403AE70F 8B FA                                         mov     edi, edx
.text:00000001403AE711 49 8B CE                                      mov     rcx, r14
.text:00000001403AE714 BA F0 D8 FF FF                                mov     edx, 0FFFFD8F0h
.text:00000001403AE719 4D 8B F9                                      mov     r15, r9
.text:00000001403AE71C E8 BF 3B 62 01                                call    sub_1419D22E0
.text:00000001403AE721 44 8B C7                                      mov     r8d, edi
.text:00000001403AE724 4C 8B CE                                      mov     r9, rsi
.text:00000001403AE727 48 8B D3                                      mov     rdx, rbx
.text:00000001403AE72A 49 8B CE                                      mov     rcx, r14
.text:00000001403AE72D E8 DE 5C 62 01                                call    pfn_luaL_loadbuffer   这里是游戏自己调用的地方,那么loadbuffer会检测返回地址是不是 00000001403AE732
.text:00000001403AE732 8B C8                                         mov     ecx, eax              检测返回地址是不是这里

解决方案

还是看代码:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
--返回地址检查
--压入返回地址,调用完毕 jmp回去,返回地址必须是游戏自己调用时的地址
 
local safe_ret = 0x00000001403AE732    --返回地址,必须用真实的,原因说了,检测地址并且检测字节码...
function luaL_loadbuffer(lua_state, buff, sz, name)
    local asmcode = [[
    push rbp
    push rsp
    sub rsp,0x100
    mov rbp,rsp
    add rbp,0x30
 
    mov r9, buff_name
    mov r8, buff_size
    mov rdx,pbuff
    mov rcx,lua_state
    mov rax,pfn_luaL_loadbuffer
 
    mov r15 ,safe_ret  --压入返回地址 jmp                  
    push r15
    jmp rax
 
--结束点
    add rsp,0x100      --代码会从返回地址处jmp回来,正常结束
    pop rsp
    pop rbp
    ret
    ]]
    asmcode = asmcode:gsub("pfn_luaL_loadbuffer", "0x" .. bit64.tohex(pfn_luaL_loadbuffer))
    asmcode = asmcode:gsub("lua_state", "0x" .. bit64.tohex(lua_state))
    asmcode = asmcode:gsub("pbuff", "0x" .. bit64.tohex(ptonumber(buff)))
    asmcode = asmcode:gsub("buff_size", "0x" .. bit64.tohex(sz))
    asmcode = asmcode:gsub("buff_name", "0x" .. bit64.tohex(ptonumber(name)))
    asmcode = asmcode:gsub("safe_ret", "0x" .. bit64.tohex(safe_ret))
    local bcode, code = luaasm.Assemble(asmcode, ptonumber(pasmaddr), "x64")
    ffi.copy(pasmaddr, bcode, #bcode)
 
    --下面是改写返回地址后的代码,跳回上面的  结束点
    local oldbuf = ffi.new("char[0x20]")
    ffi.copy(oldbuf, ffi.cast("char *", safe_ret), 0x10)--备份返回地址处的代码
 
    local jmp_code = [[
    mov r15,addr
    jmp r15
    ]]
    jmp_code = jmp_code:gsub("addr", "0x" .. bit64.tohex(ptonumber(pasmaddr) + 0x3d))
    bcode, code = luaasm.Assemble(jmp_code, ptonumber(safe_ret), "x64")
    ffi.copy(ffi.cast("char *", safe_ret + 2), bcode, #bcode) --跳转代码写在返回地址后 + 2 字节处
    local ret = asmcall_c(ptonumber(pasmaddr))  --开始调用
 
    --调用完毕恢复返回地址的代码
    ffi.copy(ffi.cast("char *", safe_ret), oldbuf, 0x10)
    return ret
end

发文目的

告诉大家,wow 从调试到注入到调用lua,不需要任何r0技术,不要再用高射炮打蚊子了......
这是2020年1.13.6.36714 作为例子写的,理解了原理套用现在的版本就没问题


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞6
打赏
分享
最新回复 (11)
雪    币: 65
活跃值: (402)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
hhstudy 1 2021-6-15 16:11
2
0
都是干货. Mark 一下。
雪    币: 4062
活跃值: (2377)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lylxd 2021-6-18 16:47
3
0
我还买了个 传说中得VT反反调试工具
雪    币: 4062
活跃值: (2377)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lylxd 2021-6-18 16:48
4
0
雪    币: 63
活跃值: (89)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
fullxu 2021-6-20 21:08
5
0
感谢大佬雪中送炭。无奈技术不行,目前是通过R0勉强过了反调试,但是卡在不能下int3断点这里,应该就是您说的代码段被重映射保护了
雪    币: 123
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
KiSyscall64 2021-6-22 01:53
6
0
int3的问题我解决了一部分。  WOW用的rdtsc指令判断CPU时间戳间隔,用VT接管即可,生成一个专门的时间队列,每次WOW调用就加1000,不调用就不加。 但WOW每秒都在自己触发INT 3,我现在虽然断了下来暂停多久都能恢复运行,但只要调试器不忽略INT3 断点,由于INT3触发太频繁根本没法单步。我卡在了附加调试器之后进入游戏,登陆界面好好的,一进入游戏就会崩溃 ,有人知道原因吗...
雪    币: 123
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
KiSyscall64 2021-6-22 01:57
7
0
还有 你们有没有遇到线程检查,我看外国佬是HOOK的DxGi->PreSent,从PreSent去call lua,有人功能做到这一步了吗
雪    币: 63
活跃值: (89)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
fullxu 2021-6-23 08:58
8
0

继续踩坑

最后于 2021-6-24 16:30 被fullxu编辑 ,原因:
雪    币: 63
活跃值: (89)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
fullxu 2021-6-25 07:56
9
0
大佬你好,我直接用调试器启动游戏可以下断调试,但1~2分钟后就会触发异常崩掉,即使你什么都不做
如果是附加,只要触发断点就立刻异常,把异常抛给被调试程序也没用。
从r3~vt都试过了,我很菜只会用现成的插件,需要自己处理的部分完全不知道怎么操作
雪    币: 40
活跃值: (38)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
happyZore 2021-7-1 22:14
10
0
我按照大佬的代码去掉了代码页的保护,  去掉页面保护之前,游戏CRC正常执行,但是去掉保护之后,个别CRC 不再执行了,请问这是什么原因
雪    币: 11
活跃值: (199)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
sabanhai 2021-8-14 11:04
11
0
Mark 一下
雪    币: 248
活跃值: (3779)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
luskyc 2021-8-14 13:00
12
0
很老的手段了,section给写就好了
游客
登录 | 注册 方可回帖
返回