NCTF2024

  1. NCTF2024
    1. ezDOS
    2. SafeProgram
    3. x1Login
    4. gogo

NCTF2024

ezDOS

MS-DOS on Intel 8086,先去一下花指令,ida打开分析过汇编,但是感觉提不出什么有用的信息,并且密文看着也很奇怪

image-20250323115241351

那就只能用DOSBox来动态调试了,断点下在0020处,就是输出提示Show me your flag:,然后输入假flag,

NCTF{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA},查看内存,使用指令d 0168

9162b12df1d510aa1d59371c0542492

可以看到这里存储输入明文的地方

然后继续往下跟,发现一直到loc_10582,才对输入的flag进行了加密

image-20250323115614372

异或处理

然后加密完之后查看内存

image-20250323115639694

发现前几位和最后一位的数据是和这里是一样的

image-20250323115708399

并且位数也对的上,38位

那么就可以根据假flag和其加密结果反推出异或的key,就可以和密文进行异或解密了

key

image-20250323115800377

解密

image-20250323115811511

拿到flag

NCTF{Y0u_4r3_Assemb1y_M4st3r_5d0b497e}


SafeProgram

魔改SM4,改了两个常量。TLS1里有反调试,直接nop掉就可以了,然后动调取sbox和FK,动调的时候记得把Sleep也nop掉了,不然还得等,然后就可以直接写脚本解密了

#include <string.h>
#include <stdio.h>
#include <time.h>
/*--------------------------------------------------------------------------------------------------------------*/
#define SM4_ENCRYPT 1
#define SM4_DECRYPT 0
/*--------------------------------------------------------------------------------------------------------------*/
/**
 * \brief          SM4 context structure
 */
typedef struct
{
    int mode;             /*!<  encrypt/decrypt   */
    unsigned long sk[32]; /*!<  SM4 subkeys       */
} sm4_context;
/*--------------------------------------------------------------------------------------------------------------*/
/**
 * \brief          SM4 key schedule (128-bit, encryption)
 *
 * \param ctx      SM4 context to be initialized
 * \param key      16-byte secret key
 */
void sm4_setkey_enc(sm4_context *ctx, unsigned char key[16]);
/*--------------------------------------------------------------------------------------------------------------*/
/**
 * \brief          SM4 key schedule (128-bit, decryption)
 *
 * \param ctx      SM4 context to be initialized
 * \param key      16-byte secret key
 */
void sm4_setkey_dec(sm4_context *ctx, unsigned char key[16]);
/*--------------------------------------------------------------------------------------------------------------*/
/**
 * \brief          SM4-ECB block encryption/decryption
 * \param ctx      SM4 context
 * \param mode     SM4_ENCRYPT or SM4_DECRYPT
 * \param length   length of the input data
 * \param input    input block
 * \param output   output block
 */
void sm4_crypt_ecb(sm4_context *ctx,
                   int mode,
                   int length,
                   unsigned char *input,
                   unsigned char *output);
/*--------------------------------------------------------------------------------------------------------------*/
/**
 * \brief          SM4-CBC buffer encryption/decryption
 * \param ctx      SM4 context
 * \param mode     SM4_ENCRYPT or SM4_DECRYPT
 * \param length   length of the input data
 * \param iv       initialization vector (updated after use)
 * \param input    buffer holding the input data
 * \param output   buffer holding the output data
 */
void sm4_crypt_cbc(sm4_context *ctx,
                   int mode,
                   int length,
                   unsigned char iv[16],
                   unsigned char *input,
                   unsigned char *output);
/*--------------------------------------------------------------------------------------------------------------*/
/*
 * 32-bit integer manipulation macros (big endian)
 */
#ifndef GET_ULONG_BE
#define GET_ULONG_BE(n, b, i)                                                                                                                             \
    {                                                                                                                                                     \
        (n) = ((unsigned long)(b)[(i)] << 24) | ((unsigned long)(b)[(i) + 1] << 16) | ((unsigned long)(b)[(i) + 2] << 8) | ((unsigned long)(b)[(i) + 3]); \
    }
#endif
/*--------------------------------------------------------------------------------------------------------------*/
#ifndef PUT_ULONG_BE
#define PUT_ULONG_BE(n, b, i)                      \
    {                                              \
        (b)[(i)] = (unsigned char)((n) >> 24);     \
        (b)[(i) + 1] = (unsigned char)((n) >> 16); \
        (b)[(i) + 2] = (unsigned char)((n) >> 8);  \
        (b)[(i) + 3] = (unsigned char)((n));       \
    }
#endif
/*--------------------------------------------------------------------------------------------------------------*/
/*
 *rotate shift left marco definition
 *
 */
#define SHL(x, n) (((x) & 0xFFFFFFFF) << n)
#define ROTL(x, n) (SHL((x), n) | ((x) >> (32 - n)))
#define SWAP(a, b)           \
    {                        \
        unsigned long t = a; \
        a = b;               \
        b = t;               \
        t = 0;               \
    }
