(虚拟机逆向, 简单的加密, 调试)

运行

Untitled

查壳

无壳, Win64程序

分析

使用IDA分析

Untitled

可以看到是不断的switch, 然后选择相应的指令来进行操作

这道题用汇编语言会更加清晰一些, 因为变量都是用同一个数组TheIp不同来进行表示, 记下标来看代码看一会就混了, 还不如看汇编还可以改名

Untitled

所以取出数据表中的数据

Untitled

开始分析:

0x12
0x8
0x12
0x9
0x10:  这里是输入,  然后只存了开头的一个字符.....再位置0x49F02C处
0x4:  再0x49F024处加一,  然后再放入output[+1]处,  注意的是这个数组是int类型
0x1:   再0x40F028处加一
0xf:   比较end(A)跟0x49F02C(也就是第一个字符)是否相等,  如果相等	类似output[0] = 0, 如果不相等则output[0] = 1,  注意output[1] = 第一个字符
0xd:  比较上一步的output[0]是否等于0,  如果不是零,  就在index -= 5,  下一次就回到了getchar()      这里的意思就是读取到了换行符的时候结束这个循环
0x2:  *(0x49F028) - 1,  一开始的值为0x11,  感觉跟输入长度有关
0x12:   *(0x49F030) += 1,   len += 1  , 再output 后面接上一个data的赋值,  data的地址为0x49F36C
0x8:    腾出栈空间,  调用了一个函数,  里面len -= 1,  同时将最后的数据(0x20)传到end中

0x12:   *(0x49F030) += 1,  len += 1,  output的原先的data的位置,  替换为下一个data的数据,  
0x9:  调用了DevLen_DataToOne函数:      len - 1,  output末尾的data数据放入了ForCycle中

0x0:  (0x49F028)中的值给chr
0x4:  调用了AddLen_ChrToLast()函数:      len += 1,  把chr的值给了output的末尾
0xf:  调用了Cmp_Chr_End()函数,  估计是比较长度,  那么输入长度应该就是32了

0xd:  调用了CmpFirst_20addCycle()函数:  比较上一个的first的结果, 如果不相等就加上Cycle的值,  也就是data的第二个值(0x2F),   然后这里就退出了,  说明了上面应该就是检查长度

0x12:  (0x49F030) += 1, len += 1,  再output的结尾接上下一个data(-10)
0x9:  调用了DevLen_DataToCycle函数:      len - 1,  output末尾的data数据放入了ForCycle中                 len的作用就是用来跳读output的结尾是替换,  还是连接
0x12:  结尾被替换为了下一个data(0)
0xa:  调用了DevLen_LastToOne()函数:  len - 1,  将output最后的值给了(0x49F028)

	
0x13:  调用了get____char()函数:      chr = 输入的字符
0x12:   (0x49F030) += 1, len += 1,  再output的结尾接上下一个data(-10)
0xb:   调用了DevLen_LastToOne()函数:  len - 1,  One是(0x49F03C),  变成了data中的一个数据(0x5E)
0x15:   调用了chr_Mult_Two()函数:     chr *= 2
0x3:  调用了xor_data()函数:  chr ^= 当前的data数据
0x14:  调用了exchange_output()函数:  把输入换成了异或后的数据
0x1:  调用了addr28_add()函数:  (0x49F028) += 1

0x0:   调用了addr28_to_chr()函数:   (0x49F028)的值给chr
0xf:   调用了Cmp_Chr_End()函数:   比较了chr(也就是index)跟end,  看看是否是加密的末尾
0xd:   通过检查first,  决定是否回到上方
			上面的这些操作可以总结为:  input[i] = (2 * input[i]) ^ data[i]

