CTF刷题记录

本文详细记录了作者在CTF赛事中挑战的多个题目,包括题目名称、比赛平台和解题方向。涉及的解题方向主要是逆向工程,使用了如IDA等工具进行分析,通过动态调试、分析加密算法、编写解密脚本等方式成功解密,最终获取到各个题目对应的FLAG。文章展示了作者在REVERSE方向上的技能和经验。

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

目录

日期:2023.5.11

题目:[ACTF新生赛2020]usualCrypt

日期:2023.5.13

题目:[MRCTF2020]Xor

日期:2023.5.14

题目:Youngter-drive

日期:2023.5.15

题目:[MRCTF2020]hello_world_go

日期:2023.5.17

题目:[FlareOn4]IgniteMe

日期:2023.5.18

题目:[GWCTF 2019]xxor

题目:[WUSTCTF2020]Cr0ssfun

日期:2023.5.19

题目:[FlareOn6]Overlong

日期:2023.5.20

题目:[UTCTF2020]basic-re

题目:[FlareOn3]Challenge1

日期:2023.5.23

题目:[ACTF新生赛2020]Oruga

日期:2023.5.25

题目:特殊的 BASE64

日期:2023.5.26

题目:[ACTF新生赛2020]Universe_final_answer

日期:2023.5.27

题目:[羊城杯 2020]easyre

日期:2023.5.28

题目:[ACTF新生赛2020]fungame

日期:2023.5.30

题目:[RoarCTF2019]polyre


日期:2023.5.11

题目:[ACTF新生赛2020]usualCrypt

刷题平台:BUUCTF

方向:REVERSE

Write UP:

获取题目附件,将文件放入IDA中进行分析,找到main函数。查看main函数:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // esi
  int v5[3]; // [esp+8h] [ebp-74h] BYREF
  __int16 v6; // [esp+14h] [ebp-68h]
  char v7; // [esp+16h] [ebp-66h]
  char v8[100]; // [esp+18h] [ebp-64h] BYREF

  sub_403CF8(&unk_40E140);
  scanf("%s", v8);
  memset(v5, 0, sizeof(v5));
  v6 = 0;
  v7 = 0;
  sub_401080(v8, strlen(v8), v5);
  v3 = 0;
  while ( *(v5 + v3) == byte_40E0E4[v3] )
  {
    if ( ++v3 > strlen(v5) )
      goto LABEL_6;
  }
  sub_403CF8(aError);
LABEL_6:
  if ( v3 - 1 == strlen(byte_40E0E4) )
    return sub_403CF8(aAreYouHappyYes);
  else
    return sub_403CF8(aAreYouHappyNo);
}

可以猜测sub_403CF8是输出函数,密文存放在byte_40E0E4中,查看sub_401080函数:

int __cdecl sub_401080(int a1, int a2, int a3)
{
  int v3; // edi
  int v4; // esi
  int v5; // edx
  int v6; // eax
  int v7; // ecx
  int v8; // esi
  int v9; // esi
  int v10; // esi
  int v11; // esi
  _BYTE *v12; // ecx
  int v13; // esi
  int v15; // [esp+18h] [ebp+8h]

  v3 = 0;
  v4 = 0;
  sub_401000();
  v5 = a2 % 3;
  v6 = a1;
  v7 = a2 - a2 % 3;
  v15 = a2 % 3;
  if ( v7 > 0 )
  {
    do
    {
      LOBYTE(v5) = *(a1 + v3);
      v3 += 3;
      v8 = v4 + 1;
      *(v8 + a3 - 1) = aAbcdefghijklmn[(v5 >> 2) & 0x3F];
      *(++v8 + a3 - 1) = aAbcdefghijklmn[16 * (*(a1 + v3 - 3) & 3) + ((*(a1 + v3 - 2) >> 4) & 0xF)];
      *(++v8 + a3 - 1) = aAbcdefghijklmn[4 * (*(a1 + v3 - 2) & 0xF) + ((*(a1 + v3 - 1) >> 6) & 3)];
      v5 = *(a1 + v3 - 1) & 0x3F;
      v4 = v8 + 1;
      *(v4 + a3 - 1) = aAbcdefghijklmn[v5];
    }
    while ( v3 < v7 );
    v5 = v15;
  }
  if ( v5 == 1 )
  {
    LOBYTE(v7) = *(v3 + a1);
    v9 = v4 + 1;
    *(v9 + a3 - 1) = aAbcdefghijklmn[(v7 >> 2) & 0x3F];
    v10 = v9 + 1;
    *(v10 + a3 - 1) = aAbcdefghijklmn[16 * (*(v3 + a1) & 3)];
    *(v10 + a3) = 61;
LABEL_8:
    v13 = v10 + 1;
    *(v13 + a3) = 61;
    v4 = v13 + 1;
    goto LABEL_9;
  }
  if ( v5 == 2 )
  {
    v11 = v4 + 1;
    *(v11 + a3 - 1) = aAbcdefghijklmn[(*(v3 + a1) >> 2) & 0x3F];
    v12 = (v3 + a1 + 1);
    LOBYTE(v6) = *v12;
    v10 = v11 + 1;
    *(v10 + a3 - 1) = aAbcdefghijklmn[16 * (*(v3 + a1) & 3) + ((v6 >> 4) & 0xF)];
    *(v10 + a3) = aAbcdefghijklmn[4 * (*v12 & 0xF)];
    goto LABEL_8;
  }
LABEL_9:
  *(v4 + a3) = 0;
  return sub_401030(a3);
}

将密文进行base64解密后,发现不是flag,猜测可能进行了换表。查看函数,发现在进行base64加密前,sub_401000函数对base64表进行了修改。

int sub_401000()
{
  int result; // eax
  char v1; // cl

  for ( result = 6; result < 15; ++result )
  {
    v1 = aAbcdefghijklmn[result + 10];
    aAbcdefghijklmn[result + 10] = aAbcdefghijklmn[result];
    aAbcdefghijklmn[result] = v1;
  }
  return result;
}

通过动态调试来获取修改后的base64表,进行解密发现仍然是错误的,继续查看问题,发现在加密的末尾还有个sub_401030函数,查看函数发现是将密文的字母大小写进行了互换。

int __cdecl sub_401030(const char *a1)
{
  __int64 v1; // rax
  char v2; // al

  v1 = 0i64;
  if ( strlen(a1) )
  {
    do
    {
      v2 = a1[HIDWORD(v1)];
      if ( v2 < 97 || v2 > 122 )
      {
        if ( v2 < 65 || v2 > 90 )
          goto LABEL_9;
        LOBYTE(v1) = v2 + 32;
      }
      else
      {
        LOBYTE(v1) = v2 - 32;
      }
      a1[HIDWORD(v1)] = v1;
LABEL_9:
      LODWORD(v1) = 0;
      ++HIDWORD(v1);
    }
    while ( HIDWORD(v1) < strlen(a1) );
  }
  return v1;
}

思路理清后,完善解密脚本,脚本如下:

import base64
def main():
    str1 = "zMXHz3TIgnxLxJhFAdtZn2fFk3lYCrtPC2l9"
    str1 = str1.swapcase()
    string1 = "ABCDEFQRSTUVWXYPGHIJKLMNOZabcdefghijklmnopqrstuvwxyz0123456789+/"
    string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    print (base64.b64decode(str1.translate(str.maketrans(string1,string2))))
if __name__ == '__main__':
    main()

# b'flag{bAse64_h2s_a_Surprise}'

FLAG:flag{bAse64_h2s_a_Surprise}

日期:2023.5.13

题目:[MRCTF2020]Xor

刷题平台:BUUCTF

方向:REVERSE

Write UP:

获取题目附件,32位,放入IDA中进行分析,找到main函数,但是无法将程序转成伪代码,因为程序很短,所以直接阅读汇编。

先将一段字符串压入栈中,再跳转进一个函数,猜测sub_2E1020是一个输出函数,而后面的sub_2E1050函数有一个"%s"参数,猜测是输入函数。

这一段指令对计算了一下输入的字符串长度,并与0x1b进行比较,这里可以知道输入长度为0x1b,也就是27。 

"xor     eax, eax"这里将eax寄存器清零,后面用al寄存器与cl进行异或,cl中存放的是输入内容,因为eax作为下标,可以判断是将输入值与其对应下标进行异或。最后再与byte_2FEA08中存放的密文进行比较。

