switch语句的逆向
在vc6.0编译器中,会自动为switch转换成4种情况的汇编语句。
case1:
当c语言源代码为
1 #include<stdio.h> 2 void myFunction(int x) 3 { 4 switch(x) 5 { 6 case 1: 7 printf("1"); 8 break; 9 case 2: 10 printf("2"); 11 break; 12 case 3: 13 printf("3"); 14 break; 15 default: 16 printf("error"); 17 } 18 } 19 int main() 20 { 21 myFunction(2); 22 return 0; 23 }
运行的结果可以看出,和if&else一样。
都是cmp&jcc的。
1 4: switch(x) 2 5: { 3 0040D7B8 mov eax,dword ptr [ebp+8] 4 0040D7BB mov dword ptr [ebp-4],eax 5 0040D7BE cmp dword ptr [ebp-4],1 6 0040D7C2 je myFunction+32h (0040d7d2) 7 0040D7C4 cmp dword ptr [ebp-4],2 8 0040D7C8 je myFunction+41h (0040d7e1) 9 0040D7CA cmp dword ptr [ebp-4],3 10 0040D7CE je myFunction+50h (0040d7f0) 11 0040D7D0 jmp myFunction+5Fh (0040d7ff) 12 6: case 1: 13 7: printf("1"); 14 0040D7D2 push offset string "1" (0042202c) 15 0040D7D7 call printf (00401110) 16 0040D7DC add esp,4 17 8: break; 18 0040D7DF jmp myFunction+6Ch (0040d80c) 19 9: case 2: 20 10: printf("2"); 21 0040D7E1 push offset string "2" (00422028) 22 0040D7E6 call printf (00401110) 23 0040D7EB add esp,4 24 11: break; 25 0040D7EE jmp myFunction+6Ch (0040d80c) 26 12: case 3: 27 13: printf("3"); 28 0040D7F0 push offset string "4" (00422024) 29 0040D7F5 call printf (00401110) 30 0040D7FA add esp,4 31 14: break; 32 0040D7FD jmp myFunction+6Ch (0040d80c) 33 15: 34 16: default: 35 17: printf("error"); 36 0040D7FF push offset string "error" (0042201c) 37 0040D804 call printf (00401110) 38 0040D809 add esp,4
当case情况小于等于3时,ifelse和switch并没有什么区别。
case2:
当case情况大于等于4种时,且4种case是连续的。
1 #include<stdio.h> 2 void myFunction(int x) 3 { 4 switch(x) 5 { 6 case 1: 7 printf("1"); 8 break; 9 case 2: 10 printf("2"); 11 break; 12 case 3: 13 printf("3"); 14 break; 15 case 4: 16 printf("4"); 17 break; 18 default: 19 printf("error"); 20 } 21 } 22 int main() 23 { 24 myFunction(2); 25 return 0; 26 }
明显的区别是
1 4: switch(x) 2 5: { 3 0040D7B8 mov eax,dword ptr [ebp+8] 4 0040D7BB mov dword ptr [ebp-4],eax 5 0040D7BE mov ecx,dword ptr [ebp-4] 6 0040D7C1 sub ecx,1 7 0040D7C4 mov dword ptr [ebp-4],ecx 8 0040D7C7 cmp dword ptr [ebp-4],3 9 0040D7CB ja $L538+0Fh (0040d813) 10 0040D7CD mov edx,dword ptr [ebp-4] 11 0040D7D0 jmp dword ptr [edx*4+40D831h] 12 6: case 1: 13 7: printf("1"); 14 0040D7D7 push offset string "1" (0042213c) 15 0040D7DC call printf (00401110) 16 0040D7E1 add esp,4 17 8: break; 18 0040D7E4 jmp $L538+1Ch (0040d820) 19 9: case 2: 20 10: printf("2"); 21 0040D7E6 push offset string "2" (0042202c) 22 0040D7EB call printf (00401110) 23 0040D7F0 add esp,4 24 11: break; 25 0040D7F3 jmp $L538+1Ch (0040d820) 26 12: case 3: 27 13: printf("3"); 28 0040D7F5 push offset string "3" (00422028) 29 0040D7FA call printf (00401110) 30 0040D7FF add esp,4 31 14: break; 32 0040D802 jmp $L538+1Ch (0040d820) 33 15: case 4: 34 16: printf("4"); 35 0040D804 push offset string "4" (00422024) 36 0040D809 call printf (00401110) 37 0040D80E add esp,4 38 17: break; 39 0040D811 jmp $L538+1Ch (0040d820) 40 18: default: 41 19: printf("error"); 42 0040D813 push offset string "error" (0042201c) 43 0040D818 call printf (00401110) 44 0040D81D add esp,4 45 20: } 46 21: }
代码分析:
将传进来的x放在ecx中
在这一段中可以明显看到的是多出了一个sub,ecx减去一个1。ecx移动回ebp-4,ebp-4和3比较。
后面有一个ja,40d813,这里是default去的地方。即x-1和3比较,大于则跳转至default
ebp-4放入edx中。
后面还有一句dword ptr [edx*4+40D831h]。
实际上,这里的sub,减去的是这4个case中的最小值。
dword ptr [edx*4+40D831h]这个就是编译器自动为我们生成的一张表。按下alt+6,将40D831拖到memery窗口中。
可以看到
可以看到40d831。
总的来说edx=x-1。即(x-1)*4+40d831h这个内存单元的内容,就是大表跳转的地方。
图中我们可以看到4个地址
40d7d7,是case1的地方。
40d7e6,是case2的地方。
40d7f5,是case3的地方。
40d804,是case4的地方。
如果中间随便调整顺序呢,结果发现调整顺序对最终结果无影响。(数据就不贴了)
那假如不是连续的呢。
原本的代码
1 #include<stdio.h> 2 void Function(int x) 3 { 4 switch(x) 5 { 6 case 1: 7 printf("1");break; 8 case 2: 9 printf("2");break; 10 case 3: 11 printf("3");break; 12 case 4: 13 printf("4");break; 14 case 5: 15 printf("5");break; 16 case 6: 17 printf("6");break; 18 case 7: 19 printf("7");break; 20 case 8: 21 printf("8");break; 22 case 9: 23 printf("9");break; 24 default: 25 printf("error");break; 26 } 27 } 28 29 void main() 30 { 31 Function(3); 32 }
删除后的代码
1 #include<stdio.h> 2 void Function(int x) 3 { 4 switch(x) 5 { 6 case 1: 7 printf("1");break; 8 case 2: 9 printf("2");break; 10 11 case 7: 12 printf("7");break; 13 case 8: 14 printf("8");break; 15 case 9: 16 printf("9");break; 17 default: 18 printf("error");break; 19 } 20 } 21 22 void main() 23 { 24 Function(3); 25 }
反编译得到
1 4: switch(x) 2 5: { 3 00401038 mov eax,dword ptr [ebp+8] 4 0040103B mov dword ptr [ebp-4],eax 5 0040103E mov ecx,dword ptr [ebp-4] 6 00401041 sub ecx,1 7 00401044 mov dword ptr [ebp-4],ecx 8 00401047 cmp dword ptr [ebp-4],8 9 0040104B ja $L540+0Fh (004010a2) 10 0040104D mov edx,dword ptr [ebp-4] 11 00401050 jmp dword ptr [edx*4+4010C0h] 12 6: case 1: 13 7: printf("1");break; 14 00401057 push offset string "1" (00422030) 15 0040105C call printf (004011f0) 16 00401061 add esp,4 17 00401064 jmp $L540+1Ch (004010af) 18 8: case 2: 19 9: printf("2");break; 20 00401066 push offset string "2" (0042202c) 21 0040106B call printf (004011f0) 22 00401070 add esp,4 23 00401073 jmp $L540+1Ch (004010af) 24 10: 25 11: case 7: 26 12: printf("7");break; 27 00401075 push offset string "7" (00422028) 28 0040107A call printf (004011f0) 29 0040107F add esp,4 30 00401082 jmp $L540+1Ch (004010af) 31 13: case 8: 32 14: printf("8");break; 33 00401084 push offset string "8" (00422024) 34 00401089 call printf (004011f0) 35 0040108E add esp,4 36 00401091 jmp $L540+1Ch (004010af) 37 15: case 9: 38 16: printf("9");break; 39 00401093 push offset string "9" (00422020) 40 00401098 call printf (004011f0) 41 0040109D add esp,4 42 004010A0 jmp $L540+1Ch (004010af) 43 17: default: 44 18: printf("error");break; 45 004010A2 push offset string "error" (00422fcc) 46 004010A7 call printf (004011f0) 47 004010AC add esp,4 48 19: } 49 20: }
删除后我们发现原本的那张表删除的位置上添加了一个地址,而这个地址我们过去查看发现正是default部分的地址。
这么弄不就浪费了么,于是我们继续删,看看编译器会如何处理。
当我们删的只有3个的时候,代码为
1 #include<stdio.h> 2 void Function(int x) 3 { 4 switch(x) 5 { 6 case 1: 7 printf("1");break; 8 case 2: 9 printf("2");break; 10 11 12 case 9: 13 printf("9");break; 14 default: 15 printf("error");break; 16 } 17 } 18 19 void main() 20 { 21 Function(3); 22 }
反编译得到的代码已经变成这样了
1 4: switch(x) 2 5: { 3 00401038 mov eax,dword ptr [ebp+8] 4 0040103B mov dword ptr [ebp-4],eax 5 0040103E cmp dword ptr [ebp-4],1 6 00401042 je Function+32h (00401052) 7 00401044 cmp dword ptr [ebp-4],2 8 00401048 je Function+41h (00401061) 9 0040104A cmp dword ptr [ebp-4],9 10 0040104E je Function+50h (00401070) 11 00401050 jmp Function+5Fh (0040107f) 12 6: case 1: 13 7: printf("1");break; 14 00401052 push offset string "7" (00422028) 15 00401057 call printf (004011f0) 16 0040105C add esp,4 17 0040105F jmp Function+6Ch (0040108c) 18 8: case 2: 19 9: printf("2");break; 20 00401061 push offset string "2" (00422024) 21 00401066 call printf (004011f0) 22 0040106B add esp,4 23 0040106E jmp Function+6Ch (0040108c) 24 10: 25 11: 26 12: case 9: 27 13: printf("9");break; 28 00401070 push offset string "9" (00422020) 29 00401075 call printf (004011f0) 30 0040107A add esp,4 31 0040107D jmp Function+6Ch (0040108c) 32 14: default: 33 15: printf("error");break; 34 0040107F push offset string "error" (00422fcc) 35 00401084 call printf (004011f0) 36 00401089 add esp,4 37 16: } 38 17: }
编译器会自动为我们做出最优化的汇编代码。当浪费超过一定的时候,还是回归到cmp&jcc上了。
那假如我的case都是连续的,只有一个很突兀,结果会如何呢。
Case3
此时代码为
1 #include<stdio.h> 2 void Function(int x) 3 { 4 switch(x) 5 { 6 case 1: 7 printf("1");break; 8 case 2: 9 printf("2");break; 10 case 3: 11 printf("3");break; 12 case 4: 13 printf("4");break; 14 case 5: 15 printf("5");break; 16 17 case 233: 18 printf("233");break; 19 20 case 7: 21 printf("7");break; 22 case 8: 23 printf("8");break; 24 case 9: 25 printf("9");break; 26 default: 27 printf("error");break; 28 29 } 30 } 31 32 void main() 33 { 34 Function(3); 35 }
汇编代码为
1 4: switch(x) 2 5: { 3 0040D898 mov eax,dword ptr [ebp+8] 4 0040D89B mov dword ptr [ebp-4],eax 5 0040D89E mov ecx,dword ptr [ebp-4] 6 0040D8A1 sub ecx,1 7 0040D8A4 mov dword ptr [ebp-4],ecx 8 0040D8A7 cmp dword ptr [ebp-4],0E8h 9 0040D8AE ja $L548+0Fh (0040d950) 10 0040D8B4 mov eax,dword ptr [ebp-4] 11 0040D8B7 xor edx,edx 12 0040D8B9 mov dl,byte ptr (0040d996)[eax] 13 0040D8BF jmp dword ptr [edx*4+40D96Eh] 14 6: case 1: 15 7: printf("1");break; 16 0040D8C6 push offset string "1" (0042203c) 17 0040D8CB call printf (004011f0) 18 0040D8D0 add esp,4 19 0040D8D3 jmp $L548+1Ch (0040d95d) 20 8: case 2: 21 9: printf("2");break; 22 0040D8D8 push offset string "2" (00422038) 23 0040D8DD call printf (004011f0) 24 0040D8E2 add esp,4 25 0040D8E5 jmp $L548+1Ch (0040d95d) 26 10: case 3: 27 11: printf("3");break; 28 0040D8E7 push offset string "3" (00422034) 29 0040D8EC call printf (004011f0) 30 0040D8F1 add esp,4 31 0040D8F4 jmp $L548+1Ch (0040d95d) 32 12: case 4: 33 13: printf("4");break; 34 0040D8F6 push offset string "4" (00422030) 35 0040D8FB call printf (004011f0) 36 0040D900 add esp,4 37 0040D903 jmp $L548+1Ch (0040d95d) 38 14: case 5: 39 15: printf("5");break; 40 0040D905 push offset string "5" (0042202c) 41 0040D90A call printf (004011f0) 42 0040D90F add esp,4 43 0040D912 jmp $L548+1Ch (0040d95d) 44 16: 45 17: case 233: 46 18: printf("233");break; 47 0040D914 push offset string "12" (0042201c) 48 0040D919 call printf (004011f0) 49 0040D91E add esp,4 50 0040D921 jmp $L548+1Ch (0040d95d) 51 19: 52 20: case 7: 53 21: printf("7");break; 54 0040D923 push offset string "7" (00422028) 55 0040D928 call printf (004011f0) 56 0040D92D add esp,4 57 0040D930 jmp $L548+1Ch (0040d95d) 58 22: case 8: 59 23: printf("8");break; 60 0040D932 push offset string "10" (00422024) 61 0040D937 call printf (004011f0) 62 0040D93C add esp,4 63 0040D93F jmp $L548+1Ch (0040d95d) 64 24: case 9: 65 25: printf("9");break; 66 0040D941 push offset string "11" (00422020) 67 0040D946 call printf (004011f0) 68 0040D94B add esp,4 69 0040D94E jmp $L548+1Ch (0040d95d) 70 26: default: 71 27: printf("error");break; 72 0040D950 push offset string "error" (00422fcc) 73 0040D955 call printf (004011f0) 74 0040D95A add esp,4 75 28: 76 29: } 77 30: }
可以很明显看到不一样的地方
有一个ja,跳转至default。然后多了个edx,且将edx归零。而重点在这0040d996。mov dl,byte ptr (0040d996)[eax],这句话的意思是将内存单元[eax+40d996]的内容放到dl中。
我们打开这个地址,发现里面是一个个数字
这张表就是小表。这里的09似乎是最多的。当[40d996+eax]为09时,edx*4+40D96Eh就是24+40D96eh。即为40d992。
然后40d992中存放的是40d950。这个地址是default的地址。
小表是两位数字。那假如突然出来一个超过255的case呢。
整个全变了。
1 4: switch(x) 2 5: { 3 0040D898 mov eax,dword ptr [ebp+8] 4 0040D89B mov dword ptr [ebp-4],eax 5 0040D89E cmp dword ptr [ebp-4],104h 6 0040D8A5 jg Function+4Dh (0040d8cd) 7 0040D8A7 cmp dword ptr [ebp-4],104h 8 0040D8AE je $L540+0Fh (0040d920) 9 0040D8B0 mov ecx,dword ptr [ebp-4] 10 0040D8B3 sub ecx,1 11 0040D8B6 mov dword ptr [ebp-4],ecx 12 0040D8B9 cmp dword ptr [ebp-4],8 13 0040D8BD ja $L548+0Fh (0040d95c) 14 0040D8C3 mov edx,dword ptr [ebp-4] 15 0040D8C6 jmp dword ptr [edx*4+40D97Ah] 16 0040D8CD jmp $L548+0Fh (0040d95c) 17 6: case 1: 18 7: printf("1");break; 19 0040D8D2 push offset string "1" (0042203c) 20 0040D8D7 call printf (004011f0) 21 0040D8DC add esp,4 22 0040D8DF jmp $L548+1Ch (0040d969) 23 8: case 2: 24 9: printf("2");break; 25 0040D8E4 push offset string "2" (00422038) 26 0040D8E9 call printf (004011f0) 27 0040D8EE add esp,4 28 0040D8F1 jmp $L548+1Ch (0040d969) 29 10: case 3: 30 11: printf("3");break; 31 0040D8F3 push offset string "3" (00422034) 32 0040D8F8 call printf (004011f0) 33 0040D8FD add esp,4 34 0040D900 jmp $L548+1Ch (0040d969) 35 12: case 4: 36 13: printf("4");break; 37 0040D902 push offset string "4" (00422030) 38 0040D907 call printf (004011f0) 39 0040D90C add esp,4 40 0040D90F jmp $L548+1Ch (0040d969) 41 14: case 5: 42 15: printf("5");break; 43 0040D911 push offset string "5" (0042202c) 44 0040D916 call printf (004011f0) 45 0040D91B add esp,4 46 0040D91E jmp $L548+1Ch (0040d969) 47 16: 48 17: case 260: 49 18: printf("260");break; 50 0040D920 push offset string "260" (0042201c) 51 0040D925 call printf (004011f0) 52 0040D92A add esp,4 53 0040D92D jmp $L548+1Ch (0040d969) 54 19: 55 20: case 7: 56 21: printf("7");break; 57 0040D92F push offset string "7" (00422028) 58 0040D934 call printf (004011f0) 59 0040D939 add esp,4 60 0040D93C jmp $L548+1Ch (0040d969) 61 22: case 8: 62 23: printf("8");break; 63 0040D93E push offset string "10" (00422024) 64 0040D943 call printf (004011f0) 65 0040D948 add esp,4 66 0040D94B jmp $L548+1Ch (0040d969) 67 24: case 9: 68 25: printf("9");break; 69 0040D94D push offset string "11" (00422020) 70 0040D952 call printf (004011f0) 71 0040D957 add esp,4 72 0040D95A jmp $L548+1Ch (0040d969) 73 26: default: 74 27: printf("error");break; 75 0040D95C push offset string "error" (00422fcc) 76 0040D961 call printf (004011f0) 77 0040D966 add esp,4 78 28: 79 29: } 80 30: }
首先把参数放到eax,eax和104h对比(104h==260)。大于等于则跳走。小于则继续走大表。
最后一种
Case4:
整个case完全没有规律。那么将按照二叉树来进行查找每个case的地址。然本人目前还没学到。暂时不多叙述了,学到二叉树时再将其补上。