NCTF2021逆向WP(部分)

NCTF2021逆向部分writeup

签到题

一键F5直接获得flag:NCTF{We1come_2_Reverse_Engineering}

Shadowbringer

C++逆向,反编译后乍一看头皮发麻:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4[16]; // [rsp+20h] [rbp-60h] BYREF
  char v5[15]; // [rsp+30h] [rbp-50h] BYREF
  char v6; // [rsp+3Fh] [rbp-41h] BYREF
  char v7[16]; // [rsp+40h] [rbp-40h] BYREF
  char v8[16]; // [rsp+50h] [rbp-30h] BYREF
  char v9[16]; // [rsp+60h] [rbp-20h] BYREF
  char v10[32]; // [rsp+70h] [rbp-10h] BYREF

  _main();
  youknowwhat();
  std::string::string((std::string *)v5);
  ((void (__fastcall *)(char *))std::allocator<char>::allocator)(&v6);
  std::string::string(v4, "U>F2UsQXN`5sXMELT=:7M_2<X]^1ThaWF0=KM?9IUhAsTM5:T==_Ns&<Vhb!", &v6);
  ((void (__fastcall *)(char *))std::allocator<char>::~allocator)(&v6);
  std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Welcome.Please input your flag:\n");
  std::operator>><char>(refptr__ZSt3cin, v5);
  std::string::string((std::string *)v8, (const std::string *)v5);
  Emet(v7, v8);
  ((void (__fastcall *)(char *, char *))std::string::operator=)(v5, v7);
  std::string::~string((std::string *)v7);
  std::string::~string((std::string *)v8);
  std::string::string((std::string *)v10, (const std::string *)v5);
  Selch(v9, v10);
  ((void (__fastcall *)(char *, char *))std::string::operator=)(v5, v9);
  std::string::~string((std::string *)v9);
  std::string::~string((std::string *)v10);
  if ( (unsigned __int8)std::operator==<char>(v5, v4) )
    std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Right.");
  else
    std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Wrong.");
  std::string::~string((std::string *)v4);
  std::string::~string((std::string *)v5);
  return 0;
}

其实大部分都是库函数,关键函数只有:Emet(v7, v8);Selch(v9, v10);
进入Emet函数

std::string *__fastcall Emet(std::string *a1, std::string *a2)
{
  int i; // ebx
  char *v3; // rax
  unsigned __int64 v4; // rax
  int j; // ebx
  unsigned int v6; // eax
  char *v7; // rax
  unsigned __int64 v9; // [rsp+20h] [rbp-90h] BYREF
  char v10; // [rsp+2Eh] [rbp-82h] BYREF
  char v11; // [rsp+2Fh] [rbp-81h] BYREF
  char v12[16]; // [rsp+30h] [rbp-80h] BYREF
  char v13[16]; // [rsp+40h] [rbp-70h] BYREF
  char v14[16]; // [rsp+50h] [rbp-60h] BYREF
  char v15[16]; // [rsp+60h] [rbp-50h] BYREF
  char v16[16]; // [rsp+70h] [rbp-40h] BYREF
  char v17[16]; // [rsp+80h] [rbp-30h] BYREF
  char v18[16]; // [rsp+90h] [rbp-20h] BYREF
  char v19[16]; // [rsp+A0h] [rbp-10h] BYREF

  std::allocator<char>::allocator(&v10);
  std::string::string(&v9, &unk_48A000, &v10);
  std::allocator<char>::~allocator(&v10);
  std::allocator<char>::allocator(&v11);
  std::string::string(a1, &unk_48A000, &v11);
  std::allocator<char>::~allocator(&v11);
  for ( i = 0; i < (unsigned __int64)std::string::size(a2); ++i )
  {
    v3 = (char *)std::string::operator[](a2, i);
    std::bitset<8ull>::bitset(v14, (unsigned int)*v3);
    std::bitset<8ull>::to_string(v13, v14);
    std::operator+<char>(v12, &v9, v13);
    std::string::operator=(&v9, v12);
    std::string::~string((std::string *)v12);
    std::string::~string((std::string *)v13);
  }
  while ( 1 )
  {
    v4 = std::string::size((std::string *)&v9);
    if ( v4 == 6 * (v4 / 6) )
      break;
    std::operator+<char>(v15, &v9, 48i64);
    std::string::operator=(&v9, v15);
    std::string::~string((std::string *)v15);
  }
  for ( j = 0; j < (unsigned __int64)std::string::size((std::string *)&v9); j += 6 )
  {
    std::string::substr((std::string *)v18, (unsigned __int64)&v9, j);
    std::bitset<6ull>::bitset<char,std::char_traits<char>,std::allocator<char>>(v17, v18, 0i64);
    v6 = std::bitset<6ull>::to_ulong(v17);
    v7 = (char *)std::string::operator[](&hisoralce, v6);
    std::operator+<char>(v16, a1, (unsigned int)*v7);
    std::string::operator=(a1, v16);
    std::string::~string((std::string *)v16);
    std::string::~string((std::string *)v18);
  }
  while ( (std::string::size(a1) & 3) != 0 )
  {
    std::operator+<char>(v19, a1, 33i64);
    std::string::operator=(a1, v19);
    std::string::~string((std::string *)v19);
  }
  std::string::~string((std::string *)&v9);
  return a1;
}

发现可以数组hisoralce,但是进入以后

发现是问号,考虑用动态调试,在Emet后面下断点:

查看该数组后转化成字符串得到一个base64索引表:

#$%&'()*+,-.s0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[h]^_`ab

用相同的方法进入Selch函数得到另一个base64索引表:

ba`_^]h[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210s.-,+*)('&%$#

正好相反,于是开心地打开base64decode脚本开始跑,然后就错辣!

发现问题

考虑明文

U>F2UsQXN`5sXMELT=:7M_2<X]^1ThaWF0=KM?9IUhAsTM5:T==_Ns&<Vhb!

和索引表

#$%&'()*+,-.s0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[h]^_`ab
ba`_^]h[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210s.-,+*)('&%$#

我们发现=也在这个索引表里,由于base64算法是在末尾补=,因此按照b64解密脚本直接跑肯是错的,正确的方法应该是建立换表到原表的映射再通过b64解密。
于是得到exp:

import base64

flag = "U>F2UsQXN`5sXMELT=:7M_2<X]^1ThaWF0=KM?9IUhAsTM5:T==_Ns&<Vhb!"
std_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
my_table = '#$%&\x27()*+,-.s0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[h]^_`ab'
my_table2 = 'ba`_^]h[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210s.-,+*)(\x27&%$#'

flag = flag.translate(str.maketrans(my_table2, std_table))
print(flag) #NkcwNzRKUCtzKVdWOlorVDwmKFExOGBLcylXVjpZNGhzOVtoOllDUz8mMGA!
flag = flag.replace("!", '=').encode()
print(flag) #b'NkcwNzRKUCtzKVdWOlorVDwmKFExOGBLcylXVjpZNGhzOVtoOllDUz8mMGA='
flag = base64.b64decode(flag).decode()
print(flag) #6G074JP+s)WV:Z+T<&(Q18`Ks)WV:Y4hs9[h:YCS?&0`
flag = flag.translate(str.maketrans(my_table, std_table))
print(base64.b64decode(flag)) #NCTF{H0m3_r1d1n9_h0m3_dy1n9_h0p3}

得到flag:NCTF{H0m3_r1d1n9_h0m3_dy1n9_h0p3}

原文地址:https://www.cnblogs.com/THRANDUil/p/15740728.html