0x12:  调用了add30_addlen_DataToLast()函数:  (0x49F030) += 1,  len += 1,  所以再结尾替换了data2的数据(data2连接再data1的下方)
0xa:  调用了DevLen_LastToOne()函数:  len -= 1,  One是(0x49F028),  现在28(功能类似于index)被置为0
0x12:  调用了add30_addlen_DataToLast()函数:   根据data2的数据来看又是为了循环做准备,  
0x12:  这里连续调用了同样0x12号元素,  说明了data并不是替换,  而是连接在了一起

0x12:  加上这个就有三个data值连在了output后面分别是:  -0x16,  0x15, 0x8e
0x8:   调用了DevLen_LastToOne()函数:   len -= 1,    把连接的最后的值(0x8e)给end
0x13:  调用了get____char()函数:      chr = 变换后的output[i]
0xf:   调用了Cmp_Chr_End()函数:   比较了chr(outpu[i])跟end(data2中的数据),  比较是否相同
0x7:   调用了DevLen_LastToOne()函数:   len -= 1,  并将len - 1的末尾(0x15)送入chr中
0x4:   调用了AddLen_ChrToLast()函数:   len += 1,  并将chr送入末尾,  但是为啥是没变......也就是说len += 1后还是第二个data2数据的位置吗.....不应该啊

if判断
0x9:   调用DevLen_LastToCycle()函数,  (0x15)放入了For_Cycle中
0xd:  调用了CmpFirst_20addCycle()函数:   如果不相等,  就ip直接加上0x15,  也就是21,  那就直接gg

0x9:   调用了DevLen_LastToCycle()函数:   len -= 1,  将尾巴数据给了Cycle, 此时cycle = -16
0x8:   调用了DevLen_LastToOne()函数:   其中One是end,  被赋值0x20
0x5:   len += 1,  并将len的值(0x20)给到output后连接的第一个数(其实感觉应该是len了)
0x6:   len += 1,  并将cycle的值给了output后连接的第二个数,  还是没变......
0x4:   len += 1,  并将chr的值给了output后连接的第三个数,  还是没变......
0x1:   (0x49F028) += 1,  类似于index
0x0:   chr = index
0xf:    调用了Cmp_Chr_End()函数:   比较了chr(index)跟end(0x20)
0xd:   回到了-16的位置,  也就是case0x12

0x12
0x9
0x12
0x8
0x12
0xa
0x12
0x7
0xf
0xc
0x11
0xe
0xff

进程已结束,退出代码为 0

这些都是一边调试一边分析出来.......越做越像是CSAPP里面的BombLab

大致的思路

  • 首先检验输入长度是否是32
  • 然后是每个数据乘以二后异或上对应的xordata(位于上面的data1中)
  • 最后是跟cmpdata进行比较(位与上面的data2中)

解题脚本

xordata = [0x5e, 0x46, 0x61, 0x43, 0x0e, 0x53, 0x49, 0x1f, 0x51, 0x5e,
           0x36, 0x37, 0x29, 0x41, 0x63, 0x3b, 0x64, 0x3b, 0x15, 0x18,
           0x5b, 0x3e, 0x22, 0x50, 0x46, 0x5e, 0x35, 0x4e, 0x43, 0x23,
           0x60, 0x3b]

output = [0x8E, 0x88, 0xA3, 0x99, 0xC4, 0xA5, 0xC3, 0xDD, 0x19, 0xEC,
          0x6C, 0x9B, 0xF3, 0x1B, 0x8B, 0x5B, 0x3E, 0x9B, 0xF1, 0x86,
          0xF3, 0xF4, 0xA4, 0xF8, 0xF8, 0x98, 0xAB, 0x86, 0x89, 0x61,
          0x22, 0xC1, 0x02]

print()

for i in range(len(xordata)):
  print(chr((output[i] ^ xordata[i]) // 2), end = '')

flag

hgame{Ea$Y-Vm-t0-PrOTeCT_cOde!!}

小结

  • 就像是hint所讲的多调试, 就能摸索出大致的逻辑了, 加密的方式非常简单, 就是要看switch的顺序, 还是推荐用汇编来看会好很多