
압축을 풀어보면, baseball(실행파일임), flag_out.txt, text_in.txt, text_out.txt 이렇게 3개의 파일이 나오는데, 메인함수에 나온 사용법을 보면, 파일을 넘기면 암호화해서 결과파일을 만들어주는것 같다.
__int64 __fastcall main(int a1, char **a2, char **a3)
{
FILE *stream; // [rsp+10h] [rbp-30h]
__int64 v5; // [rsp+18h] [rbp-28h]
FILE *v6; // [rsp+20h] [rbp-20h]
__int64 size; // [rsp+28h] [rbp-18h]
void *s; // [rsp+30h] [rbp-10h]
char *ptr; // [rsp+38h] [rbp-8h]
if ( a1 != 3 )
{
fwrite("Usage : ./baseball <table filename> <input filename>\n", 1uLL, 0x35uLL, stderr);
exit(-1);
}
stream = fopen(a2[1], "rb");
if ( !stream )
{
fwrite("File not found\n", 1uLL, 0xFuLL, stderr);
exit(-1);
}
fseek(stream, 0LL, 2);
v5 = ftell(stream);
fseek(stream, 0LL, 0);
if ( v5 != 64 )
{
fwrite("Invalid table\n", 1uLL, 0xEuLL, stderr);
exit(-1);
}
fread(&unk_4040, 0x41uLL, 1uLL, stream);
fclose(stream);
v6 = fopen(a2[2], "rb");
if ( !v6 )
{
fwrite("File not found\n", 1uLL, 0xFuLL, stderr);
exit(-1);
}
fseek(v6, 0LL, 2);
size = ftell(v6);
if ( !size )
{
fwrite("Invalid input\n", 1uLL, 0xEuLL, stderr);
exit(-1);
}
fseek(v6, 0LL, 0);
s = malloc(size + 1);
if ( !s )
{
fwrite("Allocation failed\n", 1uLL, 0x12uLL, stderr);
exit(-1);
}
memset(s, 0, size + 1);
fread(s, size, 1uLL, v6);
fclose(v6);
ptr = (char *)sub_1289(s, (unsigned int)size);
printf("%s", ptr);
free(s);
free(ptr);
return 0LL;
}
sub_1289는 아래와 같이 정의되어 있었다.
_BYTE *__fastcall sub_1289(_BYTE *a1, int a2)
{
_BYTE *v3; // rax
_BYTE *v4; // rax
_BYTE *v5; // rax
_BYTE *v6; // rax
_BYTE *v7; // rax
_BYTE *v8; // [rsp+18h] [rbp-28h]
_BYTE *v9; // [rsp+18h] [rbp-28h]
_BYTE *v10; // [rsp+18h] [rbp-28h]
_BYTE *v11; // [rsp+20h] [rbp-20h]
unsigned __int64 size; // [rsp+28h] [rbp-18h]
_BYTE *v13; // [rsp+30h] [rbp-10h]
_BYTE *v14; // [rsp+38h] [rbp-8h]
size = (4 * a2 / 3 + 4) / 0x48uLL + 4 * a2 / 3 + 4 + 1;
if ( size < a2 )
return 0LL;
v13 = malloc(size);
if ( !v13 )
return 0LL;
v14 = &a1[a2];
v11 = a1;
v8 = v13;
while ( v14 - v11 > 2 )
{
*v8 = byte_4040[*v11 >> 2];
v8[1] = byte_4040[(v11[1] >> 4) | (16 * *v11) & 0x30];
v8[2] = byte_4040[(v11[2] >> 6) | (4 * v11[1]) & 0x3C];
v3 = v8 + 3;
v8 += 4;
*v3 = byte_4040[v11[2] & 0x3F];
v11 += 3;
}
if ( v14 != v11 )
{
v4 = v8;
v9 = v8 + 1;
*v4 = byte_4040[*v11 >> 2];
if ( v14 - v11 == 1 )
{
*v9 = byte_4040[(16 * *v11) & 0x30];
v5 = v9 + 1;
v10 = v9 + 2;
*v5 = 61;
}
else
{
*v9 = byte_4040[(v11[1] >> 4) | (16 * *v11) & 0x30];
v6 = v9 + 1;
v10 = v9 + 2;
*v6 = byte_4040[(4 * v11[1]) & 0x3C];
}
v7 = v10;
v8 = v10 + 1;
*v7 = 61;
}
*v8 = 0;
return v13;
}
일단 코드는 모르겠고, 일대일 치환인것 같으니 한번 해보자.
컴파일 rustc main.rs --edition 2021
fn main() {
let tout = "7/OkZQIau/jou/R1by9acyjjutd0cUdlWshecQhkZUn1cUH1by9g4/9qNAn1byGaby9pbQSjWshgbUmqZAF+JtOBZUn1b8e1YoMPYoM1ny95ZAO+J/jaNAOB2vhrNLhVNDO0cshWNDIjbnrnZQhj4AM1S/Fmu/jou/GjN/n1bUm5JUFpNte1NyH1VA9yZUqLZQu13VR=".chars().collect::<Vec<char>>();
let tin = "UGVwZXJvIGlzIGEgY29va2llIHN0aWNrLCBkaXBwZWQgaW4gY29tcG91bmQgY2hvY29sYXRlLCBtYW51ZmFjdHVyZWQgYnkgPz8/Pz8gQ29uZmVjdGlvbmVyeSBpbiBTb3V0aCBLb3JlYQpQZXBlcm8gRGF5IGlzIGhlbGQgYW5udWFsbHkgb24gTm92ZW1iZXIgMTE=".chars().collect::<Vec<char>>();
let fout = "S/jeutjaJvhlNA9Du/GaJBhLbQdjd+n1Jy9BcD3=".chars().collect::<Vec<char>>();
let mut fin = String::new();
for i in 0..fout.len() {
for j in 0..tout.len() {
if tout[j] == fout[i] {
fin.push(tin[j]);
break;
}
}
}
println!("DH{{{}}}", base64_decode(&fin));
}
fn base64_decode(input: &str) -> String {
let mut output = Vec::new();
let mut num_bits = 0;
let mut value = 0;
for ch in input.chars().filter(|&ch| ch != '=') {
let code = match ch {
'A'..='Z' => ch as u8 - b'A',
'a'..='z' => ch as u8 - b'a' + 26,
'0'..='9' => ch as u8 - b'0' + 52,
'+' => 62,
'/' => 63,
_ => continue,
};
value = (value << 6) | code as u64;
num_bits += 6;
if num_bits >= 8 {
num_bits -= 8;
output.push((value >> num_bits) as u8);
}
}
String::from_utf8(output).unwrap()
}
이게되네...?ㅋㅋ 플래그 획득!
upx -d <file> 명령어로 언팩해줘야된다.
메인함수에는 3개의 함수가 있는데, 각 함수는 아래와 같이 생겼다
int __cdecl main(int argc, const char **argv, const char **envp)
{
sub_401080();
sub_401610();
sub_4011D0();
return 0;
}
BOOL sub_401080()
{
HANDLE hSnapshot; // [esp+0h] [ebp-234h]
PROCESSENTRY32W pe; // [esp+4h] [ebp-230h] BYREF
memset(&pe, 0, sizeof(pe));
pe.dwSize = 556;
hSnapshot = CreateToolhelp32Snapshot(0xFu, 0);
if ( Process32FirstW(hSnapshot, &pe) )
{
do
{
if ( !wcsicmp(pe.szExeFile, L"x32dbg.exe") )
{
sub_4019C0("[+] Detect it!!", (char)hSnapshot);
system("pause");
exit(0);
}
if ( !wcsicmp(pe.szExeFile, L"x64dbg.exe") )
{
sub_4019C0("[+] Detect it!!", (char)hSnapshot);
system("pause");
exit(0);
}
if ( !wcsicmp(pe.szExeFile, L"Ollydbg.exe") )
{
sub_4019C0("[+] Detect it!!", (char)hSnapshot);
system("pause");
exit(0);
}
}
while ( Process32NextW(hSnapshot, &pe) );
}
return CloseHandle(hSnapshot);
}
int sub_401610()
{
char v1; // [esp+0h] [ebp-450h]
int v2; // [esp+0h] [ebp-450h]
int Offset; // [esp+4h] [ebp-44Ch]
size_t ElementCount; // [esp+Ch] [ebp-444h]
size_t i; // [esp+10h] [ebp-440h]
size_t j; // [esp+10h] [ebp-440h]
FILE *Stream; // [esp+14h] [ebp-43Ch] BYREF
char Buffer[2]; // [esp+18h] [ebp-438h] BYREF
char v9; // [esp+1Ah] [ebp-436h]
char v10; // [esp+1Bh] [ebp-435h]
char v11; // [esp+1Fh] [ebp-431h]
_WORD v12[8]; // [esp+418h] [ebp-38h] BYREF
char v13[20]; // [esp+428h] [ebp-28h] BYREF
char v14[16]; // [esp+43Ch] [ebp-14h] BYREF
strcpy(v13, "abc!@#qwe012fgh456");
strcpy(v14, "1@!@#a134234");
*(_WORD *)&v14[13] = 0;
v14[15] = 0;
strcpy((char *)v12, "zBNdlwi102394");
v12[7] = 0;
if ( sub_401000() )
{
sub_4019C0("[+] Detect it!!", v1);
system("pause");
exit(0);
}
sub_4019C0("[+] Good!", v1);
if ( fopen_s(&Stream, "flag.txt", "r+b") )
return -1;
while ( !feof(Stream) )
{
Offset = ftell(Stream);
ElementCount = fread(Buffer, 1u, 0x400u, Stream);
if ( !ElementCount )
break;
for ( i = 0; i < ElementCount; ++i )
Buffer[i] ^= v13[i % 0x10];
for ( j = 0; j < ElementCount; ++j )
{
if ( !(j % 2) )
++Buffer[j];
if ( j % 2 == 1 )
Buffer[j] += 2;
Buffer[0] ^= v10;
v9 ^= v11;
}
v2 = ftell(Stream);
fseek(Stream, Offset, 0);
fwrite(Buffer, 1u, ElementCount, Stream);
fseek(Stream, v2, 0);
}
fclose(Stream);
return 1;
}
int sub_4011D0()
{
int v1; // [esp+0h] [ebp-430h]
int Offset; // [esp+4h] [ebp-42Ch]
unsigned int ElementCount; // [esp+Ch] [ebp-424h]
char v4[20]; // [esp+10h] [ebp-420h] BYREF
unsigned int i; // [esp+24h] [ebp-40Ch]
FILE *Stream; // [esp+28h] [ebp-408h] BYREF
char Buffer[3]; // [esp+2Ch] [ebp-404h] BYREF
char v8; // [esp+2Fh] [ebp-401h]
char v9; // [esp+30h] [ebp-400h]
char v10; // [esp+31h] [ebp-3FFh]
char v11; // [esp+33h] [ebp-3FDh]
char v12; // [esp+34h] [ebp-3FCh]
char v13; // [esp+35h] [ebp-3FBh]
char v14; // [esp+37h] [ebp-3F9h]
if ( IsDebuggerPresent() )
{
system("pause");
exit(0);
}
qmemcpy(v4, "Pp btXHE", 8);
v4[8] = -112;
v4[9] = -112;
v4[10] = 112;
v4[11] = 64;
v4[12] = 54;
v4[13] = 69;
v4[14] = 85;
v4[15] = 113;
v4[16] = 24;
v4[17] = 25;
v4[18] = 112;
if ( fopen_s(&Stream, "flag.txt", "r+b") )
return -1;
while ( !feof(Stream) )
{
Offset = ftell(Stream);
ElementCount = fread(Buffer, 1u, 0x400u, Stream);
if ( !ElementCount )
break;
for ( i = 0; i < ElementCount; ++i )
{
Buffer[i] ^= v4[i];
Buffer[0] -= 21;
v8 ^= 0x18u;
v12 -= 51;
v14 ^= 0x44u;
v13 += 71;
v9 ^= 0x88u;
v11 ^= 0x68u;
Buffer[0] += 21;
v14 ^= 0x44u;
v13 -= 71;
v12 += 51;
v10 += 17;
v8 ^= 0x18u;
v9 ^= 0x88u;
v11 ^= 0x68u;
}
v1 = ftell(Stream);
fseek(Stream, Offset, 0);
fwrite(Buffer, 1u, ElementCount, Stream);
fseek(Stream, v1, 0);
}
return fclose(Stream);
}
sub_401080는 올리디버거, x32dbg, x64dbg를 감지하는 것이다.
sub_401080는 암호화와 비슷한 무언가를 수행하며, (암호화라 하기에는 무리가 좀 있다. 왜냐하면, 유효한 글자가 아닌 글자들이 보이기 때문이다. 일단 이 함수가 하는 일은, 파일 내용을 0x400 바이트씩 읽어서 버퍼에 저장하고, 각 바이트를 v13의 해당 위치의 값으로 XOR 연산한다. 그리고 버퍼의 각 짝수 인덱스 바이트를 1 증가시키고, 각 홀수 인덱스 바이트를 2 증가시킨다. 버퍼의 첫번째 바이트를 v10 값으로 XOR 연산하고, v9와 v11 값을 XOR 연산한다. 또한 연산이 끝나면 버퍼를 파일에 쓴다.
또한 sub_4011D0 함수에서 한번 더 암호화를 수행하는데, 파일 내용을 0x400 바이트씩 읽어서 버퍼에 저장하고, 버퍼의 각 바이트를 v4 배열의 해당 인덱스 값으로 XOR 연산한다. 버퍼의 첫 번째 바이트를 21 감소시키고, v8, v12, v14, v13, v9, v11, v14, v13, v12, v10, v8, v9, v11 순서로 XOR 한다. v4는 처음에 -112를 char 타입으로 표현하면 뭔지 몰라서 좀 해멨는데, char 타입으로 표현하면 부호 있는 8비트 정수에서 -112에 해당하는 값인 0x90이 나온다고 한다. 그러면 v4는 \x50\x70\x20\x62\x74\x58\x48\x45\x90\x90\x70\x40\x36\x45\x55\x71\x18\x19\x70\x00가 될 것이다.
그럼 아래와 같이 코드를 짜면 될 것이다
컴파일 rustc main.rs --edition 2021
static V4: [u8; 20] = [0x50, 0x70, 0x20, 0x62, 0x74, 0x58, 0x48, 0x45, 0x90, 0x90, 0x70, 0x40, 0x36, 0x45, 0x55, 0x71, 0x18, 0x19, 0x70, 0x00];
static V12: &str = "abc!@#qwe012fgh456";
fn dec(data: &mut Vec<u8>) -> String {
for i in (0..data.len()).rev() {
data[5] = data[5].wrapping_sub(0x11); // 언더플로우 방지
data[i] ^= V4[i];
}
for i in (0..data.len()).rev() {
data[2] ^= data[7];
data[0] ^= data[3];
data[i] -= ((i%2)+1) as u8;
}
for i in 0..data.len() { data[i] ^= V12.as_bytes()[i % 0x10]; }
data.iter().map(|&byte| byte as char).collect()
}
fn enc(data: &mut Vec<u8>) -> String {
for i in 0..data.len() { data[i] ^= V12.as_bytes()[i % 0x10]; }
for i in (0..data.len()).rev() {
data[i] += ((i%2)+1) as u8;
data[0] ^= data[3];
data[2] ^= data[7];
}
for i in (0..20).rev() {
data[i] ^= V4[i];
data[5] = data[5].wrapping_add(0x11); // 오버플로우 방지
}
data.iter().map(|&byte| byte as char).collect()
}
fn main() {
// 버퍼는 다음 링크에서 연산함.
// https://onlinehextools.com/convert-utf8-to-hex?input=t%5C7%05D%EB%91%85%0C%EA%B3%A2%0A%1E%3CWm%0C%08%14-%5E&prefix=true&padding=true&spacing=true
let mut buffer = b"\x74\x5C\x37\x05\x44\x8A\x41\x0C\x81\xE1\x0B\x1E\x3C\x57\x6D\x0C\x08\x14\x2D\x5E".to_vec();
let result = dec(&mut buffer);
println!("Flag: {}", result);
println!("Encrypted: {}", enc(&mut (result.as_bytes().to_vec())));
}
개발자로서 배울 점이 많은 글이었습니다. 감사합니다.