buuctf刷题记录

本文详细记录了作者在CTF比赛中的逆向工程和密码学挑战经历,涵盖了多种解题技巧,包括字符串分析、IDA反编译、动态调试、加密算法逆向等。通过对不同题目解密过程的剖析,展示了如何从加密的字符串、加密函数、混淆代码中抽丝剥茧,找出解密的关键步骤,最终得出正确的flag。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2022-1-16

reverse2

扔到IDA64里解析,3-idaview.png F5生成伪代码3-idacode1.png 通过分析得出经过一定的变换后与flag进行比较的

查看flag的值,是{hacking_for_fun}3-flagval.png 再返回前面看如何对flag进行变换的,将105、114、49转换成值,3-idacode2.png 箭头所指的函数功能为,把flag中存在的’i’以及’r’转换成’1’,进而得出正确flag。

2022-1-17

内涵的软件

下载好以后发现是乱码的exe文件,拖到ExeinfoPE里查看信息,4-exeinfo.png

没有加壳,用IDA打开,查看main函数4-main.png

可以看到跳转到main0函数里 打开main0函数4-main0.png F5看伪代码,4-main0code.png 看到一串字符串,直接尝试,就是了,不得不吐槽一下,这字符串我以为还是加密的呢,然而并不是。

2022-1-18

新年快乐

先放到ExeinfoPE里查看

5-exeinfope.png

发现是加壳的

使用脱壳工具脱一下壳

5-unpack.png

脱壳后再扔到IDA里,F5查看,5-code.png

代码逻辑如图,比对str1和str2,str2的值直接就给了。就是"HappyNewYear!"。

2022-1-19

XOR

下载下来是个无后缀文件,扔到IDA64里解析,看到main函数,直接F5,6-code.png 看到是对_b跟global做对比,6-global1.png所以看一下global什么内容,跟进是字符串。6-global2.png
写一个python脚本

s = ['f',0xA,'k',0xC,'w&O.@',0x11,'x',0xD,'Z;U',0x11,'p',0x19,'F',0x1F,'v"M#D',0xE,'g',6,'h',0xF,'G2O',0]

for i in range(1,len(s)):

    if(isinstance(s[i],int)):            #如果为int类型,转换为char类型

        s[i]=chr(s[i])

s = ''.join(s)                            #将列表拼接成字符串    

flag = 'f'                                #flag的第一位不进行异或,所以直接写为f

for i in range(1,len(s)):

    flag += chr(ord(s[i])^ord(s[i-1]))    #异或操作,两次异或会还原

print(flag)

2022-1-20

reverse3

下载下来是reverse_3.exe,扔到ExeinfoPE里,没加壳,扔到IDA里,看见了main0,直接F5,

int __cdecl main_0(int argc, const char **argv, const char **envp)

{

  size_t v3; // eax

  const char *v4; // eax

  size_t v5; // eax

  char v7; // [esp+0h] [ebp-188h]

  char v8; // [esp+0h] [ebp-188h]

  signed int j; // [esp+DCh] [ebp-ACh]

  int i; // [esp+E8h] [ebp-A0h]

  signed int v11; // [esp+E8h] [ebp-A0h]

  char Destination[108]; // [esp+F4h] [ebp-94h] BYREF

  char Str[28]; // [esp+160h] [ebp-28h] BYREF

  char v14[8]; // [esp+17Ch] [ebp-Ch] BYREF

 

  for ( i = 0; i < 100; ++i )

  {

    if ( (unsigned int)i >= 0x64 )

      j____report_rangecheckfailure();

    Destination[i] = 0;

  }

  sub_41132F("please enter the flag:", v7);

  sub_411375("%20s", (char)Str);

  v3 = j_strlen(Str);

  v4 = (const char *)sub_4110BE(Str, v3, v14);

  strncpy(Destination, v4, 0x28u);

  v11 = j_strlen(Destination);

  for ( j = 0; j < v11; ++j )

    Destination[j] += j;

  v5 = j_strlen(Destination);

  if ( !strncmp(Destination, Str2, v5) )

    sub_41132F("rigth flag!\n", v8);

  else

    sub_41132F("wrong flag!\n", v8);

  return 0;

}

看出来是输入一个字符串然后经过sub_4110BE函数进行加密,然后再通过一个for循环进行变换,然后与str2进行比较。先看一下str2的值,7-str2.png 看一下sub_4110BE函数,

void *__cdecl sub_4110BE(char *a1, unsigned int a2, int *a3)

