0%

buuctf_[GKCTF2021]KillerAid

外层分析

1.先查壳,看到是C#写的程序,IDA分析也分析不出来个啥,就是个GUI,主要功能还是在那个dll,dll导出函数如下,主要看看checkcode

image-20220309111717495

分析之前用dnspy看看exe主要逻辑,当输入的code和id满足条件的时候就会调用dll里的checkcode进行逻辑判断,运行调试时,程序窗口不会出现,应该是有反调试。

image-20220309113311485

image-20220309112558876

输入的ID和code进行异或之后等于给定的array即可,异或次数为MAX(len(ID),len(Code)),长度不够的与自身长度进行取模

id固定为9个字节,code应该会比ID长(因为AES需要十六字节)

1
2
3
4
5
6
7
8
9
10
11
array = [7,90,115,1,117,99,114,97,24]
id = array
# code应该是乱写的,16位
code = list(b'))bba!!!(@@###qq')
for i in range(len(code)):
id[i%9] = code[i]^id[i%9]
idstr = ''
for i in range(9):
idstr+=chr(id[i])
print(idstr)

动态调试

2.用IDA打开dll,点进checkcode,然后用FindCrypt插件查看到用了AES加密

image-20220309161731207

函数最后将加密之后的输入和一个32字节的数据进行比较并返回结果。

image-20220309162617066

x64dbg打开程序,用API断点到IsDebuggerPresent,我们需要修改PEB结构中的BeingDebugged改为0,下面是函数的实现

所以我们输入以下命令,然后将值修改为0.(CTRL+E)

image-20220309172520848

image-20220309171841449

如上就可以跟踪到dll中的某个反调试函数。

复制当前指令的首字节地址,用ida打开dll并跳转到复制的地址。按照x64dbg的代码将函数名字修改如下

image-20220310112639564

现在需要点击X,查找一下哪里引用了该函数

image-20220310112849790

image-20220310112932194

发现在sub_18000013F0中用了该函数,并且采用的是创建线程的方式,将createThread第五个参数patch为0x00000004(CREATE_SUSPENDED)

因为dll是64位的,所以查了一下调用约定,前四位参数按顺序存放在rcx, rdx, r8, r9,后面的部分存放在堆栈,下面图中,rsp+20h就是第五个参数,rsp+28h就是第六个参数(调试发现rax为0000000000000000,eax是rax后面8位0)

发现修改后代码占据大小变了。所以直接全部nop然后保存

image-20220310170155735

image-20220310195651299

动态调试修改后的dll,就可以绕过反调试部分,其实后面发现不需要绕过该部分反调试,因为iv是全局变量,dll一家在就有数据了,所以可能通过实际加载的Imagebase计算获得key和iv向量的地址,提取数据即可。右键直接在转储中跟随,就可以得到key和iv的值

image-20220311124512305

CheckCode分析

通过这部分的分析我们得获得正确的code,图中圈起来的就是我们前面所提取的iv和key.

image-20220311124651488

函数在后面会和另一个字符串比较

image-20220311124826719

image-20220311124925694

参考了网上的博客写了如下解决方法,计算得code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# This is a sample Python script.

# Press Shift+F10 to execute it or replace it with your code.
# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings.


from Crypto.Cipher import AES
sbox = [[0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76],
[0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0],
[0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15],
[0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75],
[0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84],
[0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF],
[0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8],
[0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2],
[0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73],
[0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB],
[0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79],
[0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08],
[0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A],
[0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E],
[0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF],
[0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16]]
def SubBytes(state):
return [sbox[i][j] for i,j in [(t>>4,t&0xf) for t in state]]
def Bytesxor(data1,data2):
data1=list(data1)
data2=list(data2)
res=[]
for i in range(len(data1)):
res.append(data1[i]^data2[i])
return bytearray(res)
key=bytearray(b'\x29\x23\xBE\x84\xE1\x6C\xD6\xAE\x52\x90\x49\xF1\xF1\xBB\xE9\xEB')
iv=bytearray(b'\xB3\xA6\xDB\x3C\x87\x0C\x3E\x99\x24\x5E\x0D\x1C\x06\xB7\x47\xDE')
keyarray=[]
keyarray.append(key)
ivarray=[]
ivarray.append(iv)
data=b'\xF6\x1C\xE3\xD7\xF9\xFB\x0B\x1A\x8B\xA2\x1D\xD8\x97\x94\x05\xC4\x6D\x97\xE7\x62\xB6\x7C\xEF\x9A\x88\x1B\xA4\x4D\xFD\xB0\xE4\x6E'
for i in range(31):
iv=bytearray(SubBytes(iv))
key=Bytesxor(key, iv)
key=bytearray(SubBytes(key))
keyarray.append(key)
iv=Bytesxor(key, iv)
ivarray.append(iv)
keyarray.reverse()
ivarray.reverse()
print(len(keyarray))
for i in range(32):
key=keyarray[i]
iv=ivarray[i]
aes=AES.new(bytes(key), AES.MODE_CBC,bytes(iv))
data=aes.decrypt(data)
print(data)
'''
iv=subbytes(iv)
key=key^iv
key=subbytes(key)
iv^=key
'''
'''
key
29 23 BE 84 E1 6C D6 AE 52 90 49 F1 F1 BB E9 EB
1B C5 C5 A8 42 4F 43 09 43 E8 0B 3C 0B C9 3B 42
26 27 03 37 AE 17 98 5E 1D 76 5D 86 52 D4 15 5D
20 56 F3 DF E4 A7 7E E5 70 68 69 D5 70 F7 F9 C9
'''

image-20220311131051108

然后有个外层逻辑,也就是前面提到的,dnspy已经反编得很好认了,python代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from z3 import *
flag2 = bytearray(b'Meaningless_!$!%*@^%#%_Code')
id = [BitVec('a%d' % i, 8) for i in range(9)]
fuckId = [i for i in id]
target =[7, 90, 115, 1, 117, 99, 114, 97, 24]
tt = bytearray(target)
for j in range(len(flag2)):
id[j % len(target)] ^= flag2[j % len(flag2)]
s = Solver()
for i in range(9):
s.add(id[i] == target[i])
s.check()
res = s.model()
id_ = ''
for i in range(9):
id_ += chr(res[fuckId[i]].as_long())
print(id_)

得到得ID是ginkgo_CX

所以得到flag{ginkgo_CX@Meaningless_!$!%*@^%#%_Cod}

参考链接:这篇很详细还有其他

tips题外话

python代码中肯呢会提示没有模块名叫Crypto,如果要安装的话肯能会出现这样的问题,按照第二张图片的命令安装就好了

1
pip install pycryptodome -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com

image-20220311130907869

image-20220311130933815