DASCTF x CBCTF 2024 Reverse
总结: 只做出三道Re,还是太菜了,这次勉强排到第十,今年保送路漫漫
prese
ida打开,代码真是依托答辩呐,可恶的ollvm,还好我有D810,直接启动,然后加密逻辑很简单,就是有个反调试,会改变密文
看到exp就很简单力
#include<iostream>
using namespace std;
//unsigned char tmp[256];
unsigned char tmp[256] =
{
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9,
0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3,
0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD,
0xFE, 0xFF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7,
0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1,
0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB,
0xDC, 0xDD, 0xDE, 0xDF, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5,
0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9,
0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0x80, 0x81, 0x82, 0x83,
0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D,
0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0x60, 0x61,
0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B,
0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,
0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53,
0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D,
0x5E, 0x5F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31,
0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,
0x3C, 0x3D, 0x3E, 0x3F, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
};
unsigned char input[]="abcdefghijklmnopqrstuvwxyz012345";
unsigned char c[] =
{
0x86, 0x83, 0x91, 0x81, 0x96, 0x84, 0xB9, 0xA5, 0xAD, 0xAD,
0xA6, 0x9D, 0xB6, 0xAA, 0xA7, 0x9D, 0xB0, 0xA7, 0x9D, 0xAB,
0xB1, 0x9D, 0xA7, 0xA3, 0xB1, 0xBB, 0xAA, 0xAA, 0xAA, 0xAA,
0xBF
};
int main()
{
int len = 32;
for(int i=0;i<32;i++)
{
c[i]^=34;
}
for(int i=0;i<32;i++)
{
for(int k=32;k<128;k++)
{
if(tmp[k]==c[i])
{
printf("%c",k);
}
}
}
}
unwind
ida打开,流程非常地简单,一个标准XXTEA,看起来人畜无害,然后化身脚本小子,不出意外爆了个fake弗莱格出来
那么就是找反调试的存在,动调下,运行发现在int3断点处停了下来,可以看到有SEH的结构
那么显而易见sub_4E1424就是我们的Handler函数,点进去以后发现就是个XTEA加密,且加密轮数为36
那么可以猜想加密流程:
输入flag -> XXTEA加密 ->断点异常 -> XTEA加密 ->判断flag,多么天衣无缝的猜想啊。
然鹅当我们按照这个流程解密,会发现毛也解不出,这是为什么呢?做过DubheCTF 2024的Destination这题的小伙伴便会知道,有时候SEH的Handler函数会执行两次,这并不是因为触发了两次错误,而是因为栈展开这个操作的缘故,详细可看这位师傅的blog,(但其实我也没太搞得懂)
Windows-SEH学习笔记(2) - 云之君’s Blog (yunzh1jun.com)
那我们怎么知道有没有进行栈展开的操作呢,很简单动调康康就行了。
我们用x32dbg,因为他有ScyllaHide这个强大的反反调试插件,在sub_4E1960这个函数头下断点,会发现这个函数被调用了两次,因此正确的加密流程为:
输入flag -> XXTEA加密 ->断点异常 -> XTEA加密 ->栈展开 -> XTEA加密 -> 判断flag
也就是说最后经过了两次的XTEA加密,我们可以通过自己写一个加密流程和源程序动调结果对比以确认。
而这里还有一个坑点就是最后的密文会改变,这跟TLS里hook了比较函数有关,且也是前面加密流程改变的关键,这里就不细究了,通过x32dbg的动调可以直接发现正确的密文
exp:
XTEA
#include <stdio.h>
#include <stdint.h>
#include<iostream>
using namespace std;
//加密函数
void encrypt (uint32_t* v, uint32_t* k) {
unsigned int v0=v[0], v1=v[1], sum=0, i; //v0,v1分别为字符串的低字节高字节
unsigned int delta=0x61C88647;
int k0=k[0], k1=k[1], k2=k[2], k3=k[3];
for (i=0; i < 36; i++) { //加密32轮
v0 += (((v1 >> 5) ^ (16 * v1)) + v1) ^ (k[sum & 3] + sum);
sum -= 1640531527;
v1 += (((v0 >> 5) ^ (16 * v0)) + v0) ^ (k[(sum >> 11) & 3] + sum);
}
v[0]=v0; v[1]=v1;//加密后再重新赋值
}
//解密函数
void decrypt ( unsigned int* v, unsigned int* k) {
unsigned int v0=v[0], v1=v[1];
unsigned int delta=1640531527;
unsigned int sum=0, i;
for(int i=0;i<36;i++)
{
sum-=1640531527;
}
for (i=0; i<36; i++) {
v1 -= (((v0 >> 5) ^ (16 * v0)) + v0) ^ (k[(sum >> 11) & 3] + sum);
sum += delta;
v0 -= (((v1 >> 5) ^ (16 * v1)) + v1) ^ (k[sum & 3] + sum); //解密时将加密算法的顺序倒过来,还有+=变为-=
}
v[0]=v0; v[1]=v1;//解密后再重新赋值
}
unsigned char enc[] =
{
0xC1,0xA7,0xAA,0x87,0xB6,0x21,0x73,0x85,0x8C,0xD2,0x71,0x0E,0xF2,0x39,0xDF,0xCA,0x14,0xCA,0xEF,0x58,0xD8,0xD9,0xE7,0xD7,0x5D,0x5C,0x9F,0xF2,0x5E,0xD4,0x5E,0x5F
};
int main()
{
unsigned int v[8];
for(int i=0;i<8;i++)
{
v[i]= *((uint32_t*)&enc[i*4]);
//printf("%x ",v[i]);
}
unsigned int k[4]={'D','A','S','!'};
for(int i=0;i<8;i+=2)
{
decrypt(v+i, k);
decrypt(v+i, k);
}
unsigned char a;
for(int i=0;i<8;i++)
{
for(int k=0;k<4;k++)
{
a= *((unsigned char *)(&v[i])+k);
printf("0x%x,",a);
}
}
return 0;
}
XXTEA
#include <stdio.h>
#include <stdint.h>
#define DELTA -1640531527
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
void btea(uint32_t *v, int n, uint32_t const key[4])
{
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];
z = v[p] += MX;
}
y = v[0];
z = v[n-1] += MX;
}
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] -= MX;
}
z = v[n-1];
y = v[0] -= MX;
sum -= DELTA;
}
while (--rounds);
}
}
unsigned char enc[]={0x27,0x74,0xbe,0x58,0x3a,0xcd,0x94,0x33,0x5f,0x22,0x24,0x93,0xbb,0x2d,0xb8,0x1,0x90,0x4b,0x7d,0x4b,0x8f,0xbf,0xef,0xde,0x96,0x6e,0x1b,0x91,0x88,0x92,0x6,0x38};
int main()
{
uint32_t v[11];
for(int i=0;i<11;i++)
{
v[i]=*((uint32_t *)&enc[i*4]);
//printf("%x ",v[i]);
}
//printf("\n");
uint32_t const k[4]= {'D','A','S','!'};
int n= 8;
btea(v, -n, k);
unsigned char a;
for(int i=0;i<8;i++)
{
for(int k=0;k<4;k++)
{
a=*(((unsigned char *)&v[i])+k);
printf("%c",a);
}
}
return 0;
}
ezvm
先点开exe康康,发现输入key,输入flag后加载了一个dll,通过check.dll里的check函数进行对比
而题目附件里给的dll直接解出来是个fake flag,也是预料之中了。
我们先看sub_7FF7200911E5这个函数,前面判断了key必须由数字组成,而这个函数则是将key的每两位组成一个数
比如我输入key为1234567890123456,那么就是他就会转化为8个数:12,34,56,78,90,12,34,56
然后再看sub_7FF7200912D5这个函数,不难发现就是一个很简单的VM,然后直接copy代码,小做修改
#include<iostream>
using namespace std;
int opcode[100];
int main()
{
int eip_ = 0;
int v8 = 0;
opcode[0] = 162;
opcode[1] = 0;
opcode[2] = 132;
opcode[3] = 163;
opcode[4] = 8;
opcode[5] = 0;
opcode[6] = 163;
opcode[7] = 8;
opcode[8] = 1;
opcode[9] = 176;
opcode[10] = 8;
opcode[11] = 316;
opcode[12] = 178;
opcode[13] = 163;
opcode[14] = 9;
opcode[15] = 1;
opcode[16] = 163;
opcode[17] = 9;
opcode[18] = 2;
opcode[19] = 163;
opcode[20] = 9;
opcode[21] = 3;
opcode[22] = 176;
opcode[23] = 9;
opcode[24] = 158;
opcode[25] = 178;
opcode[26] = 166;
opcode[27] = 4;
opcode[28] = 22;
opcode[29] = 163;
opcode[30] = 0;
opcode[31] = 4;
opcode[32] = 176;
opcode[33] = 0;
opcode[34] = 889;
opcode[35] = 178;
opcode[36] = 164;
opcode[37] = 5;
opcode[38] = 11;
opcode[39] = 161;
opcode[40] = 8;
opcode[41] = 5;
opcode[42] = 163;
opcode[43] = 8;
opcode[44] = 6;
opcode[45] = 176;
opcode[46] = 8;
opcode[47] = 38;
opcode[48] = 178;
opcode[49] = 163;
opcode[50] = 7;
opcode[51] = 6;
opcode[52] = 176;
opcode[53] = 7;
opcode[54] = 96;
opcode[55] = 178;
opcode[56] = 161;
opcode[57] = 9;
opcode[58] = 1;
opcode[59] = 163;
opcode[60] = 9;
opcode[61] = 2;
opcode[62] = 165;
opcode[63] = 9;
opcode[64] = 5;
opcode[65] = 176;
opcode[66] = 9;
opcode[67] = 111;
opcode[68] = 178;
opcode[69] = 166;
opcode[70] = 5;
opcode[71] = 7;
opcode[72] = 161;
opcode[73] = 8;
opcode[74] = 0;
opcode[75] = 165;
opcode[76] = 8;
opcode[77] = 6;
opcode[78] = 163;
opcode[79] = 8;
opcode[80] = 5;
opcode[81] = 176;
opcode[82] = 8;
opcode[83] = 859;
opcode[84] = 178;
opcode[85] = 163;
opcode[86] = 3;
opcode[87] = 4;
opcode[88] = 176;
opcode[89] = 3;
opcode[90] = 706;
opcode[91] = 178;
opcode[92] = 192;
int num;
do
{
int a,b;
//printf("%d;",opcode[eip_]);
if ( opcode[eip_] == 160 )
{
a= opcode[eip_ + 1];
b= opcode[eip_ + 2];
//encs[opcode[eip_ + 1]] = opcode[eip_ + 2];
printf("encs[%d] = %d \n",a,b);
eip_ += 3;
//continue;
}
if ( opcode[eip_] == 161 )
{
a= opcode[eip_ + 1];
b= opcode[eip_ + 2];
//encs[opcode[eip_ + 1]] = encs[opcode[eip_ + 2]];
printf("encs[%d] = encs[%d] \n",a,b);
eip_ += 3;
//continue;
}
if ( opcode[eip_] == 162 )
{
a= opcode[eip_ + 1];
b= opcode[eip_ + 2];
//encs[opcode[eip_ + 1]] += opcode[eip_ + 2];
printf("encs[%d] += %d \n",a,b);
eip_ += 3;
// continue;
}
if ( opcode[eip_] == 163 )
{
a= opcode[eip_ + 1];
b= opcode[eip_ + 2];
//encs[opcode[eip_ + 1]] += encs[opcode[eip_ + 2]];
printf("encs[%d] += encs[%d] \n",a,b);
eip_ += 3;
//continue;
}
if ( opcode[eip_] == 164 )
{
a= opcode[eip_ + 1];
b= opcode[eip_ + 2];
//encs[opcode[eip_ + 1]] -= opcode[eip_ + 2];
printf("encs[%d] -= %d \n",a,b);
eip_ += 3;
//continue;
}
if ( opcode[eip_] == 165 )
{
a= opcode[eip_ + 1];
b= opcode[eip_ + 2];
//encs[opcode[eip_ + 1]] -= encs[opcode[eip_ + 2]];
printf("encs[%d] -= encs[%d] \n",a,b);
eip_ += 3;
// continue;
}
if ( opcode[eip_] == 166 )
{
a= opcode[eip_ + 1];
b= opcode[eip_ + 2];
//encs[opcode[eip_ + 1]] *= opcode[eip_ + 2];
printf("encs[%d] *= %d \n",a,b);
eip_ += 3;
// continue;
}
if ( opcode[eip_] == 167 )
{
a= opcode[eip_ + 1];
b= opcode[eip_ + 2];
//encs[opcode[eip_ + 1]] *= encs[opcode[eip_ + 2]];
printf("encs[%d] *= encs[%d] \n",a,b);
eip_ += 3;
//continue;
}
if ( opcode[eip_] == 176 )
{
a= opcode[eip_ + 1];
b= opcode[eip_ + 2];
//v12 = encs[opcode[eip_ + 1]] == opcode[eip_ + 2];
//v8 = v12;
//printf("v8 = encs[%d] == %d \n",a,b);
printf("s.add(encs[%d] == %d) \n",a,b);
//printf("v8 = ")
eip_ += 3;
//continue;
}
if ( opcode[eip_] == 177 )
{
a= opcode[eip_ + 1];
b= opcode[eip_ + 2];
//v12 = encs[opcode[eip_ + 1]] == encs[opcode[eip_ + 2]];
//v8 = v12;
//printf("v8 = encs[%d] == %d \n",a,b);
printf("s.add(encs[%d] == %d) \n",a,b);
eip_ += 3;
//continue;
}
if ( opcode[eip_] == 178 )
{
//printf("if ( !v8 ) return \n");//if ( !v8 )
//printf("")
//return 0i64;
++eip_;
//continue;
}
}
while ( opcode[eip_] != 192 );
}
然后加密流程也很简单,这里就直接用z3解
from z3 import *
s = Solver()
encs=[Int("x[%d]" %i) for i in range(10)]
#encs=enc[:]
encs[8]=0
encs[9]=0
encs[0] += 132
encs[8] += encs[0]
encs[8] += encs[1]
s.add(encs[8] == 316)
encs[9] += encs[1]
encs[9] += encs[2]
encs[9] += encs[3]
s.add(encs[9] == 158)
encs[4] *= 22
encs[0] += encs[4]
s.add(encs[0] == 889)
encs[5] -= 11
encs[8] = encs[5]
encs[8] += encs[6]
s.add(encs[8] == 38)
encs[7] += encs[6]
s.add(encs[7] == 96)
encs[9] = encs[1]
encs[9] += encs[2]
encs[9] -= encs[5]
s.add(encs[9] == 111)
encs[5] *= 7
encs[8] = encs[0]
encs[8] -= encs[6]
encs[8] += encs[5]
s.add(encs[8] == 859)
encs[3] += encs[4]
s.add(encs[3] == 706)
if s.check()==sat:
ans = s.model()
print(ans)
解出来得到key为 9787254630123759
运行程序程序,输入key,验证通过,最后返回正确值,然后进入到sub_7FF720091217,这个函数根据输入的key对data进行修改,从而得到正确的加密流程,非预期的做法则是通过观察data文件结合PE文件的知识直接拿到异或的key值
我们直接输入正确的key后运行就可以得到正确加密流程的dll了,一个非常简单的加密
exp:
#include<iostream>
using namespace std;
unsigned char enc[]={ 0x0D, 0x08, 0x1A, 0x0A, 0x1D, 0x0F, 0x32, 0x78, 0x2A, 0x7B,
0x2A, 0x7B, 0x7C, 0x7D, 0x71, 0x64, 0x7A, 0x2C, 0x7B, 0x7D,
0x64, 0x28, 0x7D, 0x71, 0x2C, 0x64, 0x78, 0x78, 0x7D, 0x7A,
0x64, 0x28, 0x7A, 0x7D, 0x70, 0x7F, 0x28, 0x7A, 0x2B, 0x7E,
0x7D, 0x79, 0x79, 0x34};
int main()
{
unsigned char a;
for(int i=0;i<44;i++)
{
a=enc[i]^0x49;
printf("%c",a);
}
}
YunV2
假如我是Retard就好了