/*--------------------------------------------------------------------------------------------------------------*/
/*
* Expanded SM4 S-boxes
/* Sbox table: 8bits input convert to 8 bits output*/       //已被魔改
static const unsigned char SboxTable[16][16] =
    {
        {0xD1, 0x90, 0xE9, 0xFE, 0xCC, 0xE1, 0x3D, 0xB7, 0x16, 0xB6, 0x14, 0xC2, 0x28, 0xFB, 0x2C, 0x05},
        {0x2B, 0x67, 0x9A, 0x76, 0x2A, 0xBE, 0x04, 0xC3, 0xAA, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99},
        {0x9C, 0x42, 0x50, 0xF4, 0x91, 0xEF, 0x98, 0x7A, 0x33, 0x54, 0x0B, 0x43, 0xED, 0xCF, 0xAC, 0x62},
        {0xE4, 0xB3, 0x17, 0xA9, 0x1C, 0x08, 0xE8, 0x95, 0x80, 0xDF, 0x94, 0xFA, 0x75, 0x8F, 0x3F, 0xA6},
        {0x47, 0x07, 0xA7, 0x4F, 0xF3, 0x73, 0x71, 0xBA, 0x83, 0x59, 0x3C, 0x19, 0xE6, 0x85, 0xD6, 0xA8},
        {0x68, 0x6B, 0x81, 0xB2, 0xFC, 0x64, 0xDA, 0x8B, 0xF8, 0xEB, 0x0F, 0x4B, 0x70, 0x56, 0x9D, 0x35},
        {0x1E, 0x24, 0x0E, 0x78, 0x63, 0x58, 0x9F, 0xA2, 0x25, 0x22, 0x7C, 0x3B, 0x01, 0x21, 0xC9, 0x87},
        {0xD4, 0x00, 0x46, 0x57, 0x5E, 0xD3, 0x27, 0x52, 0x4C, 0x36, 0x02, 0xE7, 0xA0, 0xC4, 0xC8, 0x9E},
        {0xEA, 0xBF, 0x8A, 0xD2, 0x40, 0xC7, 0x38, 0xB5, 0xA3, 0xF7, 0xF2, 0xCE, 0xF9, 0x61, 0x15, 0xA1},
        {0xE0, 0xAE, 0x5D, 0xA4, 0x9B, 0x34, 0x1A, 0x55, 0xAD, 0x93, 0x32, 0x30, 0xF5, 0x8C, 0xB1, 0xE3},
        {0x1D, 0xF6, 0xE2, 0x2E, 0x82, 0x66, 0xCA, 0x60, 0xC0, 0x29, 0x23, 0xAB, 0x0D, 0x53, 0x4E, 0x6F},
        {0xD5, 0xDB, 0x37, 0x45, 0xDE, 0xFD, 0x8E, 0x2F, 0x03, 0xFF, 0x6A, 0x72, 0x6D, 0x6C, 0x5B, 0x51},
        {0x8D, 0x1B, 0xAF, 0x92, 0xBB, 0xDD, 0xBC, 0x7F, 0x11, 0xD9, 0x5C, 0x41, 0x1F, 0x10, 0x5A, 0xD8},
        {0x0A, 0xC1, 0x31, 0x88, 0xA5, 0xCD, 0x7B, 0xBD, 0x2D, 0x74, 0xD0, 0x12, 0xB8, 0xE5, 0xB4, 0xB0},
        {0x89, 0x69, 0x97, 0x4A, 0x0C, 0x96, 0x77, 0x7E, 0x65, 0xB9, 0xF1, 0x09, 0xC5, 0x6E, 0xC6, 0x84},
        {0x18, 0xF0, 0x7D, 0xEC, 0x3A, 0xDC, 0x4D, 0x20, 0x79, 0xEE, 0x5F, 0x3E, 0xD7, 0xCB, 0x39, 0x48}};
/*--------------------------------------------------------------------------------------------------------------*/
/* System parameter */
static const unsigned long FK[4] =
    {0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc};       //已被魔改
/*--------------------------------------------------------------------------------------------------------------*/
/* fixed parameter */
static const unsigned long CK[32] =
    {
        0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
        0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
        0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
        0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
        0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
        0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
        0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
        0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279};
/*--------------------------------------------------------------------------------------------------------------*/
/*
 * private function:
 * look up in SboxTable and get the related value.
 * args:    [in] inch: 0x00~0xFF (8 bits unsigned value).
 */
static unsigned char sm4Sbox(unsigned char inch)
{
    unsigned char *pTable = (unsigned char *)SboxTable;
    unsigned char retVal = (unsigned char)(pTable[inch]);
    return retVal;
}
/*--------------------------------------------------------------------------------------------------------------*/
/*
 * private F(Lt) function:
 * "T algorithm" == "L algorithm" + "t algorithm".
 * args:    [in] a: a is a 32 bits unsigned value;
 * return: c: c is calculated with line algorithm "L" and nonline algorithm "t"
 */
static unsigned long sm4Lt(unsigned long ka)
{
    unsigned long bb = 0;
    unsigned long c = 0;
    unsigned char a[4];
    unsigned char b[4];
    PUT_ULONG_BE(ka, a, 0)
    b[0] = sm4Sbox(a[0]);
    b[1] = sm4Sbox(a[1]);
    b[2] = sm4Sbox(a[2]);
    b[3] = sm4Sbox(a[3]);
    GET_ULONG_BE(bb, b, 0)
    c = bb ^ (ROTL(bb, 2)) ^ (ROTL(bb, 10)) ^ (ROTL(bb, 18)) ^ (ROTL(bb, 24));
    return c;
}
/*--------------------------------------------------------------------------------------------------------------*/
/*
 * private F function:
 * Calculating and getting encryption/decryption contents.
 * args:    [in] x0: original contents;
 * args:    [in] x1: original contents;
 * args:    [in] x2: original contents;
 * args:    [in] x3: original contents;
 * args:    [in] rk: encryption/decryption key;
 * return the contents of encryption/decryption contents.
 */
