├── README.md └── crack.py /README.md: -------------------------------------------------------------------------------- 1 | # crack-yakpro-po 2 | 破解由 `YAK Pro - Php Obfuscator` 混淆PHP代码的手段 3 | 4 | ## 说明 5 | 6 | 本脚本执行恢复还原度80%,剩下20%可手工还原,如:排版和逻辑等效替换,非全自动破解 7 | 8 | ## 环境 9 | 10 | ``` 11 | Windows 系统 12 | Python2.7.x 13 | ``` 14 | 15 | ## 依赖 16 | 17 | ``` sh 18 | pip install pickle 19 | pip install pyperclip 20 | pip install optparse 21 | ``` 22 | 23 | ## 命令 24 | 25 | ``` 26 | [USAGE] 27 | python crack.py [options] 28 | 29 | [OPTION] 30 | -s --savedatafn #指定临时文件名,该临时文件存储临时态的代码数据 31 | -o --cache #是否关闭读取缓存,第一次使用必须关闭,每次指明则会重新训练数据 32 | -f --mktplfile #生成破解模板文件 33 | -c --cachefn #指定缓存保存的文件,该缓存文件存储了序列化训练好的数据 34 | -t --target #指定需要破解的文件,该文件被 Yak Pro 混淆说加密 35 | 36 | ``` 37 | 38 | ## 解释 39 | 40 | crack.py 的代码可读性比较差,编写的时候注意力全放在如何将代码混淆的还原度和效率提高,减少人工成本 41 | 42 | 所以没遵守任何编码规范,逻辑也看起来比较乱几乎很难一眼读懂。最底下有对破解的思路简单说明,如果有想 43 | 44 | 把代码完善度和可读性进行化优化或者重构想法的朋友可以联系我交流交流: 920248921@qq.com 45 | 46 | 47 | ## 使用方法: 48 | 49 | 1. 生成缓存文件(必须): 50 | ``` bash 51 | python crack.py -t your_encode_file.php -o 52 | ``` 53 | your_encode_file 改为你的加密文件名,需要放在同一目录下,生成缓存文件名这一步很关键,后面的命令 54 | 都依赖此条,请务必先执行,如果文件大的话,计算时间也会比较久,请耐心等待,生成一次后,就无需在执行 55 | 此命令了。一次生成,无限使用 56 | 57 | 58 | 2. 生成破解模板文件(可选): 59 | ``` 60 | python crack.py -f 61 | ``` 62 | 执行后本地会生成 你的文件名.dec.php 的文件模板,可以打开看看 63 | 64 | 65 | 3. 破解标签(核心) 66 | 67 | > 打开php代码,找到你想破解的块,然后复制第一个goto 如: 68 | 69 | ``` php 70 | public function xxx{ goto abcde; ...后面省略n个字} 71 | ``` 72 | > 复制如上 abcde标签,到命令行进行代码解码 73 | ``` bash 74 | python crack.py abcde 75 | ``` 76 | > 然后就会返回破解后的结果,结果看起来有点怪,需要第四步手工排版和逻辑等效替换调整一下 77 | 78 | 79 | 4. 逻辑等效替换 80 | 破解的代码中会经常看到如下形式 81 | 82 | ```php 83 | /*if epAfl */ 84 | if (!($uid < 1)) { 85 | $card_id = intval($_GPC["card_id"]); 86 | /*if m0FKM */ 87 | 88 | } 89 | return $this->result(1, "非法进入"); 90 | gs0uX: $card_id = intval($_GPC["card_id"]); 91 | /*if m0FKM */ 92 | ``` 93 | 94 | 其中 /\*if xxxxx \*/ 表示下一行代码的标签 xxxxx 会是一个if判断语句,之所以不采用全自动替换if代码 95 | 是因为 yakpro 加密方法将 while 循环使用 if 替换了, 自动替换会导致解码工具死循环输出 96 | 97 | 所谓等效替换就是,尽可能让代码量变少,以还原出最有可能性的代码,上面的PHP就可以等效替换成如下 98 | ```php 99 | if ($uid < 1) { 100 | return $this->result(1, "非法进入"); 101 | } 102 | $card_id = intval($_GPC["card_id"]); 103 | /*if m0FKM */ 104 | ``` 105 | 类似的还有很多,此处不一一举例了 106 | 107 | 108 | 109 | 110 | ## 太麻烦了? 111 | 联系主人,有偿破解代码: 920248921@qq.com 112 | 113 | 114 | 115 | ## 破解思路: 116 | 117 | ##### 0x00 Yarkpo 混淆代码的特征主要有两个 118 | 1. goto 代替流程 119 | 2. 字符串用10进制、16进制编码 120 | 121 | `这两个特征在文本中特别明显,如下` 122 | 123 | ``` php 124 | 125 | 218 | ``` 219 | 执行结果就是 220 | > coo 221 | 222 | 其中的执行顺序: 223 | 1. goto coo; 224 | 2. echo 'coo'; 225 | 3. goto eoo; 226 | 227 | 228 | 两个特点: 229 | 1. 执行到标签时,标签身后代码会被执行,不一定非要goto才触发 230 | 2. goto 跳过去后就算执行完了所有代码不会回溯之前的位置,除非是用goto跳回来的 231 | 232 | 233 | 然后使用深度优先替换: 见到一个goto就替换一个 234 | 235 | ``` python 236 | labelList = re.findall(r"([a-zA-Z0-9_]{5}): ",srcCode); 237 | for labelName in labelList: 238 | labelValue = re.findall(r""+labelName+": (.*?)\s+[a-zA-Z0-9_]{5}:", srcCode)[0]; 239 | srcCode.replace("goto "+labelName+";", labelValue); 240 | ``` 241 | 242 | 上面指摘去了部分核心代码,运行结果如下 243 | ``` PHP 244 | 0: 142 | i-=1; 143 | value = re.findall(r""+name+": (foreach .*?{"+mlb+".*?goto \w{5};)",content, re.S); 144 | rbraces = value[0].count("}"); 145 | lbraces = value[0].count("{"); 146 | if lbraces > rbraces: 147 | mlb = mlb + mlb * (lbraces - rbraces) 148 | elif lbraces == rbraces: 149 | break; 150 | 151 | if len(value) != 0: 152 | value = value[0].replace('\n','') 153 | gotoList = re.findall(r"goto \w{5};", value); 154 | if len(gotoList) > 1: 155 | prefix = value.split('{')[0]; 156 | lastGoto = gotoList.pop(); 157 | firstGoto = gotoList[0]; 158 | value = prefix + '{' + firstGoto + '}' + lastGoto; 159 | labelDict[name] = {'type':'fe', 'value':value, 'access':False}; 160 | labelType['fe'].append(name); 161 | 162 | #收录switch 163 | elif "switch (" in value : 164 | while i>0: 165 | i-=1; 166 | value = re.findall(r""+name+": (switch .*?{"+mlb+".*?goto \w{5};)",content, re.S); 167 | if value: 168 | rbraces = value[0].count("}"); 169 | lbraces = value[0].count("{"); 170 | if lbraces > rbraces: 171 | mlb = mlb + mlb * (lbraces - rbraces) 172 | elif lbraces == rbraces: 173 | break; 174 | 175 | if len(value) != 0: 176 | value = value[0].replace('\\n',''); 177 | lpos = value.find('{'); 178 | rpos = value.rfind('}'); 179 | inner = value[lpos:rpos]; 180 | prefix = value[:lpos]; 181 | caseList = re.findall(r"(case .*?goto \w{5};)", inner); 182 | 183 | #default 情况复杂暂不处理 184 | #defaList = re.findall(r"(default: .*?goto \w{5};)",value); 185 | #if not defaList: 186 | # defaStr = ''; 187 | #else: 188 | # defaStr = defaList[0]; 189 | 190 | value = prefix + '{\n' + '\n'.join(caseList) + '\n}' 191 | labelDict[name] = {'type':'sw', 'value':value, 'access':False}; 192 | labelType['sw'].append(name); 193 | 194 | #收录if标签 195 | elif "if " in value: 196 | while i>0: 197 | i-=1; 198 | value = re.findall(r""+name+": (if.*?{"+mlb+".*?goto \w{5};)", content, re.S); 199 | rbraces = value[0].count("}"); 200 | lbraces = value[0].count("{"); 201 | if lbraces > rbraces: 202 | mlb = mlb + mlb * (lbraces - rbraces) 203 | elif lbraces == rbraces: 204 | break; 205 | 206 | if len(value) != 0: 207 | value = value[0].replace('\n','') 208 | gotoList = re.findall(r"goto \w{5};", value); 209 | if len(gotoList) > 1: 210 | prefix = value.split('{')[0]; 211 | lastGoto = gotoList.pop(); 212 | firstGoto = gotoList[0]; 213 | value = prefix + '{' + firstGoto + '}' + lastGoto; 214 | labelDict[name] = {'type':'if', 'value':value, 'access':False}; 215 | labelType['if'].append(name); 216 | 217 | #收录普通标签 218 | else: 219 | #去除双重goto 220 | value = removeGoto(value) 221 | if value and value[0] == '}': 222 | value = ''; 223 | labelDict[name] = {'type':'od', 'value':value, 'access':False}; 224 | labelType['od'].append(name); 225 | 226 | labelData = { 227 | 'dict': labelDict, 228 | 'type': labelType, 229 | } 230 | return labelData; 231 | 232 | 233 | #训练各类标签 234 | def trainLabel(labelData): 235 | 236 | labelDict = labelData['dict']; 237 | labelType = labelData['type']; 238 | 239 | def train(dictType): 240 | for lbidx in labelType[dictType]: 241 | lbvalue = labelDict[lbidx]['value']; 242 | 243 | while "goto" in lbvalue: 244 | lbnxt = re.findall(r"goto (\w{5});", lbvalue)[0]; 245 | lbtype = labelDict[lbnxt]['type']; 246 | 247 | if lbtype == 'fe': 248 | rpstr = "/*foreach "+lbnxt+" */\n"; 249 | if not labelDict[lbnxt]['access']: 250 | labelDict[lbnxt]['access'] = True; 251 | rpstr += labelDict[lbnxt]['value']; 252 | 253 | elif lbtype == 'sw': 254 | rpstr = "/*switch "+lbnxt+" */\n"; 255 | if not labelDict[lbnxt]['access']: 256 | labelDict[lbnxt]['access'] = True; 257 | rpstr += labelDict[lbnxt]['value']; 258 | 259 | elif lbtype == 'if': 260 | rpstr = "/*if "+lbnxt+" */\n"; 261 | if not labelDict[lbnxt]['access']: 262 | labelDict[lbnxt]['access'] = True; 263 | rpstr += labelDict[lbnxt]['value']; 264 | 265 | elif lbtype == 'od': 266 | rpstr = labelDict[lbnxt]['value']; 267 | 268 | else: 269 | print "UNKNOW TYPE" 270 | sys.exit(); 271 | 272 | lbvalue = lbvalue.replace("goto "+lbnxt+";", rpstr); 273 | 274 | labelDict[lbidx]['value'] = lbvalue; 275 | 276 | 277 | train('od'); 278 | train('if'); 279 | train('fe'); 280 | train('sw'); 281 | 282 | return {'type':labelType, 'dict':labelDict}; 283 | 284 | #交换条件if 285 | def switchIf(labelData): 286 | for iflb in labelData['type']['if']: 287 | 288 | ifvl = labelData['dict'][iflb]['value']; 289 | 290 | condition = re.findall(r"if \(!(\(.*?\))\)", ifvl); 291 | 292 | if condition: 293 | 294 | gotoList = re.findall(r"(goto \w{5};)", ifvl); 295 | 296 | if len(gotoList) == 2: 297 | 298 | newifvl = 'if '+ condition[0] + '{ ' + gotoList[1] + ' } ' + gotoList[0]; 299 | labelData['dict'][iflb]['value'] = newifvl; 300 | 301 | return labelData; 302 | 303 | #去除return 后面的goto 304 | def returnRm(labelData): 305 | for odlb in labelData['type']['od']: 306 | odvl = labelData['dict'][odlb]['value']; 307 | 308 | rtncode = re.findall(r"(return .*?;)",odvl); 309 | if rtncode: 310 | labelData['dict'][odlb]['value'] = rtncode[0]; 311 | 312 | return labelData; 313 | 314 | #格式化输出结果 315 | def formatRes(string): 316 | 317 | if ";" in string: 318 | string = string.replace(";",";\n"); 319 | 320 | if "{" in string: 321 | string = string.replace("{","{\n"); 322 | 323 | if "}" in string: 324 | string = string.replace("}","\n}\n"); 325 | 326 | res = "" 327 | 328 | for s in string.split("\n"): 329 | res += s.strip() + "\n"; 330 | 331 | print res; 332 | pyperclip.copy(res.encode("utf-8")); 333 | 334 | 335 | #生成手工破解框架文件 336 | def mktplFile(content, labelDict): 337 | 338 | incres = "0: 415 | label = args[0] 416 | formatRes(labelData['dict'][label]['value']); 417 | 418 | --------------------------------------------------------------------------------