2022. 10. 6. 19:31ㆍDreamhack Wargame
[text_in.txt]
Pepero is a cookie stick, dipped in compound chocolate, manufactured by ????? Confectionery in South Korea
Pepero Day is held annually on November 11
[text_out.txt]
7/OkZQIau/jou/R1by9acyjjutd0cUdlWshecQhkZUn1cUH1by9g4/9qNAn1byGaby9pbQSjWshgbUmqZAF+JtOBZUn1b8e1YoMPYoM1ny95ZAO+J/jaNAOB2vhrNLhVNDO0cshWNDIjbnrnZQhj4AM1S/Fmu/jou/GjN/n1bUm5JUFpNte1NyH1VA9yZUqLZQu13VR=
[flag_out.txt]
S/jeutjaJvhlNA9Du/GaJBhLbQdjd+n1Jy9BcD3=
바이너리 분석 문제입니다.
문제 파일과 함께 txt 파일이 3개 딸려왔네요.
파일 내용이 text_in.txt 빼고는 전부 암호화되어있습니다.
어떠한 임의의 문자열을 넣었을 때 나오는 파일이 text_out 파일인 것 같습니다.
= flag_out 파일의 원본을 찾는 문제
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 v3; // rdx
__int64 v5; // [rsp-38h] [rbp-38h]
__int64 v6; // [rsp-30h] [rbp-30h]
__int64 v7; // [rsp-28h] [rbp-28h]
__int64 v8; // [rsp-20h] [rbp-20h]
__int64 v9; // [rsp-18h] [rbp-18h]
__int64 v10; // [rsp-10h] [rbp-10h]
__asm { endbr64 }
if ( a1 != 3 )
{
sub_1190("Usage : ./baseball <table filename> <input filename>\n", 1LL, 53LL, stderr);
sub_1180(0xFFFFFFFFLL);
}
v5 = sub_1170(a2[1], "rb", a3);
if ( !v5 )
{
sub_1190("File not found\n", 1LL, 15LL, stderr);
sub_1180(0xFFFFFFFFLL);
}
sub_1160(v5, 0LL, 2LL);
v6 = sub_1140(v5);
sub_1160(v5, 0LL, 0LL);
if ( v6 != 64 )
{
sub_1190("Invalid table\n", 1LL, 14LL, stderr);
sub_1180(0xFFFFFFFFLL);
}
sub_1100(&unk_4040, 65LL, 1LL, v5);
sub_1110(v5);
v7 = sub_1170(a2[2], "rb", v3);
if ( !v7 )
{
sub_1190("File not found\n", 1LL, 15LL, stderr);
sub_1180(0xFFFFFFFFLL);
}
sub_1160(v7, 0LL, 2LL);
v8 = sub_1140(v7);
if ( !v8 )
{
sub_1190("Invalid input\n", 1LL, 14LL, stderr);
sub_1180(0xFFFFFFFFLL);
}
sub_1160(v7, 0LL, 0LL);
v9 = sub_1150(v8 + 1);
if ( !v9 )
{
sub_1190("Allocation failed\n", 1LL, 18LL, stderr);
sub_1180(0xFFFFFFFFLL);
}
sub_1130(v9, 0LL, v8 + 1);
sub_1100(v9, v8, 1LL, v7);
sub_1110(v7);
v10 = sub_1289(v9, (unsigned int)v8);
sub_1120("%s", v10);
sub_10F0(v9);
sub_10F0(v10);
return 0LL;
}
IDA로 문제 파일을 열어서 main() 함수만 가져와봤습니다.
블로그 상에서 보기에는 코드가 좀 길어 보이니 중요 부분별로 나눠서 분석해보겠습니다.
v5 = sub_1170(a2[1], "rb", a3);
if ( !v5 )
{
sub_1190("File not found\n", 1LL, 15LL, stderr);
sub_1180(0xFFFFFFFFLL);
}
sub_1160(v5, 0LL, 2LL);
v6 = sub_1140(v5);
sub_1160(v5, 0LL, 0LL);
if ( v6 != 64 )
{
sub_1190("Invalid table\n", 1LL, 14LL, stderr);
sub_1180(0xFFFFFFFFLL);
}
sub_1100(&unk_4040, 65LL, 1LL, v5);
sub_1110(v5);
첫 번째 입력 부분.
a2라는 파일에서 테이블을 읽어옵니다.
만약 파일이 비어있거나, 테이블의 길이가 64byte가 아니라면 각 상황에 맞는 문자열을 출력해주고 프로그램을 종료합니다.
v6 = sub_1170();
if ( !v6 )
{
sub_1190("File not found\n", 1LL, 15LL, stderr);
sub_1180(0xFFFFFFFFLL);
}
sub_1160(v6, 0LL, 2LL);
v7 = sub_1140(v6);
if ( !v7 )
{
sub_1190("Invalid input\n", 1LL, 14LL, stderr);
sub_1180(0xFFFFFFFFLL);
}
sub_1160(v6, 0LL, 0LL);
v8 = sub_1150(v7 + 1);
if ( !v8 )
{
sub_1190("Allocation failed\n", 1LL, 18LL, stderr);
sub_1180(0xFFFFFFFFLL);
}
sub_1130(v8, 0LL, v7 + 1);
sub_1100(v8, v7, 1LL, v6);
sub_1110(v6);
두 번째 입력 부분.
이번에는 v6에 어떠한 입력값을 담습니다.
아마 Input을 읽어오는 것 같습니다.
첫 번째 입력 부분과 같이 입력값이 형식에 맞지 않으면 문자열을 출력해주고 실행을 종료합니다.
v9 = sub_1289(v8, (unsigned int)v7);
sub_1120("%s", v9);
sub_10F0(v8);
sub_10F0(v9);
return 0LL;
이번에는 sub_1289() 함수에 Input을 넣어서 return 값을 출력하고 실행을 종료합니다.
sub_1289() 함수를 보겠습니다.
__int64 __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-30h] [rbp-30h]
_BYTE *v9; // [rsp-30h] [rbp-30h]
__int64 v10; // [rsp-30h] [rbp-30h]
_BYTE *v11; // [rsp-28h] [rbp-28h]
unsigned __int64 v12; // [rsp-20h] [rbp-20h]
__int64 v13; // [rsp-18h] [rbp-18h]
_BYTE *v14; // [rsp-10h] [rbp-10h]
__asm { endbr64 }
v12 = (4 * a2 / 32 + 4) / 0x48uLL + 4 * a2 / 32 + 4 + 1;
if ( v12 < a2 )
return 0LL;
v13 = sub_1150(v12);
if ( !v13 )
return 0LL;
v14 = &a1[a2];
v11 = a1;
v8 = (_BYTE *)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 = (__int64)(v9 + 2);
*v5 = 61;
}
else
{
*v9 = byte_4040[(v11[1] >> 4) | (16 * *v11) & 0x30];
v6 = v9 + 1;
v10 = (__int64)(v9 + 2);
*v6 = byte_4040[(4 * v11[1]) & 0x3C];
}
v7 = (_BYTE *)v10;
v8 = (_BYTE *)(v10 + 1);
*v7 = 61;
}
*v8 = 0;
return v13;
}
원래는 이 함수를 하나하나 분석해서 곧이곧대로 테이블을 구하고, 역연산을 하는 식으로 문제를 풀어보려고 했는데, 너무 시간이 오래 걸릴 것 같아서 문제 댓글을 조금 참고해보기로 했습니다.
문제 댓글을 보니 Base64 얘기가 굉장히 많이 나왔습니다.
문제 파일 외에 주어졌던 세 개의 텍스트 파일 중 text_in.txt만 Base64가 아닌 게 수상해서 Cyber Chef 툴에 Base64 옵션을 넣어서 돌려봤습니다.
혹시나 하고 text_out.txt랑 비교해봤는데, 문자열 길이가 같았습니다.
아무래도 일대일 치환인 것 같네요.
#include<bits/stdc++.h>
using namespace std;
int main(){
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
string text_out="7/OkZQIau/jou/R1by9acyjjutd0cUdlWshecQhkZUn1cUH1by9g4/9qNAn1byGaby9pbQSjWshgbUmqZAF+JtOBZUn1b8e1YoMPYoM1ny95ZAO+J/jaNAOB2vhrNLhVNDO0cshWNDIjbnrnZQhj4AM1S/Fmu/jou/GjN/n1bUm5JUFpNte1NyH1VA9yZUqLZQu13VR=";
string text_in="UGVwZXJvIGlzIGEgY29va2llIHN0aWNrLCBkaXBwZWQgaW4gY29tcG91bmQgY2hvY29sYXRlLCBtYW51ZmFjdHVyZWQgYnkgPz8/Pz8gQ29uZmVjdGlvbmVyeSBpbiBTb3V0aCBLb3JlYQpQZXBlcm8gRGF5IGlzIGhlbGQgYW5udWFsbHkgb24gTm92ZW1iZXIgMTE=";
string flag_out="S/jeutjaJvhlNA9Du/GaJBhLbQdjd+n1Jy9BcD3=";
string flag_in="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
for(int i=0;i<=40;i++){
for(int j=0;j<=200;j++){
if (text_out[j]==flag_out[i]) {
flag_in[i]=text_in[j];
}
}
}
cout<<flag_in;
}
위와 같은 코드를 짜서 실행시켰습니다.
결괏값이 나왔습니다.
형태가 Base64이니 옵션을 Base64로 넣어서 Decoding 해보겠습니다.
왜 문제 이름이 baseball인지 이제야 알겠네요 ㅋㅋㅋ
'Dreamhack Wargame' 카테고리의 다른 글
crawling 풀이 (0) | 2023.04.23 |
---|---|
Long Sleep 풀이 (0) | 2023.04.21 |
Basic_Forensics_1 풀이 (0) | 2022.07.23 |
sint 풀이 (0) | 2022.05.31 |
patch 풀이 (0) | 2022.05.30 |