BUUCTF RE 看心情写(2)

[DASCTF X GFCTF 2022十月挑战赛!]贪玩CTF

参考:在 IDA Pro 9.1 和 x64dbg 同时调试同一个程序时,最方便、最准确的 地址对应方法。_x64 ida 地址对应-CSDN博客

看样子就是找到账号和密码

X64定位到关键字符串:

查看内存布局这里还是属于EXE文件内部:

然后我们ida修改下基址就能看到对应的代码

cpp 复制代码
// Hidden C++ exception states: #wind=3
void __fastcall sub_7FF60DCA21D0(__int64 a1, DuiLib::CDuiString *a2)
{
  const wchar_t *Data; // rax
  __int64 v5; // rax
  _WORD *v6; // rdx
  __int64 v7; // rax
  const wchar_t *Data_1; // rax
  __int64 v9; // r8
  _WORD *v10; // rdx
  struct DuiLib::CControlUI *Control; // rax
  DuiLib::CDuiString *v12; // rax
  const wchar_t *v13; // rdi
  struct DuiLib::CControlUI *v14; // rax
  DuiLib::CDuiString *v15; // rax
  const wchar_t *v16; // rbx
  int v17; // eax
  _WORD *v18; // rcx
  _WORD *v19; // rcx
  _WORD *v20; // rcx
  unsigned __int64 n61472; // r8
  _WORD *v22; // rcx
  _WORD *v23; // rcx
  __int64 v24; // r8
  _BYTE v25[144]; // [rsp+20h] [rbp-1C8h] BYREF
  _BYTE v26[144]; // [rsp+B0h] [rbp-138h] BYREF
  _BYTE v27[144]; // [rsp+140h] [rbp-A8h] BYREF

  DuiLib::CDuiString::operator==(a2, L"windowinit");
  Data = DuiLib::CDuiString::GetData(a2);
  v5 = sub_7FF60DCA1880(Data);
  v6 = &unk_7FF60DCA6278;
  v7 = v5 - (_QWORD)&unk_7FF60DCA6278;
  while ( *(_WORD *)((char *)v6 + v7) == *v6 )
  {
    if ( (__int64)++v6 >= (__int64)word_7FF60DCA6282 )
    {
      (*(void (__fastcall **)(_QWORD, _BYTE *, _WORD *))(**((_QWORD **)a2 + 34) + 16LL))(
        *((_QWORD *)a2 + 34),
        v27,
        word_7FF60DCA6282);
      Data_1 = DuiLib::CDuiString::GetData((DuiLib::CDuiString *)v27);
      v9 = sub_7FF60DCA1880(Data_1);
      v10 = &unk_7FF60DCA6268;
      while ( *(_WORD *)((char *)v10 + v9 - (_QWORD)&unk_7FF60DCA6268) == *v10 )
      {
        if ( (__int64)++v10 >= (__int64)word_7FF60DCA6270 )
        {
          Control = DuiLib::CPaintManagerUI::FindControl((DuiLib::CPaintManagerUI *)(a1 + 16), L"account");
          v12 = (DuiLib::CDuiString *)(*(__int64 (__fastcall **)(struct DuiLib::CControlUI *, _BYTE *))(*(_QWORD *)Control + 112LL))(
                                        Control,
                                        v25);
          v13 = DuiLib::CDuiString::GetData(v12);
          DuiLib::CDuiString::~CDuiString((DuiLib::CDuiString *)v25);
          v14 = DuiLib::CPaintManagerUI::FindControl((DuiLib::CPaintManagerUI *)(a1 + 16), L"password");
          v15 = (DuiLib::CDuiString *)(*(__int64 (__fastcall **)(struct DuiLib::CControlUI *, _BYTE *))(*(_QWORD *)v14 + 112LL))(
                                        v14,
                                        v26);
          v16 = DuiLib::CDuiString::GetData(v15);
          DuiLib::CDuiString::~CDuiString((DuiLib::CDuiString *)v26);
          v17 = sub_7FF60DCA19C0(v13, v16);
          if ( v17 == -2 )
          {
            MessageBoxW(0, L"账号或密码不能为空", &lpCaption_, 0);
          }
          else if ( v17 == -1 )
          {
            MessageBoxW(0, L"账号或者密码的长度不对", L"提示", 0);
          }
          else if ( v17 )
          {
            if ( v17 == 1 )
              MessageBoxW(0, L"恭喜你,但是后面的界面还没有写好", L"成功登录", 0);
          }
          else
          {
            MessageBoxW(0, L"账号或者密码不对", L"提示", 0);
          }
          goto LABEL_43;
        }
      }
      v18 = &unk_7FF60DCA6288;
      while ( *(_WORD *)((char *)v18 + v9 - (_QWORD)&unk_7FF60DCA6288) == *v18 )
      {
        if ( (__int64)++v18 >= (__int64)word_7FF60DCA6290 )
        {
          switch ( rand() % 7 )
          {
            case 0:
              MessageBoxW(0, L"你为什么会发现古天乐的脸上有东西?", L"idea", 0);
              break;
            case 1:
              MessageBoxW(0, &lpText__3, L"idea", 0);
              break;
            case 2:
              MessageBoxW(0, L"我是pysonw,系兄弟就来砍我", L"idea", 0);
              break;
            case 3:
              MessageBoxW(0, L"你是懂贪玩CTF的", L"idea", 0);
              break;
            case 4:
              MessageBoxW(0, L"界面比较烂,水平有限,师傅们轻点骂", L"idea", 0);
              break;
            case 5:
              MessageBoxW(0, L"欢迎来到贪玩CTF", L"idea", 0);
              break;
            case 6:
              MessageBoxW(0, L"想要提示吗?", L"idea", 0);
              break;
            default:
              goto LABEL_43;
          }
          goto LABEL_43;
        }
      }
      v19 = &unk_7FF60DCA6250;
      while ( *(_WORD *)((char *)v19 + v9 - (_QWORD)&unk_7FF60DCA6250) == *v19 )
      {
        if ( (__int64)++v19 >= (__int64)word_7FF60DCA6260 )
        {
          DuiLib::CWindowWnd::Close((DuiLib::CWindowWnd *)(a1 - 32), 1u);
          goto LABEL_43;
        }
      }
      v20 = &unk_7FF60DCA6298;
      while ( *(_WORD *)((char *)v20 + v9 - (_QWORD)&unk_7FF60DCA6298) == *v20 )
      {
        if ( (__int64)++v20 >= (__int64)word_7FF60DCA62A4 )
        {
          n61472 = 61472;
LABEL_42:
          DuiLib::CWindowWnd::SendMessageW((DuiLib::CWindowWnd *)(a1 - 32), 0x112u, n61472, 0);
          goto LABEL_43;
        }
      }
      v22 = &unk_7FF60DCA6240;
      while ( *(_WORD *)((char *)v22 + v9 - (_QWORD)&unk_7FF60DCA6240) == *v22 )
      {
        if ( (__int64)++v22 >= (__int64)word_7FF60DCA624C )
        {
          n61472 = 61488;
          goto LABEL_42;
        }
      }
      v23 = &unk_7FF60DCA62A8;
      v24 = v9 - (_QWORD)&unk_7FF60DCA62A8;
      while ( *(_WORD *)((char *)v23 + v24) == *v23 )
      {
        if ( (__int64)++v23 >= (__int64)word_7FF60DCA62BC )
        {
          n61472 = 61728;
          goto LABEL_42;
        }
      }
LABEL_43:
      DuiLib::CDuiString::~CDuiString((DuiLib::CDuiString *)v27);
      return;
    }
  }
}