由此理清思路后,即可写出解密脚本,脚本如下: 

cipher = "MSAWB~FXZ:J:`tQJ\"N@ bpdd}8g"
for i in range(len(cipher)):
    print(chr(ord(cipher[i]) ^ i),end="")

# MRCTF{@_R3@1ly_E2_R3verse!}

FLAG:flag{@_R3@1ly_E2_R3verse!}

日期:2023.5.14

题目:Youngter-drive

刷题平台:BUUCTF

方向:REVERSE

Write UP:

获取题目附件,通过查壳工具发现存在UPX壳。

 对其进行脱壳后,放入IDA中进行分析,找到main函数。

int __cdecl main_0(int argc, const char **argv, const char **envp)
{
  HANDLE Thread; // [esp+D0h] [ebp-14h]
  HANDLE hObject; // [esp+DCh] [ebp-8h]

  sub_3610FF();
  ::hObject = CreateMutexW(0, 0, 0);
  j_strcpy(Destination, Source);
  hObject = CreateThread(0, 0, StartAddress, 0, 0, 0);
  Thread = CreateThread(0, 0, sub_36119F, 0, 0, 0);
  CloseHandle(hObject);
  CloseHandle(Thread);
  while ( dword_368008 != -1 )
    ;
  sub_361190();
  CloseHandle(::hObject);
  return 0;
}

可以看出程序创建了两个线程来执行两个函数,查看StartAddress函数和sub_36119F函数下的sub_361B10函数。

void __stdcall __noreturn StartAddress_0(int a1)
{
  while ( 1 )
  {
    WaitForSingleObject(hObject, 0xFFFFFFFF);
    if ( dword_368008 > -1 )
    {
      sub_36112C(Source, dword_368008);
      --dword_368008;
      Sleep(0x64u);
    }
    ReleaseMutex(hObject);
  }
}
void __stdcall __noreturn sub_361B10(int a1)
{
  while ( 1 )
  {
    WaitForSingleObject(hObject, 0xFFFFFFFF);
    if ( dword_368008 > -1 )
    {
      Sleep(0x64u);
      --dword_368008;
    }
    ReleaseMutex(hObject);
  }
}

先执行第一个线程,执行一次对输入的字符串第一个元素进行操作后休眠,再执行第二个线程对下一个元素进行操作后休眠,实际就是将输入的字符串下标为奇或偶时进行不同操作,于是可以写出下面的解密脚本:

cipher = "TOiZiZtOrYaToUwPnToBsOaOapsyS"
dist = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm"
flag = ""
flag1 = []
for i in range(29):
    if i % 2 == 1:
        if ord(cipher[i]) >= ord('A') and ord(cipher[i]) <= ord('Z'):
            flag += chr(dist.index(cipher[i])+96)
        else:
            flag += chr(dist.index(cipher[i])+38)
    else:
        flag += cipher[i]
print(flag)

# ThisisthreadofwindowshahaIsES

但是提交发现是错误的,而后看到其他师傅写的wp才知道,实际加密的是30个字符,检查时只检查了29个字符。那最后一个字符需要去猜一下。

FLAG:flag{ThisisthreadofwindowshahaIsESE}

日期:2023.5.15

题目:[MRCTF2020]hello_world_go

刷题平台:BUUCTF

方向:REVERSE

Write UP:

获取题目附件,在IDA中打开,找到一个main_main函数,flag就在这里。

// main.main
void __cdecl main_main()
{
  __int64 v0; // rcx
  __int64 v1; // rax
  __int64 v2; // rax
  __int64 v3; // [rsp+20h] [rbp-90h]
  __int64 v4; // [rsp+58h] [rbp-58h]
  __int64 *v5; // [rsp+60h] [rbp-50h]
  __int64 v6[2]; // [rsp+68h] [rbp-48h] BYREF
  __int64 v7[2]; // [rsp+78h] [rbp-38h] BYREF
  __int64 v8[2]; // [rsp+88h] [rbp-28h] BYREF
  __int64 v9[2]; // [rsp+98h] [rbp-18h] BYREF

  v5 = runtime_newobject(&RTYPE_string);
  v9[0] = &RTYPE_string;
  v9[1] = &off_4EA530;
  fmt_Fprint(&go_itab__ptr_os_File_comma_io_Writer, os_Stdout, v9, 1LL, 1LL);
  v8[0] = &RTYPE__ptr_string;
  v8[1] = v5;
  fmt_Fscanf(&go_itab__ptr_os_File_comma_io_Reader, os_Stdin, "%s", 2LL, v8, 1LL, 1LL);
  v0 = v5[1];
  v1 = *v5;
  if ( v0 != 24 )
    goto LABEL_2;
  v4 = *v5;
  if ( !runtime_memequal("flag{hello_world_gogogo}", v1, 24LL) )
  {
    v1 = v4;
    v0 = 24LL;
LABEL_2:
    runtime_cmpstring("flag{hello_world_gogogo}", 24LL, v1, v0, v3);
    if ( v3 >= 0 )
      v2 = 1LL;
    else
      v2 = -1LL;
    goto LABEL_4;
  }
  v2 = 0LL;
LABEL_4:
  if ( v2 )
  {
    v6[0] = &RTYPE_string;
    v6[1] = &off_4EA550;
    fmt_Fprintln(&go_itab__ptr_os_File_comma_io_Writer, os_Stdout, v6, 1LL, 1LL);
  }
  else
  {
    v7[0] = &RTYPE_string;
    v7[1] = &off_4EA540;
    fmt_Fprintln(&go_itab__ptr_os_File_comma_io_Writer, os_Stdout, v7, 1LL, 1LL);
  }
}

FLAG:flag{hello_world_gogogo}

日期:2023.5.17

题目:[FlareOn4]IgniteMe

刷题平台:BUUCTF

方向:REVERSE

Write UP:

获取题目附件,32位,无壳。

根据Description.txt可以知道flag的格式:

 

 在IDA中找到一个sub_401050函数,这里对输入值进行了加密,并于密文进行了比较。

int sub_401050()
{
  int v1; // [esp+0h] [ebp-Ch]
  int i; // [esp+4h] [ebp-8h]
  unsigned int j; // [esp+4h] [ebp-8h]
  char v4; // [esp+Bh] [ebp-1h]

  v1 = sub_401020(byte_403078);
  v4 = sub_401000();
  for ( i = v1 - 1; i >= 0; --i )
  {
    byte_403180[i] = v4 ^ byte_403078[i];
    v4 = byte_403078[i];
  }
  for ( j = 0; j < 39; ++j )
  {
    if ( byte_403180[j] != byte_403000[j] )
      return 0;
  }
  return 1;
}

在第一个for语句处下一个断点,可以知道v4的值为4,接着就可以写解密脚本:

cipher = [  0x0D, 0x26, 0x49, 0x45, 0x2A, 0x17, 0x78, 0x44, 0x2B, 0x6C, 0x5D, 0x5E, 0x45, 0x12, 0x2F, 0x17, 0x2B, 0x44, 0x6F, 0x6E, 0x56, 0x09, 0x5F, 0x45, 0x47, 0x73, 0x26, 0x0A, 0x0D, 0x13, 0x17, 0x48, 0x42, 0x01, 0x40, 0x4D, 0x0C, 0x02, 0x69]
key = 4
flag = ""
for i in range(len(cipher)-1,-1,-1):

    cipher[i] = cipher[i] ^ key
    key = cipher[i]

for j in range(len(cipher)):
    print(chr(cipher[j]),end="")

# R_y0u_H0t_3n0ugH_t0_1gn1t3@flare-on.com

FLAG:flag{R_y0u_H0t_3n0ugH_t0_1gn1t3@flare-on.com}

日期:2023.5.18

题目:[GWCTF 2019]xxor

刷题平台:BUUCTF

方向:REVERSE

Write UP:

获取题目附件,64位,无壳。 

 在IDA中进行分析,查看main函数。