static unsigned long sm4F(unsigned long x0, unsigned long x1, unsigned long x2, unsigned long x3, unsigned long rk)
{
    return (x0 ^ sm4Lt(x1 ^ x2 ^ x3 ^ rk));
}
/*--------------------------------------------------------------------------------------------------------------*/
/* private function:
 * Calculating round encryption key.
 * args:    [in] a: a is a 32 bits unsigned value;
 * return: sk[i]: i{0,1,2,3,...31}.
 */
static unsigned long sm4CalciRK(unsigned long ka)
{
    unsigned long bb = 0;
    unsigned long rk = 0;
    unsigned char a[4];
    unsigned char b[4];
    PUT_ULONG_BE(ka, a, 0)
    b[0] = sm4Sbox(a[0]);
    b[1] = sm4Sbox(a[1]);
    b[2] = sm4Sbox(a[2]);
    b[3] = sm4Sbox(a[3]);
    GET_ULONG_BE(bb, b, 0)
    rk = bb ^ (ROTL(bb, 13)) ^ (ROTL(bb, 23));
    return rk;
}
/*--------------------------------------------------------------------------------------------------------------*/
static void sm4_setkey(unsigned long SK[32], unsigned char key[16])
{
    unsigned long MK[4];
    unsigned long k[36];
    unsigned long i = 0;

    GET_ULONG_BE(MK[0], key, 0);
    GET_ULONG_BE(MK[1], key, 4);
    GET_ULONG_BE(MK[2], key, 8);
    GET_ULONG_BE(MK[3], key, 12);
    k[0] = MK[0] ^ FK[0];
    k[1] = MK[1] ^ FK[1];
    k[2] = MK[2] ^ FK[2];
    k[3] = MK[3] ^ FK[3];
    for (; i < 32; i++)
    {
        k[i + 4] = k[i] ^ (sm4CalciRK(k[i + 1] ^ k[i + 2] ^ k[i + 3] ^ CK[i]));
        SK[i] = k[i + 4];
    }
}
/*--------------------------------------------------------------------------------------------------------------*/
/*
 * SM4 standard one round processing
 *
 */
static void sm4_one_round(unsigned long sk[32],
                          unsigned char input[16],
                          unsigned char output[16])
{
    unsigned long i = 0;
    unsigned long ulbuf[36];

    memset(ulbuf, 0, sizeof(ulbuf));
    GET_ULONG_BE(ulbuf[0], input, 0)
    GET_ULONG_BE(ulbuf[1], input, 4)
    GET_ULONG_BE(ulbuf[2], input, 8)
    GET_ULONG_BE(ulbuf[3], input, 12)
    while (i < 32)
    {
        ulbuf[i + 4] = sm4F(ulbuf[i], ulbuf[i + 1], ulbuf[i + 2], ulbuf[i + 3], sk[i]);
        i++;
    }
    PUT_ULONG_BE(ulbuf[35], output, 0);
    PUT_ULONG_BE(ulbuf[34], output, 4);
    PUT_ULONG_BE(ulbuf[33], output, 8);
    PUT_ULONG_BE(ulbuf[32], output, 12);
}
/*--------------------------------------------------------------------------------------------------------------*/
/*
 * SM4 key schedule (128-bit, encryption)
 */
void sm4_setkey_enc(sm4_context *ctx, unsigned char key[16])
{
    ctx->mode = SM4_ENCRYPT;
    sm4_setkey(ctx->sk, key);
}
/*--------------------------------------------------------------------------------------------------------------*/
/*
 * SM4 key schedule (128-bit, decryption)
 */
void sm4_setkey_dec(sm4_context *ctx, unsigned char key[16])
{
    int i;
    ctx->mode = SM4_ENCRYPT;
    sm4_setkey(ctx->sk, key);
    for (i = 0; i < 16; i++)
    {
        SWAP(ctx->sk[i], ctx->sk[31 - i]);
    }
}
/*--------------------------------------------------------------------------------------------------------------*/
/*
 * SM4-ECB block encryption/decryption
 */
void sm4_crypt_ecb(sm4_context *ctx,
                   int mode,
                   int length,
                   unsigned char *input,
                   unsigned char *output)
{
    while (length > 0)
    {
        sm4_one_round(ctx->sk, input, output);
        input += 16;
        output += 16;
        length -= 16;
    }
}
/*--------------------------------------------------------------------------------------------------------------*/
/*
 * SM4-CBC buffer encryption/decryption
 */
