2024 腾讯游戏安全大赛 mobile 决赛 wp

dump SDK

image-20250331143657656

GWorld: 0x4F5C0D0

GNAME: 0x4E2EC00

GUObjectArray: 0x4E533AC

dump失败,猜测结构体被魔改,接下来读 UE4Dumper的源码

image-20250403103444274

image-20250403110135144

算了,直接修改 ue4dumper源码,写个while循环,不断观察内存


void debug_dump_pointer(kaddr addr, uint32 size) {
    printf("\n0x%08p:\n",addr);
    
    uint32 *buffer = new uint32[size / 4];
    memset(buffer, '\0', size);
    vm_readv((void *) addr, buffer, size);
    for (int i = 0; i < size / 4; i++) {
        printf("%08x ", buffer[i]);
        if ((i + 1) % 4 == 0) {
            printf("\n");
        }
    }

    delete[] buffer;
}

void debug_hexdump(kaddr addr, uint32 size) {
    printf("\n");
    uint8 *buffer = new uint8[size];
    memset(buffer, '\0', size);
    vm_readv((void *) addr, buffer, size);

    for (int i = 0; i < size; i++) {
        printf("%02x ", buffer[i]);
        if ((i + 1) % 16 == 0) {
            printf("\n");
        }
    }
    delete[] buffer;
}

void dump_mem_plug(){
    while(1){
        printf("input addr:\n");
        uint32 target_addr = 0;
        scanf("%x", &target_addr);
        debug_dump_pointer(target_addr, 0x100);

    }
}

最后找出部分魔改点,即可成功dump sdk


    void patchUE425_32_for_tencent(){
		TUObjectArrayToNumElements = 0xc;   
        UWorldToPersistentLevel = 0x58;
        ULevelToAActors = 0x9c;
        ULevelToAActorsCount = 0xa4;
        UObjectToClassPrivate = 0x14;
        UObjectToFNameIndex = 0x18;
        UObjectToOuterPrivate = 0x20;
        UStructToSuperStruct = 0x40;
        UStructToChildren = 0x6c;
        UStructToChildProperties = 0x44;
        UFunctionToFunctionFlags = 0x84;
        UFunctionToFunc = 0xa4;
        UFieldToNext = 0x2c;

        FUObjectItemPadd = 0x4;
        FUObjectItemSize = 0x14;

    }

    void patchUE425_32(){                          

		// ...
		// ...
		// ...
        patchUE425_32_for_tencent();
    }


混淆处理

自变量的间接相加

# 1、修改参数类型,加const
import idc
import idaapi
import idautils
def change_var_type(ea, new_type):
    # current_type = idc.get_type(ea)
    idc.SetType(ea, new_type)


begin = 0x00005D8
end = 0x4D8A108
for i in range(begin,end,4):
    var_address = i
    new_type = "const unsigned __int"
    change_var_type(var_address, new_type)

seed作为函数参数传递

类似这样,直接暴力patch,

image-20250409181211574

image-20250409181231378

更绝一点,甚至可以直接patch掉开头的push指令,反正我们的目的只不过是看伪代码/

image-20250409181259283

image-20250409181304372

mov pc, xxx 混淆的处理

对于简单、稍复杂的结构,直接nop即可

image-20250409181406480

就算碰到了循环结构也可以试着直接nop

image-20250409181446369

比如这里,虽然强行的nop毁掉了循环结构,但实际上,通过部分伪代码依然可以明显的看出这是个rc4加密

字符串混淆

unidbg慢慢调试,恢复即可。这里的字符串混淆很少。

vm还原

最好的方法是trace一下,然后照着分支找相关的vm指令.一点点的恢复即可.


# inp: tlsn00112233445566778899aabbccdd =>  74 6C 73 6E 30 30 31 31  32 32 33 33 34 34 35 35 36 36 37 37 38 38 39 39  61 61 62 62 63 63 64 64
# out: 11 12 13 14 00 00 00 00 ...

# "bic r0, r5, #0xf8000000" : 按位清除,这条指令就是在清除高5位

from struct import unpack, pack
from keystone import *

flog =open("vm.log", "w+") 

xvm = open("vm_xcode.bin", "wb")
ins_cnt =0 
def pt(p):
    global pc,flog,ins_cnt,xvm
    xxx = str(ins_cnt).rjust(8,"0") + " =>   " +  hex(pc-8) + " : " + p
    flog.write(xxx + "\n")
    ins_cnt += 1

    print("pc: " + hex(pc-8) + " ins_cnt: " + str(ins_cnt) + " ===> " + p)
    asm = p
    address = pc-8
    xcode = asm2code(asm,address)
    xvm.write(bytes(xcode))

def write_data(p):
    global pc,xvm
    print(f"pc:  {hex(pc)} write_data: {p.hex()}")
    xvm.write(p)

def asm2code(code,address):
    ks = Ks(KS_ARCH_ARM,KS_MODE_ARM)          # 注意区分 ARM和 Thumb
    encoding, count =ks.asm(code, address)
    
    xcode = []
    for i in encoding:
        xcode.append(i)
    return xcode




arm32_regs = ["r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
             "r8", "r9", "r10", "r11", "r12", "sp", "lr", "pc",
             "r7", "eflag_N", "eflag_Z", "eflag_V", "eflag_C", "r21", "r22"]

def get_bit_range(r1, r2, r3):
    mask = (1 << (r3 - r2 )) - 1;
    mask = mask << r2;
    val = (r1 & mask) >> r2;
    return val

def parse_push(opcode):
    global arm32_regs
    regs = opcode & 0x7ffffff    # 只要低0x1b位

    regs_list = []
    for i in range(17):
        if (regs >> i) & 0x1:
            regs_list.append(arm32_regs[i])
    pt("push {" + ",".join(regs_list) + "}") # 取出寄存器列表

def parse_pop(opcode):
    
    global arm32_regs
    regs = opcode & 0x7ffffff    # 只要低0x1b位

    regs_list = []
    for i in range(17):
        if (regs >> i) & 0x1:
            regs_list.append(arm32_regs[i])
    pt("pop {" + ",".join(regs_list) + "}") # 取出寄存器列表
def parse_add(opcode):
    global arm32_regs
    cond = get_bit_range(opcode, 0x19, 0x1b)
    dreg = get_bit_range(opcode, 0x14, 0x19) # 4bit


    if cond == 0:
        sreg = get_bit_range(opcode, 0x0f, 0x14) # 5bit
        imm = get_bit_range(opcode, 0x0, 0x0f) # 15bit
        pt("add " + arm32_regs[dreg] + "," + arm32_regs[sreg] + ", #" + hex(imm)) # 取出寄存器列表    
    elif cond == 1:
        sreg1 = get_bit_range(opcode,0x0f,0x14)
        sreg2 = get_bit_range(opcode,0x0a,0x0f)
        pt("add " + arm32_regs[dreg] + "," + arm32_regs[sreg1] + "," + arm32_regs[sreg2])
    elif cond == 2:
        sreg1 = get_bit_range(opcode,0x0f,0x14)
        sreg2 = get_bit_range(opcode,0x0a,0x0f)
        imm = get_bit_range(opcode,0x0,0x0a)
        pt("add " + arm32_regs[dreg] + "," + arm32_regs[sreg1] + "," + arm32_regs[sreg2] + ", lsl #" + hex(imm))
    else:
        assert 0

def parse_subs(opcode):
    global arm32_regs
    cond = get_bit_range(opcode, 0x19, 0x1b)
    dreg = get_bit_range(opcode, 0x14, 0x19) # 4bit
    
    if cond == 0:
        sreg = get_bit_range(opcode, 0x0f, 0x14) # 5bit
        imm = get_bit_range(opcode, 0x0, 0x0f) # 15bit
        pt("sub " + arm32_regs[dreg] + "," + arm32_regs[sreg] + ", # " + hex(imm)) # 取出寄存器列表
    else:
        assert 0


# 这里又是经典的没考虑全面,漏了imm
# cnt[s1] = *(cnt[s4] + s6)
def parse_ldr(opcode):
    global arm32_regs
    # if pc == 0x0000984+8:
    #     print("111")
    dreg = get_bit_range(opcode, 0x16, 0x1b)
    width_tag = get_bit_range(opcode, 0x14, 0x16)
    reg_tag = get_bit_range(opcode, 0x12, 0x14)
    sreg = get_bit_range(opcode, 0x0d, 0x12)
    if reg_tag == 0:
        
        sub_tag = get_bit_range(opcode, 0x0c, 0x0d)
        imm = get_bit_range(opcode, 0x0, 0x0c)
        if sub_tag == 0:
            if width_tag == 0:
                pt("ldr " + arm32_regs[dreg] + "," + "[" + arm32_regs[sreg] + ",#" + hex(imm) + "]")
            else:
                pt("ldrb " + arm32_regs[dreg] + "," + "["+arm32_regs[sreg] + ",#" + hex(imm)+"]")
        else:
            if width_tag == 0:
                pt("ldr " + arm32_regs[dreg] + "," + "[" + arm32_regs[sreg] + ",#" + hex(imm) + "]")
            else:
                pt("ldrb " + arm32_regs[dreg] + "," + "["+arm32_regs[sreg] + ",#" + hex(imm)+"]")
    else:
        sreg2 = get_bit_range(opcode, 0x08, 0x0d)
        imm = get_bit_range(opcode, 0x0, 0x08)
        if width_tag == 0:
            pt("ldr " + arm32_regs[dreg] + "," + "["+arm32_regs[sreg] + "," + arm32_regs[sreg2] +", lsl #" + hex(imm)  + "]")
        else:
            pt("ldrb " + arm32_regs[dreg] + "," + "["+arm32_regs[sreg] + "," + arm32_regs[sreg2] +", lsl #" + hex(imm)  + "]")


