前几天的“恶意软件分析”课程作业,附加题是个elf格式的VMP文件,说是装了虚拟机壳,写个反汇编器就可以解出来。目标是输对密码,让它报告congratulations。但是放进我的linux虚拟机就是不运行,查了后是缺头文件,于是装好环境,运行了。
破解的工具是IDA,main函数调用个函数,进去后是一片变量声明,后面就是文档里说的一堆switch,也就是虚拟机壳接收指令集的方式。通过阅读和变量重命名,终于看会了它的原理。真正的代码被放在某个数据段中,这个函数的作用只是把它读出来,然后根据代码中的第一位判断指令,后面几位是对应的参数,基本就是mov ax, bx这类汇编指令的二进制版本。我的初步目标很简单,就是把这些代码翻译出来,至少翻译成逐句的文字版汇编指令。
我这段时间以来用的最多的是C++,考虑到不练就生疏的问题,决定练练python。Python是可以读取二进制文件的,一开始做感觉不适应,代码却很短,也是with open这些,然后写反汇编的时候就发现没有switch,于是费劲写了一堆elif。于是分屏对比着写反汇编码,由于其中有不少跳转指令,所以额外添加了行号的显示,其中4154到7899是从59开始(大概?)往后的代码段地址,我已经做了反xor处理,否则59往后都是乱码。可以看到其中还有很多不完善的地方,不过基本的指令已经可以看出来了,有能力的读者可以从最后硬编码的一些数据中恢复出30位的ascii码密码。
1: mov ds[0], 4154 8: mov ds[1], 7899 15: mov ds[3], ds[0] 19: mov ds[5], 24432450 26: mov ds[4], [ds[3]] 29: ds[4] = ds[5] xor ds[4] 34: mov [ds[3]], ds[4] 37: ds[3] = ds[3] + 4 45: cmp ds[1], ds[3] 49: jge not jumping 54: jmp to 58 (40fa) 59: jmp to 3008 (4c80) 3009: push 3012: push 3015: push 3018: call 2669 2670: mov ds[0], 80 2677: call 63 64: io: put_char 66: ret (2681) 2682: mov ds[0], 108 2689: call 63 64: io: put_char 66: ret (2693) 2694: mov ds[0], 101 2701: call 63 64: io: put_char 66: ret (2705) 2706: mov ds[0], 97 2713: call 63 64: io: put_char 66: ret (2717) 2718: mov ds[0], 115 2725: call 63 64: io: put_char 66: ret (2729) 2730: mov ds[0], 101 2737: call 63 64: io: put_char 66: ret (2741) 2742: mov ds[0], 32 2749: call 63 64: io: put_char 66: ret (2753) 2754: mov ds[0], 101 2761: call 63 64: io: put_char 66: ret (2765) 2766: mov ds[0], 110 2773: call 63 64: io: put_char 66: ret (2777) 2778: mov ds[0], 116 2785: call 63 64: io: put_char 66: ret (2789) 2790: mov ds[0], 101 2797: call 63 64: io: put_char 66: ret (2801) 2802: mov ds[0], 114 2809: call 63 64: io: put_char 66: ret (2813) 2814: mov ds[0], 32 2821: call 63 64: io: put_char 66: ret (2825) 2826: mov ds[0], 121 2833: call 63 64: io: put_char 66: ret (2837) 2838: mov ds[0], 111 2845: call 63 64: io: put_char 66: ret (2849) 2850: mov ds[0], 117 2857: call 63 64: io: put_char 66: ret (2861) 2862: mov ds[0], 114 2869: call 63 64: io: put_char 66: ret (2873) 2874: mov ds[0], 32 2881: call 63 64: io: put_char 66: ret (2885) 2886: mov ds[0], 112 2893: call 63 64: io: put_char 66: ret (2897) 2898: mov ds[0], 97 2905: call 63 64: io: put_char 66: ret (2909) 2910: mov ds[0], 115 2917: call 63 64: io: put_char 66: ret (2921) 2922: mov ds[0], 115 2929: call 63 64: io: put_char 66: ret (2933) 2934: mov ds[0], 119 2941: call 63 64: io: put_char 66: ret (2945) 2946: mov ds[0], 111 2953: call 63 64: io: put_char 66: ret (2957) 2958: mov ds[0], 114 2965: call 63 64: io: put_char 66: ret (2969) 2970: mov ds[0], 100 2977: call 63 64: io: put_char 66: ret (2981) 2982: mov ds[0], 58 2989: call 63 64: io: put_char 66: ret (2993) 2994: mov ds[0], 32 3001: call 63 64: io: put_char 66: ret (3005) 3006: ret (3022) 3023: mov ds[1], 30 3030: mov ds[0], 4 3037: call 128 129: push 132: push 135: mov ds[30], ds[1] ds[0] = ds[0] * ds[30] mul 144: ds[0] = ds[0] + 8 152: mov ds[29], ds[0] 156: io: return and inc 158: and ds[0], 0 162: jz to 194 167: mov ds[4], ds[29] 171: ds[4] = 3 >> ds[4] right shift 179: mov [ds[0]], ds[4] 182: ds[0] = ds[0] + 8 pop pop 194: ret (3041) 3042: mov ds[11], ds[0] 3046: jmp to 3055 (4caf) 3056: mov ds[9], 0 3063: jmp to 3430 (4e26) 3431: mov ds[30], ds[9] 3435: mov ds[0], 30 3442: cmp ds[30], ds[0] 3446: jl 3067 3451: call 75 76: io: scan char 78: ret (3455) 3456: mov ds[1], ds[0] 3460: mov ds[30], ds[1] 3464: mov ds[0], 10 3471: cmp ds[30], ds[0] 3475: jnz 3484 3480: jmp to 3774 (4f7e) 3775: mov ds[0], ds[11] 3779: call 681 682: push 685: push 688: mov ds[10], ds[0] 692: mov ds[1], 30 699: mov ds[0], 4 706: call 128 129: push 132: push 135: mov ds[30], ds[1] ds[0] = ds[0] * ds[30] mul 144: ds[0] = ds[0] + 8 152: mov ds[29], ds[0] 156: io: return and inc 158: and ds[0], 0 162: jz to 194 167: mov ds[4], ds[29] 171: ds[4] = 3 >> ds[4] right shift 179: mov [ds[0]], ds[4] 182: ds[0] = ds[0] + 8 pop pop 194: ret (710) 711: mov ds[9], ds[0] 715: mov ds[1], 4 722: mov ds[0], 4 729: call 128 129: push 132: push 135: mov ds[30], ds[1] ds[0] = ds[0] * ds[30] mul 144: ds[0] = ds[0] + 8 152: mov ds[29], ds[0] 156: io: return and inc 158: and ds[0], 0 162: jz to 194 167: mov ds[4], ds[29] 171: ds[4] = 3 >> ds[4] right shift 179: mov [ds[0]], ds[4] 182: ds[0] = ds[0] + 8 pop pop 194: ret (733) 734: mov ds[5], ds[0] 738: mov ds[30], 253 745: mov [ds[5]], ds[30] 748: mov ds[30], 1 mul 763: mov ds[0], ds[5] 767: ds[30] = ds[0] + ds[30] 772: mov ds[1], ds[30] 776: mov ds[30], 14 783: mov [ds[1]], ds[30] 786: mov ds[30], 1 mul 801: mov ds[0], ds[5] 805: ds[30] = ds[0] + ds[30] 810: mov ds[1], ds[30] 814: mov ds[30], 99 821: mov [ds[1]], ds[30] 824: mov ds[30], 1 mul 839: mov ds[0], ds[5] 843: ds[30] = ds[0] + ds[30] 848: mov ds[1], ds[30] 852: mov ds[30], 79 859: mov [ds[1]], ds[30] 862: mov ds[30], 141 869: mov [ds[9]], ds[30] 872: mov ds[30], 1 mul 887: mov ds[0], ds[9] 891: ds[30] = ds[0] + ds[30] 896: mov ds[1], ds[30] 900: mov ds[30], 111 907: mov [ds[1]], ds[30] 910: mov ds[30], 1 mul 925: mov ds[0], ds[9] 929: ds[30] = ds[0] + ds[30] 934: mov ds[1], ds[30] 938: mov ds[30], 0 945: mov [ds[1]], ds[30] 948: mov ds[30], 1 mul 963: mov ds[0], ds[9] 967: ds[30] = ds[0] + ds[30] 972: mov ds[1], ds[30] 976: mov ds[30], 36 983: mov [ds[1]], ds[30] 986: mov ds[30], 1 mul 1001: mov ds[0], ds[9] 1005: ds[30] = ds[0] + ds[30] 1010: mov ds[1], ds[30] 1014: mov ds[30], 152 1021: mov [ds[1]], ds[30] 1024: mov ds[30], 1 mul 1039: mov ds[0], ds[9] 1043: ds[30] = ds[0] + ds[30] 1048: mov ds[1], ds[30] 1052: mov ds[30], 124 1059: mov [ds[1]], ds[30] 1062: mov ds[30], 1 mul 1077: mov ds[0], ds[9] 1081: ds[30] = ds[0] + ds[30] 1086: mov ds[1], ds[30] 1090: mov ds[30], 16 1097: mov [ds[1]], ds[30] 1100: mov ds[30], 1 mul 1115: mov ds[0], ds[9] 1119: ds[30] = ds[0] + ds[30] 1124: mov ds[1], ds[30] 1128: mov ds[30], 16 1135: mov [ds[1]], ds[30] 1138: mov ds[30], 1 mul 1153: mov ds[0], ds[9] 1157: ds[30] = ds[0] + ds[30] 1162: mov ds[1], ds[30] 1166: mov ds[30], 156 1173: mov [ds[1]], ds[30] 1176: mov ds[30], 1 mul 1191: mov ds[0], ds[9] 1195: ds[30] = ds[0] + ds[30] 1200: mov ds[1], ds[30] 1204: mov ds[30], 96 1211: mov [ds[1]], ds[30] 1214: mov ds[30], 1 mul 1229: mov ds[0], ds[9] 1233: ds[30] = ds[0] + ds[30] 1238: mov ds[1], ds[30] 1242: mov ds[30], 7 1249: mov [ds[1]], ds[30] 1252: mov ds[30], 1 mul 1267: mov ds[0], ds[9] 1271: ds[30] = ds[0] + ds[30] 1276: mov ds[1], ds[30] 1280: mov ds[30], 16 1287: mov [ds[1]], ds[30] 1290: mov ds[30], 1 mul 1305: mov ds[0], ds[9] 1309: ds[30] = ds[0] + ds[30] 1314: mov ds[1], ds[30] 1318: mov ds[30], 139 1325: mov [ds[1]], ds[30] 1328: mov ds[30], 1 mul 1343: mov ds[0], ds[9] 1347: ds[30] = ds[0] + ds[30] 1352: mov ds[1], ds[30] 1356: mov ds[30], 99 1363: mov [ds[1]], ds[30] 1366: mov ds[30], 1 mul 1381: mov ds[0], ds[9] 1385: ds[30] = ds[0] + ds[30] 1390: mov ds[1], ds[30] 1394: mov ds[30], 16 1401: mov [ds[1]], ds[30] 1404: mov ds[30], 1 mul 1419: mov ds[0], ds[9] 1423: ds[30] = ds[0] + ds[30] 1428: mov ds[1], ds[30] 1432: mov ds[30], 16 1439: mov [ds[1]], ds[30] 1442: mov ds[30], 1 mul 1457: mov ds[0], ds[9] 1461: ds[30] = ds[0] + ds[30] 1466: mov ds[1], ds[30] 1470: mov ds[30], 156 1477: mov [ds[1]], ds[30] 1480: mov ds[30], 1 mul 1495: mov ds[0], ds[9] 1499: ds[30] = ds[0] + ds[30] 1504: mov ds[1], ds[30] 1508: mov ds[30], 96 1515: mov [ds[1]], ds[30] 1518: mov ds[30], 1 mul 1533: mov ds[0], ds[9] 1537: ds[30] = ds[0] + ds[30] 1542: mov ds[1], ds[30] 1546: mov ds[30], 7 1553: mov [ds[1]], ds[30] 1556: mov ds[30], 1 mul 1571: mov ds[0], ds[9] 1575: ds[30] = ds[0] + ds[30] 1580: mov ds[1], ds[30] 1584: mov ds[30], 16 1591: mov [ds[1]], ds[30] 1594: mov ds[30], 1 mul 1609: mov ds[0], ds[9] 1613: ds[30] = ds[0] + ds[30] 1618: mov ds[1], ds[30] 1622: mov ds[30], 133 1629: mov [ds[1]], ds[30] 1632: mov ds[30], 1 mul 1647: mov ds[0], ds[9] 1651: ds[30] = ds[0] + ds[30] 1656: mov ds[1], ds[30] 1660: mov ds[30], 97 1667: mov [ds[1]], ds[30] 1670: mov ds[30], 1 mul 1685: mov ds[0], ds[9] 1689: ds[30] = ds[0] + ds[30] 1694: mov ds[1], ds[30] 1698: mov ds[30], 17 1705: mov [ds[1]], ds[30] 1708: mov ds[30], 1 mul 1723: mov ds[0], ds[9] 1727: ds[30] = ds[0] + ds[30] 1732: mov ds[1], ds[30] 1736: mov ds[30], 60 1743: mov [ds[1]], ds[30] 1746: mov ds[30], 1 mul 1761: mov ds[0], ds[9] 1765: ds[30] = ds[0] + ds[30] 1770: mov ds[1], ds[30] 1774: mov ds[30], 162 1781: mov [ds[1]], ds[30] 1784: mov ds[30], 1 mul 1799: mov ds[0], ds[9] 1803: ds[30] = ds[0] + ds[30] 1808: mov ds[1], ds[30] 1812: mov ds[30], 97 1819: mov [ds[1]], ds[30] 1822: mov ds[30], 1 mul 1837: mov ds[0], ds[9] 1841: ds[30] = ds[0] + ds[30] 1846: mov ds[1], ds[30] 1850: mov ds[30], 11 1857: mov [ds[1]], ds[30] 1860: mov ds[30], 1 mul 1875: mov ds[0], ds[9] 1879: ds[30] = ds[0] + ds[30] 1884: mov ds[1], ds[30] 1888: mov ds[30], 16 1895: mov [ds[1]], ds[30] 1898: mov ds[30], 1 mul 1913: mov ds[0], ds[9] 1917: ds[30] = ds[0] + ds[30] 1922: mov ds[1], ds[30] 1926: mov ds[30], 144 1933: mov [ds[1]], ds[30] 1936: mov ds[30], 1 mul 1951: mov ds[0], ds[9] 1955: ds[30] = ds[0] + ds[30] 1960: mov ds[1], ds[30] 1964: mov ds[30], 119 1971: mov [ds[1]], ds[30] 1974: mov ds[3], 0 1981: jmp to 1990 (4886) 1991: mov ds[1], 0 1998: mov ds[2], 0 2005: jmp to 2144 (4920) 2145: mov ds[3], ds[1] 2149: mov ds[30], ds[2] 2153: mov ds[0], 30 2160: cmp ds[30], ds[0] 2164: jl 2009 2010: mov ds[30], ds[2] mul 2022: mov ds[0], ds[10] 2026: ds[30] = ds[0] + ds[30] 2031: mov ds[3], ds[30] 2035: mov ds[4], [ds[3]] reg64 / reg32, hi: 4, lo: 2 2047: mov ds[30], ds[3] mul 2059: mov ds[0], ds[5] 2063: ds[30] = ds[0] + ds[30] 2068: mov ds[3], ds[30] 2072: mov ds[3], [ds[3]] 2075: ds[4] = ds[3] xor ds[4] 2080: mov ds[30], ds[2] mul 2092: mov ds[0], ds[9] 2096: ds[30] = ds[0] + ds[30] 2101: mov ds[3], ds[30] 2105: mov ds[3], [ds[3]] 2108: mov ds[30], ds[4] 2112: mov ds[0], ds[3] 2116: cmp ds[30], ds[0] 2120:
然后再去反汇编,发现总共很短(读取到0或29自动退出了),复杂的判断输出字符串逻辑肯定没这么短。于是再读前面的逻辑,做了大半天才在字里行间读出:xor。它居然在“真正的代码段”做xor,我又过了好久才意识到这是对代码做了第二次加密。这时候反汇编器要修改原代码了,但是发现python读取文件的a+模式居然强制加在最后,没法修改前面,w模式只能覆写,于是不得已用w模式逐字复制出新文件再改。结果写xor程序又搞心态,python无法对二进制读出来的byte数据类型直接做xor,于是先转成int再转回去。
手忙脚乱做了去壳,后面的指令又读不出来了,原来有一堆跳转,我这个程序是按顺序扫描指令的,我希望跟着它的程序执行,于是加了动态化的处理,指针跟着程序的运行去扫指令。不过分支其实很多,于是另一边开了IDA远程连接虚拟机去实际运行VMP。
其中绕了很多弯路,因为代码不dry(感觉是个小项目就侥幸了),后来做忙了就觉得一堆问题。总之分析出来关键的打印、读取字符串后终于发现密码是30位的……输了30位后,用IDA跟踪,由于没打算写IDA脚本,所以想要在A代码块断下来,不想在前面B代码块的循环里转,就要看哪个指令是A里有而B里没有的,然后在那里打个断点,太蠢了,但是人陷进去了就是不想磨刀。
核心的逻辑我最后差不多是连猜带蒙。前面的字符串输出时候我心惊肉跳地看着程序硬编码了一堆数字,然后从那些数字代表的偏移里取出了字符,数字是不连续的,所以没有对照表和程序硬编码的内容中的一样,就根本没办法看出字符。我有点担心密码也这么编,结果更加离谱。我沿着程序往下读,又读出一堆硬编码来,我(幸好)全都记下来了。它们分成3个位置存,事后数分别是4、1、29个,实际上后面两组就是密码。我又确定了一下关键的比较语句里对比的是什么,于是发现我输入的明文被做了xor,和30个里面的第1个做了对比。所以甚至程序中就完全没有存明文。我手工在计算器上做xor,然后发现前面的4个数字是循环的密钥,1号作用在1、5、9个字符,2号作用在2、6、10,以此类推。好不容易在ascii表里查找到对应的密码。
其他的内容我就不贴了,主要是因为在破解过程中没有留好截图和中间结果,现在想来有些遗憾。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)