0xGame week2 Re 补
0xGame week2 Re 补
Xor::Ramdom
先看代码逻辑:
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
char v4; // bl
bool v5; // si
bool v6; // bl
_BYTE *v7; // rdx
char v8; // al
int v9; // ebx
__int64 v10; // rax
__int64 v11; // rax
__int64 v13[4]; // [rsp+20h] [rbp-60h] BYREF
char v14[32]; // [rsp+40h] [rbp-40h] BYREF
char v15[46]; // [rsp+60h] [rbp-20h] BYREF
char v16; // [rsp+8Eh] [rbp+Eh] BYREF
char v17; // [rsp+8Fh] [rbp+Fh] BYREF
char v18[32]; // [rsp+90h] [rbp+10h] BYREF
char v19[32]; // [rsp+B0h] [rbp+30h] BYREF
char v20[39]; // [rsp+D0h] [rbp+50h] BYREF
char v21; // [rsp+F7h] [rbp+77h]
int inited; // [rsp+F8h] [rbp+78h]
int v23; // [rsp+FCh] [rbp+7Ch]
_main(argc, argv, envp);
inited = 0;
v23 = 0;
v21 = 0;
std::allocator<char>::allocator(&v16);
std::string::basic_string(v15, 40i64, 0i64, &v16);
std::allocator<char>::~allocator(&v16);
std::allocator<char>::allocator(&v17);
std::string::basic_string(v14, 32i64, 0i64, &v17);
std::allocator<char>::~allocator(&v17);
v13[0] = 0x1221164E1F104F0Ci64;
v13[1] = 0x171F240A4B10244Bi64;
v13[2] = 0x1A2C5C2108074F09i64;
v13[3] = 99338668810000i64;
v3 = std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Input flag here:");
std::ostream::operator<<(v3, refptr__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_);
std::operator>><char>(refptr__ZSt3cin, v15);
v4 = 0;
v5 = 1;
if ( std::string::size(v15) == 38 )
{
std::string::substr(v18, v15, 0i64, 7i64);
v4 = 1;
if ( !(unsigned __int8)std::operator!=<char>(v18, "0xGame{") && *(_BYTE *)std::string::operator[](v15, 37i64) == 125 )
v5 = 0;
}
if ( v4 )
std::string::~string(v18);
if ( v5 )
exit(0);
std::string::substr(v19, v15, 7i64, 30i64);
std::string::operator=(v14, v19);
std::string::~string(v19);
inited = init_random();
std::string::basic_string(v20, v14);
v6 = (unsigned int)check(v20) != 0;
std::string::~string(v20);
if ( v6 )
{
srand(0x1919810u);
inited = rand();
}
v21 = rand();
do
{
v7 = (_BYTE *)std::string::operator[](v14, v23);
if ( (v23 & 1) != 0 )
v8 = v21;
else
v8 = v21 + 3;
*v7 ^= v8;
v9 = *(char *)std::string::operator[](v14, v23);
if ( v9 != *(unsigned __int8 *)std::array<unsigned char,32ull>::operator[](v13, v23) )
{
v10 = std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Wrong.Try again");
std::ostream::operator<<(v10, refptr__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_);
exit(0);
}
++v23;
}
while ( v23 <= 29 );
v11 = std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Right!");
std::ostream::operator<<(v11, refptr__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_);
std::string::~string(v14);
std::string::~string(v15);
return 0;
}
乍看上去正确的随机数种子是0x1919810,但认真看一下,发现v6的值与v20(输入)的长度有关。具体来讲,check函数返回的值是:
len(v20)!=30 ? 1 : 0
所以如果srand(0x1919810)被执行,那么v6就是1,也就意味着输入flag的长度不对。所以正确的随机数种子并不是0x1919810。
正确的种子所藏的函数比较隐秘,在第57行的init_random()方法中:
int init_random(void)
{
srand(0x77u);
return rand();
}
因此,正确的随机数种子事实上是0x77,v21即为以此为种子产生的随机数。加密逻辑比较简单,就是交替使用v21和v21+3进行异或,以此来获得密文,并将密文与v13中的字符作比较,解密思路比较明确,反异或即可。
然而还有一个问题是如何提取v13。观察发现,v13是一个__int64类型,大小为4的数组,初始化时数组的每一个值都被赋予了一个hex数。在后面的异或加密函数中使用unsigned __int8 *作为指针对v13进行访问。所以事实上是对v13的每个值进行小端序的访问,低位存在低地址,每次取8位,也就是两位十六进制。于是将v13每一行进行倒序并取两字节,就能得到密文,从而写出解密的脚本。
#include<bits/stdc++.h>
using namespace std;
unsigned char v13[] = {
0x0C, 0x4F, 0x10, 0x1F, 0x4E, 0x16, 0x21, 0x12,
0x4B, 0x24, 0x10, 0x4B, 0x0A, 0x24, 0x1F, 0x17,
0x09, 0x4F, 0x07, 0x08, 0x21, 0x5C, 0x2C, 0x1A,
0x10, 0x1F, 0x11, 0x16, 0x59, 0x5A
};
int main()
{
srand(0x77);
int init=rand();
init=rand();
for(int i=0;i<30;i++)
{
int xorx=(i&1!=0) ? init : init+3;
printf("%c",v13[i]^xorx);
}
return 0;
}
//r4nd0m_i5_n0t_alw4ys_'Random'!
ZzZ(补充)
转换整数的方法还可以:
def int_to_str(num):
return ''.join([chr((num >> (8*i)) & 0xFF) for i in range(4)])