# ==========>  [40986002,0x16,0x1b) => 0x2    <==========
# ==========>  [40986002,0x14,0x16) => 0x1    <==========
# ==========>  [40986002,0x12,0x14) => 0x2    <==========
# ==========>  [40986002,0x0d,0x12) => 0x3    <==========
# ==========>  [40986002,0x08,0x0d) => 0x0    <==========
# ==========>  [40986002,0x00,0x08) => 0x2    <==========
# ==========> ins_type: 8  <======> ins: 40986002 <==========

# [17:42:10 051][libUE4.so 0x46d15bd] [4ff42172] 0x446d15bc: "mov.w r2, #0x284" => r2=0x284
# [17:42:10 051][libUE4.so 0x46d15c1] [ddf834e0] 0x446d15c0: "ldr.w lr, [sp, #0x34]" sp=0xbffff5a0 => lr=0x2ef242e4
# [17:42:10 052][libUE4.so 0x46d15c5] [baf1020f] 0x446d15c4: "cmp.w sl, #2" sl=0x2 => cpsr: N=0, Z=1, C=1, V=0
# [17:42:10 052][libUE4.so 0x46d15c9] [08bf    ] 0x446d15c8: "it eq"
# [17:42:10 052][libUE4.so 0x46d15cb] [ec22    ] 0x446d15ca: "movs r2, #0xec" => r2=0xec
# [17:42:10 052][libUE4.so 0x46d15cd] [3168    ] 0x446d15cc: "ldr r1, [r6]" r6=0x44d89ec4 => r1=0x5247280
# [17:42:10 052][libUE4.so 0x46d15cf] [dc46    ] 0x446d15ce: "mov ip, fp" fp=0x3fb42490 => ip=0x3fb42490
# 还是考虑不全面,没考虑 reg_tag = 0x2的情况,果然,还是得用脚本来找
def parse_str(opcode):
    global arm32_regs
    dreg = get_bit_range(opcode, 0x16, 0x1b)
    width_tag = get_bit_range(opcode, 0x14, 0x16)
    reg_tag = get_bit_range(opcode, 0x12, 0x14)
    sreg = get_bit_range(opcode, 0x0d, 0x12)

    if pc == 0x0000AFC + 8:
        print("111")
    if reg_tag == 0:
        sub_tag = get_bit_range(opcode, 0x0c, 0x0d)
        imm = get_bit_range(opcode, 0x0, 0x0c)
        if sub_tag == 0:
            if width_tag == 0:
                pt("str " + arm32_regs[dreg] + "," + "[" + arm32_regs[sreg] + ",#" + hex(imm) + "]")
            else:
                pt("strb " + arm32_regs[dreg] + "," + "["+arm32_regs[sreg] + ",#" + hex(imm)+"]")
        else:
            if width_tag == 0:
                pt("str " + arm32_regs[dreg] + "," + "[" + arm32_regs[sreg] + ",#" + hex(imm) + "]")
            else:
                pt("strb " + arm32_regs[dreg] + "," + "["+arm32_regs[sreg] + ",#" + hex(imm)+"]")
    else:
        sreg2 = get_bit_range(opcode, 0x08, 0x0d)
        if width_tag == 0:
            pt("str " + arm32_regs[dreg] + ",[" + arm32_regs[sreg] + "," + arm32_regs[sreg2] + "]" )
        else:
            pt("strb " + arm32_regs[dreg] +  ",[" + arm32_regs[sreg] + "," + arm32_regs[sreg2] + "]" )


def parse_mov(opcode):
    global arm32_regs
    mov_cate = get_bit_range(opcode, 0x19, 0x1b)
    reg_tag = get_bit_range(opcode, 0x18, 0x19)
    dreg = get_bit_range(opcode, 0x13, 0x18) # 目标寄存器
    dreg_name = arm32_regs[dreg]
    # if pc == 0x000728 + 0x8:
    #     print("111")
    mne = ""
    if mov_cate == 0 or mov_cate == 2:
        mne = "mov"
    elif mov_cate == 1:
        mne = "mvns"
    else:
        assert 0

    if reg_tag == 0:                
        imm = get_bit_range(opcode, 0x0, 0x13)
        pt(f"{mne} {dreg_name}, #{hex(imm)}") # 
    else:
        sreg = get_bit_range(opcode, 0x0e, 0x13)
        sreg_name = arm32_regs[sreg]
        pt(f"{mne} {dreg_name}, {sreg_name}") #

def parse_cmp(opcode):         
    global arm32_regs
    reg_tag = get_bit_range(opcode, 0x1a, 0x1b)
    reg1 = get_bit_range(opcode, 0x15, 0x1a) # 
    mne = "cmp"
    if reg_tag == 0:
        imm = get_bit_range(opcode, 0x0, 0x15)
        pt(f"{mne} {arm32_regs[reg1]}, #{hex(imm)}") # 取出寄存器列表
    else:
        reg2 = get_bit_range(opcode, 0x10, 0x15)
        pt(f"{mne} {arm32_regs[reg1]}, {arm32_regs[reg2]}") # 取出寄存器列表


def parse_lsr(opcode):
    global arm32_regs
    imm_tag = get_bit_range(opcode, 0x1a, 0x1b)
    dreg = get_bit_range(opcode, 0x15, 0x1a) #
    mne = "lsr"
    if imm_tag == 0:
        
        sreg1 = get_bit_range(opcode, 0x10, 0x15)
        sreg2 = get_bit_range(opcode, 0x0b, 0x10)
        pt(f"{mne} {arm32_regs[dreg]}, {arm32_regs[sreg1]}, {arm32_regs[sreg2]}")
    else:
        sreg = get_bit_range(opcode, 0x10, 0x15)
        imm = get_bit_range(opcode, 0x0, 0x10)
        pt(f"{mne} {arm32_regs[dreg]}, {arm32_regs[sreg]}, #{hex(imm)}") # 

def parse_lsl(opcode):
    global arm32_regs
    dreg = get_bit_range(opcode, 0x16, 0x1b) #
    sreg = get_bit_range(opcode, 0x11, 0x16) #
    imm = get_bit_range(opcode, 0x0, 0x11)
    mne = "lsl"
    pt(f"{mne} {arm32_regs[dreg]}, {arm32_regs[sreg]}, #{hex(imm)}") # 取出寄存器列表


def parse_ror(opcode):
    global arm32_regs
    dreg = get_bit_range(opcode, 0x16, 0x1b) #
    sreg1 = get_bit_range(opcode, 0x11, 0x16) #
    sreg2 = get_bit_range(opcode, 0x0c, 0x11)
    mne = "ror"
    print(f"pc: {hex(pc)} opcode: {hex(opcode)}")
    pt(f"{mne} {arm32_regs[dreg]}, {arm32_regs[sreg1]}, {arm32_regs[sreg2]}") # 取出寄存器列表


# pc -= 4
# r1 = pc
# r2 = cnt[0x15],固定值:0x44d864a4
# r0 = s1 + r2
# ctx[pc] = r0,ctx[0x10] = r1  

def parse_bl(opcode):
    global pc
    imm = get_bit_range(opcode, 0x0, 0x1b)
    cnt_0x15 = 0
    pt(f"bl {hex(cnt_0x15 + imm)}")
    # pc += imm

def parse_ubfx(opcode):
    global arm32_regs
    dreg = get_bit_range(opcode, 0x16, 0x1b)
    sreg = get_bit_range(opcode, 0x11, 0x16)
    start = get_bit_range(opcode, 0x08, 0x11)
    end = get_bit_range(opcode, 0x0, 0x08)
    mne = "ubfx"
    pt(f"{mne} {arm32_regs[dreg]}, {arm32_regs[sreg]}, #{start}, #{end}") # 取出寄存器列表

def parse_strb(opcode):
    global arm32_regs
    dreg = get_bit_range(opcode, 0x16, 0x1b)
    sreg = get_bit_range(opcode, 0x11, 0x16)
    mne = "strb"
    pt(f"{mne} {arm32_regs[sreg]}, [{arm32_regs[dreg]}]") # 取出寄存器列表

