[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())