关于CTF两道逆向题的详细解析,包括去掉了NaCI这类不太常见的花,可实现F5阅读源码
0x00 日常查壳
给了三个文件,如题名一样,简单的文件系统,不过主逻辑在这个文件
0x01 分析simlefs
浏览了一遍主函数无疑我们关心的是plantflag
随机数出来的v21和v22我们无法逆,不过关键点就是在参数1的那次进去
注意在这打开了我们的flag文件,然后进入了关键的函数,像参数为2调用了rand根本没法逆
观看这个Encode函数完全可逆,v4动调即得,密文通过观察image.flag获得(毕竟在这个文件找不到
0x02 GetData!
设置好参数,起飞(开调
输入好参数
动调可以直接拿到v4的值和加密后的*CTF{后的值,于是我们可以通过这个值去找密文
即可得到
0x03 GetFlag!
拿这个密文直接解密即可得到flag
也可以观察整个文件发现有多出密文,从0x33000开始,每隔0x1000就会有一段密文,读取解密也可
def Decrypt(encFLag):
flag = ""
for i in range(len(encFLag)):
v5 = encFLag[i]
v5 = (v5 >> 3) | (v5 << 5) & 0xFF
v5 ^= 0xDE
v5 = (v5 >> 4) | (v5 << 4) & 0xFF
v5 ^= 0xED
v5 = (v5 >> 5) | (v5 << 3) & 0xFF
v5 ^= 0xBE;
v5 = (v5 >> 6) | (v5 << 2) & 0xFF
v5 ^= 0xEF;
v5 = (v5 >> 7) | (v5 << 1) & 0xFF
flag += chr(v5)
return flag
data = open('C:\\Users\\Pz\\Desktop\\STARCTF\\Simple File System\\image.flag', 'rb').read()
encFlags = [list(data[0x33000 + i * 0x1000: 0x33000 + i * 0x1000 + 32]) for i in range(200)]
for flag in encFlags:
p = Decrypt(flag)
if "*CTF" in p:
print(flag)
print(p)
break
# encFlags = open('C:\\Users\\Pz\\Desktop\\STARCTF\\Simple File System\\encFlag.flag', 'rb').read()
# print(Decrypt(encFlags))
GetFlag!
title: STARCTF2022-NaCI
0x00 日常查壳
无壳64位
0x01 分析主程序
我们shift + F12通过字符串查找引用,直接找到这个主函数
F5加密函数!
然而这个函数的F5给不能用了,只能动调去理解这个程序,然而!我摸透了这其中的符文!
主要是两个点去修复,顺便去个无用指令
- 奇怪的call
每个call都是这样实现的,再去看看retn
- 奇怪的retn
可以发现每次jmp rdi就是一种回跳,而这不就是retn吗
PS:上面的call和retn不是同一组call+retn
所以源程序的特性
- 把要回来的地址暂存入内存,再jmp到指定地址,同时jmp后还有段花指令
- 当要跳回时,重新把地址放到rdi,再jmp跳回去
所以我们可以改成
- 把jmp上面的所有操作去掉,直接改成call(push + jmp)
- call后面的花全部去掉
- 再把jmp rdi改成retn(pop + jmp),这时候直接用的是栈顶的地址其实就是回去的地址
以此为想法写个idapython
start = 0x807FEC0
end = 0x8080AD1
address = [0 for i in range(5)]
callTarget = ["lea", "lea", "mov", "jmp"]
retnTarget = ["lea", "mov", "and", "lea", "jmp"]
def nop(s, e):
while (s < e):
patch_byte(s, 0x90)
s += 1
def turnCall(s, e):
# nop掉call之前的值
nop(s, e)
patch_byte(e, 0xE8)
# 把后面的花指令去掉
huaStart = next_head(e)
huaEnd = next_head(huaStart)
nop(huaStart, huaEnd)
def turnRetn(s, e):
nop(s, e)
# 注意原来是jmp xxx
# 所以前面nop掉一个 后面改成retn
patch_byte(e, 0x90)
patch_byte(e + 1, 0xC3)
p = start
while p < end:
address[0] = p
address[1] = next_head(p)
address[2] = next_head(address[1])
address[3] = next_head(address[2])
address[4] = next_head(address[3])
for i in range(0, 4):
if print_insn_mnem(address[i]) != callTarget[i]:
break
else:
turnCall(address[0], address[3])
p = next_head(next_head(address[3]))
continue
for i in range(0, 5):
if print_insn_mnem(address[i]) != retnTarget[i]:
break
else:
turnRetn(address[0], address[4])
p = next_head(next_head(address[4]))
continue
p = next_head(p)
shift + F2打开idapython窗口,选择python,run一下
patch完保存一下,让ida重新解析
保存好再次打开这个文件,进入本函数,F5加密函数!
0x02 分析加密函数
可以通过创建结构体让程序很好理解,在我们不确定这些子项的具体函数,可以先用ida默认的
简单审计一下即可知道大概意思
异或加密
我们的输入分别小端序放入,经过一个可以逆的异或计算
__int64 __fastcall XOR(__int64 input)
{
__int64 v1; // rbx
__int64 v2; // r13
__int64 v3; // r15
_QWORD *v4; // r15
struct_v5 *v5; // r15
int v6; // ebx
int v7; // ebx
v4 = (v3 - 8);
*v4 = v1;
v5 = (v2 + (v4 - 56));
v5->input = input;
v5->xorKey = sub_8080360();
v5->input1 = *(v2 + v5->input);
v5->highFour = Big(HIDWORD(v5->input1)); // 高四位小端序放入
v5->lowFour = Big(v5->input1);// 低四位小端序放入
for ( v5->count = 0; v5->count <= 43; ++v5->count )
{
v5->orgLowFour = v5->lowFour;
v6 = ROL(v5->lowFour, 1);
v7 = ROL(v5->lowFour, 8) & v6;
v5->lowFour = v5->highFour ^ v7 ^ ROL(v5->lowFour, 2) ^ *(v2 + 4 * v5->count + v5->xorKey);
v5->highFour = v5->orgLowFour;
}
v5->input1 = 0LL;
v5->input1 = ((v5->input1 | v5->lowFour) << 32) | v5->highFour;// 低高互换
return v5->input1;
}
key可以通过动调直接获取,再注意一下结尾的高低32位互换即可
魔改XTEA
注意所改变的地方是
- 轮数变了
- delta数变了
__int64 __fastcall XTEA(int count, __int64 a2)
{
__int64 v2; // r13
myst *v3; // r15
__int64 result; // rax
v3[-1].t1 = count;// 轮数为传入的轮数,分别是2 4 8 16
*&v3[-2].v0 = a2;
*&v3[-1].key = key; // key可以直接拿
v3[-1].v0 = *(v2 + *&v3[-2].v0);
v3[-1].v1 = *(v2 + *&v3[-2].v0 + 4);
v3[-1].sum = 0;
v3[-1].delta = 0x10325476;// delta数变了
for ( v3[-1].t9 = 0; v3[-1].t9 < v3[-1].t1; ++v3[-1].t9 )
{
v3[-1].v0 += (((v3[-1].v1 >> 5) ^ (16 * v3[-1].v1)) + v3[-1].v1) ^ (*(v2 + 4 * (v3[-1].sum & 3) + *&v3[-1].key)
+ v3[-1].sum);
v3[-1].sum += v3[-1].delta;
v3[-1].v1 += (((v3[-1].v0 >> 5) ^ (16 * v3[-1].v0)) + v3[-1].v0) ^ (*(v2
+ 4 * ((v3[-1].sum >> 11) & 3)
+ *&v3[-1].key)
+ v3[-1].sum);
}
*(v2 + *&v3[-2].v0) = v3[-1].v0;
result = v3[-1].v1;
*(v2 + *&v3[-2].v0 + 4) = result;
return result;
}
0x03 GetFlag
直接从Check函数第一个参数拿到密文
EXP
#include <stdio.h>
#include <stdint.h>
#define SHL(x, n) ( ((x) & 0xFFFFFFFF) << n )
#define ROTL(x, n) ( SHL((x), n) | ((x) >> (32 - n)) )
unsigned int xorKey[44] = {
0x04050607, 0x00010203, 0x0C0D0E0F, 0x08090A0B, 0xCD3FE81B, 0xD7C45477, 0x9F3E9236, 0x0107F187,
0xF993CB81, 0xBF74166C, 0xDA198427, 0x1A05ABFF, 0x9307E5E4, 0xCB8B0E45, 0x306DF7F5, 0xAD300197,
0xAA86B056, 0x449263BA, 0x3FA4401B, 0x1E41F917, 0xC6CB1E7D, 0x18EB0D7A, 0xD4EC4800, 0xB486F92B,
0x8737F9F3, 0x765E3D25, 0xDB3D3537, 0xEE44552B, 0x11D0C94C, 0x9B605BCB, 0x903B98B3, 0x24C2EEA3,
0x896E10A2, 0x2247F0C0, 0xB84E5CAA, 0x8D2C04F0, 0x3BC7842C, 0x1A50D606, 0x49A1917C, 0x7E1CB50C,
0xFC27B826, 0x5FDDDFBC, 0xDE0FC404, 0xB2B30907
};
void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], delta = 0x10325476, sum=delta*num_rounds;
for (i=0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[(sum & 3)]);
}
v[0]=v0; v[1]=v1;
}
void XorRol(uint32_t v[2])
{
uint32_t encLow = v[1];
uint32_t encHigh = v[0];
uint32_t orgLow, orgHigh, v6, v7, v8;
int i;
for ( i = 43; i >= 0; i-- )
{
orgLow = encHigh;
v6 = ROTL(orgLow, 1);
v7 = ROTL(orgLow, 8) & v6;
v8 = v7 ^ ROTL(orgLow, 2);
orgHigh = encLow ^ xorKey[i] ^ v8;
encHigh = orgHigh;
encLow = orgLow;
}
v[0] = orgLow; v[1] = orgHigh;
}
int main()
{
uint32_t v[] = { 0xFDF5C266, 0x7A328286, 0xCE944004, 0x5DE08ADC, 0xA6E4BD0A, 0x16CAADDC, 0x13CD6F0C, 0x1A75D936 };
uint32_t k[4] = { 0x03020100, 0x07060504, 0x0B0A0908, 0x0F0E0D0C };
int i, j;
uint32_t teaData[8];
//uint32_t testData[] = { 0xD4C2E7AE, 0xD2E28713 };
//XorRol(testData);
//printf("0x%X, 0x%X, ", testData[0], testData[1]);
for ( i = 0; i <= 3; i++ )
{
decipher(1 << (i + 1), v + i * 2, k);
printf("0x%X, 0x%X, ", v[i * 2], v[i * 2 + 1]);
teaData[i * 2] = v[i * 2];
teaData[i * 2 + 1] = v[i * 2 + 1];
}
puts("\n");
for ( i = 0; i <= 3; i++ )
{
XorRol(teaData + i * 2);
//printf("0x%X, 0x%X, ", teaData[i * 2], teaData[i * 2 + 1]);
}
puts("\n");
unsigned char * t = (unsigned char *)&teaData;
for ( i = 0; i < 32; i += 4 )
printf("%c%c%c%c", t[i + 3], t[i + 2], t[i + 1], t[i]);
return 0;
}
GetFlag!
- 本文作者: PZZZZ
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/1512
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!