2024 腾讯游戏安全大赛 mobile 决赛 wp
dump SDK
GWorld
: 0x4F5C0D0
GNAME
: 0x4E2EC00
GUObjectArray
: 0x4E533AC
dump失败,猜测结构体被魔改,接下来读 UE4Dumper的源码
算了,直接修改 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,
更绝一点,甚至可以直接patch掉开头的push指令,反正我们的目的只不过是看伪代码/
mov pc, xxx 混淆的处理
对于简单、稍复杂的结构,直接nop即可
就算碰到了循环结构也可以试着直接nop
比如这里,虽然强行的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解析器某一两处写的不够完善,虽然如此,但问题不大.
逆算法很容易,就一魔改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处的函数解密了大量字符串:
可用看到这里的字符串基本上属于敏感字符串
。
0x46D1FE2地址处的系统调用:
实际上就是在检测这些字符串代表的文件是否存在
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),