
1. level2 crackme(base64)
level 2 크랙미 디스어셈블 결과이다.
param_2를 입력받아 encode_custom_base64로 인코딩 후에 4PpnRoanRomTze4SKPo+Zwd3IejS와 비교한다.
값이 같으면 correct가 나온다.
undefined8 main(int param_1,undefined8 *param_2)
{
int iVar1;
undefined8 uVar2;
char *__s1;
size_t __n;
if (param_1 == 2) {
__s1 = (char *)encode_custom_base64(param_2[1]);
__n = strlen("4PpnRoanRomTze4SKPo+Zwd3IejS");
iVar1 = strncmp(__s1,"4PpnRoanRomTze4SKPo+Zwd3IejS",__n);
if (iVar1 == 0) {
puts("Correct!!");
uVar2 = 0;
}
else {
puts("Wrong...");
uVar2 = 1;
}
}
else {
printf("%s flag_string_is_here\n",*param_2);
uVar2 = 0xffffffff;
}
return uVar2;
}
2. Base64
Base64란?
Base64는 8비트 이진 데이터를 공통 ASCII 문자로 변환하는 인코딩 방식이다. 원리는 8비트 버퍼 3개(24비트)를 모아 6비트씩 4개로 나누어 표현하는 것이다. 이때 3바이트 단위로 떨어지지 않아 남는 공간은 등호(=)를 사용해 패딩한다.
주된 사용 목적은 시스템 간 데이터 전송 시 깨짐을 방지하는 안전성 확보에 있지만 3바이트를 4바이트로 변환하므로 크기가 약 33% 증가한다는 단점이 있다.
표준 규격에서는 +와 /를 사용하지만, URL 등 특수문자 처리가 까다로운 환경에서는 이를 -와 _로 대체하거나 패딩(=)을 생략하는 변형 방식을 사용하기도 한다.