__int64 __fastcall main(int a1, char **input, char **a3)
{
  int i; // [rsp+8h] [rbp-68h]
  int j; // [rsp+Ch] [rbp-64h]
  __int64 v6[6]; // [rsp+10h] [rbp-60h] BYREF
  __int64 v7[6]; // [rsp+40h] [rbp-30h] BYREF

  v7[5] = __readfsqword(0x28u);
  puts("Let us play a game?");
  puts("you have six chances to input");
  puts("Come on!");
  memset(v6, 0, 40);
  for ( i = 0; i <= 5; ++i )
  {
    printf("%s", "input: ");
    input = (v6 + 4 * i);
    __isoc99_scanf("%d", input);
  }
  memset(v7, 0, 40);
  for ( j = 0; j <= 2; ++j )
  {
    dword_601078 = v6[j];
    dword_60107C = HIDWORD(v6[j]);
    input = &unk_601060;
    sub_400686(&dword_601078, &unk_601060);
    LODWORD(v7[j]) = dword_601078;
    HIDWORD(v7[j]) = dword_60107C;
  }
  if ( sub_400770(v7, input) != 1 )
  {
    puts("NO NO NO~ ");
    exit(0);
  }
  puts("Congratulation!\n");
  puts("You seccess half\n");
  puts("Do not forget to change input to hex and combine~\n");
  puts("ByeBye");
  return 0LL;
}

先看最后进行判断的函数sub_400770:

__int64 __fastcall sub_400770(_DWORD *x)
{
  if ( x[2] - x[3] == 2225223423LL
    && x[3] + x[4] == 0xFA6CB703LL
    && x[2] - x[4] == 0x42D731A8LL
    && *x == 0xDF48EF7E
    && x[5] == 0x84F30420
    && x[1] == 0x20CAACF4 )
  {
    puts("good!");
    return 1LL;
  }
  else
  {
    puts("Wrong!");
    return 0LL;
  }
}

可以知道数组x中存放的是加密的密文,于是先获取到密文:

from z3 import *
def main():
    x = [BitVec("x%d"%i,64)for i in range(6)]
    s = Solver()
    s.add( x[2] - x[3] == 0x84A236FF)
    s.add( x[3] + x[4] == 0xFA6CB703)
    s.add( x[2] - x[4] == 0x42D731A8)
    s.add( x[0] == 0xDF48EF7E)
    s.add( x[5] == 0x84F30420)
    s.add( x[1] == 0x20CAACF4)
    if s.check() == sat:
        v = s.model()
        for i in range(6):
            print(v[x[i]],end=" ")

if __name__ == '__main__':
    main()

# 3746099070 550153460 3774025685 1548802262 2652626477 2230518816

接着查看sub_400686函数,可以明显看出,是进行了tea算法加密:

__int64 __fastcall sub_400686(unsigned int *v, _DWORD *k)
{
  __int64 result; // rax
  unsigned int v3; // [rsp+1Ch] [rbp-24h]
  unsigned int v4; // [rsp+20h] [rbp-20h]
  int sum1; // [rsp+24h] [rbp-1Ch]
  unsigned int i; // [rsp+28h] [rbp-18h]

  v3 = *v;
  v4 = v[1];
  sum1 = 0;
  for ( i = 0; i <= 63; ++i )
  {
    sum1 += 0x458BCD42;
    v3 += (v4 + sum1 + 11) ^ ((v4 << 6) + *k) ^ ((v4 >> 9) + k[1]) ^ 0x20;
    v4 += (v3 + sum1 + 20) ^ ((v3 << 6) + k[2]) ^ ((v3 >> 9) + k[3]) ^ 0x10;
  }
  *v = v3;
  result = v4;
  v[1] = v4;
  return result;
}

sub_400686传了两个参数,第一个是输入的值,第二个是key,通过查看unk_601060即可知道key值。接着写解密脚本即可,尝试了一下用python来写解密tea脚本:

import ctypes
def main():
    v = [3746099070,550153460,3774025685,1548802262,2652626477,2230518816]
    k = [2,2,3,4]
    flag = ""
    for i in range(0,6,2):
        DELTA = 0x458BCD42
        sum1 = 64*DELTA
        v0 = ctypes.c_uint32(v[i])
        v1 = ctypes.c_uint32(v[i+1])
        for j in range(64):
            v1.value -= (v0.value + sum1 + 20) ^ ((v0.value << 6) + k[2]) ^ ((v0.value >> 9) + k[3]) ^ 0x10
            v0.value -= (v1.value + sum1 + 11) ^ ((v1.value << 6) + k[0]) ^ ((v1.value >> 9) + k[1]) ^ 0x20
            sum1 -= DELTA
        flag += hex(v0.value)[2:].zfill(6)
        flag += hex(v1.value)[2:].zfill(6) # 帮忙补零和去掉前缀"0x"
    for i in range(0,len(flag),2):
        print(chr(int(flag[i]+flag[i+1],16)),end="")
if __name__ == '__main__':
    main()

# flag{re_is_great!}

FLAG:flag{re_is_great!}

题目:[WUSTCTF2020]Cr0ssfun

刷题平台:BUUCTF

方向:REVERSE

Write UP:

获取题目附件,64位,无壳。

查看其中的main函数。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4[48]; // [rsp+0h] [rbp-30h] BYREF

  puts(" _    _ _   _ _____ _____   _____           ");
  puts("| |  | | | | /  ___|_   _| /  ___|          ");
  puts("| |  | | | | \\ `--.  | |   \\ `--.  ___  ___ ");
  puts("| |/\\| | | | |`--. \\ | |    `--. \\/ _ \\/ __|");
  puts("\\  /\\  / |_| /\\__/ / | |   /\\__/ /  __/ (__ ");
  puts(" \\/  \\/ \\___/\\____/  \\_/   \\____/ \\___|\\___|");
  while ( 1 )
  {
    puts("Input the flag");
    __isoc99_scanf("%s", v4);
    if ( check(v4) == 1 )
      break;
    puts("0ops, your flag seems fake.");
    puts("==============================");
    rewind(_bss_start);
  }
  puts("Your flag is correct, go and submit it!");
  return 0;
}

 可以看出关键在check函数。check函数中对输入值进行了判断,按顺序进行拼接就可以拿到flag。

_BOOL8 __fastcall iven_is_handsome(_BYTE *a1)
{
  return a1[10] == 'p' && a1[13] == '@' && a1[3] == 'f' && a1[26] == 'r' && a1[20] == 'e' && iven_is_c0ol(a1);
}

但是,感觉这个可以试一试用angr,就尝试写了一个脚本来获取flag,脚本如下:

import angr
import sys
def main(argv):
    file_path = argv[1]
    p = angr.Project(file_path,auto_load_libs=False)
    start_state = p.factory.entry_state()
    sm = p.factory.simgr(start_state)
    def is_good(state):
        res = state.posix.dumps(1)
        if b'Your flag is correct, go and submit it!' in res:
            return True
        else:
            return False

    def is_bad(state):
        res = state.posix.dumps(1)
        if b'0ops, your flag seems fake.' in res:
            return True
        else:
            return False
    sm.explore(find = is_good,avoid = is_bad)
    if sm.found:
        found = sm.found[0]
        solution = found.posix.dumps(0)
        print(solution)
    else:
        print("NONE")

if __name__ == '__main__':
    main(sys.argv)

# wctf2020{cpp_@nd_r3verse_@re_fun}

FLAG:flag{cpp_@nd_r3verse_@re_fun}

日期:2023.5.19

题目:[FlareOn6]Overlong

刷题平台:BUUCTF

方向:REVERSE

Write UP:

获取题目附件,32位,无壳。

在IDA中可以看到,程序只有三个函数。

int __stdcall start(int a1, int a2, int a3, int a4)
{
  CHAR Text[128]; // [esp+0h] [ebp-84h] BYREF
  int v6; // [esp+80h] [ebp-4h]

  v6 = sub_401160(Text, &unk_402008, 28);
  Text[v6] = 0;
  MessageBoxA(0, Text, Caption, 0);
  return 0;
}
int __stdcall start(int a1, int a2, int a3, int a4)
{
  CHAR Text[128]; // [esp+0h] [ebp-84h] BYREF
  int v6; // [esp+80h] [ebp-4h]

  v6 = sub_401160(Text, &unk_402008, 28);
  Text[v6] = 0;
  MessageBoxA(0, Text, Caption, 0);
  return 0;
}

