吾爱破解 - LCG - LSG |安卓破解|病毒分析|破解软件|澳门网上赌场 www.chengtianzy.com.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 6496|回复: 58
上一主题 下一主题

浩瀚的大洋是赌场: [调试逆向] 【已完结】与字节码解释器比耐心(动态调试)

  [复制链接]
跳转到指定楼层
楼主
发表于 2018-2-20 21:58 | 只看该作者 回帖奖励 |倒序浏览
本帖最后由 skywilling 于 2018-2-25 11:04 编辑

0x00前言
如题所言,本次的文章可能比较长,但是内容比较详细,适合新手学习,但是同样考验耐心。这次的文章严格说来是我上一篇文章遗留下来的历史问题,上一篇文章请参考【又见AES】第十届全国大学生信息安全大赛之逆向--欢迎来到加基森,同时这篇文章中的字节码解释器与【i春秋】第十届全国大学生信息安全大赛之逆向--apk题目》有异曲同工之妙。在文章开始之前,我首先要感谢一下@veritas501大神给的一些灵感。

0x01静态分析
本次要分析的代码段位于0x80495C0,如图是IDA还原出的关键伪C代码

这次我们要分析的就是disasm函数(这是改过的函数名,位于0x80495C0,),在分析这个函数之前,我们需要先分析一下传入的参数v4,图中已高亮显示,很容易看出v4可能是一个长度为4的数组(下标为0的变量存放一个数组的首地址,这个数组是一个指令集,其他3个我们在动态调试中说明)
disasm函数里面是一个有大量分支的switch分支语句,这里截取部分


0x02动态分析
在这里我是用的是Ubuntu虚拟机(该文件是ELF程序)+IDA,这里输入以0123456789012345为例,直接断点到0x0804964A(call    disasm)


此时传入的参数位于栈顶

然后在Hex View中查看内存

选中的部分就是v4[0],紧跟的三个不为零的数据分别是v4[1],v4[2],v4[3]
v4[1]存放的地址指向(其实也是一个数组)


v4[2]存放的地址指向(虽然现在指向数组尾,但是后面会发现这是一个数据临时存放区)

v4[3]存放的地址指向(待处理的数据)

v4与其解析为一个数组,不如解析为一个结构体来的简洁明了
[C] 纯文本查看 复制代码
struct code_vm{
    unsigned int *code_ip;
    unsigned int *array;
    unsigned int temp;
    unsigned int *input;
}code_vm;

这时我们把v4转换为code_vm,其中code_ip(等同v4[0])保存指令集的游标,array(等同v4[1])保存一个数组(保存一些数据)的头地址,temp(等同v4[2])保存临时数据,input(等同v4[3])指向待处理的数据头
了解了这些内容,我们开始分析disasm函数


进入后,获取code_ip游标处的数据(寄存器ebx即为code_ip)
字节码0x64
此时读取1个字节(0x64)后保存到eax中,然后再减去0xC(十进制12),最后我们得到0x64,然后又与0xF3(0xFF-0xC)比较,这个比较并没有什么重要作用,这里我们可以忽略

向下单步跟进开始跳转

接下来获取code_ip+1和code_ip+2处的数据0和0,由于都是0所以不好辨别
继续单步进入call switch_64(同样段名经过了修改)


这里有个判断,大于7的话就会跳走,但是这里一般不会跳走,先不管它(后面讲解)
往下ecx被赋值4

继续单步往下走进入call get_array(同样改过名字)

执行操作ecx=ecx-1,此时ecx=3,然后与3比较,大于3就跳转
紧接着下面是mov eax,[eax+4],在执行之前有

此时eax指向图2中指向的位置(code_ip),由于该程序为32位,寄存器都是32位的,也就是4个字节,那么eax+4就是图3指向的位置(array),那么等同于eax=array
继续往下,ecx被赋值,但是赋值的结果与ecx有关,我们定位到dword_80BDA30,

这是一个长度为4的数组,所以才有了前面与3比较的过程,是为了检测是否数组越界
这里要使用的是下标为3的数据,也就是0xFFFFFFFF,那么就有ecx=0xFFFFFFFF
紧接着就是一个ADD操作

