日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

大神论坛 利用活跃变量分析来去掉vmp的大部分垃圾指令

發布時間:2023/12/16 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 大神论坛 利用活跃变量分析来去掉vmp的大部分垃圾指令 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

聲明:為了把技術分享給更多的人,在 大神論壇 上發表了,這里也發一份
環境和工具
Windows7 7601
Ida7.5
Python3.8
qiling框架

簡介
首先簡單介紹一下數據流分析和活躍變量分析?;钴S變量分析屬于數據流分析的一種,編譯器的許多優化都依賴于數據流分析。龍書的簡介截圖如下

活躍變量分析的用途有刪除無用賦值和為基本塊分配寄存器。vmp 中的垃圾指令大部分都是些無用賦值,我們可以利用活躍變量分析來刪除這些垃圾指令

如上圖所示。如果把test指令看作是對eflags寄存器的賦值,其中的test指令就屬于無用賦值,因為eflags寄存器在當前指令被賦值之后沒有被使用,在下一條指令又被重新賦值。0064D721處對ebp的賦值在0064D748之前也沒有被使用,也是一個無用賦值。

記錄基本塊
此次活躍變量分析僅局限于合并直接跳轉后的基本塊內,不做全局的數據流分析,可以不用添加前驅和后繼,相當于做局部優化。本次使用的樣本是vmp3.5版本加的殼,只點了虛擬化,沒有做其它的處理,這個樣本的源碼在vmprotect3.5安裝目錄下Example\Code Markers\MSVC。以去除vmp1段中的垃圾代碼為例,也就是加殼后程序入口點處的那部分虛擬機代碼。由于vmp3.5展開了dispatch結構,并且利用間接跳轉干擾靜態分析工具的控制流重建。比如vmp使用大量的jmp register和push register; ret;等指令。為了構建控制流,我使用qiling框架來模擬執行來記錄這類指令的跳轉目標。qiling實現了一個pe加載器且模擬了部分系統api。經過驗證,是可以運行到原程序的入口點。有兩個api需要自己添加模擬,GetProcessAffinityMask和SetThreadAffinityMask。模擬的代碼如下:

@winsdkapi(cc=STDCALL, dllname="kernel32_dll", replace_params={"lpProcessAffinityMask":POINTER,"lpSystemAffinityMask":POINTER}) def hook_GetProcessAffinityMask(ql, address, params):lpProcessAffinityMask = params["lpProcessAffinityMask"]lpSystemAffinityMask = params["lpSystemAffinityMask"]if(ql.mem.is_mapped(lpProcessAffinityMask, 4)):ql.mem.write(lpProcessAffinityMask,ql.pack32(1))else:print("GetProcessAffinityMask->lpProcessAffinityMask(0x%08x) unmapped!" % lpProcessAffinityMask)addr = ql.os.heap.alloc(4)ql.mem.write(addr,ql.pack32(1))if(ql.mem.is_mapped(lpSystemAffinityMask, 4)):ql.mem.write(lpSystemAffinityMask,ql.pack32(1))else:print("GetProcessAffinityMask->lpSystemAffinityMask(0x%08x) unmapped!" % lpSystemAffinityMask)addr = ql.os.heap.alloc(4)ql.mem.write(addr,ql.pack32(1))return 1@winsdkapi(cc=STDCALL, dllname="kernel32_dll", replace_params={"dwThreadAffinityMask":POINTER,}) def hook_SetThreadAffinityMask(ql, address, params):hThread = params['hThread']pdwThreadAffinityMask = params["dwThreadAffinityMask"]if(ql.mem.is_mapped(pdwThreadAffinityMask, 4)):mask = ql.unpack32(ql.mem.read(pdwThreadAffinityMask, 4))print("SetThreadAffinityMask(0x%08x, 0x%08x)" % (hThread,mask))return 1

然后利用qiling的hook_code的回調函數來跟蹤指令的走向,記錄所需的跳轉信息。回調函數的部分代碼如下:

