BeginCTF 2024 Reverse WP

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]);
    }
}
其实也可以试一下排列组合爆破,就是有20! = 2,432,902,008,176,640,000种情况而已,问题不大(

在这里插入图片描述

  • 25
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值