def parse_orr(opcode):
    global arm32_regs
    reg_tag = get_bit_range(opcode, 0x1a, 0x1b)
    dreg = get_bit_range(opcode, 0x15, 0x1a) #
    sreg1 = get_bit_range(opcode, 0x10, 0x15)
    sreg2 = get_bit_range(opcode, 0x0b, 0x10)
    nme = "orr"
    if reg_tag == 0:
        imm = get_bit_range(opcode, 0x0, 0xb)
        pt(f"{nme} {arm32_regs[dreg]}, {arm32_regs[sreg1]}, {arm32_regs[sreg2]}, lsl #{hex(imm)}") # 取出寄存器列表
    else:
        pt(f"{nme} {arm32_regs[dreg]}, {arm32_regs[sreg1]}, {arm32_regs[sreg2]}") 


# 老是把reg_tag与imm_tag搞混了
def parse_xor(opcode):
    global arm32_regs
    imm_tag = get_bit_range(opcode, 0x1a, 0x1b)
    dreg = get_bit_range(opcode, 0x15, 0x1a) #
    sreg1 = get_bit_range(opcode, 0x10, 0x15)
    nme = "eor"
    if imm_tag == 1:
        imm = get_bit_range(opcode, 0x0, 0xb)
        pt(f"{nme} {arm32_regs[dreg]}, {arm32_regs[sreg1]}, #{hex(imm)} ") # 取出寄存器列表
    else:
        sreg2 = get_bit_range(opcode, 0x0b, 0x10)
        pt(f"{nme} {arm32_regs[dreg]}, {arm32_regs[sreg1]}, {arm32_regs[sreg2]}")


# 大小端互换
def parse_rev(opcode):
    global arm32_regs
    dreg = get_bit_range(opcode, 0x16, 0x1b) # 
    sreg = get_bit_range(opcode, 0x11, 0x16) # 
    mne = "rev"
    pt(f"{mne} {arm32_regs[dreg]}, {arm32_regs[sreg]}") # 取出寄存器列表

def parse_and(opcode):
    global arm32_regs
    dreg = get_bit_range(opcode, 0x16, 0x1b) #
    sreg = get_bit_range(opcode, 0x11, 0x16) #
    imm = get_bit_range(opcode, 0x0, 0x11)
    mne = "and"
    pt(f"{mne} {arm32_regs[dreg]}, {arm32_regs[sreg]}, #{hex(imm)}") 


# 经典subs打成 RSBS
def parse_sub(opcode):
    global arm32_regs
    dreg = get_bit_range(opcode, 0x16, 0x1b) # 
    sreg = get_bit_range(opcode, 0x11, 0x16) # 
    imm = get_bit_range(opcode, 0x0, 0x11)
    mne = "subs"
    pt(f"{mne} {arm32_regs[dreg]},  {arm32_regs[sreg]} , #{hex(imm)}" ) # 取出寄存器列表

def parse_asr(opcode):
    global arm32_regs
    dreg = get_bit_range(opcode, 0x16, 0x1b) # 
    sreg = get_bit_range(opcode, 0x11, 0x16) # 
    imm = get_bit_range(opcode, 0x0, 0x11)
    mne = "asr"
    pt(f"{mne} {arm32_regs[dreg]}, {arm32_regs[sreg]}, #{hex(imm)}") # 取出寄存器列表

def parse_branch(opcode):
    global arm32_regs,pc
    cnt_0x15 = 0
    cond = get_bit_range(opcode, 0x17, 0x1b)
    tag = get_bit_range(opcode, 0x16, 0x17) 
    if tag == 0:
        imm = get_bit_range(opcode, 0x00, 0x16) 
        if cond == 0x0:
            pt(f"b {hex(cnt_0x15 + imm)}")
        elif cond == 0x1:
            pt(f"beq {hex(cnt_0x15 + imm)}")
        elif cond == 0x2:
            pt(f"bne {hex(cnt_0x15 + imm)}")
        elif cond == 0x3:
            pt(f"bcc {hex(cnt_0x15 + imm)}")
        elif cond == 0x7:
            pt(f"bge {hex(cnt_0x15 + imm)}")
        elif cond == 0x6:
            pt(f"blt {hex(cnt_0x15 + imm)}")
        else:
            assert 0
    else:
        pt(f"mov pc, lr")

    

def parse_nop(opcode):
    pt("nop")

def vm_analyze(data):
    global pc
    pc = 0
    cnt = 0
    while pc < len(data):
        opcode = unpack(">I",data[pc:pc+4])[0]
        pc += 8            
        Rd = opcode >> 0x1b              # 27
        # print(f"cnt: {cnt} ===> ins_type: {Rd} ===> opcode: {hex(opcode)}")
        cnt += 1
        if pc >= 0x2000:
            write_data(data[pc-8:pc-4])
            pc -=4
        elif Rd == 0x0:
            # parse_nop(opcode)          # nop
            write_data(b"\x00\x00\x00\x00")
            pc -= 4
        elif Rd == 0x1:              # push {,,} # http://hehezhou.cn/A32-2024/push_stmdb.html    https://developer.arm.com/documentation/ddi0403/d/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-ARMv7-M-Thumb-instructions/PUSH?lang=en
            parse_push(opcode)
            pc -= 4
        elif Rd == 0x2:
            parse_pop(opcode)
            pc -= 4
        elif Rd == 0x3:                 # add dreg,sreg,imm , 不标准, 参考: http://hehezhou.cn/A32-2024/pop_ldm.html
            parse_add(opcode)
            pc -= 4
        elif Rd == 0x4:
            parse_subs(opcode)
            pc -= 4
        elif Rd == 0x5:
            parse_lsr(opcode)
            pc -= 4
        elif Rd == 0x6:
            parse_ror(opcode)
            pc -= 4
        elif Rd == 0x7:
            parse_ldr(opcode)
            pc -= 4
        elif Rd == 0x8:
            parse_str(opcode)
            pc -= 4
        elif Rd == 0x9:
            parse_mov(opcode)
            pc -= 4
        elif Rd == 0xa:
            parse_cmp(opcode)
            pc -= 4            
        elif Rd == 0xb:                    
            parse_branch(opcode)
            pc -=4
        elif Rd == 0xc:
            parse_bl(opcode)
            pc -= 4                     # 哎呀,这里不应该不加减4的!!!我这只是在解析vm啊!
        elif Rd == 0xd:
            parse_ubfx(opcode)
            pc -= 4
        elif Rd == 0xe:
            parse_strb(opcode)
            pc -= 4
        elif Rd == 0xf:
            parse_orr(opcode)
            pc -= 4
        elif Rd == 0x10:
            parse_xor(opcode)
            pc -= 4
        elif Rd == 0x11:
            parse_lsl(opcode)
            pc -= 4            
        elif Rd == 0x12:
            parse_rev(opcode)
            pc -= 4
        elif Rd == 0x13:
            parse_and(opcode)
            pc -= 4
        elif Rd == 0x14:
            parse_sub(opcode)
            pc -= 4
        elif Rd == 0x15:
            parse_asr(opcode)
            pc -= 4
        else:
            write_data(data[pc-8:pc-4])
            pc -=4 
            


fp = open("vm.bin", "rb")
data = fp.read()
vm_analyze(data)

flog.close()
xvm.close()

最后也是得到了加密逻辑,当然我的vm解析器某一两处写的不够完善,虽然如此,但问题不大.

image-20250409181716392

逆算法很容易,就一魔改AES,我没逆。

透视与自瞄的实现

本来想写 .so注入的,但后来经过尝试发现,frida注入后,代码并不卡顿,因此就写so注入.


const GWorld_Offset = 0x4F5C0D0
const GName_Offset = 0x4E2EC00
const GUObjectArray = 0x4E533AC

function pt_all_actor(){
    var libUE4_module = Module.findBaseAddress("libUE4.so")
    console.log("libUE4_module is :", libUE4_module)


    var GName = libUE4_module.add(GName_Offset);
    var GWorld = libUE4_module.add(GWorld_Offset).readPointer()
   
    // var Level_Offset = 0x20
    var Level_Offset = 0x58
    var Level = GWorld.add(Level_Offset).readPointer()
    console.log("Level :", Level)

    // var Actors_Offset = 0x70
    var Actors_Offset = 0x9c;
    var Actors = Level.add(Actors_Offset).readPointer()
    console.log("Actors Array :", Actors)


    // var AActorsCount = 0x74;
    var AActorsCount = 0xa4;
    var Actors_Num = Level.add(AActorsCount).readU32()
    console.log("Actors_num :", Actors_Num)


    for(var index = 0; index < Actors_Num; index++){
        var actor = Actors.add(index * 4).readPointer()
        //console.log("actor", actor)
        //通过角色actor获取其成员变量FName
        // var FName_Offset = 0x10
        var FName_Offset = 0x18
        var FName = actor.add(FName_Offset);

        var FNameEntryAllocator = GName
        var Blocks_Offset = 0x30
        var Blocks = FNameEntryAllocator.add(Blocks_Offset)
        //手动解析FNamePool
        var ComparisonIndex = FName.add(0).readU32()

        var FNameBlockOffsetBits = 16
        var FNameBlockOffsets = 65536
        var Block = ComparisonIndex >> FNameBlockOffsetBits
        var Offset = ComparisonIndex & (FNameBlockOffsets - 1)

        var FNameEntry = Blocks.add(Block * 4).readPointer().add(Offset * 2)
        var FNameEntryHeader = FNameEntry.readU16()

        var isWide = FNameEntryHeader & 1
        var Len = FNameEntryHeader >> 6
        if(0 == isWide){
            console.log("actor : ", actor, " ", FNameEntry.add(2).readCString(Len))
        } 
    }
}