void sm4_crypt_cbc(sm4_context *ctx,
                   int mode,
                   int length,
                   unsigned char iv[16],
                   unsigned char *input,
                   unsigned char *output)
{
    int i;
    unsigned char temp[16];

    if (mode == SM4_ENCRYPT)
    {
        while (length > 0)
        {
            for (i = 0; i < 16; i++)
                output[i] = (unsigned char)(input[i] ^ iv[i]);

            sm4_one_round(ctx->sk, output, output);
            memcpy(iv, output, 16);

            input += 16;
            output += 16;
            length -= 16;
        }
    }
    else /* SM4_DECRYPT */
    {
        while (length > 0)
        {
            memcpy(temp, input, 16);
            sm4_one_round(ctx->sk, input, output);

            for (i = 0; i < 16; i++)
                output[i] = (unsigned char)(output[i] ^ iv[i]);

            memcpy(iv, temp, 16);

            input += 16;
            output += 16;
            length -= 16;
        }
    }
}
/*--------------------------------------------------------------------------------------------------------------*/
int main()
{
    unsigned char key[16] = {0x4E, 0x43, 0x54, 0x46, 0x32, 0x34, 0x6E, 0x63, 0x74, 0x66,
                             0x4E, 0x43, 0x54, 0x46, 0x32, 0x34};
    unsigned char input[32] = {
        0xFB, 0x97, 0x3C, 0x3B, 0xF1, 0x99, 0x12, 0xDF, 
        0x13, 0x30, 0xF7, 0xD8, 0x7F, 0xEB, 0xA0, 0x6C, 
        0x14, 0x5B, 0xA6, 0x2A, 0xA8, 0x05, 0xA5, 0xF3, 
        0x76, 0xBE, 0xC9, 0x01, 0xF9, 0x36, 0x7B, 0x46
    };
    unsigned char output[16];
    sm4_context ctx;
    unsigned long i;

    /*
    //encrypt testing
    sm4_setkey_enc(&ctx,key);
    sm4_crypt_ecb(&ctx,1,16,input,output);
    for(i=0;i<16;i++)
        printf("%02x ", output[i]);
    printf("\n");
    */

    // decrypt testing
    sm4_setkey_dec(&ctx, key);
    sm4_crypt_ecb(&ctx, 0, 32, input, output);
    for (i = 0; i < 32; i++)
        printf("%c", output[i]);
    printf("\n");
    return 0;
}

NCTF{58cb925e0cd823c0d0b54fd06b820b7e}


x1Login

安卓逆向,有动调和root检测,frida写个脚本hook就能过掉。程序整体逻辑就是,native层的libsimple.so文件负责对调用的dex文件名进行加密,魔改的base64编码和异或,然后另外一个so文件就是负责密码的加密。

root检测等

image-20250323171522102

调用check方法

image-20250323171607848

image-20250323171617139

在assets文件夹里找到libsimple.so文件,把前面elf的文件头去掉,改下后缀成dex,就可以用jadx反编译了

package com.nctf.simplelogin;

import android.content.Context;
import android.widget.Toast;
import java.security.MessageDigest;

/* loaded from: C:\Users\16219\Desktop\x1Login-release\assets\libsimple.dex */
public class Check {
    Context context;
    String password;
    String username;

    public Check(Context context, String username, String password) {
        this.username = username;
        this.password = password;
        this.context = context;
    }