此时寄存器里的数据如上图,有ecx=ecx and array[edx]即ecx=array[edx]
最后有eax=array[edx],此时段名最好改为get_array,以便识别


跳出get_array段后,往下走又是一个get_array(图1)
这时我们注意到有两个与7比较的指令(同样是检测数组越界,这里就可以推断出array的长度为8),一个是esi,另一个是ebp,看我标出的红箭头,esi就是第一个参数*(code_ip+1),ebp是第二个参数*(code_ip+2),由于都为0所以不是很好分辨
第一个get_array的返回值给了ebx
第二个get_array的返回值存放于eax
后面就是ebx=ebx^eax,即ebx=array[*(code_ip+1)]^array[*(code_ip+2)]
往下走,进入call set_array(同样修改了名字)


这里分别有xor,and,xor,eax异或了两次,所以与eax无关,最后有eax=ecx
edx=array
最后有array[*(code_ip+1)]=array[*(code_ip+1)]^array[*(code_ip+2)]

code_ip+=3
整理后有

运行输出和内存数据对比,主要看array,现在对比还不明显

字节码0xD9
再次读取一个字节0xD9


这个就比较简单了

根据分析有temp=*(code_ip+1)
整理后有


程序输出和内存数据对比


字节码0xBF
再往下走就是0xBF

继续进入call

在这里有edx=*(code_ip+1)
code_ip-=1
ebx=temp
temp=ebx+0x10=temp+0x10
再往下就是set_array
这里不再进入,分析后,有array[edx]=ebx
最后整理后有

输出结果与内存数据对比

向下走又是0xD9

这里不再赘述,直接给出输出结果和内存数据的对比

再次走到switch跳转处,0xBF

输出结果和内存数据对比

字节码0xB7

再次走到switch跳转处,0xB7

首先获取了*(code_ip+1),*(code_ip+2)分别为0,2

一般检测后,调用了get_array
执行后esi=array[*(code_ip+1)]

这里再次调用get_array
执行后eax=array[*(code_ip+2)]
往下走

在这里,eax如果小于等于esi都会跳走,这里没有跳走
没有跳走直接将array[7]置0
整理后有

将输出结果与内存数据对比

字节码0x67
走到switch跳转处,0x67


根据上图有ecx=array[7]
如果ecx==0,就有code_ip+=5
否则会跳走,这里没有跳走,所以我们不再分析跳走的情况
整理后有

输出结果与内存数据对比

字节码0x65
走到switch跳转处,0x65

走到关键代码处

首先eax=*(code_ip+1)=0
进入call

这里再次遇到get_array
那么就有eax=array[*(code_ip+1)]
往下走

这里是edx=code_ip-1
往下走

在这里ecx=edx
所以最终有*(code_ip+4)=eax=array[*(code_ip+1)]
整理后有

输出结果与内存数据对比

字节码0x7E
走到switch跳转处,0x7E

走到关键代码处


同样获取*(code_ip+1)和*(code_ip+2)分别是3,0
进入call

在这里,ebx=input[*(code_ip+2)],从这里就开始对input进行操作了
这里同样注意code_ip+=3
下面调用set_array
通过跟进有array[*(code_ip+1)]=input[*(code_ip+2)]
整理后有

对比输出结果与内存数据


走到switch跳转处,0x65


对比输出结果和内存数据


走到switch跳转处,0x7E

对比输出结果和内存数据

走到switch跳转处,0x64

对比输出结果和内存数据

走到switch跳转处,0x65


对比输出结果和内存数据

字节码0x77
走到switch跳转处,0x77


首先是获取*(code_ip+1)和*(code_ip+2)分别是3,0
走到关键代码处

这里再次看见了get_array
eax=array[*(code_ip+1)]

这里有input[*(code_ip+2)]=eax
整理后有

对比输出结果和内存数据


到目前为止,我们终于看到了数据被操作,这里可以很容易看出数据处理逻辑是
input[0]=input[0]^input[0x10]
同样也可以得到一个假设,在执行0x77时就会修改input数据
走到switch跳转处,0xD9