{

  int v4; // [esp+D4h] [ebp-38h]

  int v5; // [esp+D4h] [ebp-38h]

  int v6; // [esp+D4h] [ebp-38h]

  int v7; // [esp+D4h] [ebp-38h]

  int i; // [esp+E0h] [ebp-2Ch]

  unsigned int v9; // [esp+ECh] [ebp-20h]

  int v10; // [esp+ECh] [ebp-20h]

  int v11; // [esp+ECh] [ebp-20h]

  void *v12; // [esp+F8h] [ebp-14h]

  char *v13; // [esp+104h] [ebp-8h]

 

  if ( !a1 || !a2 )

    return 0;

  v9 = a2 / 3;

  if ( (int)(a2 / 3) % 3 )

    ++v9;

  v10 = 4 * v9;

  *a3 = v10;

  v12 = malloc(v10 + 1);

  if ( !v12 )

    return 0;

  j_memset(v12, 0, v10 + 1);

  v13 = a1;

  v11 = a2;

  v4 = 0;

  while ( v11 > 0 )

  {

    byte_41A144[2] = 0;

    byte_41A144[1] = 0;

    byte_41A144[0] = 0;

    for ( i = 0; i < 3 && v11 >= 1; ++i )

    {

      byte_41A144[i] = *v13;

      --v11;

      ++v13;

    }

    if ( !i )

      break;

    switch ( i )

    {

      case 1:

        *((_BYTE *)v12 + v4) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];

        v5 = v4 + 1;

        *((_BYTE *)v12 + v5) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];

        *((_BYTE *)v12 + ++v5) = aAbcdefghijklmn[64];

        *((_BYTE *)v12 + ++v5) = aAbcdefghijklmn[64];

        v4 = v5 + 1;

        break;

      case 2:

        *((_BYTE *)v12 + v4) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];

        v6 = v4 + 1;

        *((_BYTE *)v12 + v6) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];

        *((_BYTE *)v12 + ++v6) = aAbcdefghijklmn[((byte_41A144[2] & 0xC0) >> 6) | (4 * (byte_41A144[1] & 0xF))];

        *((_BYTE *)v12 + ++v6) = aAbcdefghijklmn[64];

        v4 = v6 + 1;

        break;

      case 3:

        *((_BYTE *)v12 + v4) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];

        v7 = v4 + 1;

        *((_BYTE *)v12 + v7) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];

        *((_BYTE *)v12 + ++v7) = aAbcdefghijklmn[((byte_41A144[2] & 0xC0) >> 6) | (4 * (byte_41A144[1] & 0xF))];

        *((_BYTE *)v12 + ++v7) = aAbcdefghijklmn[byte_41A144[2] & 0x3F];

        v4 = v7 + 1;

        break;

    }

  }

  *((_BYTE *)v12 + v4) = 0;

  return v12;

}

看上去是base64加密,写个python解一下。

import base64

str = 'e3nifIH9b_C@n@dH'

flag = ''

for i in range(len(str)):

    flag += chr(ord(str[i])-i)

 

print(base64.b64decode(flag))

2022-1-21

不一样的flag

下载下来是"不一样的flag.exe",扔到ExeinfoPE,未加壳,扔到IDA里,看一下伪代码,此处贴一下看的别人的伪代码分析,8-pic.png 说实话一开始没看明白,乱七八糟的,后来看了解析发现是个迷宫题,将49跟35转成值,发现是1跟#,大概想了一下,结尾是#,就输出正确,然后就看一下字符串是以#结尾,但是还是不知道是个啥,不过说起来做这种题,也从来没有想过直接打开xxx.exe,直接打开发现是可以操纵上下左右的,看了解析才知道是一个迷宫,将字符串分为5行5列(留个疑问,为什么是5行5列)。

*1111

01000

01010

00010

1111#

贴一下伪代码

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)