看一下检验函数:

cpp 复制代码
__int64 sub_7FF60DCA19C0()
{
  char ___; // r8
  unsigned int v1; // ebx
  __int64 n192; // rdx
  __m128i ____1; // xmm2
  __int64 n192_1; // rax
  __m128i v5; // xmm2
  __m128i v6; // xmm2
  __int64 i; // rax
  char v8; // cl
  __m128i v9; // xmm2
  __int64 n192_2; // rax
  __m128i v11; // xmm2
  __m128i v12; // xmm2
  __int64 n16; // rax
  __int64 n16_1; // rax
  int v15; // r8d
  __int64 v16; // rax
  int v17; // edx
  __int64 v18; // rcx

  ___ = asc_7FF60DCA613F[0];                    // "⼖"
  v1 = 0;
  n192 = 192;
  ____1 = _mm_cvtsi32_si128(SLOBYTE(asc_7FF60DCA613F[0]));// "⼖"
  n192_1 = 0;
  v5 = _mm_unpacklo_epi8(____1, ____1);
  v6 = _mm_shuffle_epi32(_mm_unpacklo_epi16(v5, v5), 0);
  do
  {
    *(__m128i *)&asc_7FF60DCA6040[n192_1] = _mm_xor_si128(
                                              _mm_loadu_si128((const __m128i *)&asc_7FF60DCA6040[n192_1]),
                                              v6);// "橵浡緤퍹ᜦ㵱쇨悽铜毟俬슻릴늊홤ゅ⤠"
    *(__m128i *)&asc_7FF60DCA6040[n192_1 + 16] = _mm_xor_si128(
                                                   v6,
                                                   _mm_loadu_si128((const __m128i *)&asc_7FF60DCA6040[n192_1 + 16]));// "橵浡緤퍹ᜦ㵱쇨悽铜毟俬슻릴늊홤ゅ⤠"
    *(__m128i *)&asc_7FF60DCA6040[n192_1 + 32] = _mm_xor_si128(
                                                   _mm_loadu_si128((const __m128i *)&asc_7FF60DCA6040[n192_1 + 32]),
                                                   v6);// "橵浡緤퍹ᜦ㵱쇨悽铜毟俬슻릴늊홤ゅ⤠"
    *(__m128i *)((char *)&a5OL[(unsigned __int64)n192_1 / 2 + 4] + 1) = _mm_xor_si128(
                                                                          _mm_loadu_si128((const __m128i *)((char *)&a5OL[(unsigned __int64)n192_1 / 2 + 4] + 1)),
                                                                          v6);// "⋚柧⟎ሃ㗑໕ᎀᆌ阄ﷴꐱὣ㪕ഌ䱸䒶쀭㾥㧵䖒ᛇ㛻"
    n192_1 += 64;
  }
  while ( n192_1 < 192 );
  for ( i = 192; i < 255; ++i )
    asc_7FF60DCA6040[i] ^= ___;                 // "橵浡緤퍹ᜦ㵱쇨悽铜毟俬슻릴늊홤ゅ⤠"
  v8 = byte_7FF60DCA623F;
  v9 = _mm_cvtsi32_si128(byte_7FF60DCA623F);
  n192_2 = 0;
  v11 = _mm_unpacklo_epi8(v9, v9);
  v12 = _mm_shuffle_epi32(_mm_unpacklo_epi16(v11, v11), 0);
  do
  {
    *(__m128i *)((char *)&asc_7FF60DCA613F[(unsigned __int64)n192_2 / 2] + 1) = _mm_xor_si128(
                                                                                  _mm_loadu_si128((const __m128i *)((char *)&asc_7FF60DCA613F[(unsigned __int64)n192_2 / 2] + 1)),
                                                                                  v12);// "⼖"
    *(__m128i *)((char *)&unk_7FF60DCA6150 + n192_2) = _mm_xor_si128(
                                                         v12,
                                                         _mm_loadu_si128((const __m128i *)((char *)&unk_7FF60DCA6150
                                                                                         + n192_2)));
    *(__m128i *)((char *)&unk_7FF60DCA6160 + n192_2) = _mm_xor_si128(
                                                         v12,
                                                         _mm_loadu_si128((const __m128i *)((char *)&unk_7FF60DCA6160
                                                                                         + n192_2)));
    *(__m128i *)((char *)&unk_7FF60DCA6170 + n192_2) = _mm_xor_si128(
                                                         _mm_loadu_si128((const __m128i *)((char *)&unk_7FF60DCA6170
                                                                                         + n192_2)),
                                                         v12);
    n192_2 += 64;
  }
  while ( n192_2 < 192 );
  do
    *((_BYTE *)asc_7FF60DCA613F + ++n192) ^= v8;// "⼖"
  while ( n192 < 255 );
  printf(Buffer, "%ws");
  printf(&Buffer_, "%ws");
  if ( !Buffer[0] || !Buffer_ )
    return 4294967294LL;
  n16 = -1;
  do
    ++n16;
  while ( Buffer[n16] );
  if ( n16 != 16 )
    return 0xFFFFFFFFLL;
  n16_1 = -1;
  do
    ++n16_1;
  while ( *(&Buffer_ + n16_1) );
  if ( n16_1 != 16 || (unsigned int)sub_7FF60DCA1390() )
    return 0xFFFFFFFFLL;
  v15 = 0;
  Buffer[0] ^= Buffer[15];
  Buffer[1] ^= Buffer[15];
  Buffer[2] ^= Buffer[15];
  Buffer[3] ^= Buffer[15];
  Buffer[4] ^= Buffer[15];
  Buffer[5] ^= Buffer[15];
  Buffer[6] ^= Buffer[15];
  Buffer[7] ^= Buffer[15];
  Buffer[8] ^= Buffer[15];
  Buffer[9] ^= Buffer[15];
  Buffer[10] ^= Buffer[15];
  Buffer[11] ^= Buffer[15];
  Buffer[12] ^= Buffer[15];
  Buffer[13] ^= Buffer[15];
  Buffer[14] ^= Buffer[15];
  v16 = 0;
  while ( Buffer[v16] == byte_7FF60DCA4410[v16] )
  {
    if ( ++v16 >= 16 )
      goto LABEL_22;
  }
  v15 = 1;
LABEL_22:
  v17 = 0;
  v18 = 0;
  while ( byte_7FF60DCA6990[v18] == byte_7FF60DCA43D8[v18] )
  {
    if ( ++v18 >= 16 )
      goto LABEL_27;
  }
  v17 = 1;
LABEL_27:
  if ( v15 )
    return 0;
  LOBYTE(v1) = v17 == 0;
  return v1;
}