对比输出结果和内存数据

走到switch跳转处,0xBF

对比输出结果和内存数据

走到switch跳转处,0xD9

对比输出结果和内存数据

走到switch跳转处,0xBF

对比输出结果和内存数据

字节码0x79
走到switch跳转处,0x79

走进去看

获取了*(code_ip+1)和*(code_ip+2)分别是3,0
进入call

这里调用了两次get_array,有ebx=array[*(code_ip+1)]
eax=array[*(code_ip+2)]

这里有ebx-=eax
调用set_array,array[*(code_ip+1)]=ebx
整理后有

对比输出结果和内存数据

走到switch跳转处,0x79

对比输出结果和内存数据

走到switch跳转处,0x65

对比输出结果和内存数据

走到switch跳转处,0x7E

对比输出结果和内存数据

走到switch跳转处,0x65

对比输出结果和内存数据

走到switch跳转处,0x7E

对比输出结果和内存数据

这时已将input[0xF],input[0xE]放入了array中
走到switch跳转处,0x64

对比输出结果和内存数据

走到switch跳转处,0x65

对比输出结果和内存数据

走到switch跳转处,0x77

对比输出结果和内存数据


这里的数据处理逻辑是input[0xE]^=input[0xF]
所以在这里可以做个假设,假设处理整个逻辑是
input[index]^=input[0x10+index](1)
input[0xE-index]^=input[0xF-index](2)
index是从0开始的,但是不清楚i到那里结束,在这里我们也可以猜想一下
可以看出第一个式子最多可以执行0xF次,第二个式子最多可以执行0xE次
所以i的结束条件可以是0xF,也可以是0xE
毕竟这些都是猜想,需要验证是否正确
这中题目类似于找规律,求归纳式子
那么要找规律就必须至少验证两次循环了
所以我们要验证处理过程是否正确,就需要再跟进一次循环过程
下面继续分析
字节码0x00
走到switch跳转处,0x00

进入跳转

这里获取了*(code_ip+1)是0
进入call

这里面调用了get_array和set_array
分析有
eax=array[*(code_ip+1)]
esi=eax+1
array[*(code_ip+1)]=esi
整理后有

对比输出结果和内存数据

走到switch跳转处,0x00

对比输出结果和内存数据

字节码0xA0
走到switch跳转处,0xA0

进入跳转

到这里,eax=0x0FFFFFFAA=-86(十进制)
eax=edx-89
这里就可以理解为回到了循环的开始
整理后有

对比输出结果和内存数据

接下来,我们分析一下执行的流程
程序在0xA0处回到了循环的开始
那么0xA0就可以作为一次循环结束的标志
然后我们通过修改我们编写的程序来进行模拟循环的执行流程
于是我们可以有

仔细观察,我们可以发现两次循环有相同的执行流程
我们设置可以查看循环3次,4次的输出结果,会更加明显

这是我们发现第二次,第三次,第四次循环的流程是一模一样的
那么我们假设的归纳式子就是成立的了
但是我们还是不知道结束的标志在哪里
其实细心的朋友可能已经知道了
i应该执行到0xE就结束,为什么呢?
我们仔细分析了12个有用的字节码,而其他的都是根本用不到的
而这里面的操作都是不同的
那么哪个操作码是判断结束的呢?
我们可以大致浏览一下每个字节码的作用
[C] 纯文本查看 复制代码
            case 0u:
                printf("0x00->");
//                printf("\t%X %X\n",*(codeVM->code_ip+1),*(codeVM->code_ip+2));
                codeVM->array[*(codeVM->code_ip+1)]++;
                codeVM->code_ip+=2;
                break;
            case 0x64u:
                printf("0x64->");
//                printf("\t%X %X\n",*(codeVM->code_ip+1),*(codeVM->code_ip+2));
                codeVM->array[*(codeVM->code_ip+1)]^=codeVM->array[*(codeVM->code_ip+2)];
                codeVM->code_ip+=3;
                break;
            case 0x65u:
                printf("0x65->");
