BeginCTF 2024 Reverse WP
没想到居然AK了,而且题目不算简单,上一次比赛时候的我做这14题估计最多出7-9题,有种不真实的感觉TAT
这篇WP写得及其简陋且答辩,只简单写了思路,请见谅QAQ
1. arc
考点:单字节爆破,混淆
按套路反编译发现打开是一长段混淆代码,搜了一下发现Pyarmor混淆,然后发现。。。
无法反混淆![外链图片转存失败,源站可能有防盗链机制,建议将
所以如果一开始想着怎么反混淆,方向就错了,开始guess
输入begin{,居然显示True
输入begin,居然也是True,然后是begi, beg, be ,b 都是True
那么显而易见,单字节爆破就行了,这里仅显示最后一位的爆破
( begin{Y@u_aRe_g00d_aT_play1ng>witH_sNake3验证也输出True,不知道算不算bug )
2. babyvm
考点:vm,二进制文件读写,z3
ida打开,非常贴心地给出每个操作码的功能,然后发现无法动调…
静态发现从opcode.vm里读取opcode,memory.vm里读取数据
读取opcode.vm和memory.vm中的字节
#include <stdio.h>
#include <stdlib.h>
#include<iostream>
#include<string>
using namespace std;
int main() {
FILE *fp,*v5;
int n=0,c;
unsigned char a,b;
fp = fopen("opcode.vm", "rb");
//fp = fopen("opcode.vm", "rb");
while ( feof(fp) ==0)
{
n++;
a=fgetc(fp);
b=a;
printf("0x%x,",b);
}
//操作结束后关闭文件
fclose(fp);
//fclose(v5);
return 0;
}
写出vm代码(省略了从opcode[]和memory[],因为太长了。。。)
#include<iostream>
#include<stdint.h>;
using namespace std;
unsigned char opcode[]={...};
unsigned char memory[]={...};
int add(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
c=*((uint32_t*)&opcode[ep+8]);
printf("Memory[%x] = Memory[%x] + Memory[%x]",a*4,b*4,c*4);
return 3;
}
int sub(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
c=*((uint32_t*)&opcode[ep+8]);
printf("Memory[%x] = Memory[%x] - Memory[%x]",a*4,b*4,c*4);
return 3;
}
int mul(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
c=*((uint32_t*)&opcode[ep+8]);
printf("Memory[%x] = Memory[%x] * Memory[%x]",a*4,b*4,c*4);
return 3;
}
int divide(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
c=*((uint32_t*)&opcode[ep+8]);
printf("Memory[%x] = Memory[%x] / Memory[%x]",a*4,b*4,c*4);
return 3;
}
int rst_in(int ep)
{
printf("in_pointer = 0");
return 0;
}
int rst_out(int ep)
{
printf("out_pointer = 0");
return 0;
}
int pop_in(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
for(int i=0;i<b;i++)
{
printf("Memory[%x] = input[%x]\n",(a+i)*4,i);
printf("in_pointer++\n");
}
return 2;
}
int pop_out(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
for(int i=0;i<b;i++)
{
printf("output[%x] = Memory[%x]\n",i,(a+i)*4);
printf("in_pointer++\n");
}
return 2;
}
int read_buf(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
printf("input: %d",a);
return 1;
}
int write_buf(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
printf("output: output[]");
return 1;
}
int jmp(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
//if(b&&)
printf("if(%d) && !Memory[%x] jmp %d\n",b,b*4,ep/4 + 2) ;
printf("else jmp %d",ep/4 + a);
return 2;
}
int njmp(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
//if(b&&)
printf("if(%d) && !Memory[%x] jmp %d\n",b,b*4,ep/4 + 2) ;
printf("else jmp %d",ep/4 + a);
return 2;
}
int len(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
//if(b&&)
printf("Memory[%x] = strlen(input)",b);
return 2;
}
int main()
{
int v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,buf;
int in_pointer;
int ep,op=0;
while(1)
{
ep=opcode[op];
if(ep==0xff)break;
printf("%04d: ",op/4);
switch ( ep )
{
case 1:
v1 = add(op + 4);
op += (v1 + 1)*4;
break;
case 2:
v2 = sub(op + 4);
op += (v2 + 1)*4;
break;
case 3:
v3 = mul(op + 4);
op += (v3 + 1)*4;
break;
case 4:
v4 = divide(op + 4);
op += (v4 + 1)*4;
break;
case 5:
v5 = rst_in(op + 4);
op += (v5 + 1)*4;
break;
case 6:
v6 = rst_out(op + 4);
op += (v6 + 1)*4;
break;
case 7:
v7 = pop_in(op + 4);
op += (v7 + 1)*4;
break;
case 8:
v8 = pop_out(op + 4);
op += (v8 + 1)*4;
break;
case 9:
buf = read_buf(op + 4);
op += (buf + 1)*4;
break;
case 0xA:
v10 = write_buf(op + 4);
op += (v10 + 1)*4;
break;
case 0xB:
v11 = jmp(op + 4);
op += (v11 + 1)*4;
break;
case 0xC:
v12 = njmp(op + 4);
op += (v12 + 1)*4;
break;
case 0xD:
v13 = len(op + 4);
op += (v13 + 1)*4;
break;
default:
op += 4;
//exit(1);
break;
}
cout<<endl;
}
}
分析后不难发现这是个z3类的判断,修改上面的代码让他直接输出成z3格式(懒狗是这样的)
#include<iostream>
#include<stdint.h>;
using namespace std;
unsigned char opcode[]={...};
unsigned char memory[]={...};
int add(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
c=*((uint32_t*)&opcode[ep+8]);
//printf("Memory[%x] = v%d + %d",a*4,b*4,memory[c*4]);
return 3;
}
int sub(int ep)
{
uint32_t a,b,c,d;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
c=*((uint32_t*)&opcode[ep+8]);
d=*((uint32_t *)&memory[c*4]);
printf(" == %d )\n",d);
return 3;
}
int mul(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
c=*((uint32_t*)&opcode[ep+8]);
if(b-600==0)printf("s.add(");
printf("(v%d * %d) ",b-600,memory[c*4]);
if(b-600<19) printf("+");
//if(b-600==19)printf("\n");
return 3;
}
int divide(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
c=*((uint32_t*)&opcode[ep+8]);
//printf("Memory[%x] = Memory[%x] / Memory[%x]",a*4,b*4,c*4);
return 3;
}
int rst_in(int ep)
{
//printf("in_pointer = 0");
return 0;
}
int rst_out(int ep)
{
//printf("out_pointer = 0");
return 0;
}
int pop_in(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
//for(int i=0;i<b;i++)
{
// printf("Memory[%x] = input[%x]\n",(a+i)*4,i);
// printf("in_pointer++\n");
}
return 2;
}
int pop_out(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
// for(int i=0;i<b;i++)
{
// printf("output[%x] = Memory[%x]\n",i,(a+i)*4);
// printf("in_pointer++\n");
}
return 2;
}
int read_buf(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
// printf("input: %d",a);
return 1;
}
int write_buf(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
// printf("output: output[]");
return 1;
}
int jmp(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
//if(b&&)
// printf("if(%d) && !Memory[%x] jmp %d\n",b,b*4,ep/4 + 2) ;
// printf("else jmp %d",ep/4 + a);
return 2;
}
int njmp(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
//if(b&&)
// printf("if(%d) && !Memory[%x] jmp %d\n",b,b*4,ep/4 + 2) ;
// printf("else jmp %d",ep/4 + a);
return 2;
}
int len(int ep)
{
uint32_t a,b,c;
a=*((uint32_t*)&opcode[ep]);
b=*((uint32_t*)&opcode[ep+4]);
//if(b&&)
// printf("Memory[%x] = strlen(input)",b);
return 2;
}
int main()
{
int v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,buf;
int in_pointer;
int ep,op=0;
while(1)
{
ep=opcode[op];
if(ep==0xff)break;
//printf("%04d: ",op/4);
switch ( ep )
{
case 1:
v1 = add(op + 4);
op += (v1 + 1)*4;
break;
case 2:
v2 = sub(op + 4);
op += (v2 + 1)*4;
break;
case 3:
v3 = mul(op + 4);
op += (v3 + 1)*4;
break;
case 4:
v4 = divide(op + 4);
op += (v4 + 1)*4;
break;
case 5:
v5 = rst_in(op + 4);
op += (v5 + 1)*4;
break;
case 6:
v6 = rst_out(op + 4);
op += (v6 + 1)*4;
break;
case 7:
v7 = pop_in(op + 4);
op += (v7 + 1)*4;
break;
case 8:
v8 = pop_out(op + 4);
op += (v8 + 1)*4;
break;
case 9:
buf = read_buf(op + 4);
op += (buf + 1)*4;
break;
case 0xA:
v10 = write_buf(op + 4);
op += (v10 + 1)*4;
break;
case 0xB:
v11 = jmp(op + 4);
op += (v11 + 1)*4;
break;
case 0xC:
v12 = njmp(op + 4);
op += (v12 + 1)*4;
break;
case 0xD:
v13 = len(op + 4);
op += (v13 + 1)*4;
break;
default:
op += 4;
//exit(1);
break;
}
// cout<<endl;
}
}
然后写出z3脚本
flag这就出来了
3. ezpython
考点:sm4,python
常规操作,发现是sm4加密
发现key,和enc从secret库中来,然后sm4算法从gmssl中来
找到他们
secret.py里存着,key和密文
带入发现无法解开
检查sm4.py发现原来是key和37异或了
重新带入解密就出来了
c81df547915050b3bd965534e.png&pos_id=img-LqRyH9zM-1707194892560)
4. ezvm
考点:vm
其实我压根就没写出vm,因为代码全是指针,太难看懂了
最终通过动调guess出来的,还真给我guess出来了
5. goforfun
考点:go语言,rc4魔改,单字节爆破(好吧其实我不知道)
打开发现是go语言写的程序,找到主函数,发现一个rc4魔改算法
然后是一个我根本看不懂的答辩解密
最后没办法修改flag查看加密后的字节,发现是单字节加密,手动爆破,无需多言(非预期了属于是)
感谢出题人把flag设置得这么简单 orz
6. not_main
考点:TEA,XXTEA,VEH异常处理,反调试
打开ida,一眼看到TEA
动调发现失败,没发现什么特别的反调试代码,估计是异常处理,于是找到Handler函数
发现是一个朴实无华的XXTEA
最后guess一下,输入flag,然后TEA加密,然后不知怎么着触发了VEH异常,进入Handler函数,进行XXTEA加密得到真正的密文
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);
}
}
uint32_t v[8];
int main()
{
uint32_t v[8]= {0xcfbe0f1b,0x5f3083f,0x4220e43b,0x3383afee,0xfa3237ce,0xecada66e,0xa8d47ca7,0xefc51077};
uint32_t const k[4]= {0x74,0x72,0x75,0x65};
int n= 8; //n的绝对值表示v的长度,取正表示加密,取负表示解密
// v为要加密的数据是两个32位无符号整数
// k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
//printf("加密前原始数据:%u %u\n",v[0],v[1]);
//btea(v, n, k);
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("0x%02x,",a);
}
}
return 0;
}
TEA
7.real check in
考点: 检查智力是否正常
伟大的签到题
8. stick game
考点:Js,ob混淆
找到那坨混淆代码然后直接用在线网站反混淆(唉,中文网)
9. superguesser
考点:动调
这是一个无法f5的程序,老老实实动调吧,动调到最后发现关键的XOR加密,发现明文与0x44+i加密
然鹅其实是与0x33加密,因为有反调试,不过用 “begin{” 与密文直接异或也可以guess出来
10. xor
考点:耐心
认真静态分析就可以逆出来
11. 出题人的密码是什么
考点:c语言特性
一直点点点点,找到加密,非常简单
正向爆破?每8个字节一组爆破个鬼啊
然鹅在c语言里,正数×2可能会变成负数,而负数×2也可能变成正数,草了,逆向也不好做
凭借着对c的熟悉,终于肝出了脚本,细品一下
#include<iostream>
#include<stdint.h>
using namespace std;
unsigned char enc[] =
{
0xB4, 0xBB, 0xD8, 0xEB, 0xD0, 0x6E, 0xAB, 0xCA, 0x65, 0x8E,
0x4B, 0xE9, 0x4D, 0xD4, 0x4A, 0xF3, 0x7D, 0x29, 0xC2, 0xF9,
0x95, 0x89, 0xA4, 0x85, 0x9D, 0xCD, 0xDF, 0x77, 0xFD, 0x45,
0xCB, 0x5D, 0x7D, 0xFD, 0x93, 0x4B, 0xBC, 0xF6, 0x7C, 0xF3,
0x24, 0x42, 0xF5, 0xD2, 0xDD, 0xE3, 0x56, 0xAE
};
unsigned char enc1[48];
int64_t v2=0x33077D;
int64_t enc2[6];
int main()
{
int64_t a,c,d;
uint64_t b;
for(int i=0;i<48;i++)
{
enc1[i]=(enc[i]^0x25)-5;
printf("%x ",enc1[i]);
}
cout<<endl;
for(int i=0;i<48;i+=8)
{
enc2[i/8]=*((uint64_t*)&enc1[i]);
}
int64_t v6,v7;
for(int i=0;i<6;i++)
{
v6 = enc2[i];
a=v6;
for(int l=0;l<64;l++)
{
if(a<0)
{
if(a%2==-1)
{
a=(a^v2)/2;
}
else{
b=a;
b=b/2;
a=b;
}
}
else{
if(a%2==1)
{
a=(a^v2)/2+0x8000000000000000;
}
else{
a=a/2;
}
}
}
cout<<a<<endl;
}
}
解出的数字再转化位16进制,转化为字符就可以解出来了
12. 俄语学习
考点:俄语,动调,rc4
一直学习俄语,解完30题后,发现了真正的加密部分,是个rc4,但其实用不到。。。
13. 红白机
考点:asm
手搓,无需多言
14. 真龙之力
考点:XTEA,Mobile,Mobile调试
发现调用native层函数,直接打开so文件,发现是一个人畜无害的XTEA加密
解出Key为YYDS2024
带入key,开始动调,得到B_Box为[17,37,13,23,19,29,41,5,2,11,7,47,3,53,43,31]
后面的加密很容易分析,带入S_Box,B_Box和密文
#include<iostream>
using namespace std;
unsigned char B_Box1[]={17,37,13,23,19,29,41,5,2,11,7,47,3,53,43,31};
unsigned char S_BOX[] = {9, 11, 25, 20, 15, 30, 24, 23, 2, 26, 28, 13, 16, 19, 29, 0x1F, 5, 4, 17, 12, 14, 8, 27, 21, 22, 3, 7, 0, 18, 6, 10, 1};
unsigned char str[]={0x69,0x7B,0x62,0x69,0x65,0x63,0x66,0x00,0x00,0x75,0x00,0x69,0x61,0x6C,0x66,0x00,0x6C,0x6E,0x00,0x6C,0x76,0x5F,0x65,0x65,0x67,0x69,0x65,0x6F,0x7D,0x4E,0x74,0x74};
unsigned char enc[]={0x69,0x7B,0x62,0x69,0x65,0x63,0x66,0x00,0x00,0x75,0x00,0x69,0x61,0x6C,0x66,0x00,0x6C,0x6E,0x00,0x6C,0x76,0x5F,0x65,0x65,0x67,0x69,0x65,0x6F,0x7D,0x4E,0x74,0x74};
int main()
{
int a;
unsigned char tmp;
for(int k=0;k<16;k++)
{
for(int i=0;i<32;i++)
{
str[i]=enc[i];
}
for(int i=0;i<32;i++)
{
enc[S_BOX[i]]=str[i];
}
}
for(int i=0;i<32;i++)
{
// printf("%x ",enc[i]);
}
printf("\n");
for(int i=15 ; i>=0 ;i--)
{
a= (B_Box1[i] * i) %16;
tmp = enc[i];
enc[i]=enc[a];
enc[a]=tmp;
//getchar();
}
for(int i=15 ; i>=0 ;i--)
{
a= (B_Box1[i] * i) %16;
tmp = enc[i+16];
enc[i+16]=enc[a+16];
enc[a+16]=tmp;
//getchar();
}
for(int i=0;i<32;i++)
{
printf("%c",enc[i]);
}
}