def traceCode(ql, address, size, user_data=None):#print("trace address:0x%08x" % address)#trace = [address,size]#if(trace not in g_traceAddrList):#g_traceAddrList.append(trace)if(0x004012F5 == address): #真正的入口點print("execute to original entrypoint! address:0x%08x" % 0x004012F5)ql.emu_stop()#push reg;retif(1 == size and ql.mem.read(address,size)[0] == 0xc3):target = ql.unpack32(ql.mem.read(ql.reg.esp, 4))if(None != g_RetAddrDict.get(address)):g_RetAddrDict[address].add(target)else:g_RetAddrDict[address] = {target}md = ql.disassembermd.detail = TruebInsn = ql.mem.read(address,size)insn = list(md.disasm(bInsn, address))[0] #trace jmp Regif(capstone.x86_const.X86_INS_JMP == insn.id and capstone.x86_const.X86_OP_REG == insn.operands[0].type):target = ql.reg.read(insn.operands[0].reg)if(None != g_jmpRegAddrDict.get(address)):g_jmpRegAddrDict[address].add(target) else:g_jmpRegAddrDict[address] = {target}

由于qiling框架模擬執行的有點慢,所以模擬到入口點結束后就把獲取到的信息通過json序列化保存到了文件中。代碼和文件我都會上傳,這里就不用一一展開了。獲取到信息后,就要記錄所有的基本塊。大體思路是以入口點的代碼為一個作為一個新的基本塊的開始,然后不斷的把后續指令加進去,直到碰到一個無條件跳轉、條件跳轉指令或其目的地址的指令為止。具體實現是從入口點開始掃描每一條指令,把push imm; call imm;當作直接跳轉,不分析直接跳轉后面的指令,繼續從直接跳轉的目的地址開始分析。需要注意的是push register;ret;和jmp register需要看成是含有多個分支的跳轉。然后利用一個隊列來保存待分析的基本塊首地址,代碼實現如下:

def GetVmp1BasicBlock(): EntryPoint = 0x400000 + 0x0037E533 insn = ida_ua.insn_t()qInsnAddr = queue.Queue() #保留待分析的跳轉分支起始地址qInsnAddr.put(EntryPoint)while(not qInsnAddr.empty()):ea = start_ea = qInsnAddr.get() if(IsRedundant(ea)): #已經加入基本塊,不用在分析continue#print("trace start_ea:0x%08x" % start_ea)#分析start_ea開始的基本塊while(ea != 0x006FDE0C): #0x006FDE0B為虛擬機出口 #print("\tea:0x%08x" % ea) InsnLen = ida_ua.decode_insn(insn, ea)if(0 == InsnLen):print("decode_insn(ea=0x%08x) failed!" % ea)return 0 if(insn.itype in g_callInsnList and insn.ops[0].type in g_immOprand):prevInsn = ida_ua.insn_t()prevAddr = ea - 5 #push immediate;占用5個字節prevLen = ida_ua.decode_insn(prevInsn, prevAddr)#調用decode_prev_insn可能會失敗if(0 == prevLen):print("decode_insn(0x%08x) failed!" % prevAddr)return 0#push xxx;call xxx;把call看出直接跳轉if(ida_allins.NN_push == prevInsn.itype and ida_ua.o_imm == prevInsn.ops[0].type):end_ea = ea + insn.sizevbb = VMPBasicBlock(start_ea, end_ea, insn.ea)g_vmp1BlockList.append(vbb)AdjustBlockByJccTarget(insn.ops[0].addr)qInsnAddr.put(insn.ops[0].addr)#print("\tqInsnAddr.put(0x%08x)" % insn.ops[0].addr)else:#暫不分析其它類型的callea = ea + insn.sizecontinuebreak elif(insn.itype in g_jccInsnList):end_ea = ea + insn.sizevbb = VMPBasicBlock(start_ea, end_ea, insn.ea)g_vmp1BlockList.append(vbb)qInsnAddr.put(end_ea) #jcc需要分析其后面的指令#print("\tqInsnAddr.put(0x%08x)" % end_ea)jccTarget = insn.ops[0].addrAdjustBlockByJccTarget(jccTarget)qInsnAddr.put(jccTarget)#print("\tqInsnAddr.put(0x%08x)" % jccTarget) breakelif(insn.itype in g_jmpInsnList):end_ea = ea + insn.sizevbb = VMPBasicBlock(start_ea, end_ea, insn.ea)g_vmp1BlockList.append(vbb)if(insn.ops[0].type in g_immOprand): #不分析jmp immediate后面的指令JmpTarget = insn.ops[0].addrAdjustBlockByJccTarget(JmpTarget)qInsnAddr.put(JmpTarget)#print("\tqInsnAddr.put(0x%08x)" % JmpTarget) elif(ida_ua.o_reg == insn.ops[0].type): #jmp reg;for JmpTarget in g_jmpRegDict[ea]:AdjustBlockByJccTarget(JmpTarget)qInsnAddr.put(JmpTarget)#print("\tqInsnAddr.put(0x%08x)" % JmpTarget) breakelif(ida_allins.NN_retn == insn.itype):#主要是push reg;ret;指令,還有少部分跳入vmp1的ret end_ea = ea + insn.sizevbb = VMPBasicBlock(start_ea, end_ea, insn.ea)g_vmp1BlockList.append(vbb)'''prevInsn = ida_ua.insn_t()prevInsnLen = ida_ua.decode_insn(prevInsn, ea - 1)#push占用一個字節if(0 == prevInsnLen):breakif(ida_allins.NN_push != prevInsn.itype or ida_ua.o_reg != prevInsn.ops[0].type):break'''if(None == g_RetAddrDict.get(insn.ea)): #會遍歷到沒有模擬執行過的ret指令,可能由條件分支指令造成的print("warning:cannot find ret target! address:0x%08x" % insn.ea)breakfor JmpTarget in g_RetAddrDict[insn.ea]: #g_RetAddrDict的key為ret的地址#if(0x004012F5 == JmpTarget):#0x004012F5為原入口點# continueif(JmpTarget < 0x63b000 or JmpTarget > 0x820000):#vmp1 segment boundprint("ret from 0x%08x to 0x%08x" % (ea, JmpTarget))continueAdjustBlockByJccTarget(JmpTarget)qInsnAddr.put(JmpTarget)#print("\tqInsnAddr.put(0x%08x)" % JmpTarget) breakelse:ea = ea + insn.size'''L1: xxx;...L2: xxx;...L3 jcc;當有一條先跳轉到L2的指令時,后面又有一條跳轉到L1的指令,會出現L1到L2的塊且包含L2到L3的塊'''if(IsRedundant(ea)): #檢測到另一個塊的起始地址vbb = VMPBasicBlock(start_ea, ea, insn.ea)g_vmp1BlockList.append(vbb)breakreturn 1

執行完后發現有7000多個這樣的塊,所以需要合并一下那些直接跳轉的塊,也方便之后做活躍變量分析。合并和添加前驅和后繼的代碼就不展示了,具體參考源碼中的AddVMPBasicBlockPrevsAndSuccs和TryMergeBasicBlock兩個函數。
活躍變量分析
合并基本塊后就可以做活躍變量分析了,在和合并后基本塊內做活躍變量分析可以把每一條指令看作是一個結點,寄存器看作一個變量,然后利用使用和定值信息計算進入結點和結點后的活躍信息。這里給出《現代編譯原理-C語言描述》第10章活躍分析的一個例子,方便大家理解使用、定值和活躍性。

活躍性計算的方法如下:


按照上述方法計算后,會得到每一個結點的入口活躍信息和出口活躍信息。考慮到合并后的基本塊有1700多個,如果按照上面的迭代方法計算的話會很慢,所以具體實現要優化一下,加快數據流分析?;緣K內指令是線性執行的,不存在環和分支,活躍變量分析屬于逆向數據流問題。如果能夠安排每一個結點的計算都先于它的前驅,是可以通過對所有結點的一次遍歷就能完成數據流分析,得到每一個結點的入口活躍和出口活躍信息。獲取基本塊內每一個指令的use和def信息的話,可以使用capstone CsInsn類的regs_access方法,這還可以獲取到eflags寄存器。一開始是想使用ida的microcode API來實現的,但是我覺得ida提供的python接口不好用,沒有提供針對一條指令的轉換接口。
對合并后的基本塊的分析思路如下:
1、首先利用capstone反匯編基本塊內的指令,獲取每一條指令的use和def信息。
2、從基本塊的出口處的指令向入口計算每一條指令的出口活躍信息和入口活躍信息。
3、在獲取到指令的活躍信息后,然后根據每一條指令的def信息,如果def中的所有變量都不屬于出口活躍的,我們就可以刪掉這條指令。
具體代碼實現如下:

def OptimizeMergedBasicBlock(mvbb, IsRadical = False):cs_insnList = []#capstone.CsInsncs_use = {} #key為cs_insnList的下標,value為使用的寄存器集合,對變量的任何使用都會使該變量成為活躍的cs_def = {}#key為cs_insnList的下標,value為修改的寄存器集合,對變量的任何定值都會殺死該變量的活躍性vbbId = mvbb.FirstVbbIdindex = 0while(vbbId in mvbb.vbbIdList):#獲取變量和定值信息vbb = g_vmp1BlockList[vbbId]#print("vbbid %d:0x%08x - 0x%08x"%(vbb.block_id,vbb.start_ea,vbb.end_ea))bCode = idc.get_bytes(vbb.start_ea, vbb.end_ea - vbb.start_ea)if(None == bCode):print("get_bytes(0x%08x,%d) failed!" % (vbb.start_ea, vbb.end_ea - vbb.start_ea))return 0for insn in g_md.disasm(bCode, vbb.start_ea): cs_insnList.append(insn)useList,defineList = insn.regs_access()#(list-of-registers-read, list-of-registers-modified)包括eflagscs_use[index] = {cs_extendRegTo32bit(cs_reg) for cs_reg in useList}cs_def[index] = {cs_extendRegTo32bit(cs_reg) for cs_reg in defineList}index += 1if(len(vbb.succs) != 1):breakvbbId = vbb.succs[0] ''' for i in range(len(cs_insnList)):use_strList=[]def_strList=[]for cs_reg in cs_use[i]:use_strList.append(cs_insnList[i].reg_name(cs_reg))for cs_reg in cs_def[i]:def_strList.append(cs_insnList[i].reg_name(cs_reg)) print("0x%08x %s %s;%r %r" % (cs_insnList[i].address, cs_insnList[i].mnemonic, cs_insnList[i].op_str,use_strList,def_strList))''' bChanged = Truecs_insnTempList=[]while(bChanged):bChanged = FalseOut= {} #In和Out的key為cs_insnList的下標In = {}for i in range(len(cs_insnList)):Out[i]=set()In[i]=set()Exit = len(cs_insnList)if(not IsRadical):In[Exit] = {capstone.x86_const.X86_REG_EBP, capstone.x86_const.X86_REG_EDI, \capstone.x86_const.X86_REG_ESI, capstone.x86_const.X86_REG_ESP, \capstone.x86_const.X86_REG_EAX, capstone.x86_const.X86_REG_EBX, \capstone.x86_const.X86_REG_ECX, capstone.x86_const.X86_REG_EDX}else:In[Exit] = {capstone.x86_const.X86_REG_EBP, capstone.x86_const.X86_REG_EDI, \capstone.x86_const.X86_REG_ESI, capstone.x86_const.X86_REG_ESP,}#如果安排每一個結點的后繼都先于該結點計算,則有可能只通過對所有結點的一次遍歷就能完成數據流分析。#由于基本塊內是線性執行的,不存在分支和環,所以采用倒序for i in range(len(cs_insnList)-1,-1,-1):nSucc = i+1Out[i] |= In[nSucc]In[i] = cs_use[i] | (Out[i]-cs_def[i])'''for i in range(len(cs_insnList)):in1=[]out1=[]for cs_reg in In[i]:in1.append(cs_insnList[i].reg_name(cs_reg))for cs_reg in Out[i]:out1.append(cs_insnList[i].reg_name(cs_reg)) print("0x%08x in:%r out:%r" % (cs_insnList[i].address,in1,out1))''' DelIdxList=[]for i in range(len(cs_insnList)): count = len(cs_def[i]) if(0 == count): #不處理沒有產生定值的語句continuefor cs_reg in cs_def[i]: #某些指令會產生多個定值,比如sub指令會修改通用寄存器和eflags寄存器 if(cs_reg not in Out[i]): #所有產生的定值都不是出口活躍的,則刪除count -= 1 if(0 == count):#對于產生的定值不在Out集合中,則可以被刪除bChanged = TrueDelIdxList.append(i)i=0cs_insnTempList = []for index in range(len(cs_insnList)):if(index not in DelIdxList): #保留已有的use和def信息,并調整indexcs_insnTempList.append(cs_insnList[index])cs_use[i] = cs_use[index]cs_def[i] = cs_def[index]i += 1else: #刪除對應的條目 cs_use.pop(index)cs_def.pop(index)ida_bytes.patch_bytes(cs_insnList[index].address,b"\x90"*cs_insnList[index].size)#g_cs_insnMnemonicSet.add(cs_insnList[index].mnemonic)cs_insnList = cs_insnTempList#print("cs_insnList len:%d" % len(cs_insnList))'''for insn in cs_insnList:print("0x%08x %s %s" % (insn.address, insn.mnemonic, insn.op_str))''' return 1