var tag = 0
let Actor_Name = []
let Actor_addr = []   // 优化 find_actor的查找
function find_actor(Actor_name,cnt = 0){
    if(tag == 0){
        tag = 1;
        var libUE4_module = Module.findBaseAddress("libUE4.so")
        var GName = libUE4_module.add(GName_Offset);
        var GWorld = libUE4_module.add(GWorld_Offset).readPointer()
        // var Level_Offset = 0x20
        var Level_Offset = 0x58
        var Level = GWorld.add(Level_Offset).readPointer()
        // var Actors_Offset = 0x70
        var Actors_Offset = 0x9c;
        var Actors = Level.add(Actors_Offset).readPointer()
        // var AActorsCount = 0x74;
        var AActorsCount = 0xa4;
        var Actors_Num = Level.add(AActorsCount).readU32()
    
        for(var index = 0; index < Actors_Num; index++){

            try{
                var actor = Actors.add(index * 4).readPointer()
                //console.log("actor", actor)
                // var FName_Offset = 0x10
                var FName_Offset = 0x18
                var FName = actor.add(FName_Offset);
        
                var FNameEntryAllocator = GName
                var Blocks_Offset = 0x30
                var Blocks = FNameEntryAllocator.add(Blocks_Offset)
                //手动解析FNamePool
                var ComparisonIndex = FName.add(0).readU32()
        
                var FNameBlockOffsetBits = 16
                var FNameBlockOffsets = 65536
                var Block = ComparisonIndex >> FNameBlockOffsetBits
                var Offset = ComparisonIndex & (FNameBlockOffsets - 1)
        
                var FNameEntry = Blocks.add(Block * 4).readPointer().add(Offset * 2)
                var FNameEntryHeader = FNameEntry.readU16()
        
                var isWide = FNameEntryHeader & 1
                var Len = FNameEntryHeader >> 6
                var Name = FNameEntry.add(2).readCString(Len)
                if(0 == isWide){
                    Actor_Name.push(Name)
                    Actor_addr.push(actor)
                } 
            }catch(e){

            }
            
        }

    }


    for(var i in Actor_Name){
        if(Actor_Name[i] == Actor_name){
            if(cnt == 0){
                return Actor_addr[i]
            }else{
                cnt--
            }
        }
    }
    
    
}


function get_so_base(){
    var libUE4_so = Module.findBaseAddress("libUE4.so")
    return libUE4_so
}

function hook_Gworld_data(){
    try{
        var so_base = get_so_base()
        console.log("so_base is :", so_base)
        var GWorld = so_base.add(GName_Offset).readPointer()
        console.log(GWorld.readByteArray(0x100))

    }catch(e){
        console.log("hook_Gworld_data error")
    }

    
}


function hook_fun(){
    var hook_list = [0x1dc2b70]
    var so_base = get_so_base()
    console.log("so_base is :", so_base)
    for(var i in hook_list){
        const offset = hook_list[i]
        Interceptor.attach(so_base.add(offset), {
            onEnter: function(args) {
                if (offset == 0x1dc2b70){
                    console.log("1111")
                }
            },
        });
        
    }
}


function hook_dlopen() {
    var dlopen = Module.findExportByName(null, "android_dlopen_ext");
     Interceptor.attach(dlopen, {
         onEnter: function (args) {
             this.call_hook = false;
             var so_name = ptr(args[0]).readCString();
             if (so_name.indexOf("libUE4.so") >= 0) {
                 console.log("dlopen:", ptr(args[0]).readCString());
                 this.call_hook = true;
             }
 
         }, onLeave: function (retval) {
             if (this.call_hook) {
                anti_check()
                hooker()
            }
         }
     });

 }


function hex(m){
    return "0x" + m.toString(16);
}

function stack_backstace(your_this){
    console.log('stack backtrace:\n' +
    Thread.backtrace(your_this.context, Backtracer.ACCURATE)
        .map(DebugSymbol.fromAddress).join('\n') + '\n');
}

function anti_check(){
    var check_list = [0x19F637C,0x19f6354]
    var so_base = get_so_base()

    for(var i in check_list){
        const offset = check_list[i]
        console.log("so_base is :", so_base)
        Interceptor.attach(so_base.add(offset), {
            onEnter: function(args) {
                console.log(`offset: ${hex(offset)}`)
                // stack_backstace(this)
            },
        });
    }

    // anti 
    Memory.protect(so_base.add(0x19F6000), 0x1000, 'rwx')
    ptr(so_base.add(0x19F6398)).writeByteArray([0x0 ,0xF0 ,0x20 ,0xE3,0x00, 0x00, 0xA0, 0xE3])
    ptr(so_base.add(0x19F6370)).writeByteArray([0x0 ,0xF0 ,0x20 ,0xE3,0x00, 0x00, 0xA0, 0xE3])

}

function hook_check_fun(){
    var hook_list = [0x19F64A0,0x19f5a8c]
    var so_base = get_so_base()
    for(var i in hook_list){
        const offset = hook_list[i]
        Interceptor.attach(so_base.add(offset), {
            onEnter: function(args) {
                if(offset == 0x19F64A0){
                    console.log("here")
                    // var r2 = this.context.r2
                    // console.log(r2.readByteArray(0x100))

                }else if(offset == 0x19f5a8c){
                    console.log("other hook!!!!!")
                }
                // stack_backstace(this)
            },
        });
        
    }
}

function hook_skip_login(){

    // try{
    //     var so_base = get_so_base()
    //     var encry_fun = 0x19F4490
    //     Interceptor.attach(so_base.add(encry_fun), {
    //         onEnter: function(args) {
    //             this.out_cip = this.context.r1;
    
    //         },onLeave: function(retval) {
    //             try{
    //                 console.log(this.out_cip  )
    //                 if(this.out_cip){
    //                     console.log("skip login")
    //                     this.out_cip.writeByteArray([0x3d,0xf2,0x2c,0xf8,0x8f,0xfb,0x47,0x5b,0x49,0x4,0x78,0xd9,0x4e,0x31,0xef,0x3e,0xa1,0xa7,0xaa,0x7b,0xcf,0x72,0xa8,0xbc,0x53,0x2b,0x67,0x0,0xb2,0xb0,0x32,0xfa])
    //                 }
    //             }catch(e){
    //                 console.log("hook_skip_login error")
    //             }
    
    //         }
    //     });
    // }catch(e){
    //     console.log("xxx: " + e)
    // }


    try{
        var hook_fun = 0x19F646C
        var so_base = get_so_base()
        Interceptor.attach(so_base.add(hook_fun), {
            onEnter: function(args) {
                this.context.r0 = 0x1
            }
        });
    }catch(e){
        console.log("hook_skip_login error")
    }
}


function get_actor_location(Actor_name,cnt=0){              // 唯独表示不了摄像机世界坐标/第一人称世界坐标
    var Actor = find_actor(Actor_name,cnt)
    var RootComponent = Actor.add(0x100).readPointer()
    var loc1 = RootComponent.add(0x190).readFloat()
    var loc2 = RootComponent.add(0x194).readFloat()
    var loc3 = RootComponent.add(0x198).readFloat()
    return [loc1,loc2,loc3 ]

}

// 参考 GetActorEyesViewPoint 的前一半实现的功能
function get_firet_person_location(Actor_name,cnt=0){       // 只能表示摄像机世界坐标/第一人称世界坐标,或许可以起名: get_eyes_point_loc_addr
    try{
        var Actor = find_actor(Actor_name,cnt)
        var RootComponent = Actor.add(0x100).readPointer()
        var loc1 = RootComponent.add(0x190).readFloat()
        var loc2 = RootComponent.add(0x194).readFloat()
        var loc3 = RootComponent.add(0x198).readFloat()
        var BaseEyeHeight = Actor.add(0x1BC).readFloat()
        return [loc1,loc2,loc3 + BaseEyeHeight]

        
    }catch(e){
        console.log("get_firet_person_location error :", e)
    }

}

function get_eyes_point_rot_addr(Actor_name){
    try{

        // GetActorEyesViewPoint
        var Actor = find_actor(Actor_name)
        var Controller = Actor.add(0x1d4).readPointer()
        var s0 = Controller.add(0x200).readFloat()
        var s1 = Controller.add(0x204).readFloat()
        var s2 = Controller.add(0x208).readFloat()
        // console.log(`Controller: (${s0},${s1},${s2})`)
        return [s0,s1,s2]
    }catch(e){
        console.log(e)
    }

}