{

  char v3[29]; // [esp+17h] [ebp-35h] BYREF

  int v4; // [esp+34h] [ebp-18h]

  int v5; // [esp+38h] [ebp-14h] BYREF

  int i; // [esp+3Ch] [ebp-10h]

  char v7[12]; // [esp+40h] [ebp-Ch] BYREF

 

  __main();

  v4 = 0;

  strcpy(v3, "*11110100001010000101111#");

  while ( 1 )

  {

    puts("you can choose one action to execute");

    puts("1 up");

    puts("2 down");

    puts("3 left");

    printf("4 right\n:");

    scanf("%d", &v5);

    if ( v5 == 2 )

    {

      ++*(_DWORD *)&v3[25];

    }

    else if ( v5 > 2 )

    {

      if ( v5 == 3 )

      {

        --v4;

      }

      else

      {

        if ( v5 != 4 )

LABEL_13:

          exit(1);

        ++v4;

      }

    }

    else

    {

      if ( v5 != 1 )

        goto LABEL_13;

      --*(_DWORD *)&v3[25];

    }

    for ( i = 0; i <= 1; ++i )

    {

      if ( *(int *)&v3[4 * i + 25] < 0 || *(int *)&v3[4 * i + 25] > 4 )

        exit(1);

    }

    if ( v7[5 * *(_DWORD *)&v3[25] - 41 + v4] == 49 )

      exit(1);

    if ( v7[5 * *(_DWORD *)&v3[25] - 41 + v4] == 35 )

    {

      puts("\nok, the order you enter is the flag!");

      exit(0);

    }

  }

}

*号为开头,只能走0,走1直接就退出,操纵的顺序即为flag,222441144222。

2022-1-22

SimpleRev

下载下来是SimpleRev,甩到IDA64里进行分析。直接F5,9-code1.png 发现输入d/D就可以进入,紧接着是一个Decry函数,我们对Decry函数进行分析

unsigned __int64 Decry()

{

  char v1; // [rsp+Fh] [rbp-51h]

  int v2; // [rsp+10h] [rbp-50h]

  int v3; // [rsp+14h] [rbp-4Ch]

  int i; // [rsp+18h] [rbp-48h]

  int v5; // [rsp+1Ch] [rbp-44h]

  char src[8]; // [rsp+20h] [rbp-40h]

  __int64 v7; // [rsp+28h] [rbp-38h]

  int v8; // [rsp+30h] [rbp-30h]

  __int64 v9; // [rsp+40h] [rbp-20h]

  __int64 v10; // [rsp+48h] [rbp-18h]

  int v11; // [rsp+50h] [rbp-10h]

  unsigned __int64 v12; // [rsp+58h] [rbp-8h]

 

  v12 = __readfsqword(0x28u);

  *(_QWORD *)src = 0x534C43444ELL;

  v7 = 0LL;

  v8 = 0;

  v9 = 0x776F646168LL;                          //数据在内存中是小端顺序,高位在高地址处,低位在低地址处

                                                //故实际的字符顺序应为'0x4e44434c53'转为字符为'NDCLS'

  v10 = 0LL;

  v11 = 0;

  text = join(key3, (const char *)&v9);         // 让text等于key3+v9

                                                // key3 = "kills"

                                                // v9   = "hadow"   

                                                // 因为小端序存储 

                                                // 则text = "killshadow"

  strcpy(key, key1);                            // 将key1复制给key

                                                // key = "ADSFK"

  strcat(key, src);                             // 将src处的字符串拼接到key后

                                                // key = "ADSFKNDCLS"

  v2 = 0;

  v3 = 0;

  getchar();                                    // 获取输入(清空缓冲区?)

  v5 = strlen(key);                             // v5 = key的长度   v5 = 10

  for ( i = 0; i < v5; ++i )

  {

    if ( key[v3 % v5] > 64 && key[v3 % v5] <= 90 )// if(key[v3]>64 && key[v3]<=90)

                                                // key[v3] = key[v3]+32

                                                // :将大写字母转换成小写字母

      key[i] = key[v3 % v5] + 32;               // key = "adsfkndcls"

    ++v3;

  }

  printf("Please input your flag:", src);

  while ( 1 )

  {

    v1 = getchar();

    if ( v1 == '\n' )                           // 如果输入的为换行符,则退出

      break;

    if ( v1 == ' ' )

    {

      ++v2;                                     // 如果输入的为空格,则v2加一

    }

    else

    {

      if ( v1 <= 96 || v1 > 122 )               // 如果输入的v1不为小写字母

      {

        if ( v1 > 64 && v1 <= 90 )              // 如果v1为大写字母

          str2[v2] = (v1 - 39 - key[v3++ % v5] + 97) % 26 + 97;// 对str2[v2]进行处理(v2为0每次加1)

                                                // str2[v2] = (v1-key[v3]+58)%26 + 97

                                                // 变换后str2[v2]存放小写字母

      }

      else

      {                                         // 如果输入的值v1为小写字母

        str2[v2] = (v1 - 39 - key[v3++ % v5] + 97) % 26 + 97;// 做同样处理

      }                                         // 如果不为大小写字母,则不进行处理

      if ( !(v3 % v5) )                         // 如果循环到key的最后一位

        putchar(' ');                           // 打印处一个空格

      ++v2;

    }

  }

  if ( !strcmp(text, str2) )                    // 如果text和str2存储的相同,则成功

                                                // text = "killshadow"

    puts("Congratulation!\n");

  else

    puts("Try again!\n");

  return __readfsqword(0x28u) ^ v12;

}