int __cdecl sub_401000(_BYTE *a1, char *a2)
{
  int v3; // [esp+0h] [ebp-8h]
  char v4; // [esp+4h] [ebp-4h]

  if ( (int)(unsigned __int8)*a2 >> 3 == 30 )
  {
    v4 = a2[3] & 0x3F | ((a2[2] & 0x3F) << 6);
    v3 = 4;
  }
  else if ( (int)(unsigned __int8)*a2 >> 4 == 14 )
  {
    v4 = a2[2] & 0x3F | ((a2[1] & 0x3F) << 6);
    v3 = 3;
  }
  else if ( (int)(unsigned __int8)*a2 >> 5 == 6 )
  {
    v4 = a2[1] & 0x3F | ((*a2 & 0x1F) << 6);
    v3 = 2;
  }
  else
  {
    v4 = *a2;
    v3 = 1;
  }
  *a1 = v4;
  return v3;
}

 通过分析程序可以知道,是将密文进行解密后,以弹窗的形式显示出来。

但是根据程序可知,将28个字符以弹窗形式显示,实际要解密出的内容并不止那么点。

 通过修改sub_401160传的参数,也就是长度,从而可以得到flag。

 

下断点,直接截取flag。

FLAG: flag{I_a_M_t_h_e_e_n_C_o_D_i_n_g@flare-on.com}

日期:2023.5.20

题目:[UTCTF2020]basic-re

刷题平台:BUUCTF

方向:REVERSE

Write UP:

获取题目附件,64位,无壳。 

用IDA进行分析,发现,flag就藏在main函数中。

 FLAG:flag{str1ngs_1s_y0ur_fr13nd}

题目:[FlareOn3]Challenge1

刷题平台:BUUCTF

方向:REVERSE

Write UP:

获取题目附件,32位,无壳。

 用IDA进行分析,可以在main函数中找到一串密文,并猜测sub_401260函数对输入内容进行了加密。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char Buffer[128]; // [esp+0h] [ebp-94h] BYREF
  char *Str1; // [esp+80h] [ebp-14h]
  char *Str2; // [esp+84h] [ebp-10h]
  HANDLE StdHandle; // [esp+88h] [ebp-Ch]
  HANDLE hFile; // [esp+8Ch] [ebp-8h]
  DWORD NumberOfBytesWritten; // [esp+90h] [ebp-4h] BYREF

  hFile = GetStdHandle(0xFFFFFFF5);
  StdHandle = GetStdHandle(0xFFFFFFF6);
  Str2 = "x2dtJEOmyjacxDemx2eczT5cVS9fVUGvWTuZWjuexjRqy24rV29q";
  WriteFile(hFile, "Enter password:\r\n", 0x12u, &NumberOfBytesWritten, 0);
  ReadFile(StdHandle, Buffer, 0x80u, &NumberOfBytesWritten, 0);
  Str1 = sub_401260(Buffer, NumberOfBytesWritten - 2);
  if ( !strcmp(Str1, Str2) )
    WriteFile(hFile, "Correct!\r\n", 0xBu, &NumberOfBytesWritten, 0);
  else
    WriteFile(hFile, "Wrong password\r\n", 0x11u, &NumberOfBytesWritten, 0);
  return 0;
}

查看sub_401260,可以看出sub_401260函数对输入内容进行了base64加密。

_BYTE *__cdecl sub_401260(int a1, unsigned int a2)
{
  int v3; // [esp+Ch] [ebp-24h]
  int v4; // [esp+10h] [ebp-20h]
  int v5; // [esp+14h] [ebp-1Ch]
  int i; // [esp+1Ch] [ebp-14h]
  unsigned int v7; // [esp+20h] [ebp-10h]
  _BYTE *v8; // [esp+24h] [ebp-Ch]
  int v9; // [esp+28h] [ebp-8h]
  int v10; // [esp+28h] [ebp-8h]
  unsigned int v11; // [esp+2Ch] [ebp-4h]

  v8 = malloc(4 * ((a2 + 2) / 3) + 1);
  if ( !v8 )
    return 0;
  v11 = 0;
  v9 = 0;
  while ( v11 < a2 )
  {
    v5 = *(v11 + a1);
    if ( ++v11 >= a2 )
    {
      v4 = 0;
    }
    else
    {
      v4 = *(v11 + a1);
      ++v11;
    }
    if ( v11 >= a2 )
    {
      v3 = 0;
    }
    else
    {
      v3 = *(v11 + a1);
      ++v11;
    }
    v7 = v3 + (v5 << 16) + (v4 << 8);
    v8[v9] = aZyxabcdefghijk[(v7 >> 18) & 0x3F];
    v10 = v9 + 1;
    v8[v10] = aZyxabcdefghijk[(v7 >> 12) & 0x3F];
    v8[++v10] = aZyxabcdefghijk[(v7 >> 6) & 0x3F];
    v8[++v10] = aZyxabcdefghijk[v3 & 0x3F];
    v9 = v10 + 1;
  }
  for ( i = 0; i < MEMORY[0x413040][a2 % 3]; ++i )
    v8[4 * ((a2 + 2) / 3) - i - 1] = 61;
  v8[4 * ((a2 + 2) / 3)] = 0;
  return v8;
}

查看base64表,可以看出是变表base64。

.data:00413000 5A 59 58 41 42 43 44 45 46 47+aZyxabcdefghijk db 'ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/',0

由此可以写解密脚本,脚本如下:

import base64
def main():
    str1 = "x2dtJEOmyjacxDemx2eczT5cVS9fVUGvWTuZWjuexjRqy24rV29q"
    string1 = "ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/"
    string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    print(base64.b64decode(str1.translate(str.maketrans(string1,string2))))
if __name__ == '__main__':
    main()

# sh00ting_phish_in_a_barrel@flare-on.com

FLAG:flag{sh00ting_phish_in_a_barrel@flare-on.com}

日期:2023.5.23

题目:[ACTF新生赛2020]Oruga

刷题平台:BUUCTF

方向:REVERSE

Write UP:

获取题目附件,64位,无壳。

 放入IDA中进行分析,查看main函数:

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  int i; // [rsp+0h] [rbp-40h]
  char s1[6]; // [rsp+4h] [rbp-3Ch] BYREF
  char s2[6]; // [rsp+Ah] [rbp-36h] BYREF
  char s[40]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v8; // [rsp+38h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  memset(s, 0, 0x19uLL);
  printf("Tell me the flag:");
  scanf("%s", s);
  strcpy(s2, "actf{");
  for ( i = 0; i <= 4; ++i )
    s1[i] = s[i];
  s1[5] = 0;
  if ( !strcmp(s1, s2) )
  {
    if ( sub_78A(s) )
      printf("That's True Flag!");
    else
      printf("don't stop trying...");
    return 0LL;
  }
  else
  {
    printf("Format false!");
    return 0LL;
  }
}

看到这样一个结构,感觉可以用angr进行求解flag,就尝试了一下。解密脚本如下:

import angr
import sys
def main(argv):
    file_path = argv[1]
    p = angr.Project(file_path,auto_load_libs=False)
    start_state = p.factory.entry_state()
    sm = p.factory.simgr(start_state)
    def is_good(state):
        res = state.posix.dumps(1)
        if b'That\'s True Flag!' in res:
            return True
        else:
            return False
 
    def is_bad(state):
        res = state.posix.dumps(1)
        if b'don\'t stop trying...' in res:
            return True
        else:
            return False
    sm.explore(find = is_good,avoid = is_bad)
    if sm.found:
        found = sm.found[0]
        solution = found.posix.dumps(0)
        print(solution)
    else:
        print("NONE")
 
if __name__ == '__main__':
    main(sys.argv)
 

结果可以直接跑出flag。

这是一个比较方便的方法,也可以对其中函数进行分析,可以看出关键函数是sub_78A,查看函数:

_BOOL8 __fastcall sub_78A(__int64 a1)
{
  int v2; // [rsp+Ch] [rbp-Ch]
  int v3; // [rsp+10h] [rbp-8h]
  int v4; // [rsp+14h] [rbp-4h]

  v2 = 0;
  v3 = 5;
  v4 = 0;
  while ( byte_201020[v2] != '!' )
  {
    v2 -= v4;
    if ( *(v3 + a1) != 'W' || v4 == -16 )
    {
      if ( *(v3 + a1) != 'E' || v4 == 1 )
      {
        if ( *(v3 + a1) != 'M' || v4 == 16 )
        {
          if ( *(v3 + a1) != 'J' || v4 == -1 )
            return 0LL;
          v4 = -1;
        }
        else
        {
          v4 = 16;
        }
      }
      else
      {
        v4 = 1;
      }
    }
    else
    {
      v4 = -16;
    }
    ++v3;
    while ( !byte_201020[v2] )
    {
      if ( v4 == -1 && (v2 & 0xF) == 0 )
        return 0LL;
      if ( v4 == 1 && v2 % 16 == 15 )
        return 0LL;
      if ( v4 == 16 && (v2 - 240) <= 0xF )
        return 0LL;
      if ( v4 == -16 && (v2 + 15) <= 0x1E )
        return 0LL;
      v2 += v4;
    }
  }
  return *(v3 + a1) == '}';
}

分析程序,可以看出这是一个迷宫题。"W"是向上走,"E"是向右走,"M"是向下走,"J"是向左走。走法是会一直往一个方向走直到有障碍物挡着才会停下。

地图存在byte_201020数组中,地图如下:

 可以用来走的路是0,其他是障碍物,从左上角走到"!"的位置即可。

FLAG:flag{MEWEMEWJMEWJM}

日期:2023.5.25

题目:特殊的 BASE64

刷题平台:BUUCTF

方向:REVERSE

Write UP:

获取题目附件,64位,无壳。

 放入IDA中进行分析,找到main函数。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  std::ostream *v3; // rax
  std::ostream *v4; // rax
  std::string result; // [rsp+20h] [rbp-60h] BYREF
  std::string rightFlag; // [rsp+30h] [rbp-50h] BYREF
  std::string str; // [rsp+40h] [rbp-40h] BYREF
  char v9; // [rsp+4Fh] [rbp-31h] BYREF
  std::string v10; // [rsp+50h] [rbp-30h] BYREF

  _main();
  std::string::string(&str);
  std::allocator<char>::allocator(&v9);
  std::string::string(&rightFlag, "mTyqm7wjODkrNLcWl0eqO8K8gc1BPk1GNLgUpI==", &v9);
  std::allocator<char>::~allocator(&v9);
  v3 = std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Please input your flag!!!!");
  refptr__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_(v3);
  std::operator>><char>(refptr__ZSt3cin, &str);
  std::string::string(&v10, &str);
  base64Encode(&result);
  std::string::~string(&v10);
  if ( std::operator==<char>(&result, &rightFlag) )
    v4 = std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "The flag is right!!!!!!!!!");
  else
    v4 = std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "This is a wrong flag!!!!!!!!");
  refptr__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_(v4);
  std::string::~string(&result);
  std::string::~string(&rightFlag);
  std::string::~string(&str);
  return 0;
}

根据题目可知,进行的是base64加密,也在main函数中找到一个很像base64加密的密文"mTyqm7wjODkrNLcWl0eqO8K8gc1BPk1GNLgUpI==",但通过解密发现明文不正确,猜测进行了换表,查看base64Encode函数中的base64表,找到真正的表。

接着即可开始写解密脚本,脚本如下:

import base64
def main():
    str1 = "mTyqm7wjODkrNLcWl0eqO8K8gc1BPk1GNLgUpI=="
    string1 = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0987654321/+"
    string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    print(base64.b64decode(str1.translate(str.maketrans(string1,string2))))

if __name__ == '__main__':
    main()

# flag{Special_Base64_By_Lich}

FLAG:flag{Special_Base64_By_Lich}

日期:2023.5.26

题目:[ACTF新生赛2020]Universe_final_answer

刷题平台:BUUCTF

方向:REVERSE

Write UP:

获取题目附件,64位,无壳。 

在IDA中进行分析,找到main函数,发现两个特别的函数,sub_860函数和sub_C50函数。

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  __int64 v4; // [rsp+0h] [rbp-A8h] BYREF
  char v5[104]; // [rsp+20h] [rbp-88h] BYREF
  unsigned __int64 v6; // [rsp+88h] [rbp-20h]

  v6 = __readfsqword(0x28u);
  __printf_chk(1LL, "Please give me the key string:", a3);
  scanf("%s", v5);
  if ( sub_860(v5) )
  {
    sub_C50(v5, &v4);
    __printf_chk(1LL, "Judgement pass! flag is actf{%s_%s}\n", v5);
  }
  else
  {
    puts("False key!");
  }
  return 0LL;
}

查看这两个函数。

bool __fastcall sub_860(char *x)
{
  int v1; // ecx
  int v2; // esi
  int v3; // edx
  int v4; // r9d
  int v5; // r11d
  int v6; // ebp
  int v7; // ebx
  int v8; // r8d
  int v9; // r10d
  bool result; // al
  int v11; // [rsp+0h] [rbp-38h]

  v1 = x[1];
  v2 = *x;
  v3 = x[2];
  v4 = x[3];
  v5 = x[4];
  v6 = x[6];
  v7 = x[5];
  v8 = x[7];
  v9 = x[8];
  result = 0;
  if ( -85 * v9 + 58 * v8 + 97 * v6 + v7 + -45 * v5 + 84 * v4 + 95 * v2 - 20 * v1 + 12 * v3 == 12613 )
  {
    v11 = x[9];
    if ( 30 * v11 + -70 * v9 + -122 * v6 + -81 * v7 + -66 * v5 + -115 * v4 + -41 * v3 + -86 * v1 - 15 * v2 - 30 * v8 == -54400
      && -103 * v11 + 120 * v8 + 108 * v7 + 48 * v4 + -89 * v3 + 78 * v1 - 41 * v2 + 31 * v5 - (v6 << 6) - 120 * v9 == -10283
      && 71 * v6 + (v7 << 7) + 99 * v5 + -111 * v3 + 85 * v1 + 79 * v2 - 30 * v4 - 119 * v8 + 48 * v9 - 16 * v11 == 22855
      && 5 * v11 + 23 * v9 + 122 * v8 + -19 * v6 + 99 * v7 + -117 * v5 + -69 * v3 + 22 * v1 - 98 * v2 + 10 * v4 == -2944
      && -54 * v11 + -23 * v8 + -82 * v3 + -85 * v2 + 124 * v1 - 11 * v4 - 8 * v5 - 60 * v7 + 95 * v6 + 100 * v9 == -2222
      && -83 * v11 + -111 * v7 + -57 * v2 + 41 * v1 + 73 * v3 - 18 * v4 + 26 * v5 + 16 * v6 + 77 * v8 - 63 * v9 == -13258
      && 81 * v11 + -48 * v9 + 66 * v8 + -104 * v6 + -121 * v7 + 95 * v5 + 85 * v4 + 60 * v3 + -85 * v2 + 80 * v1 == -1559
      && 101 * v11 + -85 * v9 + 7 * v6 + 117 * v7 + -83 * v5 + -101 * v4 + 90 * v3 + -28 * v1 + 18 * v2 - v8 == 6308 )
    {
      return 99 * v11 + -28 * v9 + 5 * v8 + 93 * v6 + -18 * v7 + -127 * v5 + 6 * v4 + -9 * v3 + -93 * v1 + 58 * v2 == -1697;
    }
  }
  return result;
}
unsigned __int64 __fastcall sub_C50(const char *a1, _BYTE *a2)
{
  size_t v4; // rax
  unsigned int v5; // edx
  int v6; // edi
  int v7; // ecx
  __int64 v8; // r8
  __int128 *v9; // rsi
  unsigned int v10; // ecx
  int v11; // eax
  int v12; // edi
  int v13; // edx
  int v14; // eax
  _BYTE *v15; // rsi
  _BYTE *v16; // rcx
  _BYTE *v17; // r8
  int *i; // rax
  unsigned __int64 result; // rax
  __int128 v20[2]; // [rsp+0h] [rbp-48h] BYREF
  __int64 v21; // [rsp+20h] [rbp-28h]
  unsigned __int64 v22; // [rsp+28h] [rbp-20h]

  v22 = __readfsqword(0x28u);
  memset(v20, 0, sizeof(v20));
  v21 = 0LL;
  v4 = strlen(a1);
  v5 = 0;
  v6 = 9;
  while ( v5 < v4 )
  {
    v7 = a1[v5++];
    v6 ^= v7;
  }
  if ( v6 )
  {
    v8 = 0LL;
    v9 = v20;
    while ( 1 )
    {
      v9 = (v9 + 4);
      v10 = v8 + 1;
      v11 = v6 / 10;
      v12 = v6 % 10;
      *(v9 - 1) = v12;
      LOBYTE(v13) = v12;
      v6 = v11;
      if ( !v11 )
        break;
      v8 = v10;
    }
    v14 = v8 - 1;
    v15 = a2;
    v16 = &a2[v10];
    v17 = &a2[v8];
    for ( i = v20 + v14; ; --i )
    {
      *v15 = v13 + 48;
      if ( v17 == v15 )
        break;
      v13 = *i;
      ++v15;
    }
  }
  else
  {
    v16 = a2;
  }
  result = __readfsqword(0x28u) ^ v22;
  *v16 = 0;
  return result;
}