function hook_test(){
    var so_base = get_so_base()
    var hook = 0x39B5EA0
    Interceptor.attach(so_base.add(hook), {
        onEnter: function(args) {
            console.log("hook_test")
        }
    });
}


// 只有 PlayerController 可以
function get_GetViewportSize(Actor_name = "PlayerController"){
    try{
        // hook_test()
        var so_base = get_so_base()
        var noexec_GetViewportSize = so_base.add(0x39B5E68)
        var SizeX = Memory.alloc(0x100);
        var SizeY = Memory.alloc(0x100);
        var GetViewportSize = new NativeFunction(noexec_GetViewportSize, 'int', ['pointer','pointer','pointer']);
        var Actor = find_actor(Actor_name)
        GetViewportSize(Actor,SizeX,SizeY)
        var screen_width = SizeX.readU32()
        var screen_height = SizeY.readU32()
        return [screen_width,screen_height]
    }catch(e){
        console.log("get_GetViewportSize error " + e)
    }

}

class FVector {
    constructor(x, y, z) {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }
}

class FVector2D {
    constructor(x, y) {
        this.X = x;
        this.Y = y;
    }
}

function matrix_transform(target_pos,rotation,camera_pos){

    var rad_pitch = (rotation.X * Math.PI / 180.0);
    var rad_yaw = (rotation.Y * Math.PI / 180.0);
    var rad_roll = (rotation.Z * Math.PI / 180.0);

    var sp = Math.sin(rad_pitch);
    var cp = Math.cos(rad_pitch);
    var sy = Math.sin(rad_yaw);
    var cy = Math.cos(rad_yaw);

    var axis_x = new FVector(-sy, cy, 0);
    var axis_y = new FVector(-sp * cy, -sp * sy, cp);
    var axis_z = new FVector(cp * cy, cp * sy, sp);

    var delta = new FVector(target_pos.X - camera_pos.X, target_pos.Y - camera_pos.Y, target_pos.Z - camera_pos.Z);

    var transformed = new FVector(
        delta.X * axis_x.X + delta.Y * axis_x.Y + delta.Z * axis_x.Z,
        delta.X * axis_y.X + delta.Y * axis_y.Y + delta.Z * axis_y.Z,
        delta.X * axis_z.X + delta.Y * axis_z.Y + delta.Z * axis_z.Z
    );

    return transformed;
}


function world_to_screen(tar_loc,camera_rot,camera_loc,screen_width,screen_height,camera_fov){
    var target_pos = new FVector(tar_loc[0], tar_loc[1], tar_loc[2]);
    var camera_angle = new FVector(camera_rot[0], camera_rot[1], camera_rot[2]);
    var camera_location = new FVector(camera_loc[0], camera_loc[1], camera_loc[2]);

    var transformed = matrix_transform(target_pos, camera_angle, camera_location);
    if (transformed.Z < 0.0)
    {
        // 说明在背后,就没必要显示了
        return false;
    }


    var screen_center_x = screen_width / 2.0;
    var screen_center_y = screen_height / 2.0;

    var tmp_fov = Math.tan(camera_fov * Math.PI / 360.0);
    var screen_pos = new FVector2D(
        screen_center_x + transformed.X * (screen_center_x / tmp_fov) / transformed.Z,
        screen_center_y - transformed.Y * (screen_center_x / tmp_fov) / transformed.Z
    );

    return screen_pos;
}


function draw_DrawLine(Obj_addr,StartScreenX, StartScreenY, EndScreenX, EndScreenY, LineColor,LineThickness){
      try{
        var so_base = get_so_base()
        var onexec_DrawLine = so_base.add(0x3BDC3D8)
        var DrawLine = new NativeFunction(onexec_DrawLine, 'int', ['pointer','float','float','float','float','float','float','float','float','float']);

        
        DrawLine(ptr(Obj_addr), StartScreenX, StartScreenY, EndScreenX, EndScreenY,LineThickness, LineColor[0], LineColor[1], LineColor[2], LineColor[3],);
    }catch(e){
        console.log("draw_DrawLine error: " + e)
    }

}

function rot2vec(pitch,yaw){
    var viewDirection = {
        x: Math.cos(yaw * Math.PI / 180) * Math.cos(pitch * Math.PI / 180),
        y: Math.sin(yaw * Math.PI / 180) * Math.cos(pitch * Math.PI / 180),
        z: Math.sin(pitch * Math.PI / 180)
    };
    return [viewDirection.x, viewDirection.y, viewDirection.z]
}

function perspective2(){
    // ProjectWorldLocationToScreen
}
function get_distance(loc1,loc2){
    var dx = loc1[0] - loc2[0];
    var dy = loc1[1] - loc2[1];
    var dz = loc1[2] - loc2[2];
    return Math.sqrt(dx * dx + dy * dy + dz * dz);
}



// var loc_rot_fov = get_loc_rot_fov_from_Camera()
function Draw_one(Obj_addr,Actor_name, cnt, color){
    try{
        // 1_1、获取所有green坐标
        var loc = get_actor_location(Actor_name,cnt)           // 这个是可见的绿球
        // 2_1、获取我们自己的坐标
        var loc_firstperson = get_firet_person_location("FirstPersonCharacter_C")
        // 2_2、获取我们自己的朝向坐标
        var rot_first_person = get_eyes_point_rot_addr("FirstPersonCharacter_C")
        // console.log(`rot_first_person: (${rot_first_person[0]},${rot_first_person[1]},${rot_first_person[2]})`)
        // 3_1、获取手机屏幕大小
        var screen_mess = get_GetViewportSize()
        var screen_width = screen_mess[0]
        var screen_height = screen_mess[1]
        // 3_2、获取FOV
        var camera_fov = get_FOV()
        // 4_1、获取物体原本的大小(长宽高),这里固定都是50,因此不需要处理
        // var GreenBall0size = get_GetActorBounds("GreenBall",0)
        // 5_1、将green的坐标转换为屏幕坐标
        var screen_pos = world_to_screen(loc,rot_first_person,loc_firstperson,screen_width,screen_height,camera_fov)

        // 6、转化边框世界坐标为屏幕世界坐标
        var loc_z_max = loc[2] + 50
        var loc_z_min = loc[2] - 50
        var screen_z_max = world_to_screen([loc[0],loc[1],loc_z_max],rot_first_person,loc_firstperson,screen_width,screen_height,camera_fov)
        var screen_z_min = world_to_screen([loc[0],loc[1],loc_z_min],rot_first_person,loc_firstperson,screen_width,screen_height,camera_fov)
        var screen_ball_r = Math.abs(screen_z_max.Y - screen_z_min.Y)       // 这里是拿到了球在屏幕的直径
        // console.log(`screen_ball_r: ${screen_ball_r}`)

        // 7、绘制 green红框
        // console.log("screen_ball_r: ", screen_ball_r)
        var start_x = screen_pos.X - (screen_ball_r / 2)
        var start_y = screen_pos.Y - (screen_ball_r / 2) 
        var end_x = start_x + screen_ball_r 
        var end_y = start_y + screen_ball_r  
        draw_DrawLine(Obj_addr,start_x, start_y, start_x , end_y, color, 3.0);
        draw_DrawLine(Obj_addr,start_x, start_y  , end_x, start_y, color, 3.0); 
        draw_DrawLine(Obj_addr,end_x, start_y, end_x , end_y, color, 3.0);
        draw_DrawLine(Obj_addr,start_x, end_y, end_x,end_y, color, 3.0); 
    }catch(e){
        console.log("xx " + e)
    }
}


function find_obj_by_name(Obj_name,cnt= 0){
    try{
        var so_base = get_so_base()
        const FUObjectArrayToTUObjectArray = 0x10
        const TUObjectArrayToNumElements = 0xc
        
        var ocount = so_base.add(GUObjectArray).add(FUObjectArrayToTUObjectArray).add(TUObjectArrayToNumElements).readU32()
        var GName = so_base.add(GName_Offset);
        // console.log("Objects Counts: " + ocount)

        var ct = 0;
        for(var idx=0; idx<ocount;idx+=1){

            try{
                // console.log("idx: ", idx)
                const FUObjectArrayToTUObjectArray = 0x10
                const FUObjectItemPadd = 0x4
                const FUObjectItemSize = 0x14
                var TUObjectArray = so_base.add(GUObjectArray).add(FUObjectArrayToTUObjectArray).readPointer()
                var Chunk = TUObjectArray.add((idx / 0x10000) * 4).readPointer()
                var uobj = Chunk.add(FUObjectItemPadd).add((idx % 0x10000) * FUObjectItemSize).readPointer()
                
    
    
                var FName_Offset = 0x18
                var FName = uobj.add(FName_Offset);
                var FNameEntryAllocator = GName
                var Blocks_Offset = 0x30
                var Blocks = FNameEntryAllocator.add(Blocks_Offset)
                var ComparisonIndex = FName.add(0).readU32()
                var FNameBlockOffsetBits = 16
                var FNameBlockOffsets = 65536
                var Block = ComparisonIndex >> FNameBlockOffsetBits
                var Offset = ComparisonIndex & (FNameBlockOffsets - 1)
    
                var FNameEntry = Blocks.add(Block * 4).readPointer().add(Offset * 2)
                var FNameEntryHeader = FNameEntry.readU16()
    
                var isWide = FNameEntryHeader & 1
                var Len = FNameEntryHeader >> 6
                var Name = FNameEntry.add(2).readCString(Len)
                // console.log("uobj : ", uobj, " ", FNameEntry.add(2).readCString(Len))
    
                if(0 == isWide){
                    if(Name == Obj_name){
                        if(ct == cnt){
    
                            return uobj
                        }else{
                            ct++
                        }
                    }
                }
            }catch(e){
                
            }


        }
    }catch(e){
        console.log("find_obj_by_name error: " + e)
    }
    

}