這里有幾點需要說明一下,capstone的al,ah,ax等8位或16位的寄存器是單獨定義的,需要轉換到32位,因為vmp有8位或16位寄存器參與到下一個handle地址的運算?;蛘咭部梢园堰@些寄存器添加到In[Exit]中,添加In[Exit]是為了方便計算,作為整個基本塊的出口活躍信息,不屬于任意一條指令。把一些通用寄存器添加到基本塊的出口活躍信息,也是為了保證不會nop掉有用的指令。那個IsRadical的判斷主要是為了處理push imm;call target中target處的虛擬機入口。只添加ebp,esp,esi和edi作為整個基本塊出口活躍信息是為了減少target處基本塊沒有nop掉的垃圾指令。在nop掉指令前,需要注意的是,編譯器在刪除死代碼的優化中會考慮到當前被刪除的指令是否有副作用,比如是否為訪存指令、call指令等。本次樣本中vmp的垃圾指令好像都沒有副作用,不存在那些有副作用的指令,所以就沒有考慮這些,nop完之后樣本是可以正常運行的。最后在處理以下垃圾指令,直接遍歷每一個基本塊遇到這類指令直接nop掉。

這里展示一下入口處進入虛擬機那部分nop掉垃圾指令后的代碼和部分x64dbg的trace截圖