//                printf("\t%X %X\n",*(codeVM->code_ip+1),*(codeVM->code_ip+2));
                *(codeVM->code_ip+4)=codeVM->array[*(codeVM->code_ip+1)];
                codeVM->code_ip+=2;
                break;
            case 0x67u:
                printf("0x65->");
//                printf("\t%X %X\n",*(codeVM->code_ip+1),*(codeVM->code_ip+2));
                if(codeVM->array[7]==0){
                    codeVM->code_ip+=5;
                }
                break;
            case 0x77u:
                printf("0x77->");
//                printf("\t%X %X\n",*(codeVM->code_ip+1),*(codeVM->code_ip+2));
                codeVM->input[*(codeVM->code_ip+2)]=codeVM->array[*(codeVM->code_ip+1)];
                codeVM->code_ip+=3;
                break;
            case 0x79u:
                printf("0x79->");
//                printf("\t%X %X\n",*(codeVM->code_ip+1),*(codeVM->code_ip+2));
                codeVM->array[*(codeVM->code_ip+1)]-=codeVM->array[*(codeVM->code_ip+2)];
                codeVM->code_ip+=3;
                break;
            case 0x7Eu:
                printf("0x7E->");
//                printf("\t%X %X\n",*(codeVM->code_ip+1),*(codeVM->code_ip+2));
                codeVM->array[*(codeVM->code_ip+1)]=codeVM->input[*(codeVM->code_ip+2)];
                codeVM->code_ip+=3;
                break;
            case 0xA0u:
                printf("0xA0->");
                if(i<3) {
                    printf("\n\n");
                }else{
                    return;
                }
//                printf("\t%X %X\n",*(codeVM->code_ip+1),*(codeVM->code_ip+2));
                codeVM->code_ip-=0x100-*(codeVM->code_ip+1);
                i++;
                break;
            case 0xB7u:
                printf("0xB7->");
//                printf("\t%X %X\n",*(codeVM->code_ip+1),*(codeVM->code_ip+2));
                if(codeVM->array[*(codeVM->code_ip+1)]<codeVM->array[*(codeVM->code_ip+2)]){
                    codeVM->array[7]=0;
                }else{
                    return;
                }
                codeVM->code_ip+=3;
                break;
            case 0xBFu:
                printf("0xBF->");
//                printf("\t%X %X\n",*(codeVM->code_ip+1),*(codeVM->code_ip+2));
                codeVM->array[*(codeVM->code_ip+1)]=codeVM->temp;
                codeVM->temp+=0x10;
                codeVM->code_ip+=2;
                break;
            case 0xD9u:
                printf("0xD9->");
//                printf("\t%X %X\n",*(codeVM->code_ip+1),*(codeVM->code_ip+2));
                codeVM->temp=*(codeVM->code_ip+1);
                codeVM->code_ip+=5;
                break;
        }

经过分析我们可以看到在字节码0xB7处,有一个判断,我们可以看一下它需要的参数
我们单独输出0xB7的参数

这是执行三次循环后的输出,我们发现参数是固定的,都是0和2
那么array[0]和array[2],里面又是什么呢?我们同样把它们输出

这样的话就十分清晰了,那么就有index<0xF
结合我们归纳的式子,整理后有
[C] 纯文本查看 复制代码
for(i=0;i<0xF;i++){
       input[i]^=input[0x10+i];
       input[0xE-i]^=input[0xF-i];
 }

这时我们可以和程序运行的结果进行对比

到这里这个字节码解释器就分析完毕了
0x03总结
当你看到这里时就会明白,文章题目为什么是和字节码解释器比耐心了,这篇文章跟进了程序的31个字节码,而这只是程序循环的第一个循环。搞逆向需要耐心,这句话一点都不错。我说一下,我对这个字节码解释器的理解,这个字节码解释器一共有12个不同的字节码,每个字节码后面都紧跟1到2个操作数,可以形象的理解为操作码+操作数的组合,也就是说每个字节码和它需要的参数共同构成了一条或多条汇编指令。循着这条路走,就有了@veritas501大神的思路,根据每个字节码,分别输出对应的汇编指令,在通过分析汇编指令,编写模拟C程序。文章可能存在不足之处,欢迎指正。