/// 3green 3yellow,但图中只有3yellow 1green,而且有一个yellow在fly
function perspective1(Obj_addr){
    var red_color = [1.0, 0.0, 0.0, 1.0]
    var blue_color = [0.0, 0.0, 1.0, 1.0];
    var purple_color = [1.0, 0.0, 1.0, 1.0];
    try{
        Draw_one(Obj_addr,"GreenBall",0,red_color)
        // Draw_one(Obj_addr,"GreenBall",1,red_color)
        // Draw_one(Obj_addr,"GreenBall",2,red_color)

        Draw_one(Obj_addr,"YellowBall",0,blue_color)
        Draw_one(Obj_addr,"YellowBall",1,blue_color)
        Draw_one(Obj_addr,"YellowBall",2,blue_color)

    }catch(e){
        console.log("perspective1 error: " + e)
    }

}



function get_ProjectWorldToScreen(loc){
    try{

        var PlayerController_Actor = find_actor("PlayerController")
        
        var so_base = get_so_base()
        var ProjectWorldToScreen_addr = so_base.add(0x36E5CC4)
        var ProjectWorldToScreen = new NativeFunction(ProjectWorldToScreen_addr, 'int', ['pointer','pointer','pointer','int']);
        var WorldPosition = Memory.alloc(0x20);
        WorldPosition.writeFloat(loc[0])
        WorldPosition.add(4).writeFloat(loc[1])
        WorldPosition.add(8).writeFloat(loc[2])

        var ScreenPosition = Memory.alloc(0x20);
        ProjectWorldToScreen(PlayerController_Actor,WorldPosition,ScreenPosition,0)

        var screen_pos_x = ScreenPosition.readFloat()
        var screen_pos_y = ScreenPosition.add(4).readFloat()
        return new FVector2D(screen_pos_x,screen_pos_y)

    }catch(e){
        console.log(e)
    }
}


function Draw2_one(Obj_addr,Actor_name, cnt, color){
    try{

        var loc = get_actor_location(Actor_name,cnt)  
        var screen_pos = get_ProjectWorldToScreen(loc)
        var loc_z_max = loc[2] + 50
        var loc_z_min = loc[2] - 50
        var screen_z_max = get_ProjectWorldToScreen([loc[0],loc[1],loc_z_max])
        var screen_z_min = get_ProjectWorldToScreen([loc[0],loc[1],loc_z_min])
        var screen_ball_r = Math.abs(screen_z_max.Y - screen_z_min.Y)       // 这里是拿到了球在屏幕的直径
        // console.log(`screen_ball_r: ${screen_ball_r}`)


        // console.log("screen_ball_r: ", screen_ball_r)
        var start_x = screen_pos.X - (screen_ball_r / 2)
        var start_y = screen_pos.Y - (screen_ball_r / 2) 
        var end_x = start_x + screen_ball_r 
        var end_y = start_y + screen_ball_r  
        draw_DrawLine(Obj_addr,start_x, start_y, start_x , end_y, color, 3.0);
        draw_DrawLine(Obj_addr,start_x, start_y  , end_x, start_y, color, 3.0); 
        draw_DrawLine(Obj_addr,end_x, start_y, end_x , end_y, color, 3.0);
        draw_DrawLine(Obj_addr,start_x, end_y, end_x,end_y, color, 3.0); 

    }catch(e){
        console.log("Draw2_one " + e)
    }
}


// ProjectWorldToScreen
function perspective2(Obj_addr){
    var red_color = [1.0, 0.0, 0.0, 1.0]
    var blue_color = [0.0, 0.0, 1.0, 1.0];
    var purple_color = [1.0, 0.0, 1.0, 1.0];
    try{
        Draw2_one(Obj_addr,"GreenBall",0,red_color)
        // Draw_one(Obj_addr,"GreenBall",1,red_color)
        // Draw_one(Obj_addr,"GreenBall",2,red_color)

        Draw2_one(Obj_addr,"YellowBall",0,blue_color)
        Draw2_one(Obj_addr,"YellowBall",1,blue_color)
        Draw2_one(Obj_addr,"YellowBall",2,blue_color)

    }catch(e){
        console.log("perspective1 error: " + e)
    }

}


function get_loc_rot_fov_from_Camera(){
    try{
        // PlayerController -> PlayerCameraManager -> CameraCachePrivate(CameraCacheEntry) -> MinimalViewInfo
        var PlayerController_Actor = find_actor("PlayerController")
        var PlayerCameraManager = PlayerController_Actor.add(0x220).readPointer()
        var CameraCachePrivate = PlayerCameraManager.add(0x19b0)
        var MinimalViewInfo = CameraCachePrivate.add(0x10)
        var Location = MinimalViewInfo.add(0)
        var Rotation = MinimalViewInfo.add(0xc)
        var FOV = MinimalViewInfo.add(0x18).readFloat()

        var loc = [Location.readFloat(),Location.add(4).readFloat(),Location.add(8).readFloat()]
        var rot = [Rotation.readFloat(),Rotation.add(4).readFloat(),Rotation.add(8).readFloat()]


        return [loc,rot,FOV]
    }catch(e){
        console.log("get_loc_rot_fov_from_Camera error: " + e)
    }


}


function Draw3_one(Obj_addr,Actor_name, cnt, color){
    try{
        // 通过 CameraCacheEntry 拿到 rot、loc
        // 1_1、获取球坐标
        var loc = get_actor_location(Actor_name,cnt)           // 这个是可见的绿球
        // 2_1、获取我们自己的坐标
        var loc_rot_fov = get_loc_rot_fov_from_Camera()
        var loc_firstperson = loc_rot_fov[0]
        var rot_first_person = loc_rot_fov[1]
        // console.log(`rot_first_person: (${rot_first_person[0]},${rot_first_person[1]},${rot_first_person[2]})`)
        // 3_1、获取手机屏幕大小
        var screen_mess = get_GetViewportSize()
        var screen_width = screen_mess[0]
        var screen_height = screen_mess[1]
        // 3_2、获取FOV
        var camera_fov = loc_rot_fov[2]

        // 4_1、获取物体原本的大小(长宽高),这里固定都是50,因此不需要处理
        // var GreenBall0size = get_GetActorBounds("GreenBall",0)
        // 5_1、将green的坐标转换为屏幕坐标
        var screen_pos = world_to_screen(loc,rot_first_person,loc_firstperson,screen_width,screen_height,camera_fov)

        // 6、转化边框世界坐标为屏幕世界坐标
        var loc_z_max = loc[2] + 50
        var loc_z_min = loc[2] - 50
        var screen_z_max = world_to_screen([loc[0],loc[1],loc_z_max],rot_first_person,loc_firstperson,screen_width,screen_height,camera_fov)
        var screen_z_min = world_to_screen([loc[0],loc[1],loc_z_min],rot_first_person,loc_firstperson,screen_width,screen_height,camera_fov)
        var screen_ball_r = Math.abs(screen_z_max.Y - screen_z_min.Y)       // 这里是拿到了球在屏幕的直径
        // console.log(`screen_ball_r: ${screen_ball_r}`)
        // 7、绘制 green红框
        // console.log("screen_ball_r: ", screen_ball_r)
        var start_x = screen_pos.X - (screen_ball_r / 2)
        var start_y = screen_pos.Y - (screen_ball_r / 2) 
        var end_x = start_x + screen_ball_r 
        var end_y = start_y + screen_ball_r  
        draw_DrawLine(Obj_addr,start_x, start_y, start_x , end_y, color, 3.0);
        draw_DrawLine(Obj_addr,start_x, start_y  , end_x, start_y, color, 3.0); 
        draw_DrawLine(Obj_addr,end_x, start_y, end_x , end_y, color, 3.0);
        draw_DrawLine(Obj_addr,start_x, end_y, end_x,end_y, color, 3.0); 
            
    }catch(e){
        console.log("Draw3_one " + e)
    }
}