.vmp1:0064D713 56 push esi .vmp1:0064D714 90 nop .vmp1:0064D715 90 nop .vmp1:0064D716 90 nop .vmp1:0064D717 90 nop .vmp1:0064D718 90 nop .vmp1:0064D719 90 nop .vmp1:0064D71A 90 nop .vmp1:0064D71B 90 nop .vmp1:0064D71C 55 push ebp .vmp1:0064D71D 52 push edx .vmp1:0064D71E 51 push ecx .vmp1:0064D71F 9C pushf .vmp1:0064D720 90 nop .vmp1:0064D721 90 nop .vmp1:0064D722 90 nop .vmp1:0064D723 90 nop .vmp1:0064D724 90 nop .vmp1:0064D725 90 nop .vmp1:0064D726 50 push eax .vmp1:0064D727 90 nop .vmp1:0064D728 57 push edi .vmp1:0064D729 90 nop .vmp1:0064D72A 90 nop .vmp1:0064D72B 90 nop .vmp1:0064D72C 87 FF xchg edi, edi .vmp1:0064D72E 53 push ebx .vmp1:0064D72F 90 nop .vmp1:0064D730 90 nop .vmp1:0064D731 90 nop .vmp1:0064D732 90 nop .vmp1:0064D733 90 nop .vmp1:0064D734 66 0F A4 E7 8F shld di, sp, 8Fh .vmp1:0064D739 BA 00 00 00 00 mov edx, 0 .vmp1:0064D73E 90 nop .vmp1:0064D73F 90 nop .vmp1:0064D740 90 nop .vmp1:0064D741 90 nop .vmp1:0064D742 90 nop .vmp1:0064D743 90 nop .vmp1:0064D744 90 nop .vmp1:0064D745 52 push edx .vmp1:0064D746 90 nop .vmp1:0064D747 90 nop .vmp1:0064D748 8B 6C 24 28 mov ebp, [esp+24h+arg_0] .vmp1:0064D74C 81 ED 25 26 6F 1D sub ebp, 1D6F2625h .vmp1:0064D752 0F CD bswap ebp .vmp1:0064D754 90 nop .vmp1:0064D755 90 nop .vmp1:0064D756 90 nop .vmp1:0064D757 90 nop .vmp1:0064D758 90 nop .vmp1:0064D759 90 nop .vmp1:0064D75A 90 nop .vmp1:0064D75B F7 DD neg ebp .vmp1:0064D75D 90 nop .vmp1:0064D75E 90 nop .vmp1:0064D75F 90 nop .vmp1:0064D760 90 nop .vmp1:0064D761 90 nop .vmp1:0064D762 90 nop .vmp1:0064D763 90 nop .vmp1:0064D764 90 nop .vmp1:0064D765 90 nop .vmp1:0064D766 90 nop .vmp1:0064D767 90 nop .vmp1:0064D768 90 nop .vmp1:0064D769 90 nop .vmp1:0064D76A 90 nop .vmp1:0064D76B F7 D5 not ebp .vmp1:0064D76D 90 nop .vmp1:0064D76E 90 nop .vmp1:0064D76F 90 nop .vmp1:0064D770 90 nop .vmp1:0064D771 90 nop .vmp1:0064D772 81 ED 7B 53 BE 38 sub ebp, 38BE537Bh .vmp1:0064D778 90 nop .vmp1:0064D779 90 nop .vmp1:0064D77A 90 nop .vmp1:0064D77B 90 nop .vmp1:0064D77C 90 nop .vmp1:0064D77D 90 nop .vmp1:0064D77E 90 nop .vmp1:0064D77F C1 C5 03 rol ebp, 3 .vmp1:0064D782 90 nop .vmp1:0064D783 90 nop .vmp1:0064D784 90 nop .vmp1:0064D785 90 nop .vmp1:0064D786 90 nop .vmp1:0064D787 90 nop .vmp1:0064D788 90 nop .vmp1:0064D789 8D 6C 15 00 lea ebp, [ebp+edx+0] .vmp1:0064D78D 8B F4 mov esi, esp .vmp1:0064D78F 90 nop .vmp1:0064D790 90 nop .vmp1:0064D791 90 nop .vmp1:0064D792 90 nop .vmp1:0064D793 90 nop .vmp1:0064D794 90 nop .vmp1:0064D795 90 nop .vmp1:0064D796 90 nop .vmp1:0064D797 47 inc edi .vmp1:0064D798 81 EC C0 00 00 00 sub esp, 0C0h .vmp1:0064D79E 90 nop .vmp1:0064D79F 90 nop .vmp1:0064D7A0 90 nop .vmp1:0064D7A1 90 nop .vmp1:0064D7A2 90 nop .vmp1:0064D7A3 90 nop .vmp1:0064D7A4 90 nop



可以看到已經去掉大部分垃圾指令了,剩下的漏網之魚也是很容易可以看出來的。

總結
根據實際運行效果,說明我的分析思路是大體正確的。這里沒有根據基本塊作為一個結點做全局的活躍變量分析是因為把通用寄存器作為活躍分析中的變量是不適合這么做的。因為通用寄存器的數量有限,是重復使用的資源,其活躍性很容易在下一個基本塊被殺死。要做全局的活躍變量分析的話,應先把整個流圖轉換到SSA形式,這樣應該可以干掉那些漏網之魚了??紤]到工作量有點大,就沒有這么做了(更多逆向分析資源請訪問 大神論壇)。上傳的源碼中我也實現了一個獲取合并后基本塊的出口活躍和入口活躍信息的函數,不是SSA形式的。只是寫來鞏固一下自己所學知識點而已,對去除垃圾指令也沒什么作用,大家有興趣的話可以參考一下。也沒有使用迭代的方法,而是做了一部分優化,通過工作表算法和對結點的深度優先搜索遍歷序號進行計算的。代碼實現在GlobalLiveness函數中。優化方法可以參考《現代編譯原理-C語言描述》17章的加快數據流分析部分。