    public void check() {
        try {
            if (check_username()) {
                MessageDigest digest = MessageDigest.getInstance(DecStr.get("tMC2"));
                digest.update(this.username.getBytes());
                byte[] output = digest.digest();
                boolean result = check_password(output);
                if (result) {
                    Toast.makeText(this.context, "Login Successful! Now submit your flag!", 0).show();
                    return;
                }
            }
            Toast.makeText(this.context, "Login Failed!", 0).show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private boolean check_username() {
        return this.username.equals(DecStr.get("uZPOs29goMu6l38="));
    }

    private boolean check_password(byte[] key) {
        return Secure.doCheck(this.password, key);
    }
}

解密拿到用户名

image-20250323171713729

这题的check机制有问题,输入正确的用户名,不输入密码,然后登录会显示成功。接着在libnative.so文件里分析密码是如果check的,首先是将用户名的md5传进去做key,再加密

image-20250323171903740

但是分析无果,不知道是啥算法

在sub_1E30函数里看到有日志打印,但是拿不到输出的内容

image-20250323172006200

赛后p34cd0wn师傅复现出来了,这确实是3DES加密,并且是标准的,我觉得应该是被优化的太厉害了,常量,密文,key的端序都发生了改变

image-20250324174512901

image-20250324174527515

这是密文,密钥就是用户名md5

解密

c7936ade308afc82121463e5b215376

NCTF{X1c@dM1n1$t_SafePWD~5y$x?YM+5U05Gm6=}


gogo

第一次完整的复现一道vm,并且还学到了新方法,以前只会傻乎乎的找opcode,然后输出所有操作指令,再去进行分析,但这种硬撕,如果水平较低的话只能解决一些简单的vm,如果复杂的话那就很吃操作了。

go语言写的,但是整体代码逻辑还是较为清晰,但也还是要借助ai来辅助分析,不是很会go语言

// main.main
void __fastcall main_main()
{
  int v0; // ecx
  int v1; // edi
  int v2; // esi
  int v3; // r8d
  int v4; // r9d
  __int64 v5; // r14
  int v6; // ecx
  int v7; // r8d
  int v8; // r9d
  int v9; // ecx
  int v10; // r8d
  int v11; // r9d
  int v12; // r8d
  int v13; // r9d
  int v14; // r10d
  int v15; // r11d
  int v16; // r10d
  int v17; // r11d
  main_coroutVM *p_main_coroutVM; // rax
  char *v19; // rdx
  void *v20; // rcx
  void *v21; // rbx
  _QWORD *v22; // r11
  void *v23; // rsi
  _QWORD *v24; // r11
  main_coroutVM *v25; // rax
  __int64 v26; // rcx
  _QWORD *v27; // r11
  void *v28; // r8
  void *v29; // r9
  void *v30; // r10
  size_t v31; // rdx
  main_coroutVM *v32; // rsi
  uint8 *mem; // rdi
  __int64 v34; // rbx
  main_coroutVM *v35; // rbx
  _QWORD *v36; // rax
  int v37; // r8d
  int v38; // r9d
  int v39; // r10d
  main_coroutVM **v40; // r11
  main_coroutVM *v41; // rcx
  _QWORD *v42; // rax
  int v43; // r8d
  int v44; // r9d
  int v45; // r10d
  main_coroutVM **v46; // r11
  main_coroutVM *v47; // rcx
  __int64 v48; // rcx
  unsigned __int64 i; // rax
  int v50; // r8d
  int v51; // r9d
  int v52; // r10d
  int v53; // r11d
  char v54; // cl
  __int64 v55; // r8
  __int64 v56; // [rsp-3Ah] [rbp-E0h]
  __int64 v57; // [rsp-3Ah] [rbp-E0h]
  __int64 v58; // [rsp-3Ah] [rbp-E0h]
  __int64 v59; // [rsp-3Ah] [rbp-E0h]
  __int64 v60; // [rsp-3Ah] [rbp-E0h]
  __int64 v61; // [rsp-32h] [rbp-D8h]
  __int64 v62; // [rsp-32h] [rbp-D8h]
  __int64 v63; // [rsp-32h] [rbp-D8h]
  __int64 v64; // [rsp-2Ah] [rbp-D0h]
  __int64 v65; // [rsp-22h] [rbp-C8h]
  __int64 v66; // [rsp-1Ah] [rbp-C0h]
  char v67; // [rsp+0h] [rbp-A6h]
  int v68; // [rsp+1h] [rbp-A5h] BYREF
  char v69; // [rsp+5h] [rbp-A1h] BYREF
  size_t len; // [rsp+6h] [rbp-A0h]
  unsigned __int64 v71; // [rsp+Eh] [rbp-98h]
  void *v72; // [rsp+16h] [rbp-90h]
  main_coroutVM *v73; // [rsp+1Eh] [rbp-88h]
  void *v74; // [rsp+26h] [rbp-80h]
  main_coroutVM *v75; // [rsp+2Eh] [rbp-78h]
  void *v76; // [rsp+36h] [rbp-70h]
  main_coroutVM *v77; // [rsp+3Eh] [rbp-68h]
  char *ptr; // [rsp+46h] [rbp-60h] BYREF
  __int64 v79[2]; // [rsp+4Eh] [rbp-58h] BYREF
  __int64 v80[2]; // [rsp+5Eh] [rbp-48h] BYREF
  __int64 v81[2]; // [rsp+6Eh] [rbp-38h] BYREF
  __int64 v82[2]; // [rsp+7Eh] [rbp-28h] BYREF
  __int64 v83[2]; // [rsp+8Eh] [rbp-18h] BYREF
  string *p_string; // [rsp+9Eh] [rbp-8h]

  while ( &ptr <= *(v5 + 16) )
    runtime_morestack_noctxt();
  v74 = runtime_makechan(&RTYPE_chan__4_uint8, 0, v0, v1, v2, v3, v4);// 初始化三个通道
  v72 = runtime_makechan(&RTYPE_chan__4_uint8, 0, v6, v1, v2, v7, v8);
  v76 = runtime_makechan(&RTYPE_chan_bool, 2, v9, v1, v2, v10, v11);
  p_string = runtime_newobject(&RTYPE_string);
  v83[0] = &RTYPE_string;
  v83[1] = &off_478B08;
  fmt_Fprintln(off_479218, qword_510000, v83, 1, 1, v12, v13, v14, v15, v56, v61);// 交互提示输出
  v82[0] = &RTYPE__ptr_string;
  v82[1] = p_string;
  fmt_Fscanf(
    off_479238,
    qword_50FFF8,
    "%si), )(tvrRuUeEaAlLsS01bBoOxX+-nNiIfFpPLlLtLuMn: 25\"\n [(\"\")) )\n @s -> Pn=][}\n]\n> \n \t  +osnil01_EOFintmapptr",
    2,
    v82,
    1,
    1,
    v16,
    v17,
    v57,
    v62,
    v64,
    HIDWORD(v64),
    v65,
    HIDWORD(v65),
    v66,
    HIDWORD(v66));
  ptr = p_string->ptr;
  len = p_string->len;
  p_main_coroutVM = runtime_newobject(&RTYPE_main_coroutVM);
  v19 = &unk_555420;
  if ( ptr )
    v19 = ptr;
  if ( dword_555690 )
  {
    p_main_coroutVM = runtime_gcWriteBarrier2(p_main_coroutVM);
    v20 = v74;
    *v22 = v74;
    v21 = v76;
    v22[1] = v76;
  }
  else
  {
    v20 = v74;
    v21 = v76;
  }
  v77 = v19;
  p_main_coroutVM->instr = v20;
  p_main_coroutVM->checkres = v21;
  v23 = qword_50FFC0;
  if ( dword_555690 )
  {
    p_main_coroutVM = runtime_gcWriteBarrier1(p_main_coroutVM, v21, v20, 2LL, qword_50FFC0);
    *v24 = v23;
  }
  v75 = p_main_coroutVM;
  p_main_coroutVM->instrSet = v23;
  v25 = runtime_newobject(&RTYPE_main_coroutVM);
  if ( dword_555690 )
  {
    v25 = runtime_gcWriteBarrier2(v25);
    v28 = v72;
    *v27 = v72;
    v29 = v76;
    v27[1] = v76;
  }
  else
  {
    v28 = v72;
    v29 = v76;
  }
  v25->instr = v28;
  v25->checkres = v29;
  v30 = qword_50FFC8;
  if ( dword_555690 )
  {
    v25 = runtime_gcWriteBarrier1(v25, v21, v26, 2LL, v23);
    *v27 = v30;
  }
  v25->instrSet = v30;
  if ( p_string->len == 40 )                    // 长度检测,必须是40
  {
    v31 = len;
    if ( len < 20 )                             // 字符串分割,分割前20字节
      runtime_panicSliceAcap(v25, v21, 20LL);
    v73 = v25;
    v32 = v75;
    mem = v75->mem;
    v34 = v77;
    if ( v77 != v75->mem )
    {
      runtime_memmove(v75->mem, v77, 20LL);
      v25 = v73;
      v31 = len;
      v34 = v77;
      v32 = v75;
    }
    if ( v31 < 0x28 )                           // 字符串分割,分割后20字节
      runtime_panicSliceAcap(v25, v34, 40LL);
    v35 = (v34 + 20);
    if ( v35 != v25->mem )
      runtime_memmove(v25->mem, v35, 20LL);
    v36 = runtime_newobject(&unk_43CF60);       // 创建第一个协程
    *v36 = main_main_gowrap1;                   // 第一个协程入口
    if ( dword_555690 )
    {
      v36 = runtime_gcWriteBarrier1(v36, v35, main_main_gowrap1, mem, v32);
      v41 = v75;
      *v40 = v75;
    }
    else
    {
      v41 = v75;
    }
    v36[1] = v41;                               // 传参
    runtime_newproc(v36, v35, v41, mem, v32, v37, v38, v39, v40, v58);// 启动协程
    v42 = runtime_newobject(&unk_43CF60);       // 创建第二个协程
    *v42 = main_main_gowrap2;                   // 第二个协程入口
    if ( dword_555690 )
    {
      v42 = runtime_gcWriteBarrier1(v42, v35, main_main_gowrap2, mem, v32);
      v47 = v73;
      *v46 = v73;
    }
    else
    {
      v47 = v73;
    }
    v42[1] = v47;                               // 传递参数
    runtime_newproc(v42, v35, v47, mem, v32, v43, v44, v45, v46, v59);// 协程启动
    v48 = v74;
    for ( i = 0LL; qword_508478 > i; i = v71 )  // 数据发送
    {
      v68 = 0;
      if ( qword_508480 < i + 4 )               // 中间这部分是边界检查
        runtime_panicSliceAcap(i, v35, i + 4);
      if ( i > i + 4 )
        runtime_panicSliceB(i, v35, i + 4);
      v71 = i + 4;
      v55 = ((i - qword_508480) >> 63) & i;
      if ( &v68 != (off_508470 + v55) )
        v68 = *(off_508470 + v55);
      runtime_chansend1(v48);                   // 发送到第一个通道
      v35 = &v68;
      runtime_chansend1(v72);                   // 发送到第二个通道
      v48 = v74;
    }
  }
  else
  {
    v81[0] = &RTYPE_string;
    v81[1] = &off_478B18;
    fmt_Fprintln(off_479218, qword_510000, v81, 1, 1, v28, v29, p_string, v27, v58, v63);
  }
  v69 = 0;
  runtime_chanrecv1(v76, &v69);                 // 接受第一个结果
  v67 = v69;
  v69 = 0;
  runtime_chanrecv1(v76, &v69);                 // 接受第二个结果
  v54 = v69;
  v69 = v67;
  if ( v67 )
    v69 = v54;
  if ( v69 )
  {
    v80[0] = &RTYPE_string;
    v80[1] = &off_478B28;                       // correct
    fmt_Fprintln(off_479218, qword_510000, v80, 1, 1, v50, v51, v52, v53, v60, v63);
  }
  else
  {
    v79[0] = &RTYPE_string;                     // wrong
    v79[1] = &off_478B38;
    fmt_Fprintln(off_479218, qword_510000, v79, 1, 1, v50, v51, v52, v53, v60, v63);
  }
}

代码具体意思已经注释在代码中了

并且程序把每种运算的函数指令啥的都已经给出来了

image-20250325175056734

这里要注意的是,flag字符串被分成两部分,两部分加密的算法虽然都是xxtea,但是具体有些细节不一样。

那么我们就可以在每个运算函数打下断点,然后当调用该函数的时候,读取响应寄存器的值,将寄存器种的值输出,并且以该运算对应的格式输出。

base = 0x380000


def set_python_bpt(ea, cond):
    ''' Set conditional breakpoint with Python function

        Usage:
        set_python_bpt(0x08000688, 'view_regs()')
    '''
    idaapi.add_bpt(ea, 4, BPT_DEFAULT)
    bpt = idaapi.bpt_t()
    idaapi.get_bpt(ea, bpt)
    bpt.elang = 'Python'
    bpt.condition = cond
    idaapi.update_bpt(bpt)


def hook_shl():
    ebx = idc.get_reg_value("ebx")
    cl = idc.get_reg_value("cl")
    val1 = ebx
    val2 = cl
    print(f"{val1:x} << {val2:x} = {(val1 << val2) & 0xFFFFFFFF:x}")


def hook_shr():
    ebx = idc.get_reg_value("ebx")
    cl = idc.get_reg_value("cl")
    val1 = ebx
    val2 = cl
    print(f"{val1:x} >> {val2:x} = {(val1 >> val2) & 0xFFFFFFFF:x}")


def hook_add():
    edx = idc.get_reg_value("edx")
    rax = idc.get_reg_value("rax")
    rbx = idc.get_reg_value("rbx")
    val1 = edx
    val2 = idc.get_wide_dword(rax + rbx * 4)
    print(f"{val1:x} + {val2:x} = {(val1 + val2) & 0xFFFFFFFF:x}")


def hook_xor():
    edx = idc.get_reg_value("edx")
    rax = idc.get_reg_value("rax")
    rbx = idc.get_reg_value("rbx")
    val1 = edx
    val2 = idc.get_wide_dword(rax + rbx * 4)
    print(f"{val1:x} ^ {val2:x} = {(val1 ^ val2) & 0xFFFFFFFF:x}")


def hook_sub():
    edx = idc.get_reg_value("edx")
    rax = idc.get_reg_value("rax")
    rbx = idc.get_reg_value("rbx")
    val1 = edx
    val2 = idc.get_wide_dword(rax + rbx * 4)
    print(f"{val1:x} - {val2:x} = {(val1 - val2) & 0xFFFFFFFF:x}")


def hook_mul():
    edx = idc.get_reg_value("edx")
    ebx = idc.get_reg_value("ebx")
    val1 = edx
    val2 = ebx
    print(f"{val1:x} * {val2:x} = {(val1 * val2) & 0xFFFFFFFF:x}")


def hook_and():
    edx = idc.get_reg_value("edx")
    rax = idc.get_reg_value("rax")
    rbx = idc.get_reg_value("rbx")
    val1 = edx
    val2 = idc.get_wide_dword(rax + rbx * 4)
    print(f"{val1:x} & {val2:x} = {(val1 & val2) & 0xFFFFFFFF:x}")


set_python_bpt(base + 0x0000000000A8860, 'hook_shr()')
set_python_bpt(base + 0x0000000000A8820, 'hook_shl()')
set_python_bpt(base + 0x00000000000A871D, 'hook_add()')
set_python_bpt(base + 0x00000000000A87DD, 'hook_xor()')
set_python_bpt(base + 0x00000000000A875D, 'hook_sub()')
set_python_bpt(base + 0x0000000000A87A0, 'hook_mul()')
set_python_bpt(base + 0x0000000000A889A, 'hook_and()')
print('ok')

这里还有一个点是需要注意基地址要为动态调试的时候程序的基地址,不然不会有输出的。

在输出窗口拿到输出,仔细分析下来,是两部分字符串加密时混在一起的,需要分开来

这里展示一部分

9e37 << 10 = 9e370000
79b9 + 9e370000 = 9e3779b9
9e37 << 10 = 9e370000
79b9 + 9e370000 = 9e3779b9

#   tmp1 = (v[4] >> 5) ^ (v[1] <<2)
48474645 << 2 = 211d1914
54535251 >> 5 = 2a29a92
211d1914 ^ 2a29a92 = 23bf8386

#   tmp3 = (v[1] >> 3) ^ (v[4] << 4)
48474645 >> 3 = 908e8c8

54535251 << 4 = 45352510
908e8c8 ^ 45352510 = 4c3dcdd8

#   tmp4 = (v[4] >> 5) ^ (v[1] <<2) + (v[1] >> 3) ^ (v[4] <<4)
23bf8386 + 4c3dcdd8 = 6ffd515e

#   e = (sum >> 2) & 3;
9e3779b9 >> 2 = 278dde6e
278dde6e & 3 = 2

#   (p & 3) ^ e
2 ^ 0 = 2

#   (sum ^ v[1])
9e3779b9 ^ 48474645 = d6703ffc

#   (key[(p&3)^e] ^ z)
54535251 ^ a78c0b4f = f3df591e

#   (sum^y) + (key[(p&3)^e] ^ z))
d6703ffc + f3df591e = ca4f991a

#   MX = ((v[4] >> 5) ^ (v[1] <<2) + (v[1] >> 3) ^ (v[4] << 4)) ^ (sum ^ v[1]) + (key[(p&3)^e] ^ v[4]))
6ffd515e ^ ca4f991a = a5b2c844

#   z = v[0] += MX
a5b2c844 + 44434241 = e9f60a85

#   tmp2 = (v[6] >> 2) ^ (v[9] <<5)
62615a59 >> 2 = 18985696
6e6d6c6b << 5 = cdad8d60
18985696 ^ cdad8d60 = d535dbf6

#   tmp5 = (v[6] << 3) ^ (v[9] >> 4)
62615a59 << 3 = 130ad2c8
6e6d6c6b >> 4 = 6e6d6c6
130ad2c8 ^ 6e6d6c6 = 15ec040e

#   tmp6 = tmp2 + tmp5
#   tmp6 = (v[6] >> 2) ^ (v[9] << 5) + (v[6] <<3) ^ (v[9] >> 4)
d535dbf6 + 15ec040e = eb21e004

a78c << 10 = a78c0000
b4f + a78c0000 = a78c0b4f
9f1c << 10 = 9f1c0000

#   e = (sum >> 2) & 3;
2 & 3 = 2
9e3779b9 >> 2 = 278dde6e
278dde6e & 3 = 2
2 * 4 = 8
2 ^ 0 = 2
8 + 20 = 28
2 & 3 = 2

#   get key[0]
f72e + 9f1c0000 = 9f1cf72e
2 * 4 = 8

8 + 20 = 28

#   (sum ^ v[6])
9e3779b9 ^ 62615a59 = fc5623e0

#   (key[( p & 3) ^ e] ^ v[9])
6e6d6c6b ^ 9f1cf72e = f1719b45

#   (sum ^ y) + (key[(p & 3) ^ e] ^ v[9])
fc5623e0 + f1719b45 = edc7bf25
6e63 << 10 = 6e630000

#   MZ2 = ((v[6] >> 2) ^ (v[9] << 5) + (v[6] << 3) ^ (v[9] >> 4)) ^ (sum ^ y) + (key[(p & 3) ^ e] ^ v[9])
eb21e004 ^ edc7bf25 = 6e65f21
7466 + 6e630000 = 6e637466
3230 << 10 = 32300000

3234 + 32300000 = 32303234

#   z = v[5] + MZ2
6e65f21 + 58575655 = 5f3db576

key也就在输出中根据逻辑找到的

以下是解密脚本

exp1

#include <stdio.h>
#include <stdint.h>
#define DELTA 0x9e3779b9
#define MX1 (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))

void btea(uint32_t *v, int n, uint32_t *key)
{
    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];
                //printf("%x \n", (p&3)^e);
                //printf("%x \n", MX1);
                z = v[p] += MX1;
                //printf("%x ", z);
                
            }
            y = v[0];
            z = v[n-1] += MX1;
        }
        while (--rounds);
    }
    else if (n < -1)      /* Decoding Part */
    {
        n = -n;
        rounds = 6 + 52/n;
        sum = rounds*DELTA;
        //printf("%d\n",rounds);
        y = v[0];
        do
        {
            e = (sum >> 2) & 3;
            for (p=n-1; p>0; p--)
            {
                z = v[p-1];
                y = v[p] -= MX1;
            }
            z = v[n-1];
            y = v[0] -= MX1;
            sum -= DELTA;
        }
        while (--rounds);
    }
}