附件中包含代码和程序?。?!
附件:https://pan.baidu.com/s/1o9qF97W 密码:cvs9
版权声明:允许转载,但是一定要注明出处。

免费评分

参与人数 23吾爱币 +25 热心值 +22 收起 理由
acelite + 1 + 1 谢谢@Thanks!
chkds + 1 + 1 大佬真心秀啊
ws5483 + 1 + 1 我很赞同!
Xyzkst + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
q739806133 + 1 + 1 谢谢@Thanks!
xxpl123 + 1 + 1 谢谢@Thanks!
tobias + 1 + 1 用心讨论,共获提升!
virusPPP + 1 + 1 很赞&amp;amp;#128077;
SomnusXZY + 1 + 1 热心回复!
cyay + 1 + 1 鼓励转贴优秀软件安全工具和文档!
aristotlez + 1 + 1 谢谢@Thanks!
飘荡的心 + 1 + 1 谢谢@Thanks!
我要做大神 + 1 + 1 谢谢@Thanks!
龟仔龟龟 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
Ganlv + 1 + 1 用心讨论,共获提升!
Hackerpro + 1 + 1 谢谢@Thanks!
躲在角落看繁华 + 1 鼓励转贴优秀软件安全工具和文档!
malno + 1 + 1 我很赞同!
wmsuper + 2 + 1 我很赞同!
夏雨微凉 + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Ravey + 1 + 1 谢谢@Thanks!
哈哈嘿 + 1 + 1 谢谢@Thanks!
Tomatoman + 1 + 1 用心讨论,共获提升!

查看全部评分

本帖被以下淘专辑推荐:

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】;

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

推荐
发表于 2018-3-21 15:18 | 只看该作者
liphily 发表于 2018-2-27 18:07
我记得组成原理上讲,系统并不知道自己要读取的是操作指令还是数值,还是有个固定的读取模式来着了。。。。 ...

读取数据不也是一条指令吗

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】;

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

推荐
发表于 2018-2-27 18:07 | 只看该作者
我记得组成原理上讲,系统并不知道自己要读取的是操作指令还是数值,还是有个固定的读取模式来着了。。。。。

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】;

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

板凳
发表于 2018-2-20 22:04 | 只看该作者

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】;

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

报纸
发表于 2018-2-20 22:04 | 只看该作者
正巧赶上大师授课,小白有福了,谢谢大师~

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】;

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

地板
发表于 2018-2-20 23:02 | 只看该作者
mark一下,等更新

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】;

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

7#
发表于 2018-2-21 05:57 来自手机 | 只看该作者
坐等更新,大佬威武

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】;

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

8#
发表于 2018-2-22 12:39 | 只看该作者
蒙蔽进来,懵逼回去

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】;

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

9#
发表于 2018-2-23 00:30 | 只看该作者
谢谢楼主分享  等待续哦

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】;

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

10#
发表于 2018-2-24 17:29 | 只看该作者
discuz对 【i】字符识别太恶心了,把文章全搞斜了。。。

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】;

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

11#
发表于 2018-2-25 09:50 | 只看该作者
精品,虽然我没解过汇编层次的vm?;?,但是的确知道,研究机器码就是一个体力活。

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】;

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

12#
发表于 2018-2-25 13:32 | 只看该作者
想这么高深的代码,这里面的人有能读懂的没啊?

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子分类或者标题加上【已解决】;

如何回报帮助你解决问题的坛友,一个好办法就是给对方加【热心】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则


免责声明:
吾爱破解所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:Service@www.chengtianzy.com.cn

快速回复 收藏帖子 返回列表 搜索

RSS订阅|手机版|小黑屋|联系我们|澳门网上赌场 ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2018-5-6 16:36

Powered by Discuz!

© 2001-2017 Comsenz Inc.

快速回复 澳门网上赌场 返回列表