根据程序分析可以知道,sub_860函数是用来判断输入的key值的,只要将正确的key值求出并输入到程序中,即可得到flag,求解key的脚本:

from z3 import *
def main():
    x = [BitVec("x%i"%i,16) for i in range(10)]
    s = Solver()
    s.add( -85 * x[8] + 58 * x[7] + 97 * x[6] + x[5] + -45 * x[4] + 84 * x[3] + 95 * x[0] - 20 * x[1] + 12 * x[2] == 12613 )
    s.add( 30 * x[9] + -70 * x[8] + -122 * x[6] + -81 * x[5] + -66 * x[4] + -115 * x[3] + -41 * x[2] + -86 * x[1] - 15 * x[0] - 30 * x[7] == -54400 )
    s.add( -103 * x[9] + 120 * x[7] + 108 * x[5] + 48 * x[3] + -89 * x[2] + 78 * x[1] - 41 * x[0] + 31 * x[4] - (x[6] << 6) - 120 * x[8] == -10283 )
    s.add( 71 * x[6] + (x[5] << 7) + 99 * x[4] + -111 * x[2] + 85 * x[1] + 79 * x[0] - 30 * x[3] - 119 * x[7] + 48 * x[8] - 16 * x[9] == 22855 )
    s.add( 5 * x[9] + 23 * x[8] + 122 * x[7] + -19 * x[6] + 99 * x[5] + -117 * x[4] + -69 * x[2] + 22 * x[1] - 98 * x[0] + 10 * x[3] == -2944 )
    s.add( -54 * x[9] + -23 * x[7] + -82 * x[2] + -85 * x[0] + 124 * x[1] - 11 * x[3] - 8 * x[4] - 60 * x[5] + 95 * x[6] + 100 * x[8] == -2222 )
    s.add( -83 * x[9] + -111 * x[5] + -57 * x[0] + 41 * x[1] + 73 * x[2] - 18 * x[3] + 26 * x[4] + 16 * x[6] + 77 * x[7] - 63 * x[8] == -13258 )
    s.add( 81 * x[9] + -48 * x[8] + 66 * x[7] + -104 * x[6] + -121 * x[5] + 95 * x[4] + 85 * x[3] + 60 * x[2] + -85 * x[0] + 80 * x[1] == -1559 )
    s.add( 101 * x[9] + -85 * x[8] + 7 * x[6] + 117 * x[5] + -83 * x[4] + -101 * x[3] + 90 * x[2] + -28 * x[1] + 18 * x[0] - x[7] == 6308 )
    s.add( 99 * x[9] + -28 * x[8] + 5 * x[7] + 93 * x[6] + -18 * x[5] + -127 * x[4] + 6 * x[3] + -9 * x[2] + -93 * x[1] + 58 * x[0] == -1697 )
    if s.check() == sat:
        value = s.model()
        for i in range(10):
            print(value[x[i]],end=",")
            
    flag = [70,48,117,82,84,121,95,55,119,64]
    for i in range(len(flag)):
        print(chr(flag[i]),end="")
if __name__ == '__main__':
    main()

从而求出key为"F0uRTy_7w@",将其输入到程序中,即可得到flag。

flag{F0uRTy_7w@_42}

日期:2023.5.27

题目:[羊城杯 2020]easyre

刷题平台:BUUCTF

方向:REVERSE

Write UP:

获取题目附件,64位,无壳。

 放入IDA中进行分析,找到main函数,查看main函数。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  int v4; // eax
  int v5; // eax
  char Str[48]; // [rsp+20h] [rbp-60h] BYREF
  char Str1[64]; // [rsp+50h] [rbp-30h] BYREF
  char v9[64]; // [rsp+90h] [rbp+10h] BYREF
  char v10[64]; // [rsp+D0h] [rbp+50h] BYREF
  char Str2[60]; // [rsp+110h] [rbp+90h] BYREF
  int v12; // [rsp+14Ch] [rbp+CCh] BYREF

  _main();
  strcpy(Str2, "EmBmP5Pmn7QcPU4gLYKv5QcMmB3PWHcP5YkPq3=cT6QckkPckoRG");
  puts("Hello, please input your flag and I will tell you whether it is right or not.");
  scanf("%38s", Str);
  if ( strlen(Str) != 38
    || (v3 = strlen(Str), encode_one(Str, v3, v10, &v12))
    || (v4 = strlen(v10), encode_two(v10, v4, v9, &v12))
    || (v5 = strlen(v9), encode_three(v9, v5, Str1, &v12))
    || strcmp(Str1, Str2) )
  {
    printf("Something wrong. Keep going.");
    return 0;
  }
  else
  {
    puts("you are right!");
    return 0;
  }
}

可以看出程序依次进行了三个不同的加密,查看这三个函数。

__int64 __fastcall encode_one(char *a1, int a2, char *a3, int *a4)
{
  int v5; // esi
  int v6; // esi
  int v7; // esi
  int v8; // [rsp+34h] [rbp-1Ch]
  int v9; // [rsp+38h] [rbp-18h]
  int v11; // [rsp+48h] [rbp-8h]
  int i; // [rsp+4Ch] [rbp-4h]
  unsigned __int8 *v13; // [rsp+70h] [rbp+20h]

  v13 = a1;
  if ( !a1 || !a2 )
    return 0xFFFFFFFFi64;
  v11 = 0;
  if ( a2 % 3 )
    v11 = 3 - a2 % 3;
  v9 = a2 + v11;
  v8 = 8 * (a2 + v11) / 6;
  for ( i = 0; i < v9; i += 3 )
  {
    *a3 = alphabet[*v13 >> 2];
    if ( a2 + v11 - 3 == i && v11 )
    {
      if ( v11 == 1 )
      {
        v5 = cmove_bits(*v13, 6u, 2u);
        a3[1] = alphabet[v5 + cmove_bits(v13[1], 0, 4u)];
        a3[2] = alphabet[cmove_bits(v13[1], 4u, 2u)];
        a3[3] = 61;
      }
      else if ( v11 == 2 )
      {
        a3[1] = alphabet[cmove_bits(*v13, 6u, 2u)];
        a3[2] = 61;
        a3[3] = 61;
      }
    }
    else
    {
      v6 = cmove_bits(*v13, 6u, 2u);
      a3[1] = alphabet[v6 + cmove_bits(v13[1], 0, 4u)];
      v7 = cmove_bits(v13[1], 4u, 2u);
      a3[2] = alphabet[v7 + cmove_bits(v13[2], 0, 6u)];
      a3[3] = alphabet[v13[2] & 0x3F];
    }
    a3 += 4;
    v13 += 3;
  }
  if ( a4 )
    *a4 = v8;
  return 0i64;
}
__int64 __fastcall encode_two(const char *a1, int a2, char *a3, int *a4)
{
  if ( !a1 || !a2 )
    return 0xFFFFFFFFi64;
  strncpy(a3, a1 + 26, 0xDui64);
  strncpy(a3 + 13, a1, 0xDui64);
  strncpy(a3 + 26, a1 + 39, 0xDui64);
  strncpy(a3 + 39, a1 + 13, 13ui64);
  return 0i64;
}
__int64 __fastcall encode_three(const char *a1, int a2, char *a3, int *a4)
{
  char v5; // [rsp+Fh] [rbp-11h]
  int i; // [rsp+14h] [rbp-Ch]
  const char *v8; // [rsp+30h] [rbp+10h]

  v8 = a1;
  if ( !a1 || !a2 )
    return 0xFFFFFFFFi64;
  for ( i = 0; i < a2; ++i )
  {
    v5 = *v8;
    if ( *v8 <= '@' || v5 > 'Z' )
    {
      if ( v5 <= 96 || v5 > 122 )
      {
        if ( v5 <= '/' || v5 > '9' )
          *a3 = v5;
        else
          *a3 = (v5 - 48 + 3) % 10 + 48;
      }
      else
      {
        *a3 = (v5 - 97 + 3) % 26 + 97;
      }
    }
    else
    {
      *a3 = (v5 - 65 + 3) % 26 + 65;
    }
    ++a3;
    ++v8;
  }
  return 0i64;
}

