문제

 

 

 

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

 

 

 

 

ELF 파일으로 확인

 

 

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

 

 

 

 

 

 

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

Ghidra를 사용하여 디컴파일

 

 

메인 함수 확인

 

↓

메인 함수 코드

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개의 인덱스 안에서 매핑이 되어 디코딩 성공

flag 획득

 

 

 

 

 

 

+ Recent posts