(抛砖引玉)x86反-乱序/膨胀处理脚本
本帖最后由 风扫春残雪 于 2020-7-6 23:06 编辑分析了两天CM区一个乱序的(https://www.52pojie.cn/thread-1213289-1-1.html),感觉非常有意思。
虽然最后还是没能坚持下来,但顺手写了点儿程序,未来不管是分析同类乱序,还是壳内的乱序,想来都应该有用。发上来和大家一起学习交流
(大佬轻拍,主要是给我等萌新复习姿势用……这个方向细分水太深了)
这个帖子大概分为几部分
1. 反乱序处理
2. 反膨胀处理
3. 脚本说明
1. 乱序处理
这几天研究了很多怎么把一段代码进行正向乱序的帖子,我就不再班门弄斧了,参见
https://bbs.pediy.com/thread-96640.htm
https://bbs.pediy.com/thread-180456.htm
http://bbs.pediy.com/showthread.php?t=49923
http://bbs.pediy.com/showthread.php?t=155878
http://bbs.pediy.com/showthread.php?t=155215 (特别推荐)
总得来说,就是把一大段代码分割成不同的小部分,再通过跳转互相连接起来
对乱序的代码最好的方式是动态记录,即通过脚本把单步执行的结果记录下来,或者记录关键位置的寄存器,结合静态分析进行拼接
实际上,在一些高强度的壳里边,这基本是唯一的分析方式。因为push/ret的立即数会被代码膨胀保护起来。然而拿不到顺序的程序,又很难进行反混淆
动态记录/分析的好处是,哪怕程序里使用了随机数+标志位+虚拟机手段,每次的路径都不太一样,但只要有一次运行成功的,分析那一次的就可以了
这次的脚本就是通过Ollyscript的反汇编功能进行静态分析的。
静态分析的蛋疼之处是,如果遇上了一些需要依靠数值的条件跳转,可能难以理清程序的逻辑。同时遇上复杂保护下的立即数,则需要提前获取程序流程的“MAP”才能继续。不过那样的话又实际上等同于动态分析了
2. 反膨胀处理
正向实现参考
https://bbs.pediy.com/thread-223629.htm
http://bbs.pediy.com/showthread.php?t=155215 (再次安利这篇)
https://bbs.pediy.com/thread-217588.htm
水太深了,不是我这水平的菜鸡能讨论的。大概比较先进的思路是写类llvm,把膨胀的指令优化回去
我这个脚本只能处理一些低级水平的反膨胀(
3. 我的脚本
脚本分为两部分
3.1 一部分是用Ollyscript写的静态分析。论坛上很久以前有个二货写过一遍,那个也是我。这次重新入坑我又重构了一遍,加入了一些新功能。大体上没有太多变化
3.2 另一部分反膨胀,是基于手工指令进行的文本处理反膨胀。分析不同的膨胀需要加入不同的膨胀模式,这一部分是需要自己完成的
3.3 Ollyscript程序
食用方法:
1)保存为txt,
2)修改intVirtualEIP为你想开始反乱序的地址,修改SETTING_INT_LENGTH为你想进行反乱序的长度
3)OD->插件->Ollyscript->执行
4)*note: 程序里附带了一些反trick的部分,比如jejnz花指令,比如push offset/ add ,offset2,retn这样的。如果不需要请自行修改代码
var SETTING_INT_LENGTH,int_NowLength
var SETTING_INTO_CALL
var intVirtualEIP
var intCALLRetn
var intTemp
var intFlowerJMPCALL_FlagType
//setting
mov SETTING_INT_LENGTH, 500
mov SETTING_INTO_CALL, 1
//init
mov int_NowLength, 0
mov intVirtualEIP, 404520
lclr
//start
start:
opcode intVirtualEIP
scmp $RESULT, "E9", 2 //LONG JMP
je OPERATION_JMP
scmp $RESULT, "EB", 2 //SHORT JMP
je OPERATION_SHORT_JMP
scmp $RESULT, "C3", 2 //RET
je OPERATION_RET
cmp SETTING_INTO_CALL, 0
je OPERATION_NORMAL
//CALL
scmp $RESULT, "E8", 2 //CALL
je OPERATION_CALL
OPERATION_NORMAL:
//SPECIAL DE-FLOWER FUNCTION, SHOULD BE UPDATED EVERY TIME YOU ANALYSE A NEW PROGRAM
scmp $RESULT, "812C24",6 // sub ,xxxx
je OPERATION_FLOWER_SUB_ESP
scmp $RESULT, "810424",6 // add ,xxxx
je OPERATION_FLOWER_ADD_ESP
scmp $RESULT, "68",2 //PUSH (PUSH XXX, RET)
je OPERATION_FLOWER_PUSH
scmp $RESULT_1, "mov",3 //MOV REG,XXX; LEA REG,; CALL/JMP XXX
je OPERATION_FLOWER_MOVLEACALLJMP
scmp $RESULT_1, "je",2 // JE OFFSET + JNZ OFFSET
je OPERATION_FLOWER_JEJNZ
NORMAL_FUNCTION:
opcode intVirtualEIP
itoa intVirtualEIP
add $RESULT," : "
log $RESULT_1, $RESULT //Log the currect command of virtual eip
add intVirtualEIP, $RESULT_2
done:
inc int_NowLength
cmp int_NowLength, SETTING_INT_LENGTH
jnz start
ret //SCRIPT DONE
//------------------------------------
//Func:LONG JMP
OPERATION_JMP:
itoa intVirtualEIP
add $RESULT," : "
//log "LONG JMP Found"
//log $RESULT_1, $RESULT
gci intVirtualEIP, DESTINATION //get DESTINATION of the JMP
mov intVirtualEIP, $RESULT
jmp done
//------------------------------------
//Func:SHORT JMP
OPERATION_SHORT_JMP:
itoa intVirtualEIP
add $RESULT," : "
//log "SHORT JMP Found"
//log $RESULT_1, $RESULT
gci intVirtualEIP, DESTINATION //get DESTINATION of the JMP
mov intVirtualEIP, $RESULT
jmp done
//------------------------------------
//Func:CALL
OPERATION_CALL:
itoa intVirtualEIP
add $RESULT," : "
//log "CALL Found"
log $RESULT_1, $RESULT
gci intVirtualEIP, DESTINATION //get DESTINATION of the CALL
mov intCALLRetn, intVirtualEIP
add intCALLRetn, $RESULT_2 //KEY OF THE CALL
mov intVirtualEIP, $RESULT
jmp done
//------------------------------------
//Func:RET
OPERATION_RET:
itoa intVirtualEIP
add $RESULT," : "
//log "RET Found"
log $RESULT_1, $RESULT
mov intVirtualEIP, intCALLRetn
jmp done
//------------------------------------
//Func:OPERATION_FLOWER_SUB_ESP
OPERATION_FLOWER_SUB_ESP:
itoa intVirtualEIP
add $RESULT," : "
//log "FLOWER_SUB_ESP Found"
log $RESULT_1, $RESULT
sub intCALLRetn,
add intVirtualEIP, $RESULT_2
jmp done
//------------------------------------
//Func:OPERATION_FLOWER_ADD_ESP
OPERATION_FLOWER_ADD_ESP:
itoa intVirtualEIP
add $RESULT," : "
//log "FLOWER_ADD_ESP Found"
log $RESULT_1, $RESULT
add intCALLRetn,
add intVirtualEIP, $RESULT_2
jmp done
//------------------------------------
//Func:OPERATION_FLOWER_PUSH
OPERATION_FLOWER_PUSH:
mov intTemp, intVirtualEIP
add intTemp, $RESULT_2
mov intTemp,
and intTemp, FF //get the lower byte
cmp intTemp, C3 //next code is RET
jnz NORMAL_FUNCTION
itoa intVirtualEIP
add $RESULT," : "
//log "FLOWER_PUSH_RETN Found"
//log $RESULT_1, $RESULT
mov intVirtualEIP, //bypass ret
jmp done
//------------------------------------
//Func:OPERATION_FLOWER_PUSH
OPERATION_FLOWER_PUSH:
mov intTemp, intVirtualEIP
add intTemp, $RESULT_2
mov intTemp,
and intTemp, FF //get the lower byte
cmp intTemp, C3 //next code is RET
jnz NORMAL_FUNCTION
itoa intVirtualEIP
add $RESULT," : "
log "FLOWER_PUSH_RETN Found"
log $RESULT_1, $RESULT
mov intCALLRetn,
add intVirtualEIP, $RESULT_2
jmp done
//------------------------------------
//Func:OPERATION_FLOWER_MOVLEACALLJMP
OPERATION_FLOWER_MOVLEACALLJMP:
mov intFlowerJMPCALL_FlagType, 0 //1 for call, 2 for jmp
mov intTemp,intVirtualEIP
add intTemp,$RESULT_2
opcode intTemp
scmp $RESULT_1, "lea", 3
jnz NORMAL_FUNCTION
add intTemp,$RESULT_2
opcode intTemp
scmp $RESULT_1, "call", 4
jnz MOVLEACALLISJMP
mov intFlowerJMPCALL_FlagType, 1
jmp MOVLEACALLALL
MOVLEACALLISJMP:
scmp $RESULT_1, "jmp", 3
jnz NORMAL_FUNCTION
mov intFlowerJMPCALL_FlagType, 2
MOVLEACALLALL:
//NOW IT DOES
opcode intVirtualEIP
mov intTemp, //save the first value in intTemp
itoa intVirtualEIP
add $RESULT," : "
//log "FLOWER_MOVLEAJMP Found"
//Log $RESULT_1, $RESULT
//log "NOP", $RESULT
add intVirtualEIP, $RESULT_2
opcode intVirtualEIP
itoa intVirtualEIP
add $RESULT," : "
//log $RESULT_1, $RESULT
//log "NOP", $RESULT
add intTemp, // add the second value in intTemp
add intVirtualEIP, $RESULT_2
opcode intVirtualEIP
itoa intVirtualEIP
add $RESULT," : "
//log $RESULT_1, $RESULT
//log "NOP", $RESULT
mov intVirtualEIP, intTemp
cmp intFlowerJMPCALL_FlagType, 2 //if JMP type, GO NOW
je done
mov intCALLRetn, intVirtualEIP
add intCALLRetn, $RESULT_2 //CALL retn address
jmp done
//------------------------------------
//Func:OPERATION_FLOWER_JEJNZ
OPERATION_FLOWER_JEJNZ:
mov intTemp, intVirtualEIP
add intTemp, $RESULT_2
opcode intTemp
scmp $RESULT_1, "jnz", 3
jnz NORMAL_FUNCTION
gci intTemp, DESTINATION
mov intTemp, $RESULT
gci intVirtualEIP,DESTINATION
cmp intTemp, $RESULT
jnz NORMAL_FUNCTION
itoa intVirtualEIP
add $RESULT," : "
//log "FLOWER_PUSH_JEJNZ Found"
//log $RESULT_1, $RESULT
log "NOP", $RESULT
mov intVirtualEIP, intTemp
jmp done
3.4 python反膨胀脚本
这次就写了个框架。。。基本的思路是替换膨胀的模式,不过程序显然还不太完善,具体的改进之处有:
1)不要自己手写模拟引擎了,用正则表达更香
2)对于一些固定的模式应该考虑使用启发式还原(比如通过局部的寄存器分离,来让代码“分开”;提醒重复的模式等)
食用方法:
将3.3程序的脚本运行日志右键--复制表--保存为txt
请根据分析程序,自行修改__main__部分
主要的程序接口为:
ScanAndDel_MulReg(lstAsmCodes,lstAsmFiles,intUsedRegs,lstRegs,strReplaceCode)
lstAsmCodes:
以lst形式保存的一系列膨胀模式汇编代码。函数扫描到这些相邻的代码后,将会用strReplaceCode替换
*允许使用reg1/reg2/reg3/reg4指代寄存器
*允许使用idata,不过idata的值可能是立即数,也可能是寄存器
*允许使用any,使用any时,将不会替换any对应的指令
lstAsmFiles:
open("input.txt").readlines()形式的文件内容列表,输入必须为3.3程序的结果。
intUsedRegs:
在lstAsmCodes中使用regs指代寄存器的个数
lstRegs:
在lstAsmCodes中使用regs指代哪些寄存器,默认可以使用lst_AllREGS,不过会降低脚本速度
strReplaceCode:
替换的目的指令,只能为单行
lst_AllREGS = ['eax','ebx','ecx','edx','esi','edi']
def makelstRegs(intUsedRegs, lstCurrent, lstResult, lstAllOptions):
if (lstCurrent != []) and ((intUsedRegs == 0) or (len(lstCurrent) == len(lstAllOptions))):
lstResult.append(lstCurrent)
return lstCurrent
for reg in lstAllOptions:
if not reg in lstCurrent:
lstNowCurrect = lstCurrent.copy()
lstNowCurrect.append(reg)
makelstRegs(intUsedRegs - 1, lstNowCurrect, lstResult, lstAllOptions)
return lstResult
def debugOut(strLine):
f = open("debug.txt",'a')
f.write(strLine + "\n")
print(strLine)
f.close()
def getAddress(strLine):
return strLine.split(":").split(" ")[-2]
def getIdata(strLine):
strOpCode = getAllOpCode(strLine)
if strLine.find("[") < 0:
#mov eax,891023h
return strOpCode.split(",")[-1]
else:
#lea edx,dword ptr ds:
strOpCode = strOpCode.split("[")[-1]
if strOpCode.find("+")>0:
strOpCode = "+" + strOpCode.split("+")[-1]
elif strOpCode.find("-")>0:
strOpCode = "-" + strOpCode.split("-")[-1]
strOpCode = strOpCode.replace("]","")
return strOpCode
def getLongAddress(strLine):
return "*"+strLine.split(":") + ": "
def getAllOpCode(strLine):
return ":".join(strLine.split(":")).strip("\n")
def getOpCode(strLine):
strAllOp = getAllOpCode(strLine)
return strAllOp.split(" ")
def getOpLeftCode(strLine):
strAllOp = getAllOpCode(strLine)
return strAllOp.split(",")
def ScanAndDel(lstCommand, lstASMLines, regs = [], strReplaceCode = ""):
if regs == []:
regs = lst_AllREGS
for reg in regs:
lstCommandwithREG = []
for strCommand in lstCommand:
lstCommandwithREG.append(strCommand.replace('reg',reg).replace("idata",""))
for index, line in enumerate(lstASMLines):
strIdata = ""
if line!="":
strAddress = getAddress(line)
if index + len(lstCommandwithREG) <= len(lstASMLines):
line = line.strip("\n")
flag_found = True
for j, strCommand in enumerate(lstCommandwithREG):
if lstASMLines.find(strCommand) < 0:
flag_found = False
break
else:
if strCommand.find("idata") >= 0:
strIdata = getIdata(lstASMLines)
if flag_found == True:
debugOut("Found Command %s at Line %s, reg = %s"%(str(lstCommand), strAddress, reg))
for j, strCommand in enumerate(lstCommand): #erase the command
lstASMLines = ""
if strReplaceCode != "":
strReplaceCode =strReplaceCode.replace("idata", strIdata)
lstASMLines = getLongAddress(line) + strReplaceCode
return lstASMLines
def getVaildOPcode(lstASMLines, index, j):
#get the j-th opcodes in the lstASMLines with the pointer index
try:
i = -1
lines = 0
while (i < j):
if lstASMLines != "":
i += 1
lines += 1
return lstASMLines, lines - 1, 0
except:
#at the end of the file
return "",-1,-1
def ScanAndDel_MulReg(lstCommand, lstASMLines, intRegs, regs,strReplaceCode = ""):
lst_optionRegs = makelstRegs(intRegs, [], [], regs)
#debugOut("MulReg init success, regs = %s, lstlength = %s"%(regs, len(lst_optionRegs)))
for regs in lst_optionRegs:
lstCommandwithREG = []
strReplaceCodewithREG = strReplaceCode
for strCommand in lstCommand:
for index, item in enumerate(regs):
strCommand = strCommand.replace("reg%s"%(index+1), item)
strReplaceCodewithREG = strReplaceCodewithREG.replace("reg%s"%(index+1), item)
strCommand = strCommand.replace("idata","")
lstCommandwithREG.append(strCommand)
ilstASMLines = lstASMLines.copy()
for index, line in enumerate(ilstASMLines):
strIdata = ""
lstAnyCommand = []
if line!= "":
strAddress = getAddress(line)
if index + len(lstCommandwithREG) <= len(lstASMLines):
line = line.strip("\n")
flag_found = True
for j, strCommand in enumerate(lstCommandwithREG): #next j VAILD opcodes
#if (strAddress == "4035d4"):
# print(lstASMLines)
# print(lstCommandwithREG)
if getVaildOPcode(lstASMLines, index, j) == -1: #at the end of the file
flag_found = False
break
if strCommand == "any":
str_thisline = getAllOpCode(getVaildOPcode(lstASMLines, index, j)) #避免any匹配上后缀需要匹配的命令,必须对所有any之后的command一一比对,如果比对上了,立刻丢掉
flag_any_avoid = True
for k, strCommand_any in enumerate(lstCommandwithREG):
if (str_thisline.find(strCommand_any) ==0 ):
flag_any_avoid = False
flag_found = False
break
if flag_any_avoid == True:
lstAnyCommand.append(str_thisline)
continue
else:
break
if (getAllOpCode(getVaildOPcode(lstASMLines, index, j)).find(strCommand) != 0): # >0 when find 'or eax,...' in command 'xor eax,ebx'
flag_found = False
break
else:
if lstCommand.find("idata") >= 0:
strIdata = getIdata(getVaildOPcode(lstASMLines, index, j))
if (flag_found == True):
debugOut("Found Command(MulReg) %s at Address %s(Line = %s), reg = %s, idata = %s"%(str(lstCommand), index, strAddress, regs, strIdata))
if strReplaceCode != "":
strReplaceCodewithREG = strReplaceCodewithREG.replace("idata", strIdata)
lstTempToWrite = getLongAddress(getVaildOPcode(lstASMLines, index, j)) + strReplaceCodewithREG
else:
lstTempToWrite = ""
intAny = 0
lstAnyToWrite = []
for j, strCommand in enumerate(lstCommand): #erase the command
if strCommand == "any":
lstAnyToWrite.append((index + getVaildOPcode(lstASMLines, index, 0),getLongAddress(getVaildOPcode(lstASMLines, index, 0)) + lstAnyCommand))
intAny += 1
lstASMLines] = "" #the third paramter must be 0 (del the first line everytime)
if lstAnyToWrite == []:
lstASMLines = lstTempToWrite
else:
for windex, strTowrite in lstAnyToWrite:
lstASMLines = strTowrite
debugOut("-->any write(%s), %s"%(windex, strTowrite))
return lstASMLines
def WriteOutFile(strPath, lstASMLines):
fw = open(strPath, 'w')
for line in lstASMLines:
if line != "":
fw.write(line.strip("\n") + "\n")
fw.close()
#note:idata > 0 ok <0 ok
#note:any : any command
lines = open("input3.txt").readlines()
ans = lines
for i in range(0,6):
print(i)
ans = ScanAndDel(['call','add dword ptr ss:','retn'], lines, 'NONE')
ans = ScanAndDel(['call','sub dword ptr ss:','retn'], ans, 'NONE')
ans = ScanAndDel(['NOP'], ans, 'NONE')
ans = ScanAndDel(['call','add esp,0x4'], ans, 'NONE')
ans = ScanAndDel(['mov dword ptr ss:,reg','lea esp,dword ptr ss:','lea esp,dword ptr ss:','mov reg,dword ptr ss:'], ans, [])
ans = ScanAndDel(['mov dword ptr ss:,reg','sub esp,0x4','pop reg'], ans, [])
ans = ScanAndDel(['call','lea esp,dword ptr ss:'], ans, 'NONE')
ans = ScanAndDel(['sub ebp,ebp','or ebp,esp'], ans, 'NONE', 'mov ebp,esp')
ans = ScanAndDel(['lea esp,dword ptr ss:'], ans, 'NONE','mov esp,ebp')
ans = ScanAndDel_MulReg(['push reg1','mov reg1,reg2','and reg1,reg3','mov reg2,','xchg reg1,reg2','pop reg1'], ans, 3, ['eax','ebx','ecx','edx'], 'and reg2,reg3')
ans = ScanAndDel_MulReg(['push reg1','mov reg1,idata','xchg reg1,reg2','pop reg1'], ans, 2, ['eax', 'ebx', 'ecx', 'edx'], 'mov reg2,idata')
ans = ScanAndDel_MulReg(['push reg1','xchg reg2,reg1','mov reg2,idata','sub reg1,reg2','mov reg2,reg1','pop reg1'], ans, 2, ['eax', 'ebx', 'ecx', 'edx'], 'sub reg1,idata')
ans = ScanAndDel_MulReg(['xor reg1,reg1','or reg1,idata'], ans, 1, lst_AllREGS, 'mov reg1,idata')
ans = ScanAndDel_MulReg(['sub reg1,reg1','or reg1,idata'], ans, 1, lst_AllREGS, 'mov reg1,idata')
ans = ScanAndDel_MulReg(['lea reg1,dword ptr ds:'], ans, 1, lst_AllREGS, 'add reg1,idata')
ans = ScanAndDel_MulReg(['lea reg1,dword ptr ds:, ans, 1, lst_AllREGS, 'add reg1,idata')
ans = ScanAndDel_MulReg(['push reg1','mov reg1,idata','xchg reg1,reg2','pop reg1'], ans, 2, lst_AllREGS, 'mov reg2,idata')
ans = ScanAndDel_MulReg(['not reg1','not reg1'], ans, 1, lst_AllREGS, "")
ans = ScanAndDel_MulReg(['mov reg1,reg2','and reg1,reg2'], ans, 2, lst_AllREGS, 'mov reg1,reg2')
ans = ScanAndDel_MulReg(['push reg1','mov reg1,reg2','mov reg2,','xchg reg1,reg2','pop reg1'], ans, 2, lst_AllREGS, "")
ans = ScanAndDel_MulReg(['push reg1','mov reg1,reg2','and reg1,reg3','mov reg2,reg1','pop reg1'], ans, 3, lst_AllREGS,"and reg2,reg3")
ans = ScanAndDel_MulReg(['push reg1','mov reg2,','pop reg2'], ans, 2, lst_AllREGS, "mov reg2,reg1")
ans = ScanAndDel_MulReg(['push reg1','mov reg1,','pop reg1'], ans, 1, lst_AllREGS, '')
ans = ScanAndDel_MulReg(['push reg1','xchg reg1,','pop reg1'], ans, 1, lst_AllREGS, '')
ans = ScanAndDel_MulReg(['mov reg1,reg1'], ans, 1, lst_AllREGS, '')
ans = ScanAndDel_MulReg(['and reg1,reg1'], ans, 1, lst_AllREGS, '')
ans = ScanAndDel_MulReg(['lea reg1,dword ptr ds:'], ans, 2, lst_AllREGS, 'mov reg1,reg2')
ans = ScanAndDel_MulReg(['mov reg1,reg2','mov reg2,reg1'], ans, 2, lst_AllREGS, 'mov reg1,reg2')
ans = ScanAndDel_MulReg(['push reg1','mov reg1,reg2','and reg1,reg3','mov reg2,reg1','pop reg1'],ans, 3, lst_AllREGS, 'and reg2,reg3')
ans = ScanAndDel_MulReg(['push reg1','mov reg1,reg2','xchg reg2,reg1','pop reg1'], ans, 2, lst_AllREGS, "")
ans = ScanAndDel_MulReg(['push reg1','mov reg1,reg2','and reg1,reg3','mov reg2,','xchg reg2,reg1','pop reg1'], ans, 3, lst_AllREGS, "and reg2,reg3")
ans = ScanAndDel_MulReg(['not reg1','mov reg2,idata','not reg1'],ans,2,lst_AllREGS,'mov reg2,idata')
#ans = ScanAndDel_MulReg(['push reg1','mov reg1,reg2','any','any','any','xchg reg1,reg2','pop reg1'],ans,2,lst_AllREGS,"")
#ans = ScanAndDel_MulReg(['push reg1','mov reg1,reg2','mov reg2','any','xchg reg2,reg1','pop reg1'],ans,2,lst_AllREGS,"")
ans = ScanAndDel_MulReg(['push reg1','mov reg1,reg2','add reg2,','xchg reg1,reg2','pop reg1'],ans,2,lst_AllREGS,"")
WriteOutFile("output.txt", ans)
谢谢分享
感谢分享 感谢分享 感谢分享教程
学习学习 感谢分享 感谢技术分享,评分双手送上。 学习 谢谢 感谢分享
页:
[1]
2