这一篇是去年学习破解梦幻西游手游lua代码时记录的一些问题,今天将其整理并共享出来,所以不一定适合现在版本的梦幻手游,大家还是以参考为目的呗。lua相关的文章(共4篇)到此也写完了,如果以后还有新的东西会继续更新,接下来会写几篇关于2018 腾讯游戏安全竞赛的详细分析,敬请期待。
十二处bug修复 当时反编译梦幻西游手游时遇到的问题大约有12个,修改完基本上可以完美复现lua源码,这里用的luadec5.1版本。
修复一 问题1: 由于梦幻手游lua的opcode是被修改过的,之前的解决方案是找到梦幻的opcode,替换掉反编译工具的原opcode,并且修改opmode,再进行反编译。问题是部分测试的结果是可以的,但是当对整个手游的luac字节码反编译时,会出现各种错误,原因是luadec5.1 在很多地方都默认了opcode的顺序,并进行了特殊处理,所以需要找到这些特殊处理的地方一一修改。不过这样很麻烦,从而想到另外一种方式,不修改原来的opcode和opmode,而是在luadec解析到字节码的时候,将opcode还原成原来的opcode。
解决1: 定位到解析code的位置在 lundump.c –> LoadFunction –> LoadCode (位置不唯一,可以看上一篇腾讯比赛的修复),当执行完LoadCode函数的时候,f变量则指向了code的结构,在这之后执行自己写的函数ConvertCode函数,如下:
1 2 3 4 5 6 7 8 9 10 11 12 void (Proto *f) { int pnOpTbl[] = { 3 ,13 ,18 ,36 ,27 ,10 ,20 ,25 ,34 ,2 ,32 ,15 ,30 ,16 ,31 ,9 ,26 ,24 ,29 ,1 ,6 ,28 ,4 ,17 ,33 ,0 ,7 ,11 ,5 ,14 ,8 ,19 ,35 ,12 ,21 ,22 ,23 ,37 }; for (int pc = 0 ; pc < f->sizecode; pc++) { Instruction i = f->code[pc]; OpCode o = GET_OPCODE(i); SET_OPCODE(i, pnOpTbl[o]); f->code[pc] = i; } }
修复二 问题2: 在文件头部 反编译出现错误 – DECOMPILER ERROR: Overwrote pending register.
解决2: 分析发现,原来是解析OP_VARARG错误导致的。OP_VARARG主要的作用是复制B-1个参数到A寄存器中,而反编译工具复制了B个参数,多了一个。修改后的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ... case OP_VARARG: { int i; if (b==0 ) { TRY(Assign(F, REGISTER(a), "..." , a, 0 , 1 )); } else { for (i = 0 ; i < b-1 ; i++) { TRY(Assign(F, REGISTER(a+i), "..." , a+i, 0 , 1 )); } } break ; } ...
修复三 问题3: 在解析table出现反编译错误 – DECOMPILER ERROR: Confused about usage of 。registers!
解决3: 分析发现,这里的OP_NEWTABLE 的c参数表示hash table中key的大小,而反编译代码中将c参数进行了错误转换,导致解析错误,修改代码如下:
修复四 问题4: 反编译工具出错并且退出。
解决4: 跟踪发现是在AddToTable函数中,当keyed为0时会调用PrintTable,而PrintTable释放了table,下次再调用table时内存访问失败,修改代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void AddToTable (Function* F, DecTable * tbl, char *value, char *key) { DecTableItem *item; List *type; int index; if (key == NULL ) { type = &(tbl->numeric); index = tbl->topNumeric; tbl->topNumeric++; } else { type = &(tbl->keyed); tbl->used++; index = 0 ; } item = NewTableItem(value, index, key); AddToList(type, (ListItem *) item); if (tbl->keyedSize != 0 && tbl->keyedSize == tbl->used && tbl->arraySize == 0 ) { PrintTable(F, tbl->reg, 0 ); if (error) return ; } }
修复五 问题5: 当函数是多值返回结果并且赋值于多个变量时反编译错误,情况如下(lua反汇编):
1 2 3 4 5 6 7 8 21 [-]: GETGLOBAL R0 K9 ; R0 := memoryStatMap22 [-]: GETGLOBAL R1 K9 ; R1 := memoryStatMap23 [-]: GETGLOBAL R2 K2 ; R2 := preload 24 [-]: GETTABLE R2 R2 K3 ; R2 := R2["utils" ]25 [-]: GETTABLE R2 R2 K16 ; R2 := R2["getCocosStat" ]26 [-]: CALL R2 1 3 ; R2,R3 := R2()27 [-]: SETTABLE R1 K15 R3 ; R1["cocosTextureBytes" ] := R328 [-]: SETTABLE R0 K14 R2 ; R0["cocosTextureCnt" ] := R2
当上面的代码解析到27行时,从寄存器去取R3时报错,原因是前面的call返回多值时,只是在F->Rcall中进行了标记,没有在寄存器中标记,编译的结果应该为:
1 memoryStatMap.cocosTextureCnt, memoryStatMap.cocosTextureBytes = preload.utils.getCocosStat()
解决5: 当reg为空时并且Rcall不为空,增加一个return more的标记,修改2个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 char *RegisterOrConstant (Function * F, int r) { if (IS_CONSTANT(r)) { return DecompileConstant(F->f, r - 256 ); } else { char *copy; char *reg = GetR(F, r); if (error) return NULL ; if (reg == NULL && F->Rcall[r] != 0 ) { reg = "return more" ; } copy = malloc (strlen (reg) + 1 ); strcpy (copy, reg); return copy; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 void OutputAssignments (Function * F) { int i, srcs, size; StringBuffer *vars; StringBuffer *exps; if (!SET_IS_EMPTY(F->tpend)) return ; vars = StringBuffer_new(NULL ); exps = StringBuffer_new(NULL ); size = SET_CTR(F->vpend); srcs = 0 ; for (i = 0 ; i < size; i++) { int r = F->vpend->regs[i]; if (!(r == -1 || PENDING(r))) { SET_ERROR(F,"Attempted to generate an assignment, but got confused about usage of registers" ); return ; } if (i > 0 ) StringBuffer_prepend(vars, ", " ); StringBuffer_prepend(vars, F->vpend->dests[i]); if (F->vpend->srcs[i] && (srcs > 0 || (srcs == 0 && strcmp (F->vpend->srcs[i], "nil" ) != 0 ) || i == size-1 )) { if (strcmp (F->vpend->srcs[i], "return more" ) != 0 ) { if (srcs > 0 ) StringBuffer_prepend(exps, ", " ); StringBuffer_prepend(exps, F->vpend->srcs[i]); srcs++; } } } ... }
修复六 问题6: 当函数只有一个renturn的时候会反编译错误。
解决6:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 case OP_RETURN: { ... if (pc != 0 ) { for (i = a; i < limit; i++) { char * istr; if (i > a) StringBuffer_add(str, ", " ); istr = GetR(F, i); TRY(StringBuffer_add(str, istr)); } TRY(AddStatement(F, str)); } break ; }
修复七 问题7: 部分table初始化会出错。
解决7:
1 2 3 4 5 6 7 8 9 10 char *GetR (Function * F, int r) { if (IS_TABLE(r)) { return "{ }" ; } ... }
修复八 问题8: 可变参数部分解析出错,但是工具反编译时是不报错误的。
解决8: is_vararg为7时,F->freeLocal多加了一次:
1 2 3 4 5 6 7 8 9 10 if (f->is_vararg==7 ) { TRY(DeclareVariable(F, "arg" , F->freeLocal)); F->freeLocal++; } else if ((f->is_vararg&2 ) && (functionnum!=0 )) { F->freeLocal++; }
修复九 问题9: 反编译工具输出的中文为url类型的字符(类似 “230176148231150151230156175”),不是中文。
解决9: 在proto.c文件中的DecompileString函数中,注释掉default 转换字符串的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 char *DecompileString (const Proto * f, int n) {... default : ret[p++] = *s; break ; ... }
然后再下面3处增加判断的约束条件,因为中文字符的话,char字节是负数,这样isalpha和isalnum函数就会出错,所以增加约束条件,小于等于127:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 void MakeIndex (Function * F, StringBuffer * str, char * rstr, int self) {... int dot = 0 ; if (rstr[0 ] == '"' ) { if ((unsigned char )(rstr[1 ]) <= 127 && isalpha (rstr[1 ]) || rstr[1 ] == '_' ) { char *at = rstr + 1 ; dot = 1 ; while (*at != '"' ) { if (*(unsigned char *)at <= 127 && !isalnum (*at) && *at != '_' ) { dot = 0 ; break ; } at++; } } } .... } ... case OP_TAILCALL: { while (at > astr && ((unsigned char )(*at) <= 127 && isalpha (*at) || *at == '_' )) { at--; } } ...
修复十 问题10: 反汇编失败。因为一些文件中含有很长的字符串,导致sprintf函数调用失败。
解决10: 增加缓存的大小:
1 2 3 4 5 6 7 void luaU_disassemble (const Proto* fwork,版权声明: 本博客所有文章除特别声明外,均采用 null 许可协议。转载请注明来自 安全书 !