其中在处理text时,有一个函数为join,即text = join

&v9);,我们去查看一下

char *__fastcall join(const char *a1, const char *a2)

{

  size_t v2; // rbx

  size_t v3; // rax

  char *dest; // [rsp+18h] [rbp-18h]

 

  v2 = strlen(a1);                    //v2为a1的长度,即key的长度

  v3 = strlen(a2);                    //v3为a2的长度,即v9的长度

  dest = (char *)malloc(v2 + v3 + 1); //动态分配一个能存下key3和v9的空间dest

  if ( !dest )

    exit(1);

  strcpy(dest, a1);                   //将a1的值赋给dest

  strcat(dest, a2);                   //将a2的值拼接到dest

  return dest;                        //实则返回a1+a2,即key+v9

}

其中对于str2做的处理为

str2[v2] = (v1 - 39 - key[v3 % v5] + 97) % 26 + 97;

所以我们要做的就是对于str2进行逆向处理,写出脚本

#第一种 逆推

text = 'killshadow'  # str2

key = 'adsfkndcls'

v3 = 0

v5 = len(key)

n = 0

flag = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

for i in range(0, 10):

    for j in range(0, 10):

        v1 = (ord(text[j])-97)+26*i+ord(key[v3 % v5])-58

        if(65 < v1 <= 90) or (v1 >= 97 and v5 <= 122):

            flag[j] = chr(v1)

            n = n+1

            if n == 10:

                print(''.join(flag))

                break

        v3 = v3 + 1

#第二种 暴力字典破解

text = 'killshadow'  # str2

key = 'adsfkndcls'

v3 = 0

v5 = len(key)

dict1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

flag = ""

for i in range(0, 10):

    n = 0

    for char in dict1:

        x = (ord(char) - 39 - ord(key[v3 % v5])+97) % 26 + 97

        if chr(x) == text[i]:

            n = n + 1

            if n == 1:

                print(char, end="")

    v3 = v3 + 1

2022-1-23

[GXYCTF2019]luck_guy

下载下来是名为luck_guy的文件,扔到IDA里,直接F5,


int __cdecl main(int argc, const char **argv, const char **envp)

{

  welcome(argc, argv, envp);

  puts("_________________");

  puts("try to patch me and find flag");

  puts("please input a lucky number");

  __isoc99_scanf("%d");

  patch_me(0LL);

  puts("OK,see you again");

  return 0;

}

可以看到,存在一个patch_me的函数,跟踪


int __fastcall patch_me(int a1)

{

  int result; // eax

 

  if ( a1 % 2 == 1 )

    result = puts("just finished");

  else

    result = get_flag();

  return result;

}

肯定是get_flag

有用,跟踪


unsigned __int64 get_flag()

{

  unsigned int v0; // eax

  int i; // [rsp+4h] [rbp-3Ch]

  int j; // [rsp+8h] [rbp-38h]

  __int64 s; // [rsp+10h] [rbp-30h] BYREF

  char v5; // [rsp+18h] [rbp-28h]

  unsigned __int64 v6; // [rsp+38h] [rbp-8h]

 

  v6 = __readfsqword(0x28u);

  v0 = time(0LL);               //得到时间

  srand(v0);                    //使用时间作为种子生成随机数字

  for ( i = 0; i <= 4; ++i )

  {

    switch ( rand() % 200 )     // 产生1-199之间的随机数

    {

      case 1:

        puts("OK, it's flag:");

        memset(&s, 0, 0x28uLL);

        strcat((char *)&s, f1);

        strcat((char *)&s, &f2);

        printf("%s", (const char *)&s);

        break;

      case 2:

        printf("Solar not like you");

        break;

      case 3:

        printf("Solar want a girlfriend");

        break;

      case 4:

        s = 0x7F666F6067756369LL;

        v5 = 0;

        strcat(&f2, (const char *)&s);

        break;

      case 5:

        for ( j = 0; j <= 7; ++j )

        {

          if ( j % 2 == 1 )

            *(&f2 + j) -= 2;

          else

            --*(&f2 + j);

        }

        break;

      default:

        puts("emmm,you can't find flag 23333");

        break;

    }

  }

  return __readfsqword(0x28u) ^ v6;

}

可以看出来是随机组合,最后得出flag。flag的拼接是在case1里进行的,flag=f1+f2,

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值