문제

텍스트 파일 3개와 핵심 문제 파일 한 개가 존재

ELF 파일으로 확인

실행 권한을 주고 파일 실행 시 <table filename>과 <input filename>을 인자로 받는 것을 확인

entry()에서 main 함수 위치 확인

메인 함수 확인

↓
메인 함수 코드
undefined8 FUN_0010154d(int param_1,long param_2)
{
FILE *pFVar1;
long lVar2;
ulong __size;
void *__s;
void *__ptr;
if (param_1 != 3) {
fwrite("Usage : ./baseball <table filename> <input filename>\n",1,0x35,stderr);
/* WARNING: Subroutine does not return */
exit(-1);
}
pFVar1 = fopen(*(char **)(param_2 + 8),"rb");
if (pFVar1 == (FILE *)0x0) {
fwrite("File not found\n",1,0xf,stderr);
/* WARNING: Subroutine does not return */
exit(-1);
}
fseek(pFVar1,0,2);
lVar2 = ftell(pFVar1);
fseek(pFVar1,0,0);
if (lVar2 != 0x40) {
fwrite("Invalid table\n",1,0xe,stderr);
/* WARNING: Subroutine does not return */
exit(-1);
}
fread(&DAT_00104040,0x41,1,pFVar1);
fclose(pFVar1);
pFVar1 = fopen(*(char **)(param_2 + 0x10),"rb");
if (pFVar1 == (FILE *)0x0) {
fwrite("File not found\n",1,0xf,stderr);
/* WARNING: Subroutine does not return */
exit(-1);
}
fseek(pFVar1,0,2);
__size = ftell(pFVar1);
if (__size == 0) {
fwrite("Invalid input\n",1,0xe,stderr);
/* WARNING: Subroutine does not return */
exit(-1);
}
fseek(pFVar1,0,0);
__s = malloc(__size + 1);
if (__s == (void *)0x0) {
fwrite("Allocation failed\n",1,0x12,stderr);
/* WARNING: Subroutine does not return */
exit(-1);
}
memset(__s,0,__size + 1);
fread(__s,__size,1,pFVar1);
fclose(pFVar1);
__ptr = (void *)FUN_00101289(__s,__size & 0xffffffff);
printf("%s",__ptr);
free(__s);
free(__ptr);
return 0;
↑
table 파일은 정확히 64바이트여야 하고 (0x40 체크), 전역 버퍼 DAT_00104040에 65바이트(0x41)를 읽어들임 (마지막 1바이트는 아마 null terminator나 패딩용)
— 즉 이 64바이트가 커스텀 base64 알파벳 테이블로 보임 (표준 base64 알파벳도 64자 + = 패딩 문자 조합)
if (lVar2 != 0x40) {
fwrite("Invalid table\n",1,0xe,stderr);
/* WARNING: Subroutine does not return */
exit(-1);
}
input 파일은 FUN_00101289(__s, size) 에 넘기고 그 리턴값을 printf("%s", ...)로 출력
이 FUN_00101289가 인코딩 핵심 로직으로 보임
__ptr = (void *)FUN_00101289(__s,__size & 0xffffffff);
printf("%s",__ptr);
FUN_00101289 함수 확인

↓
FUN_00101289 함수 코드
undefined1 * FUN_00101289(byte *param_1,int param_2)
{
undefined1 *puVar1;
ulong uVar2;
undefined1 *puVar3;
byte *pbVar4;
undefined1 *local_30;
byte *local_28;
uVar2 = (ulong)((param_2 << 2) / 3 + 4);
uVar2 = uVar2 + uVar2 / 0x48 + 1;
if (uVar2 < (ulong)(long)param_2) {
puVar3 = (undefined1 *)0x0;
}
else {
puVar3 = malloc(uVar2);
if (puVar3 == (undefined1 *)0x0) {
puVar3 = (undefined1 *)0x0;
}
else {
pbVar4 = param_1 + param_2;
local_30 = puVar3;
for (local_28 = param_1; puVar1 = local_30, 2 < (long)pbVar4 - (long)local_28;
local_28 = local_28 + 3) {
*local_30 = (&DAT_00104040)[(int)(uint)(*local_28 >> 2)];
local_30[1] = (&DAT_00104040)[(int)((*local_28 & 3) << 4 | (uint)(local_28[1] >> 4))];
puVar1 = local_30 + 3;
local_30[2] = (&DAT_00104040)[(int)((local_28[1] & 0xf) << 2 | (uint)(local_28[2] >> 6))];
local_30 = local_30 + 4;
*puVar1 = (&DAT_00104040)[(int)(local_28[2] & 0x3f)];
}
if (pbVar4 != local_28) {
*local_30 = (&DAT_00104040)[(int)(uint)(*local_28 >> 2)];
if ((long)pbVar4 - (long)local_28 == 1) {
local_30[1] = (&DAT_00104040)[(int)((*local_28 & 3) << 4)];
local_30[2] = 0x3d;
}
else {
local_30[1] = (&DAT_00104040)[(int)((*local_28 & 3) << 4 | (uint)(local_28[1] >> 4))];
local_30[2] = (&DAT_00104040)[(int)((local_28[1] & 0xf) << 2)];
}
local_30 = local_30 + 3;
*local_30 = 0x3d;
local_30 = puVar1 + 4;
}
*local_30 = 0;
}
}
return puVar3;
}
입력 3바이트(local_28)를 6비트씩 4조각으로 쪼개서 그 6비트 값(0~63)을 인덱스로 삼아서 DAT_00104040(테이블)에서 문자를 치환하는 코드가 보임
-> Base64 인코딩 기법!!!
(참고: https://namu.wiki/w/BASE64 )
*local_30 = (&DAT_00104040)[(int)(uint)(*local_28 >> 2)];
local_30[1] = (&DAT_00104040)[(int)((*local_28 & 3) << 4 | (uint)(local_28[1] >> 4))];
puVar1 = local_30 + 3;
local_30[2] = (&DAT_00104040)[(int)((local_28[1] & 0xf) << 2 | (uint)(local_28[2] >> 6))];
local_30 = local_30 + 4;
*puVar1 = (&DAT_00104040)[(int)(local_28[2] & 0x3f)];
즉, "테이블[인덱스] = 출력문자"라는 대응관계가 존재하고 인덱스는 평문에서 직접 계산 가능
주어진 text_in.txt, text_out.txt 파일을 매칭하여 테이블을 복원
{테이블 추출 코드}
with open("text_in.txt", "rb") as f:
plain = f.read()
with open("text_out.txt", "r") as f:
encoded = f.read().strip()
mapping = {}
conflicts = []
def record(idx, ch):
if idx in mapping and mapping[idx] != ch:
conflicts.append((idx, mapping[idx], ch))
mapping[idx] = ch
i = 0
oi = 0
n = len(plain)
while i < n:
chunk = plain[i:i+3]
remain = len(chunk)
if remain == 3:
b0, b1, b2 = chunk
idxs = [
b0 >> 2,
((b0 & 3) << 4) | (b1 >> 4),
((b1 & 0xf) << 2) | (b2 >> 6),
b2 & 0x3f,
]
chars = encoded[oi:oi+4]
for idx, ch in zip(idxs, chars):
record(idx, ch)
oi += 4
elif remain == 2:
b0, b1 = chunk
idxs = [
b0 >> 2,
((b0 & 3) << 4) | (b1 >> 4),
(b1 & 0xf) << 2,
]
chars = encoded[oi:oi+3]
for idx, ch in zip(idxs, chars):
record(idx, ch)
oi += 4
elif remain == 1:
b0 = chunk[0]
idxs = [b0 >> 2, (b0 & 3) << 4]
chars = encoded[oi:oi+2]
for idx, ch in zip(idxs, chars):
record(idx, ch)
oi += 4
i += 3
table = "".join(mapping.get(x, "?") for x in range(64))
print("테이블:", table)
64개의 인덱스 중 53개만 채워진 테이블
(149바이트 평문만으로는 64개 인덱스를 전부 커버 못 함)
?hs?RF/tuI?W3d?YnSvV7OUQbZcN4J2?1GL+ejA8?r?lpg5ak?Bo0qyDHm??M9?P
flag_out.txt의 40 글자를 추출한 테이블로 매핑하여 역조회
{역조회 디코딩 코드}
char_to_idx: {'7': 20, '/': 6, 'O': 21, 'k': 48, 'Z': 25, 'Q': 23, 'I': 9, 'a': 47, 'u': 8, 'j': 37, 'o': 51, 'R': 4, '1': 32, 'b': 24, 'y': 54, '9': 61, 'c': 26, 't': 7, 'd': 13, '0': 52, 'U': 22, 'l': 43, 'W': 11, 's': 2, 'h': 1, 'e': 36, 'n': 16, 'H': 56, 'g': 45, '4': 28, 'q': 53, 'N': 27, 'A': 38, 'G': 33, 'p': 44, 'S': 17, 'm': 57, 'F': 5, '+': 35, 'J': 29, 'B': 50, '8': 39, 'Y': 15, 'M': 60, 'P': 63, '5': 46, '2': 30, 'v': 18, 'r': 41, 'L': 34, 'V': 19, 'D': 55, '3': 12}
with open("flag_out.txt", "r") as f:
flag_out = f.read().strip()
s = flag_out.rstrip("=")
idxs = [char_to_idx[c] for c in s]
out = bytearray()
i = 0
n = len(idxs)
while i < n:
group = idxs[i:i+4]
if len(group) == 4:
a, b, c, d = group
out.append((a << 2) | (b >> 4))
out.append(((b & 0xf) << 4) | (c >> 2))
out.append(((c & 3) << 6) | d)
elif len(group) == 3:
a, b, c = group
out.append((a << 2) | (b >> 4))
out.append(((b & 0xf) << 4) | (c >> 2))
elif len(group) == 2:
a, b = group
out.append((a << 2) | (b >> 4))
i += 4
print("디코딩 결과:", out.decode())
다행히 53개의 인덱스 안에서 매핑이 되어 디코딩 성공