归根结底就是经过一系列异或变换,然后进行和密文比对

对于账号是很好解密的:

python 复制代码
import string

T = [
    0x04, 0x1F, 0x1F, 0x1E,
    0x43, 0x4B, 0x43, 0x45,
    0x44, 0x00, 0x16, 0x10,
    0x55, 0x17, 0x12, 0x73
]

def gen_account(k):
    A = [0]*16
    A[15] = k
    for i in range(15):
        A[i] = T[i] ^ k
    return bytes(A)

for k in range(256):
    acc = gen_account(k)
    try:
        s = acc.decode()
        if all(c in string.printable for c in s):
            print(k, s)
    except:
        pass
#output:115 wllm08067sec&das

第二个密码逻辑就不贴了,就是AES,密钥则是账号

exp:

python 复制代码
#output:e4deb7a6510a10f7
from Crypto.Cipher import AES


def main():
    # 你逆出来的 AES-128 key
    key = b"wllm08067sec&das"   # 16 bytes

    # 密文:3C 97 72 96 5A 33 63 9C 97 30 4D 90 84 E8 5F 56
    ciphertext = bytes([
        0x3C, 0x97, 0x72, 0x96,
        0x5A, 0x33, 0x63, 0x9C,
        0x97, 0x30, 0x4D, 0x90,
        0x84, 0xE8, 0x5F, 0x56
    ])

    # 单块 AES-128,模式相当于 ECB
    cipher = AES.new(key, AES.MODE_ECB)
    plaintext = cipher.decrypt(ciphertext)

    print("ciphertext hex :", ciphertext.hex())
    print("plaintext hex  :", plaintext.hex())

    try:
        print("plaintext str  :", plaintext.decode("utf-8"))
    except UnicodeDecodeError:
        print("plaintext str  : <not valid UTF-8>")


if __name__ == "__main__":
    main()

拼起来即可

[DASCTF Apr.2023 X SU战队2023开局之战]

python 复制代码
import random
r = random.Random(322376503)
pt = input('Enter your flag: ').encode()
ct = b'\x8b\xcck\xd3\xed\x96\xffFb\x06r\x085\x82\xbc \xb2\xde)p\x88Q`\x1bf\x18\xb6QUSw\x10\xcd\xd9\x13A$\x86\xe5\xcd\xd9\xff'
buf = []
for b in pt:
    buf.append(r.randint(0, 255) ^ b)
