카테고리 없음

리버싱입문 Day 12 - CTF Crackme와 유사한 구조 분석

baa1983 2026. 4. 23. 15:44

지금까지 Ghidra를 이용한 정적 분석을 계속 진행해왔다.

처음에는 어셈블리어를 전부 이해하려 하기보다는
중요해 보이는 흐름 위주로 따라가는 방식으로 접근했는데,
분석을 반복하다 보니 포인터 이동이나 메모리 흐름도
조금씩 눈에 들어오기 시작했다.

다만, 단순 비교문이 아니라
중간에 값이 변형되는 구조(XOR, ADD 등)가 나오면
흐름을 한 번에 이어서 보는 게 쉽지 않았다.

그래서 기초 패턴을 먼저 정리하면서 접근하고 있었고,
오늘은 실제 CTF 문제를 통해
그 흐름이 얼마나 적용되는지 확인해보려고 했다.


1. 첫 CTF 문제

오늘 풀었던 문제는 문자열 검증 구조였다.

기드라로 코드를 분석하기 위해 얻은 C언어 프로그램은 (그림)과 같다.

그림

 

기드라로 분석해보니 구조는 기존에 분석하던 virus와 유사한 파일들과는 달리
굉장히 단순했다.

main 함수에서 check 함수에 도달하면 키값을 구할 수 있었고,
각각의 키값들은 다음과 같이 구성되어 있었다.

RAX, 0x6e31737233763372
MOV word ptr [RBP + local_f], 0x67
 

이를 통해 총 9개의 문자열을 구하는 구조라고 판단할 수 있었다.

또한 입력 길이에 대한 조건도 존재했다.

140001787 e8 74 CALL strlen
14000178c 48 83 CMP RAX, 0x9
 

즉, 입력값은 반드시 9자리여야 한다는 것을 확인할 수 있었다.


2. 막힌 지점

입력값 변환 과정도 일부까지는 비교적 쉽게 파악할 수 있었다.

(input[i] + 2) ^ 0x20
 

하지만 문제는 그 다음 단계였다.

SUB EAX, EDX
 

이 연산의 의미를 정확하게 파악하는 데에서 상당한 시간이 소요되었다.

처음에는 이 부분을 제외하고 계산을 진행했지만,
그럴 경우 결과값에 제어 문자가 포함되면서
정상적인 문자열이 나오지 않는 문제가 발생했다.

즉, 수식이 완성되지 않은 상태였던 것이다.


3. 해결 과정

이후 Ghidra의 레지스터 흐름을 하나씩 추적하고,
AI의 도움을 받아 해당 어셈블리 코드를 분석하면서
다음과 같은 사실을 확인할 수 있었다.

SUB EAX, EDX → t = t - i
 

즉, 반복문에서 사용되는 인덱스 i 값이
현재 문자에서 차감되는 구조였던 것이다.

이걸 이해하고 나서야 전체 수식이 완성됐다.

((input[i] + 2) ^ 0x20) - i = key[i]
 

4. 오늘 느낀 가장 중요한 포인트

이번 문제를 통해 느낀 건 하나다.

“복잡한 것보다 단순한 것을 먼저 의심해야 한다”

처음에는 구조가 너무 단순해서
오히려 다른 로직이 있을 거라고 의심했지만,

결과적으로 핵심은
단순한 문자열 비교 + 인덱스 기반 연산이었다.


5. 접근 방식 수정

기존 방식:

main → 함수 분석 → 전체 흐름 파악

오늘 추가된 방식:

문자열 찾기 → XREF → 분기 → 검사 함수


6. 디컴파일의 중요성

이번 문제에서 가장 아쉬웠던 부분은
어셈블리를 계속 보면서 복잡하게 접근했다는 점이다.

디컴파일 코드에서는 이미 구조가 거의 그대로 드러나 있었기 때문에,

“어셈블리보다 디컴파일을 먼저 본다”

이 습관이 훨씬 중요하다는 것을 느꼈다.


7. 현재

문자열 기반 리버싱
간단한 구조 분석
함수 흐름 추적

아직 판단 속도는 느리지만
방향 자체는 틀리지 않았다는 느낌이다.


마무리

이번 문제는 난이도 자체는 낮았지만,
오히려 더 중요한 경험이었다.

“풀 수 있었는데 못 푼 문제”

이 느낌이 남았고,
그만큼 다음 문제에서는 같은 실수를 줄일 수 있을 것 같다.

조금씩이지만
흐름이 보이기 시작하는 단계에 들어온 느낌이다.