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

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

2021-6-11 23:06
9361

节保护技术

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 作为例子写的,理解了原理套用现在的版本就没问题


[培训] 优秀毕业生寄语:恭喜id咸鱼炒白菜拿到远超3W月薪的offer,《安卓高级研修班》火热招生!!!

收藏
点赞3
打赏
分享