3. encode_custom_base64 분석
void * encode_custom_base64(char *input_str)
{
lVar6 = *(long *)(in_FS_OFFSET + 0x28);
i = 0;
//할당할 힙 메모리 계산 인코딩하면 33%증가한 것까지
sVar9 = strlen(input_str);
iVar7 = (int)((sVar9 * 8 + 5) / 6);
iVar8 = iVar7 + 3;
if (iVar8 < 0) {
iVar8 = iVar7 + 6;
}
base64_len = (iVar8 >> 2) * 4;
//힙 할당
__s = malloc((long)(base64_len + 1));
memset(__s,0,(long)(base64_len + 1));
while (0 < base64_len) {
if (base64_len < 3) {//base 64 패딩 ==
if (base64_len == 2) { //== 2 개
bVar1 = input_str[i * 3];
bVar2 = input_str[(long)(i * 3) + 1];
bVar3 = input_str[(long)(i * 3) + 1];
*(char *)((long)(i << 2) + (long)__s) =
"b9V12dPGtk7BKZUD/NJeX4vxjIcERzH+pQgT5iwYAlMyCWOFfnr3Soq06L8hamsu"
[(int)(char)((byte)input_str[i * 3] >> 2)];
*(char *)((long)(i << 2) + 1 + (long)__s) =
"b9V12dPGtk7BKZUD/NJeX4vxjIcERzH+pQgT5iwYAlMyCWOFfnr3Soq06L8hamsu"
[(int)(char)(bVar2 >> 4 | (bVar1 & 3) << 4)];
*(char *)((long)(i << 2) + 2 + (long)__s) =
"b9V12dPGtk7BKZUD/NJeX4vxjIcERzH+pQgT5iwYAlMyCWOFfnr3Soq06L8hamsu"
[(int)(char)((bVar3 & 0xf) << 2)];
*(undefined1 *)((long)__s + (long)(i << 2) + 3) = 0x3d;
base64_len = 0;
}
else {//=1개
bVar1 = input_str[i * 3];
*(char *)((long)(i << 2) + (long)__s) =
"b9V12dPGtk7BKZUD/NJeX4vxjIcERzH+pQgT5iwYAlMyCWOFfnr3Soq06L8hamsu"
[(int)(char)((byte)input_str[i * 3] >> 2)];
*(char *)((long)(i << 2) + 1 + (long)__s) =
"b9V12dPGtk7BKZUD/NJeX4vxjIcERzH+pQgT5iwYAlMyCWOFfnr3Soq06L8hamsu"
[(int)(char)((bVar1 & 3) << 4)];
*(undefined1 *)((long)__s + (long)(i << 2) + 2) = 0x3d;
*(undefined1 *)((long)__s + (long)(i << 2) + 3) = 0x3d;
base64_len = base64_len + -1;
}
}
else {
bVar1 = input_str[i * 3];
bVar2 = input_str[(long)(i * 3) + 1];
bVar3 = input_str[(long)(i * 3) + 1];
bVar4 = input_str[(long)(i * 3) + 2];
bVar5 = input_str[(long)(i * 3) + 2];
*(char *)((long)(i << 2) + (long)__s) =
"b9V12dPGtk7BKZUD/NJeX4vxjIcERzH+pQgT5iwYAlMyCWOFfnr3Soq06L8hamsu"
[(int)(char)((byte)input_str[i * 3] >> 2)];
*(char *)((long)(i << 2) + 1 + (long)__s) =
"b9V12dPGtk7BKZUD/NJeX4vxjIcERzH+pQgT5iwYAlMyCWOFfnr3Soq06L8hamsu"
[(int)(char)(bVar2 >> 4 | (bVar1 & 3) << 4)];
*(char *)((long)(i << 2) + 2 + (long)__s) =
"b9V12dPGtk7BKZUD/NJeX4vxjIcERzH+pQgT5iwYAlMyCWOFfnr3Soq06L8hamsu"
[(int)(char)(bVar4 >> 6 | (bVar3 & 0xf) << 2)];
*(char *)((long)(i << 2) + 3 + (long)__s) =
"b9V12dPGtk7BKZUD/NJeX4vxjIcERzH+pQgT5iwYAlMyCWOFfnr3Soq06L8hamsu"
[(int)(char)(bVar5 & 0x3f)];
base64_len = base64_len + -3;
}
i = i + 1;
}
if (lVar6 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return __s;
}
- 함수는 입력된 문자열(input_str)의 길이를 기반으로 인코딩 후의 길이를 계산하고, 힙(Heap) 메모리를 할당한다.
- 가장 중요한 특징은 표준 Base64 테이블(A-Z...)이 아닌, 커스텀 테이블을 사용한다는 점이다.
- "b9V12dPGtk7BKZUD/NJeX4vxjIcERzH+pQgT5iwYAlMyCWOFfnr3Soq06L8hamsu"
4. PyGhidra를 사용한 풀이
파이썬 스크립트로 아래와 같이 짜면
# -*- coding: utf-8 -*-
#@author
#@category _NEW_
#@keybinding
#@menupath
#@toolbar
#@runtime PyGhidra
#TODO Add User Code Here
import string
import base64
#기존 base64테이블
default_base64_table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
#커스텀 테이블에서 base64로 변경 후 디코드
def custom_base64_decode(s, custom_table):
s = s.translate(str.maketrans(custom_table, default_base64_table))
return base64.b64decode(s)
#custom_table 가져오기
base64_custom_table_addr = toAddr(0x100e00)
base64_custom_table_array = getBytes(base64_custom_table_addr, 64)
b64_table = ''.join([chr(b) for b in base64_custom_table_array])
#비교할 문자열
enc = "4PpnRoanRomTze4SKPo+Zwd3IejS"
code = custom_base64_decode(enc, b64_table)
print(''.join([chr(b) for b in code]))
기드라 스크립트로 돌리면 아래 문자열이 나온다.

CyberChef로 교차 검증해보면 Th1s_1s_cu5t0m_6ase64가 나와 맞는 걸 볼 수 있다.

5. 3줄 요약
- 이 문제는 입력값을 커스텀 Base64로 인코딩하여 특정 문자열과 비교하는 로직을 가진다.
- 분석 결과, 표준 테이블이 아닌 하드코딩된 b9V1... 형태의 커스텀 테이블을 사용하는 것이 식별되었다.
- 기드라 스크립트를 사용해 문제를 풀었다.
'Security > 리버싱' 카테고리의 다른 글
| [Ghidra] 리버스 엔지니어링 기드라 실전 가이드 스터디 - chapter 5(level4-GO바이너리) (0) | 2026.01.24 |
|---|---|
| [Ghidra] 리버스 엔지니어링 기드라 실전 가이드 스터디 - chapter 5(level3) (0) | 2026.01.24 |
| [Ghidra] 리버스 엔지니어링 기드라 실전 가이드 스터디 - 기드라 취약점 3 (0) | 2026.01.23 |
| [Ghidra] 리버스 엔지니어링 기드라 실전 가이드 스터디 - chapter 5(level1) (0) | 2026.01.22 |
| [Ghidra] 리버스 엔지니어링 기드라 실전 가이드 스터디 - 00 (0) | 2026.01.04 |