可以看出,第一个加密函数进行的是base64加密(无变表),第二个加密函数是来打乱base64加密后的字符串,第三个加密函数进行的是凯撒加密,偏移值为3。

由此,按相反的顺序来进行对应的逆运算解密,解密脚本如下:

import base64
def main():
    cipher = "EmBmP5Pmn7QcPU4gLYKv5QcMmB3PWHcP5YkPq3=cT6QckkPckoRG"
    cipher = list(cipher)
    flag = ""
    for i in range(len(cipher)):
        if cipher[i].isupper() == False:
            if cipher[i].islower() == False:
                if cipher[i].isnumeric() == False:
                    cipher[i] = cipher[i]
                else:
                    cipher[i] = chr((ord(cipher[i]) - 48 - 3) % 10 + 48)
            else:
                cipher[i] = chr((ord(cipher[i]) - 97 - 3) % 26 + 97)
        else:
            cipher[i] = chr((ord(cipher[i]) - 65 - 3) % 26 + 65)
    cipher1 = ['0'] * len(cipher)
    cipher1[13:26] = cipher[39:52]
    cipher1[39:52] = cipher[26:39]
    cipher1[0:13] = cipher[13:26]
    cipher1[26:39] = cipher[0:13]
    for i in range(len(cipher1)):
        flag += cipher1[i]
    print(base64.b64decode(flag.encode()))
if __name__ == "__main__":
    main()

# GWHT{672cc4778a38e80cb362987341133ea2}

FLAG:flag{672cc4778a38e80cb362987341133ea2}

日期:2023.5.28

题目:[ACTF新生赛2020]fungame

刷题平台:BUUCTF

方向:REVERSE

Write UP:

获取题目附件,32位,无壳。

用IDA进行分析,找到main函数。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  void *v4; // [esp+1Ch] [ebp-4h]

  __main();
  v4 = malloc(0x14u);
  memset(v4, 0, 0x14u);
  memset(x, 0, 0x18u);
  sub_401340(v4);
  sub_4013BA(v4);
  return 0;
}

在sub_401340函数中可以找到对输入值进行的加密。

int __cdecl sub_401340(int a1)
{
  char i; // [esp+1Fh] [ebp-9h]

  printf("Please input:");
  scanf("%s", a1);
  for ( i = 0; i <= 15; ++i )
  {
    if ( (*(i + a1) ^ *(y1 + i)) != y2[i] )
      exit(0);
  }
  return 0;
}

可以看出是将输入值与y1数组中值进行异或后与y2中值进行比较,那接下来只需要将y1和y2中的值提取出来进行逆运算即可,解密脚本如下:

cipher = [ 0x71, 0x04, 0x61, 0x58, 0x27, 0x1E, 0x4B, 0x22, 0x5E, 0x64, 0x03, 0x26, 0x5E, 0x17, 0x3C, 0x7A]
key = [0x23, 0x61, 0x3E, 0x69, 0x54, 0x41, 0x18, 0x4D, 0x6E, 0x3B, 0x65, 0x53, 0x30, 0x79, 0x45, 0x5B, 0x71]
for i in range(len(cipher)):
    print(chr(cipher[i]^key[i]),end="")

# Re_1s_So0_funny!

但是提交发现flag错误,于是接着寻找,猜测可能有没发现的加密,用Findcrypt插件进行查找,发现一个base64表。

 通过两次交叉引用找到了sub_40233D函数。

void __noreturn sub_40233D()
{
  char Str2[13]; // [esp+13h] [ebp-35h] BYREF
  char Str1[16]; // [esp+20h] [ebp-28h] BYREF
  char Str[12]; // [esp+30h] [ebp-18h] BYREF
  size_t v3; // [esp+3Ch] [ebp-Ch]

  printf("Please input again:");
  strcpy(Str2, "YTFzMF9wV24=");
  memset(Str, 0, sizeof(Str));
  memset(Str1, 0, sizeof(Str1));
  scanf("%s", Str);
  v3 = strlen(Str);
  sub_402421(Str, v3, Str1);
  if ( !strcmp(Str1, Str2) )
  {
    printf("%s%s", x, Str);
    exit(0);
  }
  exit(0);
}

 这里还有一串base64加密后的密文。于是继续写出解密脚本:

import base64
cipher = [ 0x71, 0x04, 0x61, 0x58, 0x27, 0x1E, 0x4B, 0x22, 0x5E, 0x64, 0x03, 0x26, 0x5E, 0x17, 0x3C, 0x7A]
key = [0x23, 0x61, 0x3E, 0x69, 0x54, 0x41, 0x18, 0x4D, 0x6E, 0x3B, 0x65, 0x53, 0x30, 0x79, 0x45, 0x5B, 0x71]
flag = ""
for i in range(len(cipher)):
    flag += chr(cipher[i]^key[i])

cipher2 = b"YTFzMF9wV24="
flag += base64.b64decode(cipher2).decode()
print(flag)
# Re_1s_So0_funny!a1s0_pWn

但是flag提交仍然有问题,在此遇到了困难,后面看了一下其他师傅写的wp才知道,还有几个字符缺少,缺的就是sub_40233D中的40 23 3D,而后知道实际这里是有一个溢出,"Re_1s_So0_funny!"后面紧接着需要跳转到的函数地址0x40233D,再接着就是base64解密出的"a1s0_pWn",所以可以写出完整的解密脚本:

import base64
cipher = [ 0x71, 0x04, 0x61, 0x58, 0x27, 0x1E, 0x4B, 0x22, 0x5E, 0x64, 0x03, 0x26, 0x5E, 0x17, 0x3C, 0x7A]
key = [0x23, 0x61, 0x3E, 0x69, 0x54, 0x41, 0x18, 0x4D, 0x6E, 0x3B, 0x65, 0x53, 0x30, 0x79, 0x45, 0x5B, 0x71]
flag = ""
for i in range(len(cipher)):
    flag += chr(cipher[i]^key[i])

value = [0x3d,0x23,0x40]

for j in range(len(value)):
    flag += chr(value[j])

cipher2 = b"YTFzMF9wV24="
flag += base64.b64decode(cipher2).decode()
print(flag)
# Re_1s_So0_funny!=#@a1s0_pWn