unsigned char cipher[]={    0x5D, 0x45, 0xD5, 0xB9, 0x8C, 0x95, 0x9C, 0x38, 0x3B, 0xB1, 
0x3E, 0x1E, 0x5F, 0xC8, 0xE8, 0xBB, 0x64, 0x38, 0x48, 0x69};

int main()
{
    uint32_t *v = (uint32_t*)cipher; 
    
    uint32_t k[4]= {0x6e637466,0x62ef0ed,0xa78c0b4f,0x32303234};
    int n= 5; 
    btea(v, -n, k);
    unsigned char a;
    
    for(int i = 0; i < 5; i++) {
            //printf("%x ", v[i]);
        }
    for(int i=0;i<32;i++)
    {
            printf("%c", cipher[i]);
        }
        
        //NCTF{H4rd_VM_with_Go
    return 0;
}

exp2

#include <stdio.h>
#include <stdint.h>
#define DELTA 0x9e3779b9
#define MX1 (((z<<5^y>>2) + (y<<3^z>>4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))

void btea(uint32_t *v, int n, uint32_t *key)
{
    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];
                //printf("%x \n", (p&3)^e);
                //printf("%x \n", MX1);
                z = v[p] += MX1;
                //printf("%x ", z);
            }
            y = v[0];
            z = v[n-1] += MX1;
        }
        while (--rounds);
    }
    else if (n < -1)      /* Decoding Part */
    {
        n = -n;
        rounds = 6 + 52/n;
        sum = rounds*DELTA;
        //printf("%d\n",rounds);
        y = v[0];
        do
        {
            e = (sum >> 2) & 3;
            for (p=n-1; p>0; p--)
            {
                z = v[p-1];
                y = v[p] -= MX1;
            }
            z = v[n-1];
            y = v[0] -= MX1;
            sum -= DELTA;
        }
        while (--rounds);
    }
}

unsigned char cipher[] = {
    0xDE, 0x81, 0xD8, 0xAD, 0xC2, 0xC4, 0xA6, 0x32, 0x1C, 0xAB,
    0x61, 0x3E, 0xCB, 0xFF, 0xEF, 0xF1, 0x27, 0x30, 0x7A, 0x16
};

int main()
{
    uint32_t *v = (uint32_t*)cipher;
    
    uint32_t k[4] = {0x32303234, 0xd6eb12c3, 0x9f1cf72e, 0x4e435446};
    int n = 5;
    btea(v, -n, k);
    unsigned char a;
    
    for(int i = 0; i < 5; i++) {
        //printf("%x ", v[i]);
    }
    for(int i = 0; i < 20; i++)
    {
        printf("%c", cipher[i]);
    }
    //r0ut1n3_5fc4b0be7ad}
    return 0;
}

NCTF{H4rd_VM_with_Gor0ut1n3_5fc4b0be7ad}

本题是参照0psu3战队的wp复现的。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1621925986@qq.com

💰

×

Help us with donation