碎碎念
第一次出题,前两个题是我出的,后两个是师父出的,师父好厉害。感觉最后一道题是我最不擅长的题,大概算算法题?想加强一下了==
easyre
有两层加密,第一层是AES,第二层是RC5
前面是padding,填充方式是PKCS#7
AES
在sub_FBE
这个函数中有明显的AES加密特征:
有十轮加密,四个步骤,第十轮少了中间的步骤。对应的是AES的:字节代换、行移位、列混淆和轮密钥加,第十轮没有列混淆。
sub_DA2
是密钥扩展步骤。代码中AES的Sbox被修改了,在文件中给出了Sbox和InvSbox。密钥扩展部分也进行了一点修改,所以生成的子密钥会有不同。
AES代码:
for (i = 1; i <= 10; i++)
{
for (j = 0; j < 4; j++)
{
unsigned char t[4];
for (r = 0; r < 4; r++)
{
t[r] = j ? w[i][r][j - 1] : w[i - 1][r][3];
}
if (j == 0)
{
unsigned char temp = t[0];
for (r = 0; r < 3; r++)
{
t[r] = Sbox[t[(r + 1) % 4]];
}
t[3] = Sbox[temp];
t[0] ^= rc[i - 1];
}
for (r = 0; r < 4; r++)
{
w[i][r][j] = w[i - 1][r][j] ^ t[r];
}
}
}
修改后的:
if ( !l )
{
for ( n = 0; n <= 3; ++n )
v12[n] = *(_BYTE *)(a1 + (unsigned __int8)v12[(n + 1) % 4] + 8);
v12[0] ^= *(&v13 + k - 1);
}
等于3的这个部分有一点更改,但是可以不用管这个更改的细节,直接dump出文件中扩展出来的密钥解密即可。
RC5
根据0xB7E15163, 0x9E3779B9这两个长度可以判断猜测是RC5,密钥长度是16,轮数是12,块大小是32.同样可以找个RC5的代码进行加密验证一下
solve
class AES:
MIX_C = [[0x2, 0x3, 0x1, 0x1], [0x1, 0x2, 0x3, 0x1], [0x1, 0x1, 0x2, 0x3], [0x3, 0x1, 0x1, 0x2]]
I_MIXC = [[0xe, 0xb, 0xd, 0x9], [0x9, 0xe, 0xb, 0xd], [0xd, 0x9, 0xe, 0xb], [0xb, 0xd, 0x9, 0xe]]
RCon = [0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000]
S_BOX = [[54, 79, 98, 216, 181, 132, 205, 246, 220, 42, 230, 237, 171, 82, 1, 175], [208, 10, 104, 20, 39, 161, 219, 135, 156, 231, 41, 102, 53, 233, 180, 145], [139, 206, 243, 52, 86, 94, 35, 97, 112, 195, 167, 50, 45, 128, 12, 196], [245, 44, 114, 164, 201, 6, 185, 110, 25, 18, 7, 34, 214, 211, 0, 113], [152, 14, 107, 202, 100, 61, 186, 254, 136, 2, 227, 70, 239, 31, 47, 143], [46, 58, 49, 155, 240, 226, 123, 153, 187, 168, 72, 99, 212, 141, 244, 105], [191, 232, 75, 222, 218, 118, 176, 16, 184, 151, 198, 159, 22, 247, 172, 221], [69, 188, 197, 225, 173, 124, 91, 252, 204, 174, 89, 154, 111, 229, 103, 163], [88, 199, 140, 101, 129, 73, 83, 131, 64, 57, 93, 55, 177, 116, 142, 77], [207, 51, 248, 162, 117, 115, 250, 215, 74, 203, 130, 120, 23, 224, 149, 125], [8, 241, 189, 65, 183, 4, 15, 138, 209, 81, 62, 56, 147, 194, 137, 210], [150, 148, 26, 251, 95, 126, 96, 36, 84, 234, 127, 11, 27, 67, 38, 43], [28, 179, 78, 133, 63, 90, 192, 30, 80, 119, 255, 9, 228, 158, 37, 157], [5, 19, 238, 213, 48, 170, 40, 121, 134, 76, 249, 87, 109, 235, 71, 178], [217, 24, 182, 190, 122, 193, 160, 13, 92, 200, 165, 68, 106, 60, 32, 166], [3, 17, 59, 21, 144, 253, 146, 236, 169, 33, 85, 29, 223, 108, 66, 242]]
I_SBOX = [[62, 14, 73, 240, 165, 208, 53, 58, 160, 203, 17, 187, 46, 231, 65, 166], [103, 241, 57, 209, 19, 243, 108, 156, 225, 56, 178, 188, 192, 251, 199, 77], [238, 249, 59, 38, 183, 206, 190, 20, 214, 26, 9, 191, 49, 44, 80, 78], [212, 82, 43, 145, 35, 28, 0, 139, 171, 137, 81, 242, 237, 69, 170, 196], [136, 163, 254, 189, 235, 112, 75, 222, 90, 133, 152, 98, 217, 143, 194, 1], [200, 169, 13, 134, 184, 250, 36, 219, 128, 122, 197, 118, 232, 138, 37, 180], [182, 39, 2, 91, 68, 131, 27, 126, 18, 95, 236, 66, 253, 220, 55, 124], [40, 63, 50, 149, 141, 148, 101, 201, 155, 215, 228, 86, 117, 159, 181, 186], [45, 132, 154, 135, 5, 195, 216, 23, 72, 174, 167, 32, 130, 93, 142, 79], [244, 31, 246, 172, 177, 158, 176, 105, 64, 87, 123, 83, 24, 207, 205, 107], [230, 21, 147, 127, 51, 234, 239, 42, 89, 248, 213, 12, 110, 116, 121, 15], [102, 140, 223, 193, 30, 4, 226, 164, 104, 54, 70, 88, 113, 162, 227, 96], [198, 229, 173, 41, 47, 114, 106, 129, 233, 52, 67, 153, 120, 6, 33, 144], [16, 168, 175, 61, 92, 211, 60, 151, 3, 224, 100, 22, 8, 111, 99, 252], [157, 115, 85, 74, 204, 125, 10, 25, 97, 29, 185, 221, 247, 11, 210, 76], [84, 161, 255, 34, 94, 48, 7, 109, 146, 218, 150, 179, 119, 245, 71, 202]]
def SubBytes(self, State):
# 字节替换
return [self.S_BOX[i][j] for i, j in
[(_ >> 4, _ & 0xF) for _ in State]]
def SubBytes_Inv(self, State):
# 字节逆替换
return [self.I_SBOX[i][j] for i, j in
[(_ >> 4, _ & 0xF) for _ in State]]
def ShiftRows(self, S):
# 行移位
return [S[ 0], S[ 5], S[10], S[15],
S[ 4], S[ 9], S[14], S[ 3],
S[ 8], S[13], S[ 2], S[ 7],
S[12], S[ 1], S[ 6], S[11]]
def ShiftRows_Inv(self, S):
# 逆行移位
return [S[ 0], S[13], S[10], S[ 7],
S[ 4], S[ 1], S[14], S[11],
S[ 8], S[ 5], S[ 2], S[15],
S[12], S[ 9], S[ 6], S[ 3]]
def MixColumns(self, State):
# 列混合
return self.Matrix_Mul(self.MIX_C, State)
def MixColumns_Inv(self, State):
# 逆列混合
return self.Matrix_Mul(self.I_MIXC, State)
def RotWord(self, _4byte_block):
# 用于生成轮密钥的字移位
return ((_4byte_block & 0xffffff) << 8) + (_4byte_block >> 24)
def SubWord(self, _4byte_block):
# 用于生成密钥的字节替换
result = 0
for position in range(4):
i = _4byte_block >> position * 8 + 4 & 0xf
j = _4byte_block >> position * 8 & 0xf
result ^= self.S_BOX[i][j] << position * 8
return result
def mod(self, poly, mod = 0b100011011):
# poly模多项式mod
while poly.bit_length() > 8:
poly ^= mod << poly.bit_length() - 9
return poly
def mul(self, poly1, poly2):
# 多项式相乘
result = 0
for index in range(poly2.bit_length()):
if poly2 & 1 << index:
result ^= poly1 << index
return result
def Matrix_Mul(self, M1, M2): # M1 = MIX_C M2 = State
# 用于列混合的矩阵相乘
M = [0] * 16
for row in range(4):
for col in range(4):
for Round in range(4):
M[row + col*4] ^= self.mul(M1[row][Round], M2[Round+col*4])
M[row + col*4] = self.mod(M[row + col*4])
return M
def round_key_generator(self, _16bytes_key):
# 轮密钥产生
w = [_16bytes_key >> 96,
_16bytes_key >> 64 & 0xFFFFFFFF,
_16bytes_key >> 32 & 0xFFFFFFFF,
_16bytes_key & 0xFFFFFFFF] + [0]*40
for i in range(4, 44):
temp = w[i-1]
if not i % 4:
temp = self.SubWord(self.RotWord(temp)) ^ self.RCon[i//4-1]
w[i] = w[i-4] ^ temp
return [self.num_2_16bytes(
sum([w[4 * i] << 96, w[4*i+1] << 64,
w[4*i+2] << 32, w[4*i+3]])
) for i in range(11)]
def AddRoundKey(self, State, RoundKeys, index):
# 异或轮密钥
return self._16bytes_xor(State, RoundKeys[index])
def _16bytes_xor(self, _16bytes_1, _16bytes_2):
return [_16bytes_1[i] ^ _16bytes_2[i] for i in range(16)]
def _16bytes2num(cls, _16bytes):
# 16字节转数字
return int.from_bytes(_16bytes, byteorder = 'big')
def num_2_16bytes(cls, num):
# 数字转16字节
return num.to_bytes(16, byteorder = 'big')
def aes_encrypt(self, plaintext_list, RoundKeys):
State = plaintext_list
State = self.AddRoundKey(State, RoundKeys, 0)
for Round in range(1, 10):
State = self.SubBytes(State)
State = self.ShiftRows(State)
State = self.MixColumns(State)
State = self.AddRoundKey(State, RoundKeys, Round)
State = self.SubBytes(State)
State = self.ShiftRows(State)
State = self.AddRoundKey(State, RoundKeys, 10)
return State
def aes_decrypt(self, ciphertext_list, RoundKeys):
State = ciphertext_list
State = self.AddRoundKey(State, RoundKeys, 10)
for Round in range(1, 10):
State = self.ShiftRows_Inv(State)
State = self.SubBytes_Inv(State)
State = self.AddRoundKey(State, RoundKeys, 10-Round)
State = self.MixColumns_Inv(State)
State = self.ShiftRows_Inv(State)
State = self.SubBytes_Inv(State)
State = self.AddRoundKey(State, RoundKeys, 0)
return State
class RC5:
def __init__(self, w, R, key, strip_extra_nulls=False):
self.w = w # block size (32, 64 or 128 bits)
self.R = R # number of rounds (0 to 255)
self.key = key # key (0 to 2040 bits)
self.strip_extra_nulls = strip_extra_nulls
# some useful constants
self.T = 2 * (R + 1)
self.w4 = w // 4
self.w8 = w // 8
self.mod = 2 ** self.w
self.mask = self.mod - 1
self.b = len(key)
self.__keyAlign()
self.__keyExtend()
self.__shuffle()
def __lshift(self, val, n):
n %= self.w
return ((val << n) & self.mask) | ((val & self.mask) >> (self.w - n))
def __rshift(self, val, n):
n %= self.w
return ((val & self.mask) >> n) | (val << (self.w - n) & self.mask)
def __const(self): # constants generation
if self.w == 16:
return 0xB7E1, 0x9E37 # return P, Q values
elif self.w == 32:
return 0xB7E15163, 0x9E3779B9
elif self.w == 64:
return 0xB7E151628AED2A6B, 0x9E3779B97F4A7C15
def __keyAlign(self):
if self.b == 0: # key is empty
self.c = 1
elif self.b % self.w8:
self.key += b'\x00' * (self.w8 - self.b % self.w8) # fill key with \x00 bytes
self.b = len(self.key)
self.c = self.b // self.w8
else:
self.c = self.b // self.w8
L = [0] * self.c
for i in range(self.b - 1, -1, -1):
L[i // self.w8] = (L[i // self.w8] << 8) + self.key[i]
self.L = L
def __keyExtend(self):
P, Q = self.__const()
self.S = [(P + i * Q) % self.mod for i in range(self.T)]
def __shuffle(self):
i, j, A, B = 0, 0, 0, 0
for k in range(3 * max(self.c, self.T)):
A = self.S[i] = self.__lshift((self.S[i] + A + B), 3)
B = self.L[j] = self.__lshift((self.L[j] + A + B), A + B)
i = (i + 1) % self.T
j = (j + 1) % self.c
def encryptBlock(self, data):
A = int.from_bytes(data[:self.w8], byteorder='little')
B = int.from_bytes(data[self.w8:], byteorder='little')
A = (A + self.S[0]) % self.mod
B = (B + self.S[1]) % self.mod
for i in range(1, self.R + 1):
A = (self.__lshift((A ^ B), B) + self.S[2 * i]) % self.mod
B = (self.__lshift((A ^ B), A) + self.S[2 * i + 1]) % self.mod
return (A.to_bytes(self.w8, byteorder='little')
+ B.to_bytes(self.w8, byteorder='little'))
def decryptBlock(self, data):
A = int.from_bytes(data[:self.w8], byteorder='little')
B = int.from_bytes(data[self.w8:], byteorder='little')
for i in range(self.R, 0, -1):
B = self.__rshift(B - self.S[2 * i + 1], A) ^ A
A = self.__rshift(A - self.S[2 * i], B) ^ B
B = (B - self.S[1]) % self.mod
A = (A - self.S[0]) % self.mod
return (A.to_bytes(self.w8, byteorder='little')
+ B.to_bytes(self.w8, byteorder='little'))
def encryptFile(self, inpFileName, outFileName):
with open(inpFileName, 'rb') as inp, open(outFileName, 'wb') as out:
run = True
while run:
text = inp.read(self.w4)
if not text:
break
if len(text) != self.w4:
text = text.ljust(self.w4, b'\x00')
run = False
text = self.encryptBlock(text)
out.write(text)
def decryptFile(self, inpFileName, outFileName):
with open(inpFileName, 'rb') as inp, open(outFileName, 'wb') as out:
while True:
text = inp.read(self.w4)
if not text:
break
text = self.decryptBlock(text)
if self.strip_extra_nulls:
text = text.rstrip(b'\x00')
out.write(text)
def encryptBytes(self, data):
res, run = b'', True
while run:
temp = data[:self.w4]
if len(temp) != self.w4:
data = data.ljust(self.w4, b'\x00')
run = False
res += self.encryptBlock(temp)
data = data[self.w4:]
if not data:
break
return res
def decryptBytes(self, data):
res, run = b'', True
while run:
temp = data[:self.w4]
if len(temp) != self.w4:
run = False
res += self.decryptBlock(temp)
data = data[self.w4:]
if not data:
break
return res.rstrip(b'\x00')
if __name__ == '__main__':
enc = [0x70, 0x24, 0x76, 0xfd, 0xc7, 0x29, 0xc5, 0x97, 0xef, 0xee, 0xb6, 0x22, 0x5e, 0xb5, 0x46, 0xf2, 0x39, 0x47, 0x8f, 0xc2, 0x9e, 0x9c, 0x88, 0x2b, 0xfa, 0xd8, 0x7f, 0xd3, 0xeb, 0x6c, 0x9c, 0xa6, 0x5e, 0x30, 0x18, 0xd9, 0xdb, 0x96, 0xc2, 0x2b, 0xa5, 0x57, 0x36, 0x47, 0xd5, 0x72, 0xa6, 0xd5]
## 解RC5
rc5 = RC5(32, 12, b'welcometotsctf\x00\x00')
data = bytes(enc)
result = rc5.decryptBytes(data)
invRC5 = [int(t) for t in result]
# 解AES
aes = AES()
# 子密钥(从程序中dump)
w = [0x77,0x6f,0x6f,0x74,0x65,0x6d,0x74,0x66,0x6c,0x65,0x73,0x0,0x63,0x74,0x63,0x0,0xc6,0xa9,0xc6,0xb2,0x53,0x3e,0x4a,0x2c,0x5a,0x3f,0x4c,0x4c,0xf5,0x81,0xe2,0xe2,0xe9,0x40,0x86,0x34,0xbc,0x82,0xc8,0xe4,0xec,0xd3,0x9f,0xd3,0x75,0xf4,0x16,0xf4,0x97,0xd7,0x51,0x65,0x69,0xeb,0x23,0xc7,0x7c,0xaf,0x30,0xe3,0x2c,0xd8,0xce,0x3a,0x81,0x56,0x7,0x62,0xd7,0x3c,0x1f,0xd8,0x7b,0xd4,0xe4,0x7,0x98,0x40,0x8e,0xb4,0x17,0x41,0x46,0x24,0x21,0x1d,0x2,0xda,0x24,0xf0,0x14,0x13,0xcb,0x8b,0x5,0xb1,0xce,0x8f,0xc9,0xed,0x35,0x28,0x2a,0xf0,0xb0,0x40,0x54,0x47,0xea,0x61,0x64,0xd5,0x8d,0x2,0xcb,0x26,0xcb,0xe3,0xc9,0x39,0x1a,0x5a,0xe,0x49,0x32,0x53,0x37,0xe2,0x1f,0x1d,0xd6,0xf0,0xc9,0x2a,0xe3,0xda,0xac,0xf6,0xf8,0xb1,0x5a,0x9,0x3e,0xdc,0xfd,0xe0,0x36,0xc6,0x5d,0x77,0x94,0x4e,0xc1,0x37,0xcf,0x7e,0x7b,0x72,0x4c,0x90,0xe4,0x4,0x32,0xf4,0x3a,0x4d,0xd9,0x97,0xe,0x39,0xf6,0x88,0xbf,0xcd,0x81,0x11]
RoundKeys = []
for i in range(11):
temp = w[16*i:16*(i+1)]
r = []
for j in range(4):
for k in range(4):
r.append(temp[4*k+j])
RoundKeys.append(bytes(r))
flag = []
for i in range(0, len(invRC5), 16):
ciphertext = bytes(invRC5[i:i+16])
plaintext = aes.aes_decrypt(ciphertext, RoundKeys)
flag = flag + plaintext
print("".join(chr(i) for i in flag))
something else
作为re第一题,是我面向源码做题了...我自己感觉AES改的少应该还可以,但是其实拿到题改动未知的时候会想很多,这个代码又很复杂。我不应该改动它的,或者换一个简单的算法 (跪
babywasm
题目描述中出现了wasi,稍微搜一下应该能搜到这份使用教程: https://docs.wasmer.io/integrations/c/setup 里面包含了很多c程序调用wasm代码的示例。这道题用了wasmer-c-api来构建,主程序为re, program.wasm为子程序。
对照着示例(eg: https://docs.wasmer.io/integrations/c/examples/host-functions)不难看出,ELF的main
函数中首先导入boom
函数到wasm的环境变量中,然后调用了wasm的start
函数。
对program.wasm逆向分析.基础教程:
https://xz.aliyun.com/t/5170
反汇编的话,可以把wasm转成c语言的格式,用wasm2c
$ ./wasm2c wasm.wasm -o wasm.c
==> 得到wasm.c和wasm.h
但是因为生成的c语言很长而且基本跟看wat没什么区别,所以需要再编译成二进制文件放到ida里面去看
将之前反编译出来的wasm.c,wasm.h,以及wabt项目内的wasm-rt.h,wasm-rt-impl.c,wasm-rt-impl.h三个文件放到同一个文件夹。
直接gcc wasm.c会报错,因为很多wasm的函数没有具体的实现。但是我们可以只编译不链接,我们关心的只是程序本身的逻辑,不需要真正编译出能运行的elf来。
$ gcc -c wasm.c -o wasm.o
得到的还未连接的elf文件wasm.o, 将wasm.o放到ida里面分析会比较清楚一些。
得到二进制文件后首先可以搜索一下字符串,可以发现一个特殊的字符串There is a fire in each one's heart
,查找引用可以看到在init_memory
中找到,这部分主要是初始化字符串,这个字符串存储在了w2c_memory + 2176
的偏移处。
查看start
函数,这个函数中引用了四个函数,在w2c_f9
中调用了Z_envZ_boomZ_ii
,这个函数是在ELF中导入的,所以这里应该是程序的关键点。
__int64 __fastcall w2c_f9(__m128i a1)
{
int v1; // ST30_4
int v2; // ST34_4
unsigned int v3; // ST38_4
unsigned int v4; // ST3C_4
unsigned int v5; // ST6C_4
unsigned int v7; // [rsp+Ch] [rbp-64h]
if ( ++wasm_rt_call_stack_depth > 0x1F4u )
wasm_rt_trap(7LL);
w2c_g0 -= 16;
v7 = w2c_g0;
i32_store(&w2c_memory, (unsigned int)w2c_g0 + 12LL, 0);
i32_store(&w2c_memory, v7, 2752);
w2c_f11(1024u, v7, a1);
v1 = w2c_f106(2752u);
i32_store(&w2c_memory, v7 + 8LL, v1);
v2 = w2c_f63(256LL);
i32_store(&w2c_memory, v7 + 4LL, v2);
v3 = i32_load(&w2c_memory, v7 + 4LL);
w2c_f7(v3, 2176LL, 36LL);
v4 = i32_load(&w2c_memory, v7 + 4LL);
v5 = i32_load(&w2c_memory, v7 + 8LL);
w2c_f8(v4, 2752LL, v5);
if ( (unsigned int)Z_envZ_boomZ_ii(2752LL) == 0 )
w2c_f103(1034LL, 0LL);
else
w2c_f103(1027LL, 0LL);
w2c_g0 = v7 + 16;
--wasm_rt_call_stack_depth;
return 0LL;
}
在这个函数中首先调用了w2c_f11
函数,参数是1024,猜测是字符串内容,可以找到1024偏移处字符串的值(0x39480 - 2147 + 1024 -> 0x3901d),这个地方的字符串是%s
所以这个地方应该是scanf
函数。输入的字符串存储在2752
处。w2c_f106
的参数是输入字符串的位置,里面的代码很明显时求输入字符串的长度。长度存储在v7 + 8LL
处。
然后将一个与输入无关的函数w2c_f63
返回值存储在v7 + 4LL
处,然后w2c_f7(v3, 2176LL, 36LL);
这个函数第一个参数时w2c_f63
的返回值,第二个参数是There is a fire in each one's heart
这个字符串,第三个参数是字符串的长度加1.
进入w2c_f7
这个函数内部可以发现,进行了两个循环,每个循环执行256次,第一个循环中对长度取模(第三个参数),第二个循环中对256取模。这时候应该猜测出来是RC4(当然不猜直接看也还比较容易看懂,就是数据赋值变成了store和load而已,给了流密钥的提示就更容易看了)。
所以w2c_f7
这个函数是密钥的初始化,随后调用w2c_f8(v4, 2752LL, v5)
,第一个参数是初始化变换后的密钥,第二个参数是输入的字符串,第三个参数是字符串的长度(这个函数中的加密也有对256取模 & 数据交换的各种操作)
在加密完成过后调用Z_envZ_boomZ_ii
函数,通过打印Right
。
solve
from Crypto.Cipher import ARC4
def myRC4(data,key):
rc41 = ARC4.new(key)
encrypted = rc41.encrypt(data)
return encrypted.encode('hex').upper()
enc = [0xbf, 0xcf, 0x61, 0x4c, 0xed, 0x4c, 0x29, 0x24, 0x5, 0x8a, 0x60, 0x87, 0x35, 0x81, 0x73, 0xf, 0xde, 0x96, 0x65, 0xa5, 0x41, 0x18, 0xac, 0xf5, 0x1c, 0x42, 0xda, 0x26, 0x96, 0xad, 0x35, 0xde, 0xf4, 0xc3, 0xcd, 0x1c, 0x96, 0xeb]
s = "".join(chr(i) for i in enc)
key = "There is a fire in each one's heart\0"# 因为初始化密钥的长度比这个字符串的长度长1,所以记得补个0,当然如果找的是实现代码可以直接用长度36
enc = myRC4(s, key)
print enc.decode('hex')
babybios
这道题在uboot arm的bios里添加了一个getflag指令。在指令的回调函数里实现了对flag的校验逻辑。动态调试可以使用gdb-multiarch+qemu去调试,静态直接用IDA分析即可。
这道题的关键是找到指令的回调函数,而线索就是uboot在添加和搜索指令的机制。https://blog.csdn.net/itxiebo/article/details/50991049
观察cmd_tbl结构体可以发现,结构体包含了命令名字符串指针name
,回调函数指针cmd
,命令帮助字符串指针help
。
struct cmd_tbl {
char *name; /* Command Name */
int maxargs; /* maximum number of arguments */
/*
* Same as ->cmd() except the command
* tells us if it can be repeated.
* Replaces the old ->repeatable field
* which was not able to make
* repeatable property different for
* the main command and sub-commands.
*/
int (*cmd_rep)(struct cmd_tbl *cmd, int flags, int argc,
char *const argv[], int *repeatable);
/* Implementation function */
int (*cmd)(struct cmd_tbl *cmd, int flags, int argc,
char *const argv[]);
char *usage; /* Usage message (short) */
#ifdef CONFIG_SYS_LONGHELP
char *help; /* Help message (long) */
#endif
#ifdef CONFIG_AUTO_COMPLETE
/* do auto completion on the arguments */
int (*complete)(int argc, char *const argv[],
char last_char, int maxv, char *cmdv[]);
#endif
};
那么在IDA中搜索命令名字符串指针,也就是getflag
字符串的指针0x88EEA
,就找到了下图的位置(IDA按D键可以修改数据的类型)。
对照cmd_tbl
结构体,能够确定命令回调函数的地址为0x11A00
。反编译这个函数。
逆一下几个函数,基本能够确定流程,sub_116D8函数对输入做base64解码,sub_118A8函数之后的部分首先填充了一个9*9的数独,之后对数独进行校验。
把数独从ida中抠出来放进在线数独求解器,再做base64编码,就得到了flag。
wbenc
这道题来自某IoT设备中的白盒加密(white box encrypt)。
加密分为三个部分,第一和第三部分进行了一些简单的变换,可以直接逆向。
中间的大段代码需要花功夫逆一下。
逆向之后,整个中间部分共有10轮,每一轮的操作如下。
v13 = int32_split(magic_tbl[buf[5] | 0x100] ^ magic_tbl[buf[0]] ^ magic_tbl[buf[10] | 0x200] ^ magic_tbl[buf[15] | 0x300])
v15 = int32_split(magic_tbl[buf[4] | 0x400] ^ magic_tbl[buf[9] | 0x500] ^ magic_tbl[buf[14] | 0x600] ^ magic_tbl[buf[3] | 0x700])
v17 = int32_split(magic_tbl[buf[13] | 0x900] ^ magic_tbl[buf[8] | 0x800] ^ magic_tbl[buf[2] | 0xA00] ^ magic_tbl[buf[7] | 0xB00])
v18 = int32_split(magic_tbl[buf[11] | 0xF00] ^ magic_tbl[buf[1] | 0xD00] ^ magic_tbl[buf[12] | 0xC00] ^ magic_tbl[buf[6] | 0xE00])
分析可知,这四行代码的是独立的,也就是每行的4byte输入转换为4byte输出。那么对这样的4byte变换直接暴破的话,时间复杂度高。2^32很难在普通计算机下暴破。
我们可以利用中间相遇攻击的思想对暴力破解进行优化。例如我们对下面的第一行进行逆向(暴破)。
v13 = int32_split(magic_tbl[buf[5] | 0x100] ^ magic_tbl[buf[0]] ^ magic_tbl[buf[10] | 0x200] ^ magic_tbl[buf[15] | 0x300])
首先枚举所有可能的magic_tbl[buf[5] | 0x100] ^ magic_tbl[buf[0]]
这样一个异或和,也就是0xff * 0xff种可能,存储在一张有序表当中。
再枚举所有的magic_tbl[buf[10] | 0x200] ^ magic_tbl[buf[15] | 0x300]
,也就是后面两个字节的异或和,把异或的结果和v13去做异或,得到的值在前面存储的表中查询是否存在,有序表的查询操作时间复杂度是O(logn)。
如果存在,那么说明我们找到了一组(buf[5],buf[0]],buf[10],buf[15])
,满足magic_tbl[buf[5] | 0x100] ^ magic_tbl[buf[0]] ^ magic_tbl[buf[10] | 0x200] ^ magic_tbl[buf[15] | 0x300]=v13
,也就是从输出的4byte逆向得到了输入的4byte。
利用这样一个空间换时间的优化,能够在可以接受的时间复杂度下逆向得到输入。
之后编写解密脚本就能够从输出获得输入。
解密脚本如下
import sys
magic_tbl = []
def loadTable(filename):
global magic_tbl
fp = open(filename, 'r')
content = fp.read()
fp.close()
content = content.split(' ')
content = [int(content[i], 16) for i in xrange(len(content))]
tbl = [content[i] | content[i+1]<<8 | content[i+2]<<16 | content[i+3]<<24 for i in xrange(0, len(content), 4)]
print hex(len(tbl))
magic_tbl = tbl
def listDump(l):
for x in xrange(len(l)):
print hex(l[x]),
print ''
def int32_split(x):
return [x & 0xff, (x >> 8) & 0xff, (x >> 16) & 0xff, (x >> 24) & 0xff]
def ROTR(ch, n, sz):
tmp = ((ch >> (n%sz)) | (ch << (sz - (n % sz) ))) & ((1 << sz) - 1)
return tmp
def ROTL(ch, n, sz):
tmp = ((ch << (n%sz)) | (ch >> (sz - (n % sz) ))) & ((1 << sz) - 1)
return tmp
def decode(input):
buf = [0] * 16
# ROT decode
v64 = 0
v66 = 0
v68 = 0
v70 = 0
for i in xrange(16):
tmp = ord(input[i]) ^ v64
buf[i] = ROTR(tmp, i*2, 8)
v64 += 0xd
v66 += 0xca
v68 += 0x73
v70 += 0x9b
#listDump(buf)
#print buf
v56 = int32_split(buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24))
v57 = int32_split(buf[4] | (buf[5] << 8) | (buf[6] << 16) | (buf[7] << 24))
v67 = int32_split(buf[8] | (buf[9] << 8) | (buf[10] << 16) | (buf[11] << 24))
v71 = int32_split(buf[12] | (buf[13] << 8) | (buf[14] << 16) | (buf[15] << 24))
# MITM attack
# init
def get_dict(x, y):
dic = {}
for i in xrange(256):
for j in xrange(256):
tmp = magic_tbl[i | x] ^ magic_tbl[j | y]
dic[tmp] = [i, j]
return dic
def mitm(dic, res, u, v):
res = res[0] | res[1] << 8 | res[2] << 16 | res[3] << 24
for i in xrange(256):
for j in xrange(256):
tmp = magic_tbl[i | u] ^ magic_tbl[j | v]
tmp ^= res
if dic.has_key(tmp):
[k, l] = dic[tmp]
#print [i, j, k, l]
return [i, j, k, l]
# round 9
v51 = [0] * 4
v52 = [0] * 4
v53 = [0] * 4
v55 = [0] * 4
v55[3], v53[2], v52[1], v51[0] = mitm(get_dict(0x9100, 0x9000), v56, 0x9300, 0x9200)
v52[0], v53[1], v55[2], v51[3] = mitm(get_dict(0x9600, 0x9700), v57, 0x9400, 0x9500)
v51[2], v55[1], v53[0], v52[3] = mitm(get_dict(0x9800, 0x9B00), v67, 0x9A00, 0x9900)
v51[1], v55[0], v52[2], v53[3] = mitm(get_dict(0x9E00, 0x9F00), v71, 0x9D00, 0x9C00)
# round 8
v47 = [0] * 4
v48 = [0] * 4
v49 = [0] * 4
v50 = [0] * 4
v49[2], v48[1], v47[0], v50[3] = mitm(get_dict(0x8000, 0x8300), v51, 0x8200, 0x8100)
v48[0], v49[1], v50[2], v47[3] = mitm(get_dict(0x8600, 0x8700), v52, 0x8400, 0x8500)
v49[0], v50[1], v47[2], v48[3] = mitm(get_dict(0x8A00, 0x8B00), v53, 0x8800, 0x8900)
v49[3], v50[0], v47[1], v48[2] = mitm(get_dict(0x8D00, 0x8E00), v55, 0x8F00, 0x8C00)
# round 7
v42 = [0] * 4
v43 = [0] * 4
v44 = [0] * 4
v46 = [0] * 4
v44[2], v43[1], v42[0], v46[3] = mitm(get_dict(0x7000, 0x7300), v47, 0x7200, 0x7100)
v43[0], v44[1], v46[2], v42[3] = mitm(get_dict(0x7600, 0x7700), v48, 0x7400, 0x7500)
v43[3], v44[0], v46[1], v42[2] = mitm(get_dict(0x7900, 0x7A00), v49, 0x7B00, 0x7800)
v46[0], v42[1], v43[2], v44[3] = mitm(get_dict(0x7E00, 0x7F00), v50, 0x7C00, 0x7D00)
# round 6
v37 = [0] * 4
v38 = [0] * 4
v40 = [0] * 4
v41 = [0] * 4
v38[1], v37[0], v40[2], v41[3] = mitm(get_dict(0x6200, 0x6300), v42, 0x6100, 0x6000)
v38[0], v40[1], v41[2], v37[3] = mitm(get_dict(0x6600, 0x6700), v43, 0x6400, 0x6500)
v40[0], v41[1], v37[2], v38[3] = mitm(get_dict(0x6A00, 0x6B00), v44, 0x6800, 0x6900)
v41[0], v37[1], v38[2], v40[3] = mitm(get_dict(0x6E00, 0x6F00), v46, 0x6C00, 0x6D00)
# round 5
v33 = [0] * 4
v34 = [0] * 4
v35 = [0] * 4
v36 = [0] * 4
v34[1], v33[0], v35[2], v36[3] = mitm(get_dict(0x5200, 0x5300), v37, 0x5100, 0x5000)
v34[0], v35[1], v36[2], v33[3] = mitm(get_dict(0x5600, 0x5700), v38, 0x5400, 0x5500)
v35[0], v36[1], v33[2], v34[3] = mitm(get_dict(0x5A00, 0x5B00), v40, 0x5800, 0x5900)
v36[0], v33[1], v34[2], v35[3] = mitm(get_dict(0x5E00, 0x5F00), v41, 0x5C00, 0x5D00)
# round 4
v28 = [0] * 4
v30 = [0] * 4
v31 = [0] * 4
v32 = [0] * 4
v28[0], v30[1], v31[2], v32[3] = mitm(get_dict(0x4200, 0x4300), v33, 0x4000, 0x4100)
v31[1], v30[0], v32[2], v28[3] = mitm(get_dict(0x4600, 0x4700), v34, 0x4500, 0x4400)
v31[0], v32[1], v28[2], v30[3] = mitm(get_dict(0x4A00, 0x4B00), v35, 0x4800, 0x4900)
v32[0], v28[1], v30[2], v31[3] = mitm(get_dict(0x4E00, 0x4F00), v36, 0x4C00, 0x4D00)
# round 3
v24 = [0] * 4
v25 = [0] * 4
v26 = [0] * 4
v27 = [0] * 4
v25[1], v24[0], v26[2], v27[3] = mitm(get_dict(0x3200, 0x3300), v28, 0x3100, 0x3000)
v25[0], v26[1], v27[2], v24[3] = mitm(get_dict(0x3600, 0x3700), v30, 0x3400, 0x3500)
v26[0], v27[1], v24[2], v25[3] = mitm(get_dict(0x3A00, 0x3B00), v31, 0x3800, 0x3900)
v27[0], v24[1], v25[2], v26[3] = mitm(get_dict(0x3E00, 0x3F00), v32, 0x3C00, 0x3D00)
# round 2
v19 = [0] * 4
v21 = [0] * 4
v22 = [0] * 4
v23 = [0] * 4
v19[0], v21[1], v22[2], v23[3] = mitm(get_dict(0x2200, 0x2300), v24, 0x2000, 0x2100)
v22[1], v21[0], v23[2], v19[3] = mitm(get_dict(0x2600, 0x2700), v25, 0x2500, 0x2400)
v22[0], v23[1], v19[2], v21[3] = mitm(get_dict(0x2A00, 0x2B00), v26, 0x2800, 0x2900)
v23[0], v19[1], v21[2], v22[3] = mitm(get_dict(0x2E00, 0x2F00), v27, 0x2C00, 0x2D00)
# round 1
v13 = [0] * 4
v15 = [0] * 4
v17 = [0] * 4
v18 = [0] * 4
v17[2], v13[0], v15[1], v18[3] = mitm(get_dict(0x1100, 0x1300), v19, 0x1200, 0x1000)
v15[0], v17[1], v18[2], v13[3] = mitm(get_dict(0x1600, 0x1700), v21, 0x1400, 0x1500)
v17[0], v18[1], v13[2], v15[3] = mitm(get_dict(0x1A00, 0x1B00), v22, 0x1800, 0x1900)
v18[0], v13[1], v15[2], v17[3] = mitm(get_dict(0x1E00, 0x1F00), v23, 0x1C00, 0x1D00)
# round 0
buf = [0] * 16
buf[5], buf[0], buf[10], buf[15] = mitm(get_dict(0x200, 0x300), v13, 0x100, 0x000)
buf[4], buf[9], buf[14], buf[3] = mitm(get_dict(0x600, 0x700), v15, 0x400, 0x500)
buf[13], buf[8], buf[2], buf[7] = mitm(get_dict(0xA00, 0xB00), v17, 0x900, 0x800)
buf[11], buf[1], buf[12], buf[6] = mitm(get_dict(0xC00, 0xE00), v18, 0xF00, 0xD00)
#print buf
#ROT Decode
v4 = 0
v5 = 0
v6 = 0
v7 = 0
msg = [0] * 16
idx = 0
while True:
c = 0
for i in xrange(256):
v9 = ROTL(i, v6, 8)
v10 = ROTL(i, v5, 8) ^ v9
v11 = ROTL(i, v4, 8)
v12 = v11 ^ v10 ^ v7
v12 &= 0xff
#print v12, buf[idx]
if v12 == buf[idx]:
c = i
break
v4 += 0xF1
v5 += 0x54
v6 += 0xC3
v7 += 0x6C
msg[idx] = c
idx += 1
if v7 == 0x6C0:
break
#msg = [chr(msg[i]) for i in xrange(len(msg))]
#msg = ''.join(msg)
return msg
# 从IDA中导出magic_tbl
loadTable('export_results.txt')
res = decode("4bf7f78ef088f4c94472f9f61f2c3331".decode('hex'))
print res
a = ''.join(["%c" % res[x] for x in xrange(len(res))])
res = decode("e68ac8bbcbac1e982cc246cd9dd34308".decode('hex'))
a += ''.join(["%c" % res[x] for x in xrange(len(res))])
print "TSCTF{%s}" % a