플랫폼: Dreamhack
카테고리: Reversing
난이도: 중급
IDA로 바이너리를 열고 main 함수를 디컴파일했다.
int __fastcall main(int argc, const char **argv, const char **envp)
{
if ( argc == 2 )
{
if ( strlen(argv[1]) == 24 &&
std::operator==(&vlkjbkldsajfksdkfl2, argv[1]) )
{
// 플래그 출력
std::cout << "DH{" << lkjasvhkjsldhkl << "}" << std::endl;
return 0;
}
}
// ... 에러 처리
}
main 함수에서 핵심 조건을 찾았다:
┌────────────────────────────────┬───────────────────────────┐
│ 조건 │ 설명 │
├────────────────────────────────┼───────────────────────────┤
│ argc == 2 │ 인자 1개 필요 │
├────────────────────────────────┼───────────────────────────┤
│ strlen(argv[1]) == 24 │ 입력값 길이 24자 │
├────────────────────────────────┼───────────────────────────┤
│ argv[1] == vlkjbkldsajfksdkfl2 │ 특정 문자열과 일치해야 함 │
└────────────────────────────────┴───────────────────────────┘
목표: vlkjbkldsajfksdkfl2 변수의 값을 알아내야 한다
긴 main 함수를 분석할 때는 역추적(Backtracking) 방법을 사용한다
성공 조건 발견
↓
비교 대상 변수 확인 (vlkjbkldsajfksdkfl2)
↓
변수 초기화 위치 추적
↓
초기화 함수 분석
↓
원본 데이터 복호화
IDA에서 vlkjbkldsajfksdkfl2의 크로스 레퍼런스(Xrefs)를 확인했다.
사용처:
// __static_initialization_and_destruction_0 함수
sldkhjlhvfiuh(&vlkjbkldsajfksdkfl2);
sldkhjlhvfiuh 함수가 vlkjbkldsajfksdkfl2에 값을 채워넣는다.
포인트: & (주소 전달)로 함수에 변수 주소를 넘기면, 함수 내부에서 해당 변수를 직접 수정할 수 있다.
__int64 sldkhjlhvfiuh(__int64 a1)
{
std::string::basic_string(a1, &salkdhvkhlklhkjfdhkjd::segment, ...);
return a1;
}
결론: vlkjbkldsajfksdkfl2 = salkdhvkhlklhkjfdhkjd::segment
다시 초기화 함수에서:
v8 = Getslifdhiuil3();
std::string_view::basic_string_view(&salkdhvkhlklhkjfdhkjd::segment, v8);
결론: segment는 Getslifdhiuil3() 함수의 반환값!
_BYTE *Getslifdhiuil3(void)
{
if ( isEncrypted )
{
for ( i = 0; i <= 0x17; ++i ) // 24번 반복
data[i] ^= 87 - i; // XOR 복호화
isEncrypted = 0;
}
return data;
}
암호화 로직: data[i] ^= (87 - i)
디스어셈블리에서 data 주소 확인:
lea rdx, Getslifdhiuil3::data ; 0xd1a0
import idc
addr = 0xd1a0
data = []
for i in range(24):
data.append(idc.get_wide_byte(addr + i))
print(data)
[0x15, 0x35, 0x07, 0x11, 0x2b, 0x63, 0x05, 0x05,
0x2a, 0x71, 0x20, 0x0e, 0x76, 0x23, 0x73, 0x74,
0x14, 0x3e, 0x1a, 0x35, 0x33, 0x08, 0x16, 0x74]
import idc
addr = 0xd1a0
result = ""
for i in range(24):
byte = idc.get_wide_byte(addr + i)
result += chr(byte ^ (87 - i))
print(result)
BcREx1TUe?mB=i:<Sx_qpJW4
복호화 결과: BcREx1TUe?mB=i:<Sx_qpJW4
.text:0000555555557DA8 call std::operator==
.text:0000555555557DAD test al, al ← 브레이크포인트
4.3 std::string 구조 이해
디버깅 중 RAX 레지스터의 주소로 이동하면 std::string 객체가 보인다:
std::string 객체 구조 (24바이트):
├─ [+0x00] 문자열 포인터 ← 실제 문자열은 여기를 따라가야 함
├─ [+0x08] 길이
└─ [+0x10] 용량
메모리 덤프 예시:
0x555555561460:
10 3F 57 55 55 55 00 00 ← 포인터 (0x555555573F10)
18 00 00 00 00 00 00 00 ← 길이 (0x18 = 24)
포인터 주소(0x555555573F10)로 이동하면 실제 문자열을 확인할 수 있다.
./stop_before_stops 'BcREx1TUe?mB=i:<Sx_qpJW4'
주의: 문자열에 ?, <, : 같은 특수문자가 있으므로 작은따옴표로 감싸야 한다!
┌────────┬────────────────────┐
│ 따옴표 │ 특수문자 처리 │
├────────┼────────────────────┤
│ "..." │ 일부 해석됨 │
├────────┼────────────────────┤
│ '...' │ 전부 그대로 전달 ✓ │
└────────┴────────────────────┘
Congratulations! The flag is:
DH{**}
전체 추적 흐름
main 함수
↓
성공 조건: argv[1] == vlkjbkldsajfksdkfl2
↓
vlkjbkldsajfksdkfl2 초기화 추적
↓
sldkhjlhvfiuh() → salkdhvkhlklhkjfdhkjd::segment 복사
↓
Getslifdhiuil3() → XOR 복호화 후 반환
↓
암호화된 data (0xd1a0) 읽기
↓
data[i] ^ (87 - i) 복호화
↓
"BcREx1TUe?mB=i:<Sx_qpJW4" 획득
↓
프로그램 실행 → 플래그!
배운 점
역추적 분석: 성공 조건에서 시작해서 변수의 생성 위치까지 추적
XOR 복호화: 정적 분석으로 암호화 로직 파악 후 직접 복호화
std::string 구조: C++ 문자열은 포인터를 한번 더 따라가야 실제 데이터 확인 가능
IDA Python 활용: idc.get_wide_byte()로 메모리 데이터 추출
쉘 특수문자: 작은따옴표로 특수문자 포함 문자열 전달
사용 도구