function perspective3(Obj_addr){
    var red_color = [1.0, 0.0, 0.0, 1.0]
    var blue_color = [0.0, 0.0, 1.0, 1.0];
    var purple_color = [1.0, 0.0, 1.0, 1.0];
    try{
        Draw3_one(Obj_addr,"GreenBall",0,purple_color)
        // Draw3_one(Obj_addr,"GreenBall",3,purple_color)
        // Draw3_one(Obj_addr,"GreenBall",4,purple_color)

        Draw3_one(Obj_addr,"YellowBall",0,red_color)
        Draw3_one(Obj_addr,"YellowBall",1,red_color)
        Draw3_one(Obj_addr,"YellowBall",2,red_color)

    }catch(e){
        console.log("perspective1 error: " + e)
    }
}


function hook_perspective(){
    // 透视绘制出地图中所有的黄色和绿色的小球,使用不同颜色的框来区分不同类型的小球;
    // 注入到 0x1dc2b70
    try{
        const Obj_addr = find_obj_by_name("DebugCanvasObject")

        var so_base = get_so_base()
        const offset = 0x1dc2b70
        var Inject_addr = so_base.add(offset)
        Interceptor.attach(Inject_addr, {
            onEnter: function(args) {
                try{
                    perspective1(Obj_addr)
                    // perspective2(Obj_addr)
                    // perspective3(Obj_addr)
                }catch(e){
                    console.log("perspective1 error: " + e)

                }
            }
        })
    }catch(e){
        console.log("hook_perspective error: " + e)
    }
}


function get_shoot_location(){
    try{
        var so_base = get_so_base()
        var FirstPersonCharacter_C = find_actor("FirstPersonCharacter_C")
        var GunOffset = FirstPersonCharacter_C.add(0x3f4)
        var GunOffset_x = GunOffset.readFloat()
        var GunOffset_y = GunOffset.add(4).readFloat()
        var GunOffset_z = GunOffset.add(8).readFloat()
        
        var eyeview = get_firet_person_location("FirstPersonCharacter_C")
        var eyeview_x = eyeview[0]
        var eyeview_y = eyeview[1]
        var eyeview_z = eyeview[2]
        var shoot_loc_x = eyeview_x + GunOffset_x
        var shoot_loc_y = eyeview_y + GunOffset_y
        var shoot_loc_z = eyeview_z + GunOffset_z

        return [shoot_loc_x,shoot_loc_y,shoot_loc_z]
    }catch(e){
        console.log("get_shoot_location error :", e)
    }
}
function set_shoot_location(loc1,loc2,loc3){
    try{
        var so_base = get_so_base()
        var FirstPersonCharacter_C = find_actor("FirstPersonCharacter_C")
        var GunOffset = FirstPersonCharacter_C.add(0x3f4)
        var GunOffset_x = GunOffset.readFloat()
        var GunOffset_y = GunOffset.add(4).readFloat()
        var GunOffset_z = GunOffset.add(8).readFloat()

        var new_loc_x = loc1 - GunOffset_x
        var new_loc_y = loc2 - GunOffset_y
        var new_loc_z = loc3 - GunOffset_z
        

        var Actor = find_actor("FirstPersonCharacter_C")        // 03C46A0C
        var SceneComponent = Actor.add(0x100).readPointer()
        SceneComponent.add(0x190).writeFloat(new_loc_x)
        SceneComponent.add(0x194).writeFloat(new_loc_y)
        SceneComponent.add(0x198).writeFloat(new_loc_z)


    
    }catch(e){
        console.log("check_shoot_location error :", e)
    }
}


function get_view_eyes_location(Actor_name,cnt=0){       // 只能表示摄像机世界坐标/第一人称世界坐标,或许可以起名: get_eyes_point_loc_addr
    try{
        var Actor = find_actor(Actor_name,cnt)
        var RootComponent = Actor.add(0x100).readPointer()
        var loc1 = RootComponent.add(0x190).readFloat()
        var loc2 = RootComponent.add(0x194).readFloat()
        var loc3 = RootComponent.add(0x198).readFloat()
        var BaseEyeHeight = Actor.add(0x1BC).readFloat()
        return [loc1,loc2,loc3 + BaseEyeHeight]
    }catch(e){
        console.log("get_firet_person_location error :", e)
    }

}

function set_GetControlRotation(Crosshair_rot){
    try{
        var Actor = find_actor("FirstPersonCharacter_C")
        var Controller = Actor.add(0x1d4).readPointer()
        var ControlRotation = Controller.add(0x200)

        ControlRotation.writeFloat(Crosshair_rot[0])
        ControlRotation.add(4).writeFloat(Crosshair_rot[1])
        ControlRotation.add(8).writeFloat(Crosshair_rot[2])

    }catch(e){
        console.log(e)
    }

}


function converloc2rot(target_loc,first_person_loc){
    var dx = target_loc[0] - first_person_loc[0];
    var dy = target_loc[1] - first_person_loc[1];
    var dz = target_loc[2] - first_person_loc[2];

    const pi_2 = 1.5707963;
    const pitch_range = 90.0;

    // atan2(dz, sqrt(dx * dx + dy * dy))得到a角的弧度
    // 之后再乘以 (pitch_range / pi_2) 得到a角的角度
    var pitch =  Math.atan2(dz,  Math.sqrt(dx * dx + dy * dy)) * (pitch_range / pi_2);

    // 上述方法计算出来的pitch范围是-90~+90,需要加上360°,使其符合范围要求
    if (pitch < 0.0)
    {
        pitch += 360.0;
    }


    const yaw_range = 360.0;
    var yaw =  Math.atan2(dy, dx) * yaw_range / 4 / pi_2;

    if (yaw < 0.0)
    {
        yaw += 360.0;
    }

    return [pitch, yaw, 0.0];
}
function autom_aim_one_ball(Actor_name,cnt=0){
    try{
        // 0、自动获取到黄色小球的宽度
        var width = 100.0;

        // 1、获取黄色小球的世界坐标,
        var loc_yellow = get_actor_location(Actor_name,cnt)       // cnt=2时是飞球        
        // 2、转化为屏幕坐标
        var screen_mess = get_GetViewportSize()
        var screen_width = screen_mess[0]
        var screen_height = screen_mess[1]
        var rot_first_person = get_eyes_point_rot_addr("FirstPersonCharacter_C")
        var loc_firstperson = get_firet_person_location("FirstPersonCharacter_C")
        var camera_fov = get_FOV()
        var yellow_screen_pos = world_to_screen(loc_yellow,rot_first_person,loc_firstperson,screen_width,screen_height,camera_fov)
        
        var CrosshairScreenX = screen_width / 2.0;
        var CrosshairScreenY = screen_height / 2.0;

        // 3、开火的时候,判断准星的屏幕坐标与黄色小球屏幕坐标的距离是否在一定范围内,如果在范围内,则自动对准
        if (Math.abs(CrosshairScreenX - yellow_screen_pos.X) < width && Math.abs(CrosshairScreenY - yellow_screen_pos.Y) < width){
            // 4、如果在范围内,则自动对准黄色小球的坐标
            // 4_1、首先枪口朝向黄色小球的坐标,即修改 准星的 ControlRotation.
                
            var view_eyes_loc = get_view_eyes_location("FirstPersonCharacter_C")
            var Crosshair_rot = converloc2rot(loc_yellow,view_eyes_loc)
            set_GetControlRotation(Crosshair_rot)

            // 4_2、其次子弹发射方向也朝向黄色小球的坐标



        }

    }catch(e){
        console.log("autom_aim_none_ball error: " + e)
    }



    // try{
    //     // 0、自动获取到黄色小球的宽度
    //     var width = 150.0;
    //     var loc = get_actor_location(Actor_name,cnt)       // cnt=2时是飞球        
    //     var screen_pos = get_ProjectWorldToScreen(loc)
        
    //     var screen_mess = get_GetViewportSize()
    //     var screen_width = screen_mess[0]
    //     var screen_height = screen_mess[1]
    //     var CrosshairScreenX = screen_width / 2.0;
    //     var CrosshairScreenY = screen_height / 2.0;

    //     // 3、开火的时候,判断准星的屏幕坐标与黄色小球屏幕坐标的距离是否在一定范围内,如果在范围内,则自动对准
    //     if (Math.abs(CrosshairScreenX - screen_pos.X) < width && Math.abs(CrosshairScreenY - screen_pos.Y) < width){
    //         var loc_firstperson = get_actor_location("FirstPersonCharacter_C")
    //         var rot = converloc2rot(loc,loc_firstperson)
    //         set_GetControlRotation("FirstPersonCharacter_C",rot[0],rot[1],rot[2])
    //     }

    // }catch(e){
    //     console.log("autom_aim_none_ball error: " + e)
    // }
}

function autom_aim1(){
    try{
        autom_aim_one_ball("YellowBall",0)
        autom_aim_one_ball("YellowBall",1)
        autom_aim_one_ball("YellowBall",2)      

    }catch(e){
        console.log("autom_aim1 error: " + e)
    }
}


