2023年春秋杯网络安全联赛冬季赛coos
0x00 题目分析
VM,重点在于解释器,把opcode整出来
0x01 解
无壳
直接拖IDA看看
main函数内容还算清晰,可以知道的信息是flag长度为32,下面的for循环应该是用于判断
sub_41103C跟进去可以发现有个scanf
sub_41145B将参数与0x66异或,测试了一下byte_41F5C4,应该只是用于解密输出
1 | s = [0x01, 0x03, 0x12, 0x46, 0x12, 0x0e, 0x03, 0x46, 0x0A, 0x07, 0x01, 0x47, 0x66] |
那么关键的函数就应该是sub_4112D0,sub_41128A和sub_41118B了
先跟进sub_41118B看看
给v9附了很多值,然后调了一个sub_4112DF
是VM,而且case不少
可以回收站了
先不碰这个了,回到main看看另外两个函数sub_4112D0和sub_41128A
先看看sub_4112D0,进去可以发现有很多赋值为0的,对比可以发现这些值都在vm的函数中出现
所以合理猜测这些是寄存器
改下名比较好认
这样虚拟机也就比较好看了
往下翻翻可以看到函数sub_41141A,将dword_41E000[r0]给r1或将dowrd_41E080[r2]中的值给r1
之后是三个函数sub_411307,sub_411334和sub_41127B
这三个函数除了寄存器外,参数里还有数组
这些函数跟进去可以发现又回到了VM这里,应该是属于子函数一样的东西
这些数组里面都没有东西,运行起来才知道里面是什么
这题有几点不一样的地方,题目文件是32位的,但这里虚拟机却是64位的。跟进
sub_41141A
,就可以发现这里把64位拆成两个32位,后续所有的操作中都是如此。即通过32位数组的形式实现64位的操作。还有一点在于这里的虚拟机更像是虚拟函数,因为这里面嵌套了虚拟机,根据opcode的不同实现不同的功能。
写写解释器吧
1 | def vm(opcode, eip): |
重点说明一下那个push,在push函数里面可以看到这个
和pop函数是相对应的
解释器写完了就先去看看主函数
1 | from vm import vm |
解出来是这玩意
1 | """ |
一个pattern被重复31(127-1)/4=31次,这个pattern为
1 | """ |
看得出来这个r2应该被用于判断轮数了,然后pattern内调用了三个子函数,该看看子函数是什么了
1 | import struct |
子函数一:
1 | """ |
结合一下主函数的内容,不难判断这是把dword_4203A8[index]
进行了一个异或的操作
子函数二:
1 | """ |
这也是一个pattern不断循环,循环的pattern为:
1 | """ |
结合一下主函数,这里是把dword_4203A8[index]
进行了更进一步的操作
其中需要知道dword_47E000
动调可得
1 | dword_47E000 = [2, 1, 7, 4, 8, 15, 14, 3, 13, 10, 0, 9, 11, 6, 5, 12] |
由于这个dword_47E000
只有16个值,因此上面的pattern只会循环16次
结合一下子函数一,将汇编转成python
1 | def calc_sum_1(state,key_index): |
子函数三
1 | """ |
同样也是存在一个pattern不断循环
1 | """ |
同样需要知道dword_47E080
,同理动调
1 | dword_47E080 = [63, 47, 31, 15, 62, 46, 30, 14, 61, 45, 29, 13, 60, 44, 28, 12, 59, 43, 27, 11, 58, 42, 26, 10, 57, 41, 25, 9, 56, 40, 24, 8, 55, 39, 23, 7, 54, 38, 22, 6, 53, 37, 21, 5, 52, 36, 20, 4, 51, 35, 19, 3, 50, 34, 18, 2, 49, 33, 17, 1, 48, 32, 16, 0] |
同理,这个pattern只会循环64次
转成python可得
1 | def calc_sum_2(sum_1): |
再配合动调得出dword_4203A8
1 | dword_4203A8 = [7596569225765413736, 14892793705278438672, 14897626102458855899, 15127138055849948981, 17864553494577085546, 1138107802150484470, 8911548945514081943, 3235953879216500034, 11010657345162096808, 14479365304648704483, 12150232467342833339, 12487849248796427385, 13416156134946011488, 5920119153271060313, 8053781650739871525, 9558394794771259844, 4339208423645057876, 16476906405380659427, 11912452293618884519, 1406977800464934583, 2088792745124906531, 12099790537768180319, 2503949348962038751, 1807992343561743431, 1910441527462264642, 10680376501786139932, 3656171727515946310, 8846275897411559763, 15016467168181810528, 14129026293696237890, 127218696325272760, 14650765194520711557] |
结合上面全部,可以得到
1 | dword_47E000 = [2, 1, 7, 4, 8, 15, 14, 3, 13, 10, 0, 9, 11, 6, 5, 12] |
经过对比,发现是present
魔改加密
不过还是先获取密文吧,此程序的密文需要先动调再从表中获取
dword_420578是存放密文索引的地方(不过只使用0~3四个索引)
而dword_41E280和dword_41E284则是存放密文表的地方
需要注意:
1.所获取的密文为8个字节(64bit),比较时被分为高4字节(32bit)和低4字节(32bit)进行比较
2.dword_420578需要动调才能被填充
3.dword_41E280和dword_41E284实际上是连着的,高32位从dword_41E280和dword_41E284的结合中取,而低32位仅从dword_41E284中取
1 | import struct |
在github上有原始present解密(https://github.com/wuhanstudio/present/tree/master/present-python),进行相应修改
1 | Sbox = [2, 1, 7, 4, 8, 15, 14, 3, 13, 10, 0, 9, 11, 6, 5, 12] |