April 3, 2022

日常随缘复现

如题

VNCTF

BabyMaze

pyc加花。基本思路跟exe文件去花指令差不多,一般是各种跳来跳去的指令之间夹一些没用的指令,扰乱反编译器分析。直接dis看字节码,然后去修十六进制文件。

这个题可以很明显看出来,0处的指令就是跳转到4,而4处的指令是跳转到2,2处的指令是跳转到6,6处的指令就是正常的创建迷宫的指令了,所以把前三句patch掉。
在十六进制编辑器里把这三句删掉,然后改字节码长度co_code,减去6(因为上面三条指令是6个字节)。

pyc文件格式

如果实在不知道co_code在哪里,可以用hex(len(code.co_code))看一下指令长度,然后在十六进制编辑器前几行里找这个值。

但是这个题很奇怪,为什么时间戳的地方都是0……
然后uncompyle6,看源码走迷宫就好了。

时空飞行

官方wp都有脚本,我就不抄过来了(逃
说一下中间那一步为什么要深搜:
因为那个方程是有很多组解的,直接逆向写脚本的话只能得到1组解,而z3也只能得到少数几组解,只有深搜能保证得到所有解。
此后对所有解中进行下一步逆向验证,符合条件的就正确,也就是在所有解里爆破了。

DASCTF

easyre

脚本不贴了,都是抄IDA代码,懒得改那一坨很丑的代码了
简单说一下我遇到的问题:
1.我刚开始手动脱壳的,虽然跑不了,但是IDA可以反编译出来。后来去网上搜了搜,大部分都是脱壳机……我下了好几个脱壳机,都说没有ASPack,很迷。有没有带师有什么很nb的脱壳机,请务必告诉我
2.加密是用的一个魔改的RC4,代码逻辑是什么不管,直接复制IDA代码,简单改一下然后跑。按道理来讲这种RC4不应该管他的逻辑,直接输入然后动调取值来异或,但是我手动脱的壳可能有些问题,(看别人说要用一个工具修复导入表,但是我摆弄了半天那个程序,没搞懂怎么玩的),跑不了所以没法动调。OD调试也8太会,输入之后直接退出了,麻。
3.刚开始跑出来是乱码,因为我最开始改的python,都用的range函数,改成C为了方便都用了while循环,结果我把计数器自增的代码写到最前面了,我是什么憨批。
3.改完之后跑是能跑出来了,但是中间有几个字符是乱码。虽然已经半夜两点多,但是不知道原因我是真!的!难!受!啊!!!网上找了份WP,两个代码一起调,发现是数据类型的锅。RC4里s盒正常人都用的unsigned char类型,我写脚本也顺手写成无符号字符型了,但是由于这个题是魔改,对s盒运算的时候有一些奇怪的运算,要用int才能保证运算结果的正确性,unsigned char会溢出而损失部分运算结果……我!#%^@$@^*#

HayyimCTF

ezrev

异或值不知道,后面有解base64,爆破一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enc=[0x73, 0x5A, 0x5C, 0x78, 0x76, 0x55, 0x48, 0x25, 0x76, 0x55, 0x7A, 0x7A, 0x70, 0x7F, 0x66, 0x68, 0x4A, 0x20, 0x48,0x64, 0x71, 0x7E, 0x2B, 0x7E, 0x70, 0x7F, 0x62, 0x64, 0x77,0x45, 0x7E, 0x67, 0x48, 0x23, 0x2B, 0x7D, 0x71, 0x20, 0x5C,0x22, 0x48, 0x7B, 0x54, 0x2B]
import base64
x=0
while 1:
flag=''
x+=1
for i in enc:
flag+=chr(i^x)
if x>0xff:
break
try:
f=base64.b64decode(flag.encode())
if b'ctf{' in f:
print(f)
break
except:
continue

hgame

upx0

没壳,被骗了
crc16,打表搜就行。

1
2
3
4
5
6
7
8
9
10
11
12
crc=[]
v4=[36200,40265,10770,43802,52188,47403,11826,40793,56781,40265,43274,3696,62927,2640,23285,65439,40793,48395,22757,14371,48923,30887,43802,18628,43274,11298,40793,23749,24277,30887,9842,22165]
for i in range(0x7f):
t=i<<8
for j in range(8):
if t&0x8000!=0:
t=(t<<1)^0x1021
else:
t<<=1
crc.append(t&0xffff)
for i in v4:
print(chr(crc.index(i)),end='')

upx1

https://xz.aliyun.com/t/6881

还能改magic,这波属实长见识了(什
解密都一样,就8说了,之后专门写一篇关于壳的(挖坑++

Answer’s Windows

好大的文件……关键字符串
background-image: url(:/new/prefix1/C:/Users/Answer/Desktop/right.png);
定位到核心逻辑。往上翻可以翻到一个像密文的东东:
;'>B<76\\=82@-8.@=T\"@-7ZU:8*F=X2J<G>@=W^@-8.@9D2T:49U@1aa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
if ( v21 != 56
|| (unsigned int)((__int64 (__fastcall *)(__int64 *, const char *))unk_1408D7348)(
v11,
";'>B<76\\=82@-8.@=T\"@-7ZU:8*F=X2J<G>@=W^@-8.@9D2T:49U@1aa") )
{
((void (__fastcall *)(_QWORD))sub_1401F4D70)(*(_QWORD *)(*(_QWORD *)(a1 + 48) + 16i64));
((void (__fastcall *)(_QWORD))sub_1401F4D70)(*(_QWORD *)(*(_QWORD *)(a1 + 48) + 24i64));
v15 = (volatile signed __int32 *)sub_1406E43A0(
"background-image: url(:/new/prefix1/C:/Users/Answer/Desktop/wrong.png);",
71i64);
sub_140200B40(*(_QWORD *)(*(_QWORD *)(a1 + 48) + 8i64), &v15);
if ( !*v15 || *v15 != -1 && _InterlockedExchangeAdd(v15, 0xFFFFFFFF) == 1 )
((void (__fastcall *)(volatile signed __int32 *, __int64, __int64))unk_1406DCF20)(v15, 2i64, 8i64);
sub_140202A00(*(_QWORD *)(*(_QWORD *)(a1 + 48) + 32i64));
}
else
{
((void (__fastcall *)(_QWORD))sub_1401F4D70)(*(_QWORD *)(*(_QWORD *)(a1 + 48) + 16i64));
((void (__fastcall *)(_QWORD))sub_1401F4D70)(*(_QWORD *)(*(_QWORD *)(a1 + 48) + 24i64));
v14 = (volatile signed __int32 *)sub_1406E43A0(
"background-image: url(:/new/prefix1/C:/Users/Answer/Desktop/right.png);",
71i64);
sub_140200B40(*(_QWORD *)(*(_QWORD *)(a1 + 48) + 8i64), &v14);
if ( !*v14 || *v14 != -1 && _InterlockedExchangeAdd(v14, 0xFFFFFFFF) == 1 )
((void (__fastcall *)(volatile signed __int32 *, __int64, __int64))unk_1406DCF20)(v14, 2i64, 8i64);
}

看判断是v11,往上看,v11来源于v20,v20又经过sub_140001F90(v10, v20);,进去这个函数康,康到base64加密的特征,码表是qword_140E82000。康码表的交叉引用,有两个函数引用,sub_140001000sub_140001A50
看这两个函数的时候IDA有时候栈指针有问题……
按alt+k,自己算一下栈指针调一调就好了。
后者的核心逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if ( !IsDebuggerPresent() )
{
sub_140001E20(&qword_140E82000, &unk_1408E9A50, 0i64);
for ( i = 33; i <= 97; ++i )
{
v5 = qword_140E82010;
if ( qword_140E82010 >= (unsigned __int64)qword_140E82018 )
{
sub_140001800(&qword_140E82000, 1i64, 0i64, (unsigned __int8)i);
}
else
{
++qword_140E82010;
v6 = &qword_140E82000;
if ( (unsigned __int64)qword_140E82018 >= 0x10 )
v6 = (__int64 *)qword_140E82000;
*((_BYTE *)v6 + v5) = i;
*((_BYTE *)v6 + v5 + 1) = 0;
}
}
}

分析半天,感觉逻辑好复杂,没分析明白……直接上手动调。
(额……无意间回来看到这里,突然发现他码表不就是ASCII码从33打到97吗……我当时到底是哪里没看明白……)
if里有反调,下断到这一句,手动改ZF标志位跳过反调,然后调完for循环去康qword_140E82000,拿到码表:

1
!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a

注意码表写到字符串里要转义一下。
直接去translate的话会报错:码表长度是65,与正常码表不一致……
这是因为最后的a是相当于原码表里的等号,是在最后padding用的,不属于码表范围。
把码表里最后的a删掉,密文里最后padding的a改成等号,写脚本解base:

1
2
3
4
5
import base64
enc=";'>B<76\\=82@-8.@=T\"@-7ZU:8*F=X2J<G>@=W^@-8.@9D2T:49U@1=="
table='!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`'
model="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
print(base64.b64decode(enc.translate(str.maketrans(table,model))))

creakme3

这是PCC架构的文件,和以往的arm,x86有所不同,由PowerPC编译,所以IDA不能分析,linux不能运行
此题有提示,使用Ghidra分析便可得知主体逻辑

不过我用IDA还是能分析出来……
主逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [sp+1Ch] [-184h]
int j; // [sp+20h] [-180h]
int k; // [sp+24h] [-17Ch]
_BYTE v7[372]; // [sp+28h] [-178h] BYREF

memset(v7, 0, 0x164u);
printf("Welcome my whitegive re task! This is your flag: ");
do
{
for ( i = 0; i <= 88; ++i )
*(_DWORD *)&v7[4 * i] = rand() % 89;
for ( j = 1; j <= 88 && a[2 * *(_DWORD *)&v7[4 * j] + 1] >= a[2 * *(_DWORD *)&v7[4 * j - 4] + 1]; ++j )
;
}
while ( j != 89 );
for ( k = 0; k <= 88; ++k )
putchar(a[2 * *(_DWORD *)&v7[4 * k]]);
return 0;
}

在我的wsl上跑不起来……数组a里是一堆数据,两个一组,第一个1字节,第二个2字节。
看结果是直接输出偶数处的字符,而且前面的欢迎语也说了会直接给flag,应该就是那个随机数满足一定条件就会给……
看for里的逻辑,是要奇数处的数字满足2j处大于等于2j-1,所以后面的2字节数字应该是排序。对2字节数字排序然后按顺序输出前面的1字节数据,那么写个脚本排序输出。
不会写用含两个元素的元组排序呜呜呜

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enc=[0x30, 0x4E7D, 0x30, 0x67BD, 0x30, 0x7A48, 0x30, 0x82A2, 0x30, 0x933E, 0x31, 0x9C18, 0x32, 0x5AFF, 0x32, 0x6CD7, 0x32, 0xA6CA, 0x32, 0xBD79, 0x32, 0xCEBD, 0x33, 0x324A, 0x33, 0x3292, 0x33, 0x3905, 0x33, 0x4291, 0x33, 0x5ADE, 0x33
,0x6E9F, 0x33, 0xA52A, 0x33, 0xBE35, 0x33, 0xCB63, 0x35, 0x7F3B, 0x38, 0x3914, 0x38, 0xB2AD, 0x39, 0x38DA, 0x39, 0x4E50, 0x39, 0x6A02, 0x39, 0xB10F, 0x42, 0x78E5, 0x5F, 0x7EF6, 0x5F, 0x89A3, 0x5F, 0x8EBD, 0x5F, 0x95E3, 0x61, 0x73DA
,0x64, 0x538C, 0x64, 0x633B, 0x64, 0x9E9C, 0x64, 0xB78B, 0x64, 0xC866, 0x65, 0x32AE, 0x65, 0x7679, 0x66, 0x2AE7, 0x66, 0x4D6A, 0x66, 0x5708, 0x66, 0x6610, 0x66, 0xA258, 0x66, 0xB80C, 0x66, 0xC885, 0x67, 0x710A, 0x67, 0x7CF4, 0x68
,0x3F76, 0x68, 0x702B, 0x68, 0xA3EE, 0x68, 0xAD50, 0x68, 0xBAC7, 0x69, 0x4024, 0x69, 0x8A22, 0x69, 0xC055, 0x6A, 0x2B52, 0x6A, 0xC687, 0x6B, 0x5F00, 0x6B, 0xC417, 0x6C, 0x6182, 0x6D, 0x75DB, 0x6E, 0x3C61, 0x6E, 0x4996, 0x6E, 0x5DC1
,0x6F, 0x2D76, 0x6F, 0x7D17, 0x6F, 0xA91B, 0x70, 0x9AED, 0x72, 0x45D0, 0x72, 0x8467, 0x72, 0xAB5D, 0x73, 0x5083, 0x73, 0x6222, 0x73, 0x8D93, 0x73, 0x923A, 0x73, 0x971E, 0x73, 0xB4BA, 0x73, 0xC785, 0x74, 0x3558, 0x74, 0x86BD, 0x74
,0x9738, 0x75, 0x3710, 0x75, 0x9779, 0x77, 0x2F3F, 0x77, 0x44DD, 0x7B, 0x78E1, 0x7D, 0x9F42]
ptr=[]
flag=[]
for i in range(0,len(enc),2):
flag.append(enc[i])
ptr.append(enc[i+1])
sort_ptr=sorted(ptr)
for i in sort_ptr:
print(chr(flag[ptr.index(i)]),end='')

康题解说是猴子排序。搜了一下感觉这种排序好神必……不知道是谁想出来的哈哈哈哈

关于本文

本文作者 云之君, 许可由 CC BY-NC 4.0.