本文所有的分析文件和源碼打包都在附件鏈接帖子末尾中 https://www.dslt.tech/article-97-1.html,歡迎下載交流學習,哈哈~~,更多逆向學習資料,可訪問 大神論壇

版權聲明:本文由 白云點綴的藍 原創,歡迎分享本文,轉載請保留出處

總結

以上是生活随笔為你收集整理的大神论坛 利用活跃变量分析来去掉vmp的大部分垃圾指令的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 免费看污黄网站在线观看 | 精品视频在线免费 | 国模一区二区 | 亚洲国产精品女人久久久 | wwwwxxxx国产 | 国产日韩欧美激情 | 热久久国产精品 | 国产伦精品一区二区三区88av | 亚州a级片 | 女人下边被添全过视频 | av在观看 | 欧美日韩一区二区三区在线播放 | 蜜臀av性久久久久蜜臀aⅴ流畅 | 亚洲在线免费视频 | 侵犯亲女在线播放视频 | 免费h片在线观看 | 国产又粗又猛视频免费 | 韩日三级视频 | 青青青国产在线 | 97超碰总站 | 99精品一级欧美片免费播放 | 中文字幕亚洲综合 | 成人免费视频国产在线观看 | 娇妻之欲海泛舟无弹窗笔趣阁 | 中文字幕一区二区久久人妻 | 欧美成人精品一区二区男人小说 | 狠狠干综合网 | 国产视频一区二区三区四区 | 精品亚洲一区二区 | 久草在现| 欧美成人精品三级网站 | 全部孕妇毛片丰满孕妇孕交 | 久久视频免费看 | 黑人与日本少妇高潮 | 日韩一区二区三区四区在线 | 国产精品99久久久久 | 亚州成人| 老司机午夜福利视频 | 国产亚洲精品女人久久久久久 | 超碰66| sm捆绑调教视频 | 人成在线视频 | 香港三级日本三级三69 | 色福利hd写真video | 五月婷婷综合久久 | 亚洲一区久久久 | 亚洲乱熟女一区二区 | 男同av在线观看一区二区三区 | 精品熟女一区 | 人妻洗澡被强公日日澡 | 国产丝袜av | 亚洲夜夜爽 | 美女搞黄在线观看 | 免费激情片 | 亚洲中文无码久久 | 精品视频在线观看 | 人妻无码一区二区三区四区 | asian性开放少妇pics | 伊人网大 | 极品国产在线 | www青青草 | 涩涩在线观看 | 中文精品无码中文字幕无码专区 | 国产无遮挡a片又黄又爽 | 中文字幕人妻一区二 | 天天干干天天 | 日日精品| 黄色一级片视频 | 午夜毛片在线观看 | 国产色站 | 国产精品午夜未成人免费观看 | 欧美一区二区三区久久妖精 | 精品无码m3u8在线观看 | 色播av| 国产一线二线三线在线观看 | 色婷婷激情五月 | 在线播放不卡 | 91国内精品 | 国产女人18毛片水18精 | 国产视频97 | 国产一区二区在线免费观看视频 | 日批视频在线免费看 | 日本三级日本三级日本三级极 | 亚洲综合视频在线播放 | 中国一区二区视频 | 亚洲黄色小说图片 | 深夜福利影院 | 中国在线观看视频高清免费 | 一久久久| 国产精品精品久久久 | 亚洲黄色网址大全 | 国产不卡视频一区二区三区 | 国产在线观看一区二区三区 | 亚洲精品a| 欧美久久久久久久久久久久久久 | 久久色视频| 在线se| 精品国产一区二区视频 | 国产午夜在线观看 |