FLAG:flag{Re_1s_So0_funny!=#@a1s0_pWn}

日期:2023.5.30

题目:[RoarCTF2019]polyre

刷题平台:BUUCTF

方向:REVERSE

Write UP:

获取题目附件,64位,无壳。

 用IDA来进行分析,在main函数中看到一堆while(1)给看愣住了。 

尝试硬啃,发现属实啃不明白,在此陷入困难,感觉题目不是要这样去硬啃的,于是去看了一下其他师傅写的wp,才知道这里进行了混淆,将程序进行了控制流平坦化。关于控制流平坦化的介绍和去除,可以参考这两篇文章利用符号执行去除控制流平坦化 - 博客 - 腾讯安全应急响应中心 (tencent.com)

X86-64下Ollvm平坦化处理的恢复 - 知乎 (zhihu.com)

通过这个脚本可以对这个程序进行去混淆GitHub - cq674350529/deflat: use angr to deobfuscation

python deflat.py -f attachment --addr 0x400620

 去掉混淆后的程序还有一部分没有去干净。

接着就是用IDApython去掉剩下的混淆,但IDApython了解还是太少了,发现了一处自己的薄弱点,这里先用别的师傅写的脚本。

st = 0x0000000000400620 #main开始
end = 0x0000000000402144 #main结束
 
def patch_nop(start,end):
    for i in range(start,end):
        ida_bytes.patch_byte(i, 0x90)		#修改指定地址处的指令  0x90是最简单的1字节nop
 
def next_instr(addr):
    return addr+idc.get_item_size(addr)		#获取指令或数据长度,这个函数的作用就是去往下一条指令
    
 
 
addr = st
while(addr<end):
    next = next_instr(addr)
    if "ds:dword_603054" in GetDisasm(addr):	#GetDisasm(addr)得到addr的反汇编语句
        while(True):
            addr = next
            next = next_instr(addr)
            if "jnz" in GetDisasm(addr):
                dest = idc.get_operand_value(addr, 0)		#得到操作数,就是指令后的数
                ida_bytes.patch_byte(addr, 0xe9)     #0xe9 jmp后面的四个字节是偏移
                ida_bytes.patch_byte(addr+5, 0x90)   #nop第五个字节
                offset = dest - (addr + 5)  #调整为正确的偏移地址 也就是相对偏移地址 - 当前指令后的地址
                ida_bytes.patch_dword(addr + 1, offset) #把地址赋值给jmp后
                print("patch bcf: 0x%x"%addr)
                addr = next
                break
    else:
        addr = next

最后去掉混淆后的程序如下:

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  signed __int64 v4; // [rsp+1E0h] [rbp-110h]
  int j; // [rsp+1E8h] [rbp-108h]
  int i; // [rsp+1ECh] [rbp-104h]
  int k; // [rsp+1ECh] [rbp-104h]
  char s1[48]; // [rsp+1F0h] [rbp-100h] BYREF
  char s[60]; // [rsp+220h] [rbp-D0h] BYREF
  unsigned int v10; // [rsp+25Ch] [rbp-94h]
  char *v11; // [rsp+260h] [rbp-90h]
  int v12; // [rsp+26Ch] [rbp-84h]
  bool v13; // [rsp+272h] [rbp-7Eh]
  unsigned __int8 v14; // [rsp+273h] [rbp-7Dh]
  int v15; // [rsp+274h] [rbp-7Ch]
  char *v16; // [rsp+278h] [rbp-78h]
  int v17; // [rsp+284h] [rbp-6Ch]
  int v18; // [rsp+288h] [rbp-68h]
  bool v19; // [rsp+28Fh] [rbp-61h]
  char *v20; // [rsp+290h] [rbp-60h]
  int v21; // [rsp+298h] [rbp-58h]
  bool v22; // [rsp+29Fh] [rbp-51h]
  __int64 v23; // [rsp+2A0h] [rbp-50h]
  bool v24; // [rsp+2AFh] [rbp-41h]
  __int64 v25; // [rsp+2B0h] [rbp-40h]
  __int64 v26; // [rsp+2B8h] [rbp-38h]
  __int64 v27; // [rsp+2C0h] [rbp-30h]
  __int64 v28; // [rsp+2C8h] [rbp-28h]
  int v29; // [rsp+2D0h] [rbp-20h]
  int v30; // [rsp+2D4h] [rbp-1Ch]
  char *v31; // [rsp+2D8h] [rbp-18h]
  int v32; // [rsp+2E0h] [rbp-10h]
  int v33; // [rsp+2E4h] [rbp-Ch]
  bool v34; // [rsp+2EBh] [rbp-5h]

  v10 = 0;
  memset(s, 0, 0x30uLL);
  memset(s1, 0, sizeof(s1));
  printf("Input:");
  v11 = s;
  __isoc99_scanf("%s", s);
  for ( i = 0; ; ++i )
  {
    v12 = i;
    v13 = i < 64;
    if ( i >= 64 )
      break;
    v14 = s[i];
    v15 = v14;
    if ( v14 == 10 )
    {
      v16 = &s[i];
      *v16 = 0;
      break;
    }
    v17 = i + 1;
  }
  for ( j = 0; ; ++j )
  {
    v18 = j;
    v19 = j < 6;
    if ( j >= 6 )
      break;
    v20 = s;
    v4 = *&s[8 * j];
    for ( k = 0; ; ++k )
    {
      v21 = k;
      v22 = k < 64;
      if ( k >= 64 )
        break;
      v23 = v4;
      v24 = v4 < 0;
      if ( v4 >= 0 )
      {
        v27 = v4;
        v28 = 2 * v4;
        v4 *= 2LL;
      }
      else
      {
        v25 = 2 * v4;
        v26 = 2 * v4;
        v4 = (2 * v4) ^ 0xB0004B7679FA26B3LL;
      }
      v29 = k;
    }
    v30 = 8 * j;
    v31 = &s1[8 * j];
    *v31 = v4;
    v32 = j + 1;
  }
  v33 = memcmp(s1, &unk_402170, 0x30uLL);
  v34 = v33 != 0;
  if ( v33 )
    puts("Wrong!");
  else
    puts("Correct!");
  return v10;
}

分析加密部分,可以看出是对输入值的正负数进行不同的操作,unk_402170中存放着密文。可以通过按B改变数据进制形式。

 

接着即可写解密脚本,但在写解密脚本时遇到一些困难,解密始终错误,看了一下其他师傅wp才知道,程序中的这个加密算法实际进行的是一个CRC校验。这又是一个不太熟悉的算法,看了这个师傅写的文章学习了一下CRC校验码原理、实例、手动计算 - 步孤天 - 博客园 (cnblogs.com)

然后发现了自己脚本的一些问题,因为加密程序是通过判断正负来进行不同的加密,所以解密脚本中就用判断符号位的方式来决定正负。但这样是错的。修改后脚本如下,注释部分是错误方式:

# from ctypes import *
from Crypto.Util.number import *
def main():
    # cipher = [0xBC8FF26D43536296,0x520100780530EE16,0x4DC0B5EA935F08EC,0x342B90AFD853F450,0x8B250EBCAA2C3681,0x55759F81A2C68AE4]
    # cipher1 = c_int64()
    # for i in range(6):
    #     cipher1.value = cipher[i]
    #     for k in range(64):
    #         if cipher1.value < 0:
    #             cipher1.value = (cipher1.value ^ 0xB0004B7679FA26B3)//2
    #             cipher1.value |= 0x8000000000000000
    #         else:
    #             cipher1.value = cipher1.value // 2
    #     print(hex(cipher1.value))
    cipher = [0xBC8FF26D43536296,0x520100780530EE16,0x4DC0B5EA935F08EC,0x342B90AFD853F450,0x8B250EBCAA2C3681,0x55759F81A2C68AE4,0xB0004B7679FA26B3]
    cc = []
    for c in cipher:
        for i in range(64):
            # if(((c & 0x8000000000000000)>>63) == 1):
            if c & 1 == 1:
                c = c ^ 0xB0004B7679FA26B3
                c = c // 2 # 相当于向右位移一位
                c |= 0x8000000000000000
            else:
                c = c // 2 # 相当于向右位移一位
        # print(hex(c))
        cc = list(long_to_bytes(c)[::-1])
        for d in cc:
            print(chr(d),end="")
if __name__ == '__main__':
    main()

脚本解密判断条件是"c & 1 == 1",这是在判断密文是奇数还是偶数,根据加密程序可以看出:

如果是正数,乘二之后会成偶数。

如果是负数,乘二之后也会成偶数,但是后面又异或了一个0xB0004B7679FA26B3,这会使值变成奇数。

所以正数加密后始终会是偶数,而负数始终会是奇数。

同时在奇数判断中将值进行"c |= 0x8000000000000000",来保证负数的符号位不会在位移时被变为0。

最后去除所有错误操作的脚本如下:

from Crypto.Util.number import *
def main():
    cipher = [0xBC8FF26D43536296,0x520100780530EE16,0x4DC0B5EA935F08EC,0x342B90AFD853F450,0x8B250EBCAA2C3681,0x55759F81A2C68AE4]
    cc = []
    for c in cipher:
        for i in range(64):
            if c & 1 == 1:
                c = c ^ 0xB0004B7679FA26B3
                c = c // 2 # 相当于向右位移一位
                c |= 0x8000000000000000
            else:
                c = c // 2 # 相当于向右位移一位
        cc = list(long_to_bytes(c)[::-1])
        for d in cc:
            print(chr(d),end="")
if __name__ == '__main__':
    main()

# flag{6ff29390-6c20-4c56-ba70-a95758e3d1f8}

FLAG:flag{6ff29390-6c20-4c56-ba70-a95758e3d1f8}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值