TSCTF202-re

碎碎念

第一次出题,前两个题是我出的,后两个是师父出的,师父好厉害。感觉最后一道题是我最不擅长的题,大概算算法题?想加强一下了==

easyre

有两层加密,第一层是AES,第二层是RC5
前面是padding,填充方式是PKCS#7

AES

sub_FBE这个函数中有明显的AES加密特征:

image

有十轮加密,四个步骤,第十轮少了中间的步骤。对应的是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键可以修改数据的类型)。

image

对照cmd_tbl结构体,能够确定命令回调函数的地址为0x11A00。反编译这个函数。

image

逆一下几个函数,基本能够确定流程,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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,814评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,124评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,814评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,924评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,815评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,562评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,944评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,582评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,859评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,881评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,700评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,493评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,943评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,115评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,413评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,978评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,182评论 2 339