assert bytes(buf) == ct
print('Correct!')

稍微修改下润色脚本即可

python 复制代码
import random

# 随机数种子(固定)
r = random.Random(322376503)

# 密文
ct = b'\x8b\xcck\xd3\xed\x96\xffFb\x06r\x085\x82\xbc \xb2\xde)p\x88Q`\x1bf\x18\xb6QUSw\x10\xcd\xd9\x13A$\x86\xe5\xcd\xd9\xff'

buf = []
# 逐个字节异或(核心修复)
for c in ct:
    random_byte = r.randint(0, 255)  # 每次生成一个字节
    plain_byte = random_byte ^ c    # 字节和数字异或
    buf.append(plain_byte)

# 转成字节串输出
result = bytes(buf)
print(result)

[NewStarCTF 2023 公开赛道]Let's Go

代码就不贴了,看出来是一个AES

但是刚开始解密一直错误

后来才知道init函数会在main函数之前执行,这个函数更改了iv[NewStarCTF2023]Week3 - Tree_24 - 博客园

先异或操作再解密

[DDCTF2018]RSA

这是java代码:

java 复制代码
package com.didictf.guesskey2018one;

import android.os.Bundle;
import android.support.v7.p013a.ActivityC0456u;
import android.view.View;
import android.widget.TextView;

/* loaded from: classes.dex */
public class MainActivity extends ActivityC0456u {

    /* renamed from: m */
    private TextView f1715m;

    /* renamed from: n */
    private TextView f1716n;

    static {
        System.loadLibrary("hello-libs");
    }

    public void onClickTest(View view) {
        this.f1716n.setText("Empty Input");
        if (stringFromJNI(this.f1715m.getText().toString())) {
            this.f1716n.setText("Correct");
        } else {
            this.f1716n.setText("Wrong");
        }
    }

    @Override // android.support.v7.p013a.ActivityC0456u, android.support.v4.p002a.ActivityC0085x, android.support.v4.p002a.AbstractActivityC0078q, android.app.Activity
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_main);
        this.f1715m = (TextView) findViewById(R.id.flag_entry);
        this.f1716n = (TextView) findViewById(R.id.flag_result);
    }

    public native boolean stringFromJNI(String str);
}

我们接下来去so层:

如下,很多关键函数都是需要导入

apk内还有两个so,我们看他们导出表即可得知谁提供了这些函数

但是都是标准实现,所以还得看hello_libc.so文件

跟踪main找到这个函数:

cpp 复制代码
int __fastcall __aeabi_wind_cpp_prj(int *a1)
{
  int v1; // r0
  int v2; // r6
  int i; // r5
  char v4; // r2
  unsigned int v5; // r0
  unsigned __int8 *v6; // r1
  int v7; // r7
  unsigned int v8; // r3
  int n5; // r5
  unsigned int v10; // r6
  unsigned __int64 n2; // kr00_8
  int v12; // r0
  int v13; // r0
  unsigned int v14; // r4
  int v15; // r5
  char *v16; // r0
  unsigned __int64 v17; // kr08_8
  unsigned __int64 n2_1; // r0
  _BOOL4 v19; // r2
  int v20; // r2
  bool v21; // cf
  int v22; // r0
  unsigned int v24; // [sp+4h] [bp-8Ch]
  char *v25; // [sp+4h] [bp-8Ch]
  char v27; // [sp+10h] [bp-80h]
  unsigned __int8 *v28; // [sp+10h] [bp-80h]
  char *v29; // [sp+14h] [bp-7Ch] BYREF
  int v30; // [sp+18h] [bp-78h] BYREF
  _DWORD v31[5]; // [sp+1Ch] [bp-74h] BYREF
  int v32; // [sp+30h] [bp-60h] BYREF
  int v33; // [sp+34h] [bp-5Ch] BYREF
  int v34; // [sp+38h] [bp-58h] BYREF
  void *v35; // [sp+3Ch] [bp-54h] BYREF
  __int16 v36; // [sp+42h] [bp-4Eh] BYREF
  char src[44]; // [sp+4Ch] [bp-44h] BYREF

  v1 = *a1;
  v2 = 0;
  if ( *(_DWORD *)(v1 - 12) == 43 )
  {
    for ( i = 0; i != -43; --i )
    {
      v4 = *((_BYTE *)&unk_4DEF3 - i);
      if ( *(int *)(v1 - 4) >= 0 )
      {
        v27 = *((_BYTE *)&unk_4DEF3 - i);
        sub_2FF8C(a1);
        v4 = v27;
        v1 = *a1;
      }
      src[-i] = *(_BYTE *)(v1 - i) ^ v4;
    }
    v5 = *(_DWORD *)(v1 - 12);
    v6 = (unsigned __int8 *)&v36;
    v7 = 0;
    v8 = 0;
    n5 = 0;
LABEL_7:
    v24 = v8;
    v28 = v6;
    while ( 1 )
    {
      if ( n5 >= 1 && v8 < v5 )
      {
        v2 = 0;
        if ( v6[10] != *v6 )
          break;
      }
      ++v8;
      ++v6;
      if ( ++v7 >= 10 )
      {
        v8 = v24 + 10;
        v6 = v28 + 10;
        ++n5;
        v7 = 0;
        if ( n5 < 5 )
          goto LABEL_7;
        v10 = 0;
        src[10] = 0;
        new_string(&v30, src, (int)&v33);
        sub_2F50C(a1, &v30);
        sub_308E4((int *)(v30 - 12));
        sub_30A08(&v34, a1);
        j_str2vec(&v35, &v34);
        sub_308E4((int *)(v34 - 12));
        n2 = j_atoll((const char *)*a1);
        new_string(&v33, "deknmgqipbjthfasolrc", (int)&v30);
        new_string(&v32, "jlocpnmbmbhikcjgrla", (int)&v30);
        memset(v31, 0, sizeof(v31));
        v31[2] = v31;
        v31[3] = v31;
        v12 = v33;
        if ( *(_DWORD *)(v33 - 12) )
        {
          do
          {
            if ( *(int *)(v12 - 4) >= 0 )
            {
              sub_2FF8C(&v33);
              v12 = v33;
            }
            *(_DWORD *)j_std::map<char,int>::operator[](&v30, v12 + v10) = (int)v10 / 2;
            v12 = v33;
            ++v10;
          }
          while ( v10 < *(_DWORD *)(v33 - 12) );
        }
        sub_30A08((int *)&v29, &v32);
        v13 = v32;
        if ( *(_DWORD *)(v32 - 12) )
        {
          v14 = 0;
          do
          {
            if ( *(int *)(v13 - 4) >= 0 )
            {
              sub_2FF8C(&v32);
              v13 = v32;
            }
            v15 = *(_DWORD *)j_std::map<char,int>::operator[](&v30, v13 + v14) + 48;
            v16 = v29;
            if ( *((int *)v29 - 1) >= 0 )
            {
              sub_2FF8C(&v29);
              v16 = v29;
            }
            v16[v14] = v15;
            v13 = v32;
            ++v14;
          }
          while ( v14 < *(_DWORD *)(v32 - 12) );
        }
        v25 = v29;
        v17 = j_atoll(v29);
        if ( v17 % n2 )
          goto LABEL_36;
        n2_1 = v17 / n2;
        v2 = 1;
        v19 = (unsigned int)n2 < 2;
        if ( HIDWORD(n2) )
          v19 = (n2 & 0x8000000000000000LL) != 0LL;
        if ( v19 )
          goto LABEL_36;
        v20 = 1;
        v21 = (unsigned int)n2 >= (unsigned int)n2_1;
        v22 = 1;
        if ( v21 )
          v22 = 0;
        if ( HIDWORD(n2) >= HIDWORD(n2_1) )
          v20 = 0;
        if ( HIDWORD(n2) != HIDWORD(n2_1) )
          v22 = v20;
        if ( !v22 )
LABEL_36:
          v2 = 0;
        sub_308E4((int *)v25 - 3);
        j_std::_Rb_tree<char,std::pair<char const,int>,std::_Select1st<std::pair<char const,int>>,std::less<char>,std::allocator<std::pair<char const,int>>>::_M_erase(
          &v30,
          v31[1]);
        sub_308E4((int *)(v32 - 12));
        sub_308E4((int *)(v33 - 12));
        if ( v35 )
          j_operator delete(v35);
        return v2;
      }
    }
  }
  return v2;
}

