分类 WriteUps 下的文章

whereThel1b

在kali下运行程序,发现可以运行,然后测试输入,发现长度和base64很像(加新的东西后,长度不一定变长,但是总体稳定增长,且只影响前面后面的结果),构造一个字符串,使得加密后长度和encry数组长度一致,测出来flag长度,测试得到flag{uuid},然后得到xor表(换输入后发现在长度一致的时候xor表一致),然后解密encry,得到flag,不过并没有对so文件进行研究,或者是对trytry上面一个函数研究,就猜出来结果了。

flag{7f9a2d3c-07de-11ef-be5e-cf1e88674c0b}

gdb_debug

#include <iostream>
#include <cstring>
#include <vector>

void bur(int seed)
{
        srand(seed << (32 - 4));
        char result[] = "congratulationstoyoucongratulationstoy";
        unsigned char byte_62333B8010A0[40] = {
        0xBF, 0xD7, 0x2E, 0xDA, 0xEE, 0xA8, 0x1A, 0x10, 0x83, 0x73, 0xAC, 0xF1, 0x06, 0xBE, 0xAD, 0x88,
        0x04, 0xD7, 0x12, 0xFE, 0xB5, 0xE2, 0x61, 0xB7, 0x3D, 0x07, 0x4A, 0xE8, 0x96, 0xA2, 0x9D, 0x4D,
        0xBC, 0x81, 0x8C, 0xE9, 0x88, 0x78, 0x00, 0x00
        };
        char input[] = "flag{2b838a97ade9f743bb07ce476e02804c}";

        size_t len = strlen(input);
        char* pull1 = (char*)malloc(len);
        char* box = (char*)malloc(len);
        char* pull3 = (char*)malloc(len);

        char r1[38] = {0};
        char r2[38] = {0};
        char r3[38] = {0};
        for (int i = 0; i < len; i++)
        {
                r1[i] = rand();
                // std::cout << "test:r1[i] = " << seed << ' ' << std::hex << (int)r1[i] << std::endl;
        }

        for (size_t k = len - 1; k; --k)
        {
                r2[k] = rand() % (k + 1);
        }


        for (int i = 0; i < len; i++)
        {
                r3[i] = rand();
        }

        // Generate
        for (size_t i = 0; i < len; i++)
                box[i] = i;

        // Generate
        for (size_t i = 0; i < len; i++)
                pull1[i] = input[i] ^ r1[i];


        // Exchange
        for (size_t k = len - 1; k; --k)
        {
                //char s = rand() % (k + 1);
                char c = box[k];
                box[k] = box[r2[k]];
                box[r2[k]] = c;
        }

        for (int i = 0; i < len; i++)
        {
                pull3[i] = pull1[box[i]] ^ r3[i] ^ byte_62333B8010A0[i];
        }




        // Decrypt

        for (int i = 0; i < len; i++)
        {
                pull1[box[i]] = result[i] ^ byte_62333B8010A0[i] ^ r3[i];
        }

        for (size_t i = 0; i < len; i++)
        {
                pull1[i] ^= r1[i];
                putchar(pull1[i]);
        }
        /*

        for (size_t m = 0; ; ++m)
        {
                if (m >= len)
                        break;
                pull3[m] = pull1[box[m]];
        }
        // ^ rand() ^ pull1[m]

        for (size_t m = 0; ; ++m)
        {
                if (m >= len)
                        break;
                pull3[m] = pull1[box[m]] ^ r3[m] ^ byte_62333B8010A0[m];
        }
*/


        // pull3 == congratulationstoyoucongratulationstoy
}


int main()
{
        for (int i = 0; i < 15; i++)
        {
                bur(i);
                std::cout << std::endl;
        }

        return 0;
}

结果是,9,然后flag:

[9�4�_�G�9���v ��,��V�[<�ZQӂ�s�
j���}�p@�o���i�V}{(��5���h�]��+o���Ʉ
5��X:�
&���
    �� �0V�O����l<�7��
�7��d�0���q>�;�a��s�EF�6�
���wX�dmL�֜�h|y7�m>4
Xꙟ�����^8�
�(�Uv�K����(J����=/����g��w�_��2��
flag{78bace5989660ee38f1fd980a4b4fbcd}
��:�ó��<���(��7hy�����i�y_f��
oQ�rh^0wLU���>\k��m���7��y�X7��/ƙ@
Ǧ�
  p߿9�p�H(���"b��$D�����Z��<GF�
��σ?6���oV^�^w���n����:K@܃�
��C&.�D2_�6�5��~%�X�+�t�+l@ѩ`1�3Ƕ
`��i)��)�b�%��`�g�ݓ�yq�^���B��y�
��mU�r�#�F�.5-}����O"�n�
                        B��+#d��y��
qFUgrh����;�C��?���1ʐ��}�� #��KF

GoReverse

拿到结果后先base64解密放成hex,
AES Decrypt(key=dPGWgcLpqmxw3uOXhKpKV009Cql@@XE6, iv=dPGWgcLpqmxw3uOXhKpKV009Cql@@XE6)
Mode = CBC
然后SM4 Decrypt (key=pg5g#k6Qo3L&1EzT, iv=上个结果的前32字符) MODE= CTR
最后是XXTEA(非标准),被更改了delta和mx,逆了3个小时才发现mx被改了。。。

#define MX (z ^ (key[(p&3)^e])) + (sum ^ y ^ (((16 * z) ^ (y >> 3)) + ((z >> 5) ^ (4 * y))))

这是标准的MX

#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
上面解密的数据
0x6f8c082d,0x91d710f1,0x13cae9c4,0x28640b11,0x61831577,0x07f8bf0a,0xeb9905f0,0x6397b535,0x42237994,0x0d8269de,0x1fb90102
提取出来的key
0x385E7342, 0x345A772A, 0x6F38756C, 0x6B402652
DELTA 0x7fab4cad
网上搜脚本

btea(v, -n, k);
void btea(uint32_t* v, int n, uint32_t const key[4])
{
    uint32_t y, z, sum;
    unsigned p, rounds, e;
    if (n > 1)            /* Coding Part */
    {
        rounds = 6 + 52 / n;
        sum = 0;
        z = v[n - 1];
        do
        {
            sum += DELTA;
            e = (sum >> 2) & 3;
            for (p = 0; p < n - 1; p++)
            {
                y = v[p + 1];
                z = v[p] += MX;
            }
            y = v[0];
            z = v[n - 1] += MX;
        } while (--rounds);
    }
    else if (n < -1)      /* Decoding Part */
    {
        n = -n;
        rounds = 6 + 52 / n;
        sum = rounds * DELTA;
        y = v[0];
        do
        {
            e = (sum >> 2) & 3;
            for (p = n - 1; p > 0; p--)
            {
                z = v[p - 1];
                y = v[p] -= MX;
            }
            z = v[n - 1];
            y = v[0] -= MX;
            sum -= DELTA;
        } while (--rounds);
    }
}

asm_re

打开后发现是ARM汇编,并且给了一个IDA反汇编的结果
2024-05-30T11:17:18.png
找到关键点
2024-05-30T11:17:27.png
EOR就是XOR,分析得到以下算法

W8 = (W8 * 0x50) + 0x14;
W8 ^= 0x4D;x
W8 += 0x1E;

再继续分析

__text:0000000100003D88                               loc_100003D88                           ; CODE XREF: _main+1C8↑j
__text:0000000100003D88 A8 03 50 F8                   LDUR            X8, [X29,#var_100]
__text:0000000100003D8C A9 83 90 B8                   LDURSW          X9, [X29,#var_F8]        
__text:0000000100003D90 08 79 69 B8                   LDR             W8, [X8,X9,LSL#2]        W8 = [X8 + [X29 + var_F8] * 4]    
__text:0000000100003D94 AA 83 90 B8                   LDURSW          X10, [X29,#var_F8]       
__text:0000000100003D98 A9 C3 02 D1                   SUB             X9, X29, #-__dst         X9 = X29 + dst
__text:0000000100003D9C 29 79 6A B8                   LDR             W9, [X9,X10,LSL#2]       W9 = [X9 + [X29 + var_F8] * 4]
__text:0000000100003DA0 08 01 09 6B                   SUBS            W8, W8, W9               W8 == W9
__text:0000000100003DA4 E8 17 9F 1A                   CSET            W8, EQ
__text:0000000100003DA8 88 00 00 37                   TBNZ            W8, #0, loc_100003DB8
__text:0000000100003DA8
__text:0000000100003DAC 01 00 00 14                   B               loc_100003DB0
__text:0000000100003DAC

注意到,W8和W9相等时候正确
再找到开始的时候的memcpy

__text:0000000100003BEC A0 C3 02 D1                   SUB             X0, X29, #-__dst        ; __dst
__text:0000000100003BF0 01 00 00 90 21 40 3C 91       ADRL            X1, unk_100003F10       ; __src
__text:0000000100003BF8 02 13 80 D2                   MOV             X2, #0x98               ; __n
__text:0000000100003BFC 99 00 00 94                   BL              _memcpy

得到加密后数据(exp里面的a)
然后反向计算输出,得到exp:

a=[0x1FD7,0x21B7,0x1E47,0x2027,0x26E7,0x10D7,0x1127,0x2007,0x11C7,0x1E47,0x1017,0x1017,0x11F7,0x2007,0x1037,0x1107,0x1F17,0x10D7,0x1017,0x1017,0x1F67,0x1017,0x11C7,0x11C7,0x1017,0x1FD7,0x1F17,0x1107,0x0F47,0x1127,0x1037,0x1E47,0x1037,0x1FD7,0x1107,0x1FD7,0x1107,0x2787]

for i in a:
        print(chr((((i-0x1e)^0x4d)-0x14) // 0x50),end='')
flag{67e9a228e45b622c2992fb5174a4f5f5}

androidso_re

getkey:先用rc4解出enc,再异或key
Key: A8UdWaeq
https://gchq.github.io/CyberChef/#recipe=RC4(%7B'option':'UTF8','string':'YourRC4Key'%7D,'UTF8','Latin1')XOR(%7B'option':'Hex','string':'038933b8540c206a'%7D,'Standard',false)&input=VEZTZWNyZXRfS2V5&oeol=FF
Getiv:

a="F2IjBOh1mRW="
for i in range(len(a)):
  if (ord(a[i])>65 and ord(a[i])<90) or (ord(a[i])>97 and ord(a[i])<127):
    if ord(a[i])-65<=0x1a:
        v9=65
        v10=-49
    elif  ord(a[i])-97<0x19:
        v9=97
        v10=-81
    print(chr(v9+(v10+ord(a[i]))%26),end="")
  else:
     print(a[i],end="")
     #V2YzREx1cHM=   IV: Wf3DLups

descbc解密即可
https://gchq.github.io/CyberChef/#recipe=From_Base64('A-Za-z0-9%2B/%3D',true,false)DES_Decrypt(%7B'option':'UTF8','string':'A8UdWaeq'%7D,%7B'option':'UTF8','string':'Wf3DLups'%7D,'CBC','Raw','Raw')&input=SnFzbEhyZHZ0Z0pyUnMyUUFwK0ZFVmR3UlBOTHN3cm55a0Qvc1pNaXZtakdSS1VNVklDL3J3PT0

flag{188cba3a5c0fbb2250b5a2e590c391ce}

rust_baby

exp

#include <iostream>
#include <vector>
#include <Windows.h>

int main()
{
        char* flag = (char*)"flag{abjhgkjagshjfhasjjfhasjkfasffjfhasjkfasfffhasjkfasffgdffgf}EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE";
        char* res = (char*)malloc(0x114);

        unsigned char a[] = {
                0x50,0x81,0x92,0x93,0xB4,0x75,0x16,0xE7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x18,0x41,0xA2,0x7B,0x4C,0x5D,0xC6,0x37, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x20,0x69,0xB2,0xC3,0x84,0x05,0x6E,0x2F, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x78,0x19,0x32,0xF3,0xEC,0x25,0x46,0xC7, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x40,0x49,0x72,0xFB,0xF4,0x15,0xFE,0x9F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0xB0,0xE1,0xF2,0x53,0x14,0x9D,0xC6,0x27, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x50,0x81,0x92,0x93,0xB4,0x75,0x16,0xE7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x18,0x41,0xA2,0x7B,0x4C,0x5D,0xC6,0x37, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x20,0x69,0xB2,0xC3,    0x84,0x05,0x6E,0x2F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x78,0x19,0x32,0xF3,0xEC,0x25,0x46,0xC7, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x40,0x49,0x72,0xFB,0xF4,0x15,0xFE,0x9F, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0xB0,0xE1,0xF2,0x53,0x14,0x9D,0xC6,0x27, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x50,0x81,0x92,0x93,0xB4,0x75,0x16,0xE7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        };

        unsigned char b[] = {0xDC, 0x5F, 0x20, 0x22, 0xC2, 0x79, 0x19, 0x56, 0x35, 0xDA, 0x8B, 0x47, 0xD3, 0x19, 0xFC, 0x55,0x14, 0xCD, 0xD2, 0x7B, 0x58, 0x59, 0x09, 0x42, 0xDE, 0x2C, 0xB4, 0x48, 0xD9, 0xF2, 0x1B, 0xA9,0x40, 0xE1, 0xA6, 0xFB, 0xFF, 0x38, 0xC1, 0xD5, 0xE2, 0xE8, 0x77, 0x78, 0x6F, 0x22, 0x04, 0xE6,0x16, 0x3E, 0x0C, 0x35, 0x52, 0x5C, 0xFD, 0xC1, 0xE5, 0x59, 0x1C, 0xD0, 0xAE, 0x5A, 0xB2, 0xDD,0x19, 0xF8, 0x42, 0xE6, 0x2C, 0x89, 0x59, 0xE5, 0x11, 0x9C, 0xC8, 0x7B, 0x81, 0x70, 0x7F, 0x6F,0xBC, 0x6F, 0x02, 0x8F, 0xF7, 0xF4, 0xC8, 0x70, 0xAE, 0x02, 0xF8, 0x5B, 0xE2, 0x72, 0x08, 0x09,0x6F, 0xBF, 0x4B, 0x39, 0xB5, 0xD0, 0x1E, 0xA3, 0x23, 0xAB, 0x9B, 0x43, 0xB1, 0x15, 0xD7, 0xBE};


/*
        int id = 0;
        for (int k = 0; k < 13*8; k += 8)
        {
                for (int index = 0; index < 4; index++) {
                        DWORD p1 = a[9 + index * 2 + 16 * (k / 8)] ^ a[1 + index * 2 + 16 * (k / 8)];
                        DWORD p2 = a[8 + index * 2 + 16 * (k / 8)] ^ a[0 + index * 2 + 16 * (k / 8)];

                        DWORD col = (p1 | p2) & 0x1;
                        //res[id++] = (char)(flag[0 + index * 2 + 8 * (k / 8)] - col + index) ^ 0x33;
                        //res[id++] = (char)(flag[1 + index * 2 + 8 * (k / 8)] - col + index) ^ 0x33;
                }
        }
        for (int i = 0; i < 0x70; i++)
        {
                res[i] ^= b[i];
        }


*/

        unsigned char c[] = { 0x8a,0x07,0x72,0x76,0x8d,0x7d,0x4d,0x51,0x35,0xde,0x88,0x16,0xd4,0x04,0xf9,0x0e,0x08,0xcf,0xcc,0x7c,0x0f,0x0d,0x09,0x5e,0xd5,0x7e,0xe4,0x4b,0xc4,0xf3,0x1c,0xaf,0x12,0xe4,0xa0,0xae,0xf6,0x69,0xc9,0xd2,0xe0,0xa7,0x01,0x0e,0x1a,0x57,0x70,0x92,0x61,0x49,0x7a,0x43,0x27,0x29,0x89,0xb5,0x92,0x2e,0x6a,0xa6,0xdb,0x2f,0xc6,0xa9,0x6e,0x8f,0x34,0x90,0x59,0xfc,0x2d,0x91,0x66,0xeb,0xbe,0x0d,0xf4,0x05,0x0b,0x1b,0xcb,0x18,0x74,0xf9,0x82,0x81,0xbc,0x04,0xd9,0x75,0x8e,0x2d,0x97,0x07,0x7c,0x7d,0x18,0xc8,0x3d,0x4f,0xc0,0xa5,0x6a,0xd7 };
        for (int i = 0; i < 0x70; i++)
        {
                c[i] ^= b[i];
        }


        int id = 0;
        for (int k = 0; k < 13 * 8; k += 8)
        {
                for (int index = 0; index < 4; index++) {
                        DWORD p1 = a[9 + index * 2 + 16 * (k / 8)] ^ a[1 + index * 2 + 16 * (k / 8)];
                        DWORD p2 = a[8 + index * 2 + 16 * (k / 8)] ^ a[0 + index * 2 + 16 * (k / 8)];

                        DWORD col = (p1 | p2) & 0x1;
                        for (int jj = 0x20; jj <= 0x7f; jj++)
                        {
                                char pp = (char)(jj - col + index) ^ 0x33;
                                if (pp == c[0 + index * 2 + 8 * (k / 8)])
                                {
                                        res[id++] = jj;
                                        break;
                                }
                        }
                        
                        for (int jj = 0x20; jj <= 0x7f; jj++)
                        {
                                char pp = (char)(jj - col + index) ^ 0x33;
                                if (pp == c[1 + index * 2 + 8 * (k / 8)])
                                {
                                        res[id++] = jj;
                                        break;
                                }
                        }


                }
        }

        std::cout << res;

        return 0;
}
flag{6e2480b3-4f02-4cf1-9bc0-123b75f9a922}

longlongcall

首先挂在linux下,IDA远程调试运行,发现Hack,退出了,搜字符串,找到后找x-ref
2024-05-30T11:25:11.png
发现有两处,如下两张图,分别把这两处的上一层函数跳转改成强制的(不走hacker,过反调试)
2024-05-30T11:25:21.png
2024-05-30T11:25:31.png

然后重新打开运行发现就正常了,然后跟着走,来到了scanf("%44s", xx),意味着flag长度是44,接着走,发现他两个两个读取你输入的flag,然后加起来的值再分别和他俩进行异或以加密flag,最后再对flag进行对比,拿到里面加密后的flag后,可以通过暴力枚举来写出exp

ans = bytes.fromhex("BBBFB9BEC3CCCEDC9E8F9D9BA78CD795B0ADBDB488AF92D0CFA1A392B7B4C99E94A7AEF0A199C0E3B4B4BFE3")

def decrypt(a, b):
    for i in range(0, 256):
        for j in range(0, 256):
            if a == (i+j)^i and b == (i+j)^j:
                return (i, j)

for i in range(0, len(ans), 2):
    b1, b2 = decrypt(ans[i],ans[i+1])
    print(chr(b1)+chr(b2),end='')
# miniLCTF{just_s1mple_x0r_1n_lon9_l0ng_c@ll!}

补一个之后用z3写的wp

from z3 import *
ans = bytes.fromhex("BBBFB9BEC3CCCEDC9E8F9D9BA78CD795B0ADBDB488AF92D0CFA1A392B7B4C99E94A7AEF0A199C0E3B4B4BFE3")

sol = Solver()

v = [BitVec(f"v{i}", 8) for i in range(44)]
for i in range(0, 44, 2):
    p = (v[i] + v[i+1])
    v[i] = v[i] ^ p
    v[i+1] = v[i+1] ^ p

for i in range(44):
    sol.add(v[i] == ans[i])

print(sol.check())

m = sol.model()
result = [0] * 44

for p in m.decls():
    # print(chr(m[v[i]].as_long()),end='')
    result[int((p.name()[1:]))] = m[p].as_long()

print(list(map(chr, result)))
print("".join(map(chr, result)))

Bigbanana

从switch表中找到,F2 00 00 00这个后面跟着的,
2024-05-30T11:26:35.png
稍微整理一下
2024-05-30T11:26:45.png
再稍微分析一下

F4, 01 立即数加法
F3 两个异或
F2 checkAnswer 后面跟着应该的结果
FE 看是否播放banana
F0 好像是把上一次结果保存
10 getchar并推上栈

F8 push 栈[0]字符
F7 push 栈[1]字符


10 00 00 00 10 00 00 00 F8 00 00 00 F7 00 00 00
F4 00 00 00 4D 69 4E 69  栈[1]字符 ('m')
01 00 00 00 4C 2D 63 74  栈[0]字符 ('i')
F4 00 00 00 00 00 00 00
F3 00 00 00 栈上xor   000000001D2D440F

F2 00 00 00 0F 44 2D 1D  check Answer
FE 00 00 00 66 00 00 00 banana

F0 00 00 00   ' 拿 栈[0]字符 ('i') + 结果 74632DB5

这里f8是74632DB5
10 00 00 00 F8 00 00 00 F4 00 00 00 16 00 00 00
01 00 00 00 21 00 00 00  ' 结果 000000000000008F


F4 00 00 00 14 45 11 00

F3 00 00 00
F2 00 00 00 50 72 74 74 FE 00 00 00
...

exp

flag = "mi"

f4List = [0x16,0x00114514,   
0x21,0x00228A28,   
0x2C,0x0033CF3C,   
0x0B,0x00451450,   
0x16,0x00565964,   
0x21,0x00679E78,   
0x2C,0x0078E38C,   
0x0B,0x008A28A0,   
0x16,0x009B6DB4,   
0x21,0x00ACB2C8,   
0x2C,0x00BDF7DC,   
0x0B,0x00CF3CF0,   
0x16,0x00E08204,   
0x21,0x00F1C718,   
0x2C,0x01030C2C,   
0x0B,0x01145140,   
0x16,0x01259654,   
0x21,0x0136DB68,   
0x2C,0x0148207C,   
0x0B,0x01596590,   
0x16,0x016AAAA4,   
0x21,0x017BEFB8,   
0x2C,0x018D34CC,   
0x0B,0x019E79E0,   
0x16,0x01AFBEF4,   
0x21,0x01C10408,   
0x2C,0x01D2491C,   
0x0B,0x01E38E30,   
0x16,0x01F4D344,   
0x21,0x02061858,   
0x2C,0x02175D6C,   
0x0B,0x0228A280,   
0x16,0x0239E794,   
0x21,0x024B2CA8,   
0x2C,0x025C71BC,   
0x0B,0x026DB6D0,   
0x16,0x027EFBE4,   
0x21,0x029040F8,   
0x2C,0x02A1860C,   
0x0B,0x02B2CB20,   
0x16,0x02C41034,   
0x21,0x02D55548,   
0x2C,0x02E69A5C]

f01List = [0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B]

ans = [0x74747250,0x00228A4D,0x0033CFAA,0x004514CB,0x00565966,0x00679FBC,0x0078E4CC,0x008A2949,0x009B6EC8,0x00ACB3E0,0x00BDF8F6,0x00CF3D22,0x00E082EB,0x00F1C745,0x01030C9C,0x0114518E,0x01259634,0x0136DC9C,0x0148217D,0x015965AE,0x016AABB8,0x017BF02F,0x018D352A,0x019E7AE7,0x01AFBF19,0x01C1043C,0x01D249A4,0x01E38E3E,0x01F4D3B0,0x02061853,0x02175E76,0x0228A241,0x0239E866,0x024B2D81,0x025C72F0,0x026DB738,0x027EFCFC,0x029041F1,0x02A186E7,0x02B2CBE3,0x02C4105D,0x02D55595,0x02E69A7B]

# 第一轮(因为读取了两个字符所以不好处理,直接拿第一轮结果)
ll = 0x74632DB5
for i in range(43):
        succeed = False
        for c in range(0,256):
                aa = c
                temp_ll = ll + f4List[i*2] + f4List[i*2+1]
                aa += f01List[i]
                if aa ^ temp_ll == ans[i]:
                        ll = aa
                        flag += chr(c)
                        print(i,flag)
                        succeed  = True
                        break
        if not succeed:
                print("err",i)
                
                
        
'''
0 min
1 mini
2 miniL
3 miniLc
4 miniLct
5 miniLctf
6 miniLctf{
7 miniLctf{b
8 miniLctf{bi
9 miniLctf{big
10 miniLctf{bigb
11 miniLctf{bigb4
12 miniLctf{bigb4n
13 miniLctf{bigb4na
14 miniLctf{bigb4nan
15 miniLctf{bigb4nan4
16 miniLctf{bigb4nan4_
17 miniLctf{bigb4nan4_i
18 miniLctf{bigb4nan4_i5
19 miniLctf{bigb4nan4_i5_
20 miniLctf{bigb4nan4_i5_v
21 miniLctf{bigb4nan4_i5_v3
22 miniLctf{bigb4nan4_i5_v3r
23 miniLctf{bigb4nan4_i5_v3ry
24 miniLctf{bigb4nan4_i5_v3ry_
25 miniLctf{bigb4nan4_i5_v3ry_i
26 miniLctf{bigb4nan4_i5_v3ry_in
27 miniLctf{bigb4nan4_i5_v3ry_int
28 miniLctf{bigb4nan4_i5_v3ry_int3
29 miniLctf{bigb4nan4_i5_v3ry_int3r
30 miniLctf{bigb4nan4_i5_v3ry_int3r5
31 miniLctf{bigb4nan4_i5_v3ry_int3r5t
32 miniLctf{bigb4nan4_i5_v3ry_int3r5t1
33 miniLctf{bigb4nan4_i5_v3ry_int3r5t1n
34 miniLctf{bigb4nan4_i5_v3ry_int3r5t1ng
35 miniLctf{bigb4nan4_i5_v3ry_int3r5t1ng_
36 miniLctf{bigb4nan4_i5_v3ry_int3r5t1ng_r
37 miniLctf{bigb4nan4_i5_v3ry_int3r5t1ng_r1
38 miniLctf{bigb4nan4_i5_v3ry_int3r5t1ng_r1g
39 miniLctf{bigb4nan4_i5_v3ry_int3r5t1ng_r1gh
40 miniLctf{bigb4nan4_i5_v3ry_int3r5t1ng_r1ght
41 miniLctf{bigb4nan4_i5_v3ry_int3r5t1ng_r1ght?
42 miniLctf{bigb4nan4_i5_v3ry_int3r5t1ng_r1ght?}

补一个之后用z3写的exp

from z3 import *
flag = "mi"


f4List = [0x16,0x00114514,   
0x21,0x00228A28,   
0x2C,0x0033CF3C,   
0x0B,0x00451450,   
0x16,0x00565964,   
0x21,0x00679E78,   
0x2C,0x0078E38C,   
0x0B,0x008A28A0,   
0x16,0x009B6DB4,   
0x21,0x00ACB2C8,   
0x2C,0x00BDF7DC,   
0x0B,0x00CF3CF0,   
0x16,0x00E08204,   
0x21,0x00F1C718,   
0x2C,0x01030C2C,   
0x0B,0x01145140,   
0x16,0x01259654,   
0x21,0x0136DB68,   
0x2C,0x0148207C,   
0x0B,0x01596590,   
0x16,0x016AAAA4,   
0x21,0x017BEFB8,   
0x2C,0x018D34CC,   
0x0B,0x019E79E0,   
0x16,0x01AFBEF4,   
0x21,0x01C10408,   
0x2C,0x01D2491C,   
0x0B,0x01E38E30,   
0x16,0x01F4D344,   
0x21,0x02061858,   
0x2C,0x02175D6C,   
0x0B,0x0228A280,   
0x16,0x0239E794,   
0x21,0x024B2CA8,   
0x2C,0x025C71BC,   
0x0B,0x026DB6D0,   
0x16,0x027EFBE4,   
0x21,0x029040F8,   
0x2C,0x02A1860C,   
0x0B,0x02B2CB20,   
0x16,0x02C41034,   
0x21,0x02D55548,   
0x2C,0x02E69A5C]

f01List = [0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B,0x16,0x21,0x2C,0x0B]

ans = [0x74747250,0x00228A4D,0x0033CFAA,0x004514CB,0x00565966,0x00679FBC,0x0078E4CC,0x008A2949,0x009B6EC8,0x00ACB3E0,0x00BDF8F6,0x00CF3D22,0x00E082EB,0x00F1C745,0x01030C9C,0x0114518E,0x01259634,0x0136DC9C,0x0148217D,0x015965AE,0x016AABB8,0x017BF02F,0x018D352A,0x019E7AE7,0x01AFBF19,0x01C1043C,0x01D249A4,0x01E38E3E,0x01F4D3B0,0x02061853,0x02175E76,0x0228A241,0x0239E866,0x024B2D81,0x025C72F0,0x026DB738,0x027EFCFC,0x029041F1,0x02A186E7,0x02B2CBE3,0x02C4105D,0x02D55595,0x02E69A7B]

# 第一轮(因为读取了两个字符所以不好处理,直接拿第一轮结果)


s = Solver()

x = [BitVec(f"x{i}", 8) for i in range(43)]
ll = 0x74632DB5
for i in range(43):
        succeed = False
        aa = x[i]
        temp_ll = ll + f4List[i*2] + f4List[i*2+1]
        aa += f01List[i]
        s.add(aa ^ temp_ll == ans[i])
        ll = aa
        s.add(x[i] < 0x7f)
        s.add(x[i] > 0x20)

print(s.check())
m = s.model()

result = [0] * 43
for p in m:    
    result[int(p.name()[1:])] = m[p].as_long()

print("".join(map(chr, result)))

RustedRobot

首先拿到app,把libmyrust.so分离出来,拖入IDA64进行分析(我还用了IDARust Demangler插件来辅助我进行分析)。
首先花了一下午给Xiaomi13刷了KernelSU,学了个Frida,如下是Hook的Frida脚本,有时候正常hook,有时候hook不上(目前不知道什么原因)

function main(){
    Java.perform(function() {
    var CryptoClass = Java.use("com.doctor3.androidrusttest.CryptoClass");
        console.log("Hooked CryptoClass\n");
    CryptoClass.encrypt.overload('[Ljava.lang.String;').implementation = function(strArr) {
        var str = strArr[0];
        var str2 = strArr[1];
        console.log("Encrypt method called with parameters: " + str + ", " + str2 + "\n");
        // 调用原始方法
        this.encrypt(strArr);
    };
});
}
setImmediate(main);

随后IDA里面分析so,发现它创建了一个数组(长度2), 也就是对应Java代码里面的函数参数,然后在函数末尾进行了Java static method invoke。

    public static void encrypt(String[] strArr) {
        int i = 0;
        String str = strArr[0];
        String str2 = strArr[1];
        byte[] bArr = {49, -93, 51, -59, 24, -5, -59, 60, -45, -32, -55, -54, -89, 67, 42, -94, 47, 110, 72, 13, 31, 55, 55, 34, 127, 65, -120, 13, -109, -92, -71, -97};
        byte[] bytes = str.getBytes();
        byte[] bytes2 = str2.getBytes();
        SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, AES);
        try {
            Cipher cipher = Cipher.getInstance(AES);
            cipher.init(1, secretKeySpec);
            byte[] doFinal = cipher.doFinal(bytes2);
            while (i < 32) {
                i = (bArr[i] == doFinal[i] && doFinal.length == 32) ? i + 1 : 0;
                Toast.makeText(context, "Wrong", 1).show();
                return;
            }
            Toast.makeText(context, "Right", 1).show();
        } catch (Exception e) {
            Toast.makeText(context, "Wrong", 1).show();
            e.printStackTrace();
        }
    }

然后,使用Frida进行Hook,我们得知,第一个元素恒为定值,而第二个元素随着输入而改变(我不断更改miniLCTF{xxx}中的值,发现当miniLCTF{3}的时候,第二个字符是2,而且继续更改也之影响第二个字符,我就猜想这个字符很可能被反转了)。随后在CyberChef,先解密掉bArr,获取被混淆后的真正flag,我们得到

Encrypt method called with parameters: btdfA2jeeljf.1bp, ~2|GUDMjojn
flag,但是混淆后
~u1c1s`E4UTVS|GUDMjojn

随后就进行字符串反转,而后减一得到flag
于是我们可以得到exp:
https://gchq.github.io/CyberChef/#recipe=AES_Decrypt(%7B'option':'UTF8','string':'btdfA2jeeljf.1bp'%7D,%7B'option':'Hex','string':''%7D,'ECB/NoPadding','Hex','Raw',%7B'option':'Hex','string':''%7D,%7B'option':'Hex','string':''%7D)Remove_whitespace(true,true,true,true,true,false)Reverse('Character')ADD(%7B'option':'Decimal','string':'-1'%7D)&input=MzEgYTMgMzMgYzUgMTggZmIgYzUgM2MgZDMgZTAgYzkgY2EgYTcgNDMgMmEgYTIgMmYgNmUgNDggMGQgMWYgMzcgMzcgMjIgN2YgNDEgODggMGQgOTMgYTQgYjkgOWY&oeol=NEL

OLLessVM

00007FF6C5E8A4 | 8D041F            | lea eax,qword ptr ds:[rdi+rbx]                                |
00007FF6C5E8A4 | 48:63D3           | movsxd rdx,ebx                                                |
00007FF6C5E8A4 | 48:63C8           | movsxd rcx,eax                                                |
00007FF6C5E8A4 | 8A440D F7         | mov al,byte ptr ss:[rbp+rcx-9]                                |
00007FF6C5E8A4 | 320432            | xor al,byte ptr ds:[rdx+rsi]                                  |
00007FF6C5E8A4 | 32C3              | xor al,bl                                                     |
00007FF6C5E8A4 | 42:880432         | mov byte ptr ds:[rdx+r14],al                                  |

x64dbg打开,找到,输入一个东西下硬件断点找到关键点,拿到xor表,main函数的ans表和bl(counter)即可写出wp:

xort = bytes.fromhex("9199417B79814BCBA9EC2E02CB94E526910BA60F2881A160D1525FC47AAD4FFFE299D57A286EC037F570E6460707A2F54B393A97328EB0E7BBE8C7D2B7087B628ED06FBF369F0000A0F4A55946024602")


ans = bytes.fromhex("FCF12D1131C7198ADABC147C98EADB65F729D04348FC8428F92923AC59CD51E0C2B8F7590C4BE610DD59CC6D2B2A8CDA7B0808A406BB86D083D1FDE98B35455D514CD172F6B8E69EE2B72D7525712B4B864587A1C947C55A165E1AD1179D186E3FD275E9E35156C206046D1A50657DFDA912")

for i in range(len(ans)):
    print(chr(i^xort[i]^ans[i]),end='')
# miniLCTF{Y0u_s0Lv3d_th3_0bfs?}

OLLessVM_RENVEGE

发现使用ReadConsoleW来读取控制台

BOOL WINAPI ReadConsole(__in HANDLE hConsoleInput,__out LPVOID lpBuffer,__in DWORD nNumberOfCharsToRead,__out LPDWORD lpNumberOfCharsRead,__in_opt LPVOID pInputControl);
#include <iostream>
#include <vector>
#include <Windows.h>

uint8_t x0r[16] = { 0x1,0x2,0x4,0x8,0x10,0x20,0x40,0x80,0xff,0xfe,0xfc,0xf8,0xf0,0xe0,0xc0,0x80 };
int main()
{
        const char* f = "miniLCTF{1141414141414111111111}";
        char* flag = (char*)malloc(33);
        memcpy_s(flag, 33, f, strlen(f));
        if (!flag)
        {
                return -1;
        }

        for (size_t i = 0; i < 32; i++)
        {
                flag[i] ^= x0r[i % 16];
        }

        auto inn = (DWORD*)flag;
        auto xnn = (DWORD*)x0r;
        
        std::cout << std::hex;
        DWORD offset = 0xEEB7B2B6;
        DWORD keyOffset = 0xBADECADA;
        DWORD sum = 0x2EB7B2B6;
        uint32_t key = 0xBADECADA;
        for (int k = 0; k < 12; k++)
        {
                for (int i = 0; i < 8; i++)
                {
                        DWORD e1 = ((inn[(7 + i) % 8] >> 5) ^ (inn[(1 + i) % 8] << 2));
                        DWORD e2 = ((inn[(1+ i) % 8] >> 3) ^ (inn[(7 + i) % 8] << 4));
                        //cout << e1 << endl << e2 << endl;
                        /*
                        0x1fbe03fe
                        0xc7df907b
                        */
                        DWORD p = e1 - ~e2 - 1;
                        //std::cout << "sum=0x" << sum << std::endl;
                        DWORD e3 = xnn[(sum ^ i) & 3] ^ inn[(7 + i) % 8];
                        DWORD p2 = inn[(1 + i) % 8] ^ key;
                        //cout << p << endl << e3 << endl << p2 << endl;

                        DWORD p3 = p2 - ~e3 - 1;
                        DWORD e4 = p3 ^ p;
                        //cout << p3 << endl << e4 << endl;
                        DWORD p4 = inn[(i) % 8] - ~e4 - 1;
                        //std::cout << "i=" << i << "|" << p4 << std::endl; // p4 is ANS
                        inn[i] = p4;
                        //std::cout << "-------------\n";
                }
                //std::cout << "k=" << k << "----------------------------------------------------\n";

                key += keyOffset;
                sum = sum + offset + !(k % 2);
        }
        char* result = (char*)malloc(33);
        if (!result) return -1;
        for (size_t i = 0; i < 32; i++)
        {
                result[31 - i] = flag[i] ^ x0r[(31 - i) % 16] ^ 0x18;
        }
        if (*(DWORD*)result != 0xc293a546)
        {
                std::cout << "errrr";
        }
        char ans[] = {0x4b ,0xa0 ,0x0c ,0xff ,0xab ,0x0a ,0x13 ,0xb0,0x32 ,0x91 ,0x6d ,0x87 ,0x8b ,0xab ,0xf5 ,0xa5,0xdc ,0x77 ,0xd4 ,0x95 ,0xb9 ,0x02 ,0xa6 ,0xac,0xe4 ,0x74 ,0x2c ,0x6b ,0xeb ,0xe1 ,0x5e ,0x25};
        return 0;
}

注意到(sum ^ i) & 3,立马想到了TEA加密算法,再看一下这个结构,完全不像??
不过e1, e2好像key中的l和r,所以想办法得搞成TEA,也方便写解密脚本

注意到程序中p2 - ~e3 - 1这种模式,加密过程中出现了三次,所以想办法看看能不能化简。
这种模式立即让我想到了VMProtect 3.5,虚拟化后的加法过程,随即我翻阅了去年组会中我写的blog:
https://组织内部神秘博客地址/d/955-redi-er-ci-zu-hui-idapythonji-chu-appcallshi-yong/9

2024-05-30T11:29:06.png

可以注意到,化简后是

p2 - ~e3 - 1  <====>  p2 + e3

这样就可以化简整个加密函数

uint8_t lkey[16] = { 0x1,0x2,0x4,0x8,0x10,0x20,0x40,0x80,0xff,0xfe,0xfc,0xf8,0xf0,0xe0,0xc0,0x80 };
void encryption(DWORD* inn)
{
        DWORD offset_ = 0xBADECADA;
        DWORD key = 0x2EB7B2B6;
        uint32_t sum_ = 0xBADECADA;
        auto xnn = (DWORD*)lkey;
        for (int k = 0; k < 12; k++)
        {
                for (int i = 0; i < 8; i++)
                {
                        DWORD l = ((inn[(7 + i) % 8] >> 5) ^ (inn[(1 + i) % 8] << 2));
                        DWORD r = ((inn[(1 + i) % 8] >> 3) ^ (inn[(7 + i) % 8] << 4));
                        DWORD p4 = inn[(i) % 8] + (((inn[(1 + i) % 8] ^ sum_) + (xnn[(key ^ i) & 3] ^ inn[(7 + i) % 8])) ^ (l + r));
                        inn[i] = p4;
                }
                sum_ += offset_;
                key += 0xEEB7B2B6 + !(k % 2);
        }
}

xnn是lkey, lkey是xor(key)表,至此我们需要写解密函数,我的处理方法是把key和sum先算12次,得到最后结果,然后反着来用,按照TEA的方法来逆向。

void decryption(DWORD* inn)
{
        DWORD offset_ = 0xBADECADA;
        DWORD key = 0x2EB7B2B6;
        uint32_t sum_ = 0xBADECADA;
        auto xnn = (DWORD*)lkey;

        for (int k = 0; k < 12; k++)
        {
                sum_ += offset_;
                key += 0xEEB7B2B6 + !(k % 2);
        }

        for (int k = 11; k >= 0; k--)
        {
                sum_ -= offset_;
                key -= 0xEEB7B2B6 + !(k % 2);
                for (int i = 7; i >= 0; i--)
                {
                        DWORD l = ((inn[(7 + i) % 8] >> 5) ^ (inn[(1 + i) % 8] << 2));
                        DWORD r = ((inn[(1 + i) % 8] >> 3) ^ (inn[(7 + i) % 8] << 4));
                        inn[i] -= (((inn[(1 + i) % 8] ^ sum_) + (xnn[(key ^ i) & 3] ^ inn[(7 + i) % 8])) ^ (l + r));
                }
        }
}

至此,给出完整exp:

#include <iostream>
#include <vector>
#include <Windows.h>

uint8_t lkey[16] = { 0x1,0x2,0x4,0x8,0x10,0x20,0x40,0x80,0xff,0xfe,0xfc,0xf8,0xf0,0xe0,0xc0,0x80 };

void encryption(DWORD* inn)
{
        DWORD offset_ = 0xBADECADA;
        DWORD key = 0x2EB7B2B6;
        uint32_t sum_ = 0xBADECADA;
        auto xnn = (DWORD*)lkey;


        for (int k = 0; k < 12; k++)
        {
                for (int i = 0; i < 8; i++)
                {
                        DWORD l = ((inn[(7 + i) % 8] >> 5) ^ (inn[(1 + i) % 8] << 2));
                        DWORD r = ((inn[(1 + i) % 8] >> 3) ^ (inn[(7 + i) % 8] << 4));
                        DWORD p4 = inn[(i) % 8] + (((inn[(1 + i) % 8] ^ sum_) + (xnn[(key ^ i) & 3] ^ inn[(7 + i) % 8])) ^ (l + r));
                        inn[i] = p4;
                }
                sum_ += offset_;
                key += 0xEEB7B2B6 + !(k % 2);
        }
}

void decryption(DWORD* inn)
{
        DWORD offset_ = 0xBADECADA;
        DWORD key = 0x2EB7B2B6;
        uint32_t sum_ = 0xBADECADA;
        auto xnn = (DWORD*)lkey;

        for (int k = 0; k < 12; k++)
        {
                sum_ += offset_;
                key += 0xEEB7B2B6 + !(k % 2);
        }

        for (int k = 11; k >= 0; k--)
        {
                sum_ -= offset_;
                key -= 0xEEB7B2B6 + !(k % 2);
                for (int i = 7; i >= 0; i--)
                {
                        DWORD l = ((inn[(7 + i) % 8] >> 5) ^ (inn[(1 + i) % 8] << 2));
                        DWORD r = ((inn[(1 + i) % 8] >> 3) ^ (inn[(7 + i) % 8] << 4));
                        inn[i] -= (((inn[(1 + i) % 8] ^ sum_) + (xnn[(key ^ i) & 3] ^ inn[(7 + i) % 8])) ^ (l + r));
                }
        }
}

int main()
{
        char ans[] = { 0x4b ,0xa0 ,0x0c ,0xff ,0xab ,0x0a ,0x13 ,0xb0,0x32 ,0x91 ,0x6d ,0x87 ,0x8b ,0xab ,0xf5 ,0xa5,0xdc ,0x77 ,0xd4 ,0x95 ,0xb9 ,0x02 ,0xa6 ,0xac,0xe4 ,0x74 ,0x2c ,0x6b ,0xeb ,0xe1 ,0x5e ,0x25 };
        char* result = (char*)malloc(33);
        if (!result) return -1;
        result[32] = 0;
        for (size_t i = 0; i < 32; i++)
        {
                result[i] = ans[31 - i] ^ lkey[(31 - i) % 16] ^ 0x18;
        }
        decryption((DWORD*)result);
        for (size_t i = 0; i < 32; i++)
        {
                result[i] ^= lkey[i % 16];
        }
        std::cout << result;

        return 0;
}

MiniLCTF 2024

真正意义的打了5天的CTF,时间长,事件多,题目有趣。
0x Team
rank list
最后也是真不容易地拿到了总分第一,认识了许多大佬,队友也都很猛,废寝忘食地capture flag,最后一天也是Web手连着K掉了两个难题,挽救了低分(Web差一道就AK了)。
第一次AK所有逆向题目(可能只是因为比赛时间较长吧,我们没有专门的Misc手,我也打了打Misc)
第一次AK所有Misc题目(队友是神,0基础CTF就能AK)

还是得吐槽一下区块链方向,最后一天上了个题让我们人心惶惶,因为大家都没做出来,但是最后也没人解出来,好像是容器有一些问题,或者只是题目比较难吧(

以后的打算:主攻Reverse,学一学Pwn,顺便当个小小运维,其他题和队友齐力解决。

我们队伍(0x)的Writeups(2024/5/9开放围观!):
飞书WP链接

RandomVM

ptrace反调试,第一个绕,第二个绕​
把算法抄出来,暴力​
载入linux,通过ida64远程调试,这个程序开始srand了一下,确定了随机数,然后后面vmhandler全部都是用rand来决定控制流的,我还试了下,好像win下同seed产生的rand还不一样。。​
第二个函数是vm了,进去后每个函数都有一个功能块和一个跳转块
2024-05-30T10:57:34.png
只看功能块,跳转快不看,很快就能看到有syscall,通过查表,0x65是ptrace,刚开始没发现这个可能和反调试有关系,导致第三块数据一直都是0xff,搞了3个小时都不知道哪里错了,在暴力的时候甚至无解。。最后重新细心F8了一遍VM才发现这茬,整个过程好像有3次ptrace,经过测试,第一次把返回值从0xff改成0x0,过反调试,第二三次不要管,如果改会导致程序崩溃(好像是检测?)然后再把((val >> bits) | (val << (8 - bits) )) & 0xff这个函数抄出来,慢慢还原代码即可。
2024-05-30T10:58:09.png
通过逆向工程把生成加密文本的过程用python模拟出来,然后暴力破解​
Exp

ans = [0x9D,0x6B,0xA1,0x02,0xD7,0xED,0x40,0xF6,0x0E,0xAE,0x84,0x19]​
import string​
​
def fun(val, bits):​
        if bits >= 8:​
                return 0​
        return ((val >> bits)  | (val << (8 - bits) )) & 0xff​
​
def getAns(input):​
        context = [0 for i in range(13)]​
        MAGIC = [3, 5, 6, 7, 4, 4, 7, 7, 2, 4, 4, 7]​
        for i in range(12):​
                context[i] ^= input[i]​
                context[i+1] = input[i]​
                #print(i,[hex(j) for j in context ])​
                context[i+1] = fun(context[i+1], MAGIC[i])​
                if i != 1 and i != 2 and i != 5 and i != 7 and i != 8 and i != 9 and i != 10:​
                        context[i+1] ^= MAGIC[i]​
                #print(i,[hex(j) for j in context ])​
​
        for i in range(1, 12):​
                context[i+1] ^= context[i]​
        return context[1:]​
​
a = getAns(("m3owJumpVm1111111111111").encode())​
print([hex(j) for j in a])​
​
ab = 'abcdefghijklmnopqrstuvwxyz0123456789'​
​
for i in range(0,256):​
        a = getAns(("m3owJumpVmvm"+chr(i)+ "11111111111111").encode())​
        if a[9]==ans[9]:​
                print([hex(eee) for eee in a])​
                print(i)​
​
for i in ab:​
        for j in ab:​
                for k in ab:​
                        for w in ab:​
                                a = getAns(("m3ow"+i+j+k+w+ "111111111111").encode())​
                                if a[0]==ans[0] and a[1]==ans[1] and a[2] == ans[2] and a[3]==ans[3]:​
                                        print([hex(eee) for eee in a])​
                                        print(i+j+k+w)​

注意,最底下的是开始手动爆破的,因为发现前三个字符会影响结果list的前几个,而不会扩散到后面,所以直接这么写了,然后找到m3ow后,看倒数第二个,从ascii 0-256手动遍历,然后就出来了。
补一下z3的exp

from z3 import *
ans = [0x9D,0x6B,0xA1,0x02,0xD7,0xED,0x40,0xF6,0x0E,0xAE,0x84,0x19]

sol = Solver()

def fun(v, b):
    r = ((v >> b) | v << (8 - b)) & 0xff
    print(r)
    return r

v = [BitVec(f"v{i}", 8) for i in range(12)]

MAGIC = [3,5,6,7,4,4,7,7,2,4,4,7]

for i in range(12):
    v[i] = fun(v[i], MAGIC[i])
    if not i in [1,2,5,7,8,9,10]:
        v[i] ^= MAGIC[i]
    if i != 11:
        v[i] ^= v[i+1]

for i in range(11):
    v[i+1] ^= v[i]

for i in range(12):
    sol.add(v[i] == ans[i])


print(sol.check())

m = sol.model()
result = [0] * 12

for p in m.decls():
    # print(chr(m[v[i]].as_long()),end='')
    result[int((p.name()[1:]))] = m[p].as_long()

print(list(map(chr, result)))
print("".join(map(chr, result)))
# m3owJumpVmvM

ezJunk

如下是Exp,table是过了反调试的Table,如果没有反调试,程序会在TLS中修改为错误的表,从而无法正确解密。​
sub_401917是魔改TEA的加密函数,sub_401917_d是魔改TEA的解密函数。​
hashxx是在fakeflag之后的验证

0000000000401786 | C745 A0 A9B3DDB6       | mov dword ptr ss:[rbp-60],B6DDB3A9         | !​
000000000040178D | C745 A4 232C1636       | mov dword ptr ss:[rbp-5C],36162C23         |​
0000000000401794 | C745 A8 BFFA8918       | mov dword ptr ss:[rbp-58],1889FABF         |​
000000000040179B | C745 AC 3BE7E46C       | mov dword ptr ss:[rbp-54],6CE4E73B         |​
00000000004017A2 | C745 B0 FCF85A0A       | mov dword ptr ss:[rbp-50],A5AF8FC          |​
00000000004017A9 | C745 B4 1584FF21       | mov dword ptr ss:[rbp-4C],21FF8415         |​
00000000004017B0 | C745 B8 57958544       | mov dword ptr ss:[rbp-48],44859557         |​
00000000004017B7 | C745 BC B727C22D       | mov dword ptr ss:[rbp-44],2DC227B7         |​
00000000004017BE | C745 EC 00000000       | mov dword ptr ss:[rbp-14],0                |​
​
如上,是hash后的对应表,如下程序通过暴力破解了hash。
#include <iostream>​
#include <vector>​
#include <Windows.h>​
using namespace std;​
​
​
unsigned int table[] = { 21588,17922,17527,24158,51,67,84,70,68,94,51,67,84,70,68,94,51,67,84,70,68,94,51,67,84,70,68,94,51,67,84,70,68,94,51,67,84,70,68,94,51,67,84,70,68,94,51,67,84,70,68,94,51,67,84,70,68,94,51,67,84,70,68,94,51,67,84,70,68,94,51,67,84,70,68,94,51,67,84,70,68,94,51,67,84,70,68,94,51,67,84,70,68,94,51,67,84,70,68,94,51,67,84,70 };​
//unsigned int table[] = {21520, 17990, 17476, 24173, 51, 67, 84, 70, 68, 94, 51, 67, 84, 70, 68, 94, 51, 67, 84, 70, 68, 94, 51, 67, 84, 70, 68, 94, 51, 67, 84, 70, 68, 94, 51, 67, 84, 70, 68, 94, 51, 67, 84, 70, 68, 94, 51, 67, 84, 70, 68, 94, 51, 67, 84, 70, 68, 94, 51, 67, 84, 70, 68, 94, 51, 67, 84, 70, 68, 94, 51, 67, 84, 70, 68, 94, 51, 67, 84, 70, 68, 94, 51, 67, 84, 70, 68, 94, 51, 67, 84, 70, 68, 94, 51, 67, 84, 70, 68, 94, 51, 67, 84, 70};​
void __fastcall sub_401917(unsigned int* src, unsigned int sum, int offset)​
{​
        unsigned int v6;​
        unsigned int v7;​
        v7 = *src;​
        v6 = src[1];​
        for (int i = 0; i <= 31; ++i)​
        {​
                v7 += (v6 + ((16 * v6) ^ (v6 >> 5))) ^ (table[sum & 3] + sum) ^ 0x44;​
                v6 += (v7 + ((32 * v7) ^ (v7 >> 6))) ^ (table[(sum >> 11) & 3] + sum) ^ 0x33;​
                sum -= offset;​
        }​
        src[0] = v7;​
        src[1] = v6;​
}​
​
void __fastcall sub_401917_d(unsigned int* src, unsigned int sum, int offset)​
{​
        unsigned int v6;​
        unsigned int v7;​
        v7 = *src;​
        v6 = src[1];​
        sum -= 32 * offset;​
        for (int i = 0; i <= 31; ++i)​
        {​
                sum += offset;​
                v6 -= (v7 + ((32 * v7) ^ (v7 >> 6))) ^ (table[(sum >> 11) & 3] + sum) ^ 0x33;​
                v7 -= (v6 + ((16 * v6) ^ (v6 >> 5))) ^ (table[sum & 3] + sum) ^ 0x44;​
        }​
        src[0] = v7;​
        src[1] = v6;​
}​
​
int hashxx(int src)​
{​
        for (int i = 0; i < 32; i++)​
        {​
                if (src < 0)​
                {​
                        src *= 2;​
                        src ^= 0x84A6972F;​
                }​
                else​
                {​
                        src *= 2; ​
                }​
        }​
​
        return src;​
}​
​
int main()​
{​
        // 0xDD243DAB 0x898587D8​
        //cout << hashxx() << endl;​
​
​
        DWORD flag[] = {​
                0xB6DDB3A9, 0x36162C23, 0x1889FABF, 0x6CE4E73B, 0x0A5AF8FC, 0x21FF8415, 0x44859557, 0x2DC227B7​
        };​
/*​
​
​
        for (size_t i = 0; i <= 0xffffffff; i++)​
        {​
                if (hashxx((int)i) == 0x0A5AF8FC)​
                {​
                        cout << 3 << "|" << hex << i << endl;​
                        break;​
                }​
        }​
​
        for (size_t i = 0; i <= 0xffffffff; i++)​
        {​
                if (hashxx((int)i) == 0x21FF8415)​
                {​
                        cout << 4<< "|" << hex << i << endl;​
                        break;​
                }​
        }​
​
        for (size_t i = 0; i <= 0xffffffff; i++)​
        {​
                if (hashxx((int)i) == 0x44859557)​
                {​
                        cout <<5 << "|" << hex << i << endl;​
                        break;​
                }​
        }​
​
        for (size_t i = 0; i <= 0xffffffff; i++)​
        {​
                if (hashxx((int)i) == 0x2DC227B7)​
                {​
                        cout << 6 << "|" << hex << i << endl;​
                        break;​
                }​
        }​
​
​
*/​
        DWORD ffff[] =​
        {​
                0xDD243DAB,0x898587D8, 0x2fdf1d,0xbc4aca2, 0x4d68a16a, 0x616c5d29, 0x5b911af5, 0x4c7710dc​
        };​
        //char flag[] = { 0xB1,0xCB,0x06,0x54,0xA2,0x1E,0xA4,0xA4,0xC5,0x9A,0x48,0x34,0x97,0x87,0xD6,0x53,0x6F,0xC0,0xE0,0xB8,0xDB,0xF2,0x59,0x02,0x82,0x8D,0xE3,0x52,0x1D,0x5E,0x5D,0x59 };​
        char* v = (char*)malloc(114);​
​
        hashxx(0x0F9FC445);​
​
        strcpy_s(v, 114,"11111111111111111111111111111111111111111111111111111111111111111");​
        sub_401917((unsigned int*)v, 0xE8017300, 0xFF58F981);​
        sub_401917((unsigned int*)v + 2, 0xE8017300, 0xFF58F981);​
        sub_401917((unsigned int*)v + 4, 0xE8017300, 0xFF58F981);​
        sub_401917((unsigned int*)v + 6, 0xE8017300, 0xFF58F981);​
        sub_401917_d((unsigned int*)ffff, 0xE8017300, 0xFF58F981);​
        sub_401917_d((unsigned int*)ffff +2, 0xE8017300, 0xFF58F981);​
        sub_401917_d((unsigned int*)ffff +4, 0xE8017300, 0xFF58F981);​
        sub_401917_d((unsigned int*)ffff +6, 0xE8017300, 0xFF58F981);​
}

补一下z3 exp

from z3 import *

keys = [0xB6DDB3A9, 0x36162C23, 0x1889FABF, 0x6CE4E73B, 0x0A5AF8FC, 0x21FF8415, 0x44859557, 0x2dc227B7]
x = [BitVec(f"x{i}", 32) for i in range(len(keys))]

def hash(v):
    for i in range(32):
        v = If(v < 0 , (v << 1) ^ 0x84A6972F, v << 1)
    return v

s = Solver()

for i in range(len(keys)):
    s.add(hash(x[i]) == keys[i])
s.check()
m = s.model()

result = [0] * len(keys)

for p in m.decls():
    result[int(p.name()[1:])] = m[p].as_long()

print([hex(i) for i in result])

中文编程1

载入x64dbg,注意到一堆浮点运算,果断载入IDA。
2024-05-30T10:42:35.png
由于易语言中的整数操作,全部都是拿浮点数进行的,所以这里直接大胆假设为整数,进行计算。
唯一坑人的地方在于,有一个数字,xxxx.e11,.e11去掉后是错误的,因为少了个0,这个卡了很久。

from z3 import *
s = Solver()

flag_length = 11
v4=[i for i in range(flag_length)]

for i in range(flag_length):
    v4[i] = Real("v{}".format(i))

s.add(v4[0]*52+v4[1]*93+v4[2]*15+v4[3]*72+v4[4]*61+v4[5]*21+v4[6]*83+v4[7]*87+v4[8]*75+v4[9]*75+v4[10]*88==786241466532)
s.add(v4[0]*24+v4[1]*3 +v4[2]*22+v4[3]*53+v4[4]*2 +v4[5]*88+v4[6]*30+v4[7]*38+v4[8]*2 +v4[9]*64+v4[10]*60==376271212978)
s.add(v4[0]*21+v4[1]*33+v4[2]*76+v4[3]*58+v4[4]*22+v4[5]*89+v4[6]*49+v4[7]*91+v4[8]*59+v4[9]*42+v4[10]*92==647642467922)
s.add(v4[0]*60+v4[1]*80+v4[2]*15+v4[3]*62+v4[4]*62+v4[5]*47+v4[6]*62+v4[7]*51+v4[8]*55+v4[9]*64+v4[10]*3==670839740597)
s.add(v4[0]*51+v4[1]*7 +v4[2]*21+v4[3]*73+v4[4]*39+v4[5]*18+v4[6]*4 +v4[7]*89+v4[8]*60+v4[9]*14+v4[10]*9==549200140865)
s.add(v4[0]*90+v4[1]*53+v4[2]*2 +v4[3]*84+v4[4]*92+v4[5]*60+v4[6]*71+v4[7]*44+v4[8]*8 +v4[9]*47+v4[10]*35==664730113280)
s.add(v4[0]*78+v4[1]*81+v4[2]*36+v4[3]*50+v4[4]*4 +v4[5]*2 +v4[6]*6 +v4[7]*54+v4[8]*4 +v4[9]*54+v4[10]*93==476762422687)
s.add(v4[0]*63+v4[1]*18+v4[2]*90+v4[3]*44+v4[4]*34+v4[5]*74+v4[6]*62+v4[7]*14+v4[8]*95+v4[9]*48+v4[10]*15==644352175854)
s.add(v4[0]*72+v4[1]*78+v4[2]*87+v4[3]*62+v4[4]*40+v4[5]*85+v4[6]*80+v4[7]*82+v4[8]*53+v4[9]*24+v4[10]*26==787224288556)
s.add(v4[0]*89+v4[1]*60+v4[2]*41+v4[3]*29+v4[4]*15+v4[5]*45+v4[6]*65+v4[7]*89+v4[8]*71+v4[9]*9 +v4[10]*88==667891172792)
s.add(v4[0]   +v4[1]*8 +v4[2]*88+v4[3]*63+v4[4]*11+v4[5]*81+v4[6]*8 +v4[7]*35+v4[8]*35+v4[9]*33+v4[10]*5==417587420064)


flag = []
if s.check() == sat:
    ans = s.model()
    for i in range(flag_length):   
        flag.append(ans[v4[i]])
else:
    print("unable to solve...")
for x in flag:
    print(x,end=" ")

有11组DWORD,每一组都是flag的ASCII码拼接而成,用z3进行爆破,最后还原就行。

Jvav

先拖入jadx,发现混淆直接给jadx干爆了,然后拖入GDA,发现同样被干爆。最后拖入JByteMod中,发现成功读取,也不需要进行反混淆,效果如图:
2024-05-30T10:43:12.png
然后在Idea里面新建一个项目,手动拷贝(由于部分解密函数有调用栈检测,所以不好扣出来)
2024-05-30T10:43:22.png
进行了一会人工反混淆后,注意flag判断:(var4是用户输入的东西异或51的结果)
if(ALLATORIxDEMO(var4).equals("😉😶😌😕😃😀😃😄😉😂🙂😀🤐😂🤗☹️🤗😐🤗😱😃🤣😀😘😐😄😔😄😃🤣🤨😋🤐😑😌🙂🤗😂😌🤐😃😀🤨😄🤗🤨🙂🤐😉🤩😔😘😐🙂😛😍😤😘😌😚😗🤩😧🤗"));
那么前面的这个ALLATORIxDEMO一定就是关键函数。通过更改输入,发现这个函数对于一个字符的输入可能会引起1-2个emoji的变化。我这里采用了爆破的方法:

static int simi(String a) {
    int sum = 0;
    String re = "😉😶😌😕😃😀😃😄😉😂🙂😀🤐😂🤗☹️🤗😐🤗😱😃🤣😀😘😐😄😔😄😃🤣🤨😋🤐😑😌🙂🤗😂😌🤐😃😀🤨😄🤗🤨🙂🤐😉🤩😔😘😐🙂😛😍😤😘😌😚😗🤩😧🤗";
    for (int i = 0;i < re.length();i++)
    {
        if(re.charAt(i) != a.charAt(i)) continue;
        sum++;
    }
    return sum;
}

这个函数返回加密后的flag和真正加密后flag的相似度(字符匹配,如果相同位置一样则++)
然后通过一个函数进行爆破

public static void test()
{
    var arr = new char[]{'a', 'b', 'c', 'd', 'e', 'f', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
    String flag = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
    StringBuilder strBuilder = new StringBuilder(flag);
    for(int j = 0;j < flag.length();j++)
    {
        char max_char = '0';
        int lastSimi = 0;
        for(int k = 0; k<arr.length;k++)
        {
            strBuilder.setCharAt(j, arr[k]);
            var bf = strBuilder.toString().getBytes();
            for(int i = 0;i < flag.length();i ++) {
                bf[i] = (byte)(bf[i] ^ 51);
            }
            String result = ALLATORIxDEMO(bf);
            int s = simi(result);
            if(s >= lastSimi)
            {
                max_char = arr[k];
                lastSimi = s;
            }
        }
        strBuilder.setCharAt(j, max_char);

        System.out.println(strBuilder.toString());
    }
a                                   
a9                                  
a97                                 
a979                                
a979b                               
a979b9                              
a979b92                             
a979b923                            
a979b923-                           
a979b923-6                          
a979b923-68                         
a979b923-68c                        
a979b923-68c6                       
a979b923-68c6-                      
a979b923-68c6-7                     
a979b923-68c6-7c                    
a979b923-68c6-7c0                   
a979b923-68c6-7c0f                  
a979b923-68c6-7c0f-                 
a979b923-68c6-7c0f-b                
a979b923-68c6-7c0f-b7               
a979b923-68c6-7c0f-b7f              
a979b923-68c6-7c0f-b7f9             
a979b923-68c6-7c0f-b7f9-            
a979b923-68c6-7c0f-b7f9-a           
a979b923-68c6-7c0f-b7f9-a7          
a979b923-68c6-7c0f-b7f9-a7c         
a979b923-68c6-7c0f-b7f9-a7c1        
a979b923-68c6-7c0f-b7f9-a7c14       
a979b923-68c6-7c0f-b7f9-a7c147      
a979b923-68c6-7c0f-b7f9-a7c1476     
a979b923-68c6-7c0f-b7f9-a7c14769    
a979b923-68c6-7c0f-b7f9-a7c14769c   
a979b923-68c6-7c0f-b7f9-a7c14769cb  
a979b923-68c6-7c0f-b7f9-a7c14769cb4 
a979b923-68c6-7c0f-b7f9-a7c14769cb49

输入测试,发现是错的,但是大致雏形已经有了。
然后进行更精细化的爆破

String flag = "a973b923-68bf-430f-b42a-a7a1472b\0\0\0\0";
StringBuilder strBuilder = new StringBuilder(flag);
var arr = new char[]{'a', 'b', 'c', 'd', 'e', 'f', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};

for(char a1 : arr)
{
    for(char a2 : arr)
    {
        for(char a3 : arr)
        {
            for(char a4 : arr)
            {
                for(int i = 0;i < arr.length;i++)
                {
                    strBuilder.setCharAt(32, a1);
                    strBuilder.setCharAt(33, a2);
                    strBuilder.setCharAt(34, a3);
                    strBuilder.setCharAt(35, a4);
                    var bf = strBuilder.toString().getBytes();
                    for(int j = 0;j < flag.length();j ++) {
                        bf[j] = (byte)(bf[j] ^ 51);
                    }

                    String result = ALLATORIxDEMO(bf);
                    //System.out.println(result);
                    if(result.startsWith("\uD83D\uDE09\uD83D\uDE36\uD83D\uDE0C\uD83D\uDE15\uD83D\uDE03\uD83D\uDE00\uD83D\uDE03\uD83D\uDE04\uD83D\uDE09\uD83D\uDE02\uD83D\uDE42\uD83D\uDE00\uD83E\uDD10\uD83D\uDE02\uD83E\uDD17☹\uFE0F\uD83E\uDD17\uD83D\uDE10\uD83E\uDD17\uD83D\uDE31\uD83D\uDE03\uD83E\uDD23\uD83D\uDE00\uD83D\uDE18\uD83D\uDE10\uD83D\uDE04\uD83D\uDE14\uD83D\uDE04\uD83D\uDE03\uD83E\uDD23\uD83E\uDD28\uD83D\uDE0B\uD83E\uDD10\uD83D\uDE11\uD83D\uDE0C\uD83D\uDE42\uD83E\uDD17\uD83D\uDE02\uD83D\uDE0C\uD83E\uDD10\uD83D\uDE03\uD83D\uDE00\uD83E\uDD28\uD83D\uDE04\uD83E\uDD17\uD83E\uDD28\uD83D\uDE42\uD83E\uDD10\uD83D\uDE09\uD83E\uDD29\uD83D\uDE14\uD83D\uDE18\uD83D\uDE10\uD83D\uDE42\uD83D\uDE1B\uD83D\uDE0D\uD83D\uDE24\uD83D\uDE18\uD83D\uDE0C\uD83D\uDE1A\uD83D\uDE17\uD83E\uDD29\uD83D\uDE27\uD83E\uDD17"))
                    {
                      System.out.println(strBuilder.toString());
                    }

                }
            }
        }
    }
}

通过手动修改,每四个进行爆破,逐个敲出(时间复杂度$O(n^4)$),最后得到了flag:
flag{a973b923-68bf-430f-b42a-a7a1472bcb49}(本题用时2小时)
比赛结束后听别人说,这是base64改的,直接爆了。

ezVM

拖入x64dbg和IDA进行对比,注意到有upx壳,所以直接upx -d,然后继续进行分析。拖入IDA后,发现
2024-05-30T10:44:19.png
但是其实F5后,VM的架构已经很清晰了。我们只需要拿到vm字节码,然后进行自动化或者手动分析即可。

经过一段时间的F8单步跑后,注意到vm中许多指令并没有用,switch中直接走了default路径;但是这对我们的静态分析是比较困难的。我们其实可以直接从gets_s开始分析。
方法一、由于动态调试x64dbg的方便性,我们可以采用对输入的东西进行内存软件/硬件断点,然后逐个追踪并分析,逐字节拿到flag,而且我们也知道flag长度是44,且格式为flag{.?},这个方法虽然很慢,但是能保证做出来。
2024-05-30T10:44:34.png
方法二、静态对字节码进行分析,注意到

      case 50u:
        v7 = byte_140004040[v4];
        v5 = (unsigned int)(v5 - 1);
        dword_14001FF94 = v5;
        LODWORD(v4) = v4 + 1;
        byte_14002058F[v5 + 1] = v7;
        continue;

实际上是从字节码下一位拿到一个值,并把它推入vStack上,我们记作vmLdImm8

    case 184u:
        byte_14002058F[v5 + 1] = *(_BYTE *)(byte_14002058F[v5 + 1] + *(_QWORD *)&byte_14002058F[v5 + 2]);
        continue;

这个是从栈上拿到一个值(offset)和一个指针ptr,进行寻址并push到栈上
这两个其实完成了从某个地址拿数据的操作。注意到vm里面其实一直在进行这样的操作,且input handler会push buffer的地址

   case 123u:
        v14 = (char)byte_14002058F[v5 + 1];
        v15 = (unsigned int)(v5 - 7);
        dword_14001FF94 = v15;
        *(_QWORD *)&byte_14002058F[v15 + 1] = Buffer;
        gets_s(Buffer, v14);
        goto LABEL_2;

如此以来,对于VM的数据读入,我们就可以对其字节码进行正则匹配或者是python中自动处理,而且我们无需管垃圾指令。数据拿到以后,我们需要继续分析VM。发现VM其实是在对数据进行xor操作并在最后判断是否为0,而且每个xor指令中都没有掺入垃圾指令,这在VM的字节码中重复率很高,非常显眼,而且没组相同数据之前都有一个vmLdImm8, imm的形式,记录下来每一个立即数,在最后即可还原flag。
不过在还原xor的时候,还需要用到一些万用门的知识(实际上可以才出来是xor)

万用门实现逻辑指令
理论知识:

vmp里面只有1个逻辑运算指令 not_not_and 设这条指令为P

P(a,b) = ~a & ~b

这条指令的神奇之处就是能模拟 not and or xor 4条常规的逻辑运算指令

怕忘记了,直接给出公式,后面的数字指需要几次P运算

not(a) = P(a,a) 1

and(a,b) = P(P(a,a),P(b,b)) 3

or(a,b) = P(P(a,b),P(a,b)) 2

xor(a,b) = P(P(P(a,a),P(b,b)),P(a,b)) 5
————————————————
版权声明:本文为CSDN博主「鱼无伦次」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u014738665/article/details/120722455
 // VM部分字节码
 VmgetInput
..trash
 vmLdImm8, 输入字符的索引,
 vLoadMemoryImm8,
 vmLdImm8, Imm
 vm_xor
 ..trash
  vmLdImm8, Imm
 vm_xor
 ..trash
  vmLdImm8, Imm
 vm_xor
 ....
 重复很多次

最后得到Flag:
flag{O1SC_VM_1s_h4rd_to_r3v3rs3_#a78abffaa#}