function call_xchange_control_rotation(new_rot){
    try{
        // console.log(`new_rot: (${new_rot[0]},${new_rot[1]},${new_rot[2]})`)
        var Actor = find_actor("FirstPersonCharacter_C")
        var Controller_actor = Actor.add(0x1d4).readPointer()
        var so_base = get_so_base()
        var XchangeControlRotation_addr = so_base.add(0x3622054)
        var XchangeControlRotation = new NativeFunction(XchangeControlRotation_addr, 'int', ['pointer','pointer']);
        var new_buff = Memory.alloc(0x20)
        new_buff.writeFloat(new_rot[0])
        new_buff.add(4).writeFloat(new_rot[1])
        new_buff.add(8).writeFloat(new_rot[2])
        XchangeControlRotation(Controller_actor, new_buff)
    }catch(e){
        console.log("call_xchange_control_rotation error: " + e)
    }

}

function autom_aim_one_ball2(Actor_name,cnt=0){   // 还没有完全实现,先看算法去了
    try{
        // 0、自动获取到黄色小球的宽度
        var width = 100.0;

        // 1、获取黄色小球的世界坐标,
        var loc_yellow = get_actor_location(Actor_name,cnt)       // cnt=2时是飞球        
        // 2、转化为屏幕坐标
        var screen_mess = get_GetViewportSize()
        var screen_width = screen_mess[0]
        var screen_height = screen_mess[1]
        var rot_first_person = get_eyes_point_rot_addr("FirstPersonCharacter_C")
        var loc_firstperson = get_firet_person_location("FirstPersonCharacter_C")
        var camera_fov = get_FOV()
        var yellow_screen_pos = world_to_screen(loc_yellow,rot_first_person,loc_firstperson,screen_width,screen_height,camera_fov)
        
        var CrosshairScreenX = screen_width / 2.0;
        var CrosshairScreenY = screen_height / 2.0;

        // 3、开火的时候,判断准星的屏幕坐标与黄色小球屏幕坐标的距离是否在一定范围内,如果在范围内,则自动对准
        if (Math.abs(CrosshairScreenX - yellow_screen_pos.X) < width && Math.abs(CrosshairScreenY - yellow_screen_pos.Y) < width){
            // 4、如果在范围内,则自动对准黄色小球的坐标
            // 4_1、首先枪口朝向黄色小球的坐标,即修改 准星的 ControlRotation.
                
            var view_eyes_loc = get_view_eyes_location("FirstPersonCharacter_C")
            var Crosshair_rot = converloc2rot(loc_yellow,view_eyes_loc)
            // console.log(`Crosshair_rot: (${Crosshair_rot[0]},${Crosshair_rot[1]},${Crosshair_rot[2]})`)
            call_xchange_control_rotation(Crosshair_rot)
            
            return 1
        }else{
            return 0
        }
        
    }catch(e){
        console.log("autom_aim_one_ball2 error: " + e)
    }
}
function autom_aim2(){
    if(autom_aim_one_ball2("YellowBall",0)==1){
        return
    }
    if(autom_aim_one_ball2("YellowBall",1)==1){
        return
    }
    if(autom_aim_one_ball2("YellowBall",2)==1){
        return
    }      
}

function int2float(int_data){
    var floatArray = new Float32Array(1);
    var buffer = new ArrayBuffer(4);
    new DataView(buffer).setUint32(0, int_data, true);
    floatArray[0] = new Float32Array(buffer)[0];
    return floatArray[0]
}

function hook_autom_aim(){
    // 当枪口指向黄色小球附近时,实现开火时枪口自动对准该小球的功能
        // 注入到 0x34DFAE0
        // 或者主动调用: 0x3622218
        try{
            var hook_list = [
                0x34DFAE0,
            ]
            var so_base = get_so_base()
            for(var i in hook_list){
                const offset = hook_list[i]
                Interceptor.attach(so_base.add(offset), {
                    onEnter: function(args) {
                        try{
                            if(offset == 0x34DFAE0){
                                // autom_aim1()
                                autom_aim2()
                            }

                        }catch(e){
                            console.log("perspective1 error: " + e)
        
                        }
                    }
                })
            }

        }catch(e){
            console.log("hook_perspective error: " + e)
        }
}

// hook_perspective()
// 
function  hooker(){
    hook_check_fun()
    hook_skip_login()
    // hook_perspective()          
    // hook_autom_aim()
}

function get_FOV(Actor_name="PlayerCameraManager"){
    try{
        // 0x39AF378
        // var so_base = get_so_base()
        // var Actor = find_actor(Actor_name)
        // // var FOV = Actor.add(0x1c8).readFloat()
        // // var DefaultFOV = Actor.add(0x1c4).readFloat()
        // // console.log(`FOV: ${FOV} DefaultFOV: ${DefaultFOV}`)

        // var fun = Actor.readPointer().add(0x36C).readPointer()
        // console.log("fun :", fun.sub(so_base))
        // var fun2 = Actor.readPointer().add(0x33C).readPointer()
        // console.log("fun2 :", fun2.sub(so_base))
    
        var so_base = get_so_base()
        var Actor = find_actor(Actor_name)
        var exec_GetFOVAngle = so_base.add(0x3DE11C8)
        var GetFOVAngle = new NativeFunction(exec_GetFOVAngle, 'float', ['pointer','pointer','pointer']);
        var buf = Memory.alloc(0x100);
        GetFOVAngle(Actor,buf,buf);
        var FOV = Memory.readFloat(buf)
        return FOV
    }catch(e){
        console.log("get_FOV error")
    }
}


function get_GetActorBounds(Actor_name,cnt=0){
    try{
        var so_base = get_so_base()
        var Actor = find_actor(Actor_name,cnt)
        var noexec_GetActorBounds = so_base.add(0x33FBC2C)
        var GetActorBounds = new NativeFunction(noexec_GetActorBounds, 'void', ['pointer','int','pointer','pointer','int']);
        var Origin = Memory.alloc(0x100);
        var BoxExtent = Memory.alloc(0x100);

        // BoxExtent 表示边界框从中心点到各个边的距离(即边界框的半边长)。
        //(Origin)。它是一个 FVector 类型,表示边界框的中心坐标。
        GetActorBounds(Actor,0,Origin,BoxExtent,0)          
        var Origin_x = Origin.readFloat()
        var Origin_y = Origin.add(4).readFloat()    
        var Origin_z = Origin.add(8).readFloat()
        var BoxExtent_x = BoxExtent.readFloat()
        var BoxExtent_y = BoxExtent.add(4).readFloat()
        var BoxExtent_z = BoxExtent.add(8).readFloat()
        // console.log(`${Actor_name}${cnt} Origin: (${Origin_x},${Origin_y},${Origin_z}) BoxExtent: (${BoxExtent_x},${BoxExtent_y},${BoxExtent_z})`)
        return [BoxExtent_x,BoxExtent_y,BoxExtent_z]
    }catch(e){
        console.log("get_GetActorBounds error")
    }
}

function hook_fire(){
    // 估计还得下硬件访问断点找开枪函数 
    var hook_list = [
        // 0x3DE9624,0x3DEF288,0x3E5D1F0,0x12BA15C,0x1AB17A0,0x3de9624,0x2778150,0x3DE3D04,0x34DFAE0
        // 0x34DFAE0,          // 就他了,反正只要开枪的时候触发
        0x1A83BC0,0x1AD36DC,0x34fd548
    ]
    var so_base = get_so_base()
    console.log("so_base is :", so_base)
    for(var i in hook_list){
        const offset = hook_list[i]
        Interceptor.attach(so_base.add(offset), {
            onEnter: function(args) {
                if(offset == 0x1AB17A0){
                    // stack_backstace(this)
                }else if(offset == 0x34DFAE0){
                    console.log(`offset: ${hex(offset)}`)
                    stack_backstace(this)
                }
                console.log(`offset: ${hex(offset)}`)

            },
        });
        
    }
}

// hook_dlopen();
// hooker()

hook_perspective()
hook_autom_aim()

// 我的问题: 弹道偏右下,不知道为什么,何晨光的枪?
// 构造输入: tlsn00112233445566778899aabbccdd
// ./eDBG -p com.tencent.ace.gamematch2024final -l libUE4.so -b 0x1C31114
// ./stackplz --pid `pidof com.tencent.ace.gamematch2024final` --brk 0x7b1a2aedd0:r  --stack

反调试分析

遭千杀的,去混淆后的idb文化找不到了,只找到了之前保留的一点点内容。反正patch之后,用unidbg慢慢调完事了。unidbg模拟不了的函数就直接patch掉。

0x46D4B80处的函数解密了大量字符串:

image-20250410194828475

可用看到这里的字符串基本上属于敏感字符串

0x46D1FE2地址处的系统调用:

实际上就是在检测这些字符串代表的文件是否存在

image-20250410194734228

image-20250410194746244

filter[0] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, recvmsg, 0, 1),
filter[1] = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
filter[2] = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_THREAD),


image-20250410222806503

posted @ 2025-04-22 19:50  TLSN  阅读(119)  评论(0)    收藏  举报