逻辑也很简单:

1.先是对输入长度进行校验,随后输入异或一个字符串数组。

2.然后是将一个本来长度是10数组填充到30长度,后面又赋值某数组第十位为0,总体来说就是让一个十位长度字符串变为一个数字

3.先构造一个映射表:

*j_std::map<char,int>::operator[](&v30, str1_1 + v10) = v10 / 2;

字符 数字
d, e 0
k, n 1
m, g 2
q, i 3
p, b 4
j, t 5
h, f 6
a, s 7
o, l 8
r, c 9

第二个构造规则类似于map[chr]+48,先返回一个index再将他转为字符(因为48是字符串0的ASCII码)

总体来说就是根据映射表,确定在str2中字符对应的数字,然后进行输出

结果是:

python 复制代码
str1= "deknmgqipbjthfasolrc"
str2 = "jlocpnmbmbhikcjgrla"
print(''.join(str(str1.index(ch) // 2) for ch in str2))

5889412424631952987

上图n2是a1就是input字符串,后面校验要求这个a1是比较小的那个除数

在这个网站分解即可:
factordb.com

然后和开头的const char异或就眉毛了

这个是官方wp,我没有找到这个异或表在哪里发现的,可惜之!

python 复制代码
a = [73, 90, 75, 10, 67, 92, 65, 80, 65, 75, 85, 93, 67, 13, 70, 64, 65, 1, 92, 6, 1, 89, 91, 14, 90, 82, 65, 93, 8, 94, 6]
r = "1499419583"*4
for i in range(31):
    print(chr(ord(r[i])^a[i]), end='')

[DASCTF X BUUOJ 五月大联动]end

脱壳之后看Main函数:

cpp 复制代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  int v4; // eax
  size_t v5; // eax
  int v7[22]; // [esp+18h] [ebp-47Ch] BYREF
  char Buffer[19]; // [esp+71h] [ebp-423h] BYREF
  _BYTE v9[16]; // [esp+84h] [ebp-410h] BYREF
  int v10; // [esp+94h] [ebp-400h] BYREF
  _DWORD buf[250]; // [esp+98h] [ebp-3FCh] BYREF
  int v12; // [esp+480h] [ebp-14h]
  int v13; // [esp+484h] [ebp-10h]
  int v14; // [esp+488h] [ebp-Ch]
  int i; // [esp+48Ch] [ebp-8h]

  __main();
  i = 0;
  v14 = 0;
  memset(buf, 0, sizeof(buf));
  v12 = 0;
  do
  {
    while ( 1 )
    {
      printf("Please input:");
      scanf("%d", &v10);
      v3 = i++;
      buf[v3] = v10;
      switch ( v10 )
      {
        case 2:
          v14 += 7;
          break;
        case 8:
          v14 -= 7;
          break;
        case 6:
          ++v14;
          break;
        case 4:
          --v14;
          break;
        default:
          exit(1);
      }
      if ( !amp[v14] )
        exit(1);
      if ( amp[v14] != 1 )
        break;
      printf("continue");
    }
  }
  while ( amp[v14] != 35 );
  while ( buf[v12] )
  {
    v4 = v12++;
    v13 += buf[v4];
  }
  sprintf(Buffer, "%d", v13);
  printf("%s", Buffer);
  MD5Init(v7);
  v5 = strlen(Buffer);
  MD5Update((int)v7, Buffer, v5);
  MD5Final(v7, v9);
  printf("congratulation");
  printf("the flag is {");
  for ( i = 0; i <= 15; ++i )
    printf("%02x", (unsigned __int8)v9[i]);
  putchar(125);
  return 0;
}

我们的输入只能是2、4、6、8,每次输入都会对一个num进行校验,必须得让数值对应的num的地方是1

经过AI提示才知道这是迷宫题

2是上一行,8是下一行,6向右,4向左

python 复制代码
a = ['^', 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
     0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
     0x0, 0x1, 0x0, 0x1, 0x1, 0x1, 0x0,
     0x0, 0x1, 0x1, 0x1, 0x0, 0x1, 0x0,
     0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0,
     0x1, 0x0, 0x0, 0x1, 0x1, 0x1, 0x0,
     0x1, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
     0x0, 0x0, 0x0, 0x1, 0x1, 0x1, '#']

for i in range(8):
    for j in range(7):
        x = a[i * 7 + j]
        print(x, end='')
    print()

[DASCTF2022.07赋能赛]ezGo

这应该是类似于RSA的一种算法。

那也就是说V24=result+p*k再开个根号

我们暴力搜索即可

问了ChatGPT说是需要用:Tonelli-Shanks 算法

exp:

python 复制代码
import gmpy2
from Crypto.Util.number import long_to_bytes

n = 131453094564548508772284336424680998857035326273571981446094083416917514535349876760437096547435610190391556347148927592380050533193934285571983556924577144473815598516557161
c = 33529281532734294938614341047870321616766628114182320093600990983456360122704185955921012051918080449587733939007294096845300395098833835443815283246602601870001850089370636

p = 17489158711316178659
q = 7516261744453902635364442762653073356746063224482072262455102025715350278471780391042196223686233375846890331396948280463168691132631674699134296333350979

assert p * q == n

# 分别在 mod p / mod q 下开平方
mp = pow(c, (p + 1) // 4, p)
mq = pow(c, (q + 1) // 4, q)

# CRT 合并
inv_p = gmpy2.invert(p, q)
inv_q = gmpy2.invert(q, p)

r1 = (inv_p * p * mq + inv_q * q * mp) % n
r2 = n - r1
r3 = (inv_p * p * mq - inv_q * q * mp) % n
r4 = n - r3

roots = [r1, r2, r3, r4]

for i, r in enumerate(roots, 1):
    b = long_to_bytes(int(r))
    print(f'root{i}:', b)
    if len(b) == 40:
        print('40 bytes ->', b.decode(errors='ignore'))

[DASCTF 2023六月挑战赛|二进制专项]unsym

代码也不贴了,大概逻辑是前面是RSA看key是否为正确,后面是AES的CBC模式,估计是解密题目给的附件

yafu分解一手n

RSA的exp:

python 复制代码
import gmpy2
from Crypto.Util.number import long_to_bytes

n = 0x1d884d54d21694ccd120f145c8344b729b301e782c69a8f3073325b9c5
p = 37636318457745167234140808130156739
q = 21154904887215748949280410616478423
c = 0xfad53ce897d2c26f8cad910417fbdd1f0f9a18f6c1748faca10299dc8
e = 0x10001
phi = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m))

密钥:E@sy_RSA_enc7ypt

AESexp:

python 复制代码
from Crypto.Cipher import AES

KEY = b"E@sy_RSA_enc7ypt"
IV = KEY

def pkcs7_unpad(data: bytes) -> bytes:
    if not data:
        raise ValueError("empty plaintext")
    pad = data[-1]
    if pad < 1 or pad > 16:
        raise ValueError(f"invalid padding length: {pad}")
    if data[-pad:] != bytes([pad]) * pad:
        raise ValueError("invalid PKCS#7 padding")
    return data[:-pad]

def main():
    with open("encrypted.bin", "rb") as f:
        enc = f.read()

    if len(enc) % 16 != 0:
        raise ValueError("ciphertext length is not a multiple of 16")

    cipher = AES.new(KEY, AES.MODE_CBC, IV)
    dec = cipher.decrypt(enc)
    dec = pkcs7_unpad(dec)

    with open("decrypted.bin", "wb") as f:
        f.write(dec)

    print("[+] decrypted -> decrypted.bin")

if __name__ == "__main__":
    main()

出来的文件ida打开:

[XMAN2018排位赛]easywasm

直接wabt反编译会失败,因为wasm2c不支持动态的连接

整体逻辑是异常复杂的,这里用jeb和ghidra联合来看

写一个调试的脚本:

这里参考了BUU-Reveser-XMAN2018排位赛_easywasm(WebAssembly逆向分析)_buuctf[[xman2018排位赛]easywasm的flag-CSDN博客

javascript 复制代码
const imports = {  
  env: {  
    memory: new WebAssembly.Memory({ initial: 256 }),  
    table: new WebAssembly.Table({ initial: 16, element: 'anyfunc' }),  
    memoryBase: 0,  
    tableBase: 0,  
    abort: function(param) {  
      // 实现 abort 函数的逻辑  
    }  
  }  
};  

  
fetch('easywasm.wasm')  
  .then(response => response.arrayBuffer())  
  .then(buffer => WebAssembly.instantiate(buffer, imports))  
  .then(results => {  
  const instance = results.instance;  //导入函数
  const env1 = imports.env; //导入变量
  const memoryBuffer = new Uint8Array(env1.memory.buffer);  //创建一片空间指向env1.memory.buffer
      
  // 要传递给 Wasm 的字符串  
  const str = "abcdaaaaaaaaaaaaaaaaaaaaaaaaaaaa";  
    
  // 使用 TextEncoder 将字符串转换为 UTF-8 字节数组  
  const encoder = new TextEncoder();  
  const strBytes = encoder.encode(str);  
    
  // 将字节数组写入 WebAssembly 内存  
  const offset = 1800; // 从内存的哪个位置开始写入  ,相当于内存地址
  memoryBuffer.set(strBytes, offset);  //将数据写入内存

  // 调用 Wasm 函数,传递字节数组的地址和长度  
  const result = instance.exports._check(1800);  //这里的1800相当于我们存储的字符串的地址
    
  console.log('Result:', result);  
});

我们必须给wasm运行的时候的各种依赖,他才能跑起来。这也是AI生成的

然后我们在call md5处下断点

右键保存内存变量

输入以下提取数据的脚本:

javascript 复制代码
// 1. 把 Memory 对象转成 Uint8Array 字节视图
const memView = new Uint8Array(temp1.buffer);

// 2. 读取你关心的地址(比如之前的 112)
const addr = 112;
const byteAtAddr = memView[addr]; // 地址112处的单个字节
console.log("地址 112 处的字节:", byteAtAddr);

// 3. 读取一段连续内存(比如从 112 开始读 32 字节)
const slice = memView.slice(addr, addr + 32);
console.log("地址 112 开始的 32 字节:", slice);

// 4. 如果这段数据是字符串,直接解码
const str = new TextDecoder().decode(slice);
console.log("解码后的字符串:", str);

解码的字符串是 2333333333333333333333333333333a

a就是我们输入的字符串,也就是说第一次md5其实是2333拼接我们的字符串第一个字节

第二处md5同理获得参数为:8ac44c16486e4d259983938fad820a7a

然后就是字符串比较函数,参数为64c286cfc623aa8d7df7c088ebf7d718

看大佬博客介绍,我们MemroryBase设错了,导致var0处一直被覆盖为0

javascript 复制代码
<script>
  // 【固定正确】WebAssembly 必需的导入环境
  const imports = {
    env: {
      memory: new WebAssembly.Memory({ initial: 256 }),
      table: new WebAssembly.Table({ initial: 16, element: 'anyfunc' }),
      memoryBase: 1900,     
      tableBase: 0,       
      abort: (ptr) => {   // ✅ 崩溃时会打印错误信息
        console.error('Wasm abort! 内存地址:', ptr);
        throw new Error('WebAssembly 中止');
      }
    }
  };

  fetch('easywasm.wasm')
    .then(res => {
      if (!res.ok) throw new Error('wasm 文件加载失败!404 了');
      return res.arrayBuffer();
    })
    .then(buffer => WebAssembly.instantiate(buffer, imports))
    .then(({ instance }) => {
      console.log('✅ Wasm 加载成功');

      const memory = instance.exports.memory || imports.env.memory;
      const memoryView = new Uint8Array(memory.buffer);

      // ======================
      // 字符串写入内存(安全区域)
      // ======================
      const str = "abcdaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
      const encoder = new TextEncoder();
      const strBytes = encoder.encode(str);

      // ✅ 使用安全偏移 1024(避开系统占用区)
      const STR_OFFSET = 1024;
      memoryView.set(strBytes, STR_OFFSET);

      // ======================
      // 调用 Wasm 导出函数 _check
      // ======================
      const result = instance.exports._check(STR_OFFSET);
      console.log('✅ 调用 _check 结果:', result);

    })
    .catch(err => {
      // ✅ 关键:所有错误都会在这里打印,你之前看不到就是因为没有这个
      console.error('❌ Wasm 运行失败:', err);
    });
</script>

这样就可进行参数完全提取了

然后控制台输入:

javascript 复制代码
const memView = new Uint8Array(temp1.buffer);

// 2. 读取你关心的地址(比如之前的 112)
const addr = 1900;
const byteAtAddr = memView[addr]; // 地址112处的单个字节
console.log("地址 112 处的字节:", byteAtAddr);

// 3. 读取一段连续内存(比如从 112 开始读 32 字节)
const slice = memView.slice(addr, addr + 33*32);
console.log("地址 112 开始的 32 字节:", slice);

// 4. 如果这段数据是字符串,直接解码
const str = new TextDecoder().decode(slice);
console.log("解码后的字符串:", str);

提取之后,我们根据逻辑写一个脚本爆破即可

exp:

python 复制代码
import hashlib
target=['562fe3cc50014c260d9e8cf4ed38c77a',
'c022ad0cc0075a9ab14b412a1082d5f3',
'64c286cfc623aa8d7df7c088ebf7d718',
'83664bdee4b613b7e7a51b5213470a8d',
'b020bf598aaa2b3e03ed02c85436268a',
'4fdac5ac807506938103e775c50099ed',
'4fdac5ac807506938103e775c50099ed',
'c231d607b6823fd0a68e813760809754',
'd168c21d10371a5ab61bcfe6c759ef6e',
'f60d709ccf989d849028f97a03d2f3ba',
'a0184f8240e2fe46861dc8d15a819cb0',
'9dbec414336e741e9c73422df59de297',
'6fb5209d8fc8bb8507245bcfa24ae11f',
'6fb5209d8fc8bb8507245bcfa24ae11f',
'00c77fbc60a5bfc466d3d069876ec348',
'00c77fbc60a5bfc466d3d069876ec348',
'df33464fb471c46abaf691c000a0e30d',
'4fdac5ac807506938103e775c50099ed',
'f60d709ccf989d849028f97a03d2f3ba',
'fcc94a20596f2619868f3a4bf52eadf7',
'00c77fbc60a5bfc466d3d069876ec348',
'd168c21d10371a5ab61bcfe6c759ef6e',
'9dbec414336e741e9c73422df59de297',
'fcc94a20596f2619868f3a4bf52eadf7',
'9b37db091979bedf00a7095851ba6f59',
'00c77fbc60a5bfc466d3d069876ec348',
'f60d709ccf989d849028f97a03d2f3ba',
'fcc94a20596f2619868f3a4bf52eadf7',
'd168c21d10371a5ab61bcfe6c759ef6e',
'f60d709ccf989d849028f97a03d2f3ba',
'183342997ffed4b3189e977d077a60b4',
'f404a3368d2d8f57464f739d4ed01c0e']
flag=""
for i in range(32):
    for j in range(0,128):
        data=b"2333333333333333333333333333333"+bytes([j])
        result=hashlib.md5(data).hexdigest()
        result1=hashlib.md5(result.encode()).hexdigest()
        if result1 == target[i]:
            flag+=chr(j)
            break
print(flag)
        

[DASCTF 2023 & 0X401七月暑期挑战赛]controlflow

简单分析一会,就可以发现这道题利用栈更改了执行流,IDA根本识别不出来了

因为题目更改了很多栈结构,因此我们纯静态分析已经不太行了。这里可结合动调观察栈来解决这道题

现在先尝试下静态分析:

我们点击进入看看发生了什么事

可以看到就这个过程就压栈了一些东西,我们难道要每一步对栈的操作都画出来吗,那也太耗时间耗精力,我们动调看就行

断点下在scanf后面,这时候栈如图所示:

当我们试图return,必不可少要经过这三个函数

执行流程大概如下:

第一步:某个地方的值加i^2

这里看了[DASCTF 2023 & 0X401七月暑期挑战赛] REV1 controlflow复现_dasctf7月wp-CSDN博客

大佬博客得知下面还有东西:

调用了一个290

第二步:从V1+40(也就是第十个元素)开始异或

第二步执行完成之后:

这里先去4470,4470执行以下操作也就是-i:

然后乘一个三

这里的43A8就是input

最终回到了比较函数290

这里用了混淆,将current和wrong用异或方式隐藏了,不过不影响咱们正向追溯到这个流程

整体流程总结一下:

1.main函数先异或0x401,然后栈里藏东西跳到下一步执行

2.+i^2,异或i*(i+1)(这个异或是只针对第十个元素开始到第二十元素)

3.-i,乘三

4.交换

5.比较函数和v3进行比较

详细可以参考这位大佬的总结:

逆向写出exp:

python 复制代码
v3 = [
    3279, 3264, 3324, 3288, 3363, 3345, 3528, 3453, 3498, 3627,
    3708, 3675, 3753, 3786, 3930, 3930, 4017, 4173, 4245, 4476,
    4989, 4851, 5166, 5148, 4659, 4743, 4596, 5976, 5217, 4650,
    6018, 6135, 6417, 6477, 6672, 6891, 7056, 7398, 7650, 7890
]

# step1: 撤销 100,偏移10开始的20个数,两两交换
a = v3[:]
for i in range(10, 30, 2):
    a[i], a[i + 1] = a[i + 1], a[i]

# step2: 撤销 1E4,/3 后 +i
b = [((x // 3) + i) & 0xffffffff for i, x in enumerate(a)]

# step3: 撤销 560,偏移10开始的20个数 ^(i*(i+1))
c = b[:]
for i in range(20):
    c[10 + i] ^= i * (i + 1)

# step4: 撤销 220,-i*i
d = [((x - i * i) & 0xffffffff) for i, x in enumerate(c)]

# step5: 撤销 main,^0x401
flag = bytes([(x ^ 0x401) & 0xff for x in d])

print(flag)
print(flag.decode())
相关推荐
2401_879693872 小时前
C++跨平台开发实战
开发语言·c++·算法
第二只羽毛2 小时前
第四章 串
大数据·数据结构·c#
皙然2 小时前
深度解析 “池化思想”:从设计模式到 Java 技术栈的落地与实践
java·开发语言·设计模式
旺仔.2912 小时前
C++ String 详解
开发语言·c++·算法
智算菩萨2 小时前
OpenCV+Python3.13图像读写实战:从文件加载到内存操作的全流程详解(附源码)
开发语言·图像处理·python·opencv·yolo
ejjdhdjdjdjdjjsl2 小时前
三角形缺陷检测,划痕检测和毛刺检测
c#
2301_816651222 小时前
模板代码跨平台适配
开发语言·c++·算法
m0_743470372 小时前
C++代码静态检测
开发语言·c++·算法
m0_738098022 小时前
C++中的代理模式实战
开发语言·c++·算法