오늘은 angr라는 툴에 대해 알아보고, 실습해보도록 하겠습니다.
이번에 읽어보는 논문에서 Symbolic Execution이란 개념이 나오는데 해당 개념은, 프로그램을 데이터로 실행하는게 아닌 수식으로 실행하는 기술입니다. 쉽게 설명하면 프로그램의 변수들을 x와 같은 미지수로 보고 수학적으로 분석하고, if문을 만날 때마다 가능한 모든 경로를 탐색하며, 조건을 수립합니다.
마지막에 해당 수식을 풀어주는 도구(SMT solver, Z3)를 이용해 원하는 경로로 가는 실제 입력값을 알아냅니다.

pip install angr
위의 명령어로 간단하게 설치가능한데, 설치할 때 pwntools와 의존성에서 충돌이 자주 발생하기때문에 따로 가상환경을 만들어서 사용하였습니다.
실습을 위해 사용할 문제를 찾아봤는데, 저번에 나갔던 문제중에서 어렵지 않은 역산 문제가 있어서 가져왔습니다.
문제는 2025년 ACS 대회에 나갔을 때 풀었던 문제입니다.
문제의 코드는 다음과 같습니다.

원래 풀이는 내부의 sub_1209, sub_126A, sub1254를 분석해서 역연산을 해야합니다.
sub_1209는 ROL을 담당하는 함수이므로, 다음과 같은 코드를 작성해야합니다.
import struct
# 1. ROR (Rotate Right) 함수 구현
# 8비트 값을 오른쪽으로 n만큼 회전시키는 함수
def ror(val, n):
n = n & 7 # 8비트 로테이션이므로 7로 마스킹
return ((val >> n) | (val << (8 - n))) & 0xFF
# 2. 메모리 스택 재현 (비교값 복원)
# 전체를 담을 넉넉한 버퍼(40바이트) 생성
memory = bytearray(40)
# (1) s2 = 0x8ECD06886F6A6828
# <Q : 리틀 엔디안 unsigned long long (8바이트)
memory[0:8] = struct.pack('<Q', 0x8ECD06886F6A6828)
# (2) v5[0] = 0x26098EEB2B4A8EEB (Offset 8)
memory[8:16] = struct.pack('<Q', 0x26098EEB2B4A8EEB)
# (3) *(_QWORD *)((char *)v5 + 6) = ... (Offset 8 + 6 = 14)
# 여기서 덮어쓰기가 일어납니다.
memory[14:22] = struct.pack('<Q', 0x2F48864CEB6E2609)
# (4) *(_QWORD *)((char *)&v5[1] + 6) = ... (Offset 8 + 8 + 6 = 22)
memory[22:30] = struct.pack('<Q', 0xAFC9ACEC2B666DEB)
# 최종적으로 비교하는 30바이트 추출
target_bytes = memory[:30]
print("[*] 복원된 암호문(Hex):")
print(" ".join(f"{b:02X}" for b in target_bytes))
# 3. 역연산 (Decrypt)
# 암호화 로직: ROL 5
# 복호화 로직: ROR 5
flag = []
for byte in target_bytes:
decoded_char = ror(byte, 5)
flag.append(decoded_char)
# 결과 출력
print("\n[*] 정답(Flag):")
print("".join(chr(c) for c in flag))

문제풀이가 그렇게 어렵지 않아서 비교적 간단하지만 angr로도 한번 풀어보겠습니다.
먼저 라이브러리 로드입니다.
import angr
import claripy
angr은 분석 엔진 본체이고, claripy는 angr의 수학 연산기입니다.
다음은 바이너리 분석입니다.
p = angr.Project()를 통해 바이너리를 불러올수있습니다.
p = angr.Project("./baby_rev", auto_load_libs=False)
auto_load_libs = false는 시스템 라이브러리 분석 없이 해당 프로그램만 분석하라는 의미입니다.
다음은 상태 초기화입니다.
state = p.factory.entry_state()
entry_state는 프로그램이 실행되기 전 첫 메모리, 레지스터 상태를 찍습니다. 이 상태에서 agnr은 사용자의 입력을 아직 정해지지 않은 미지수로 설정합니다.
다음은 매니저 생성입니다.
simgr = p.factory.simulation_manager(state)
simulation_manager은 평행우주 관리자로, 프로그램이 if문을 만나서 갈라질 때, 여러개의 우주를 동시에 관리하고 조종하는 역할을 합니다.
다음은 목표 설정입니다.
simgr.explore(
find=lambda s: b"Correct!" in s.posix.dumps(1),
avoid=lambda s: b"Wrong!" in s.posix.dumps(1)
)
posix.dump(1)은 stdout을 의미합니다. 즉 stdout에 Correct가 뜨는 환경을 찾으라는 명령어입니다.
다음은 탐색입니다
if simgr.found:
found_state = simgr.found[0]
flag = found_state.posix.dumps(0)
print(f"Flag found: {flag.strip()}")
find조건에 딱 걸린 답을 가져옵니다.
그 이후 posix.dumps(0) 은 stdin을 의미하고, 성공한 케이스에 사용자가 입력했던 값이 무엇인지 가져옵니다.
전체 코드를 보면 다음과 같이 나옵니다.
import angr
import claripy
def solve():
p = angr.Project("./baby_rev", auto_load_libs=False)
state = p.factory.entry_state()
simgr = p.factory.simulation_manager(state)
simgr.explore(
find=lambda s: b"Correct!" in s.posix.dumps(1),
avoid=lambda s: b"Wrong!" in s.posix.dumps(1)
)
if simgr.found:
found_state = simgr.found[0]
flag = found_state.posix.dumps(0)
print(f"Flag found: {flag.strip()}")
else:
print("Flag not found...")
if __name__ == "__main__":
solve()
사실 그렇게 짧아진 느낌은 아니긴 한데, 복잡한 구조 분석 없이, 쉽게 코드작성이 가능하다는 점이 굉장히 좋은 거 같습니다.
프로그램을 돌리면 다음과 같은 결과가 나옵니다.

밑줄친 곳에 flag가 나오는 것을 확인할 수 있습니다.

실제로 정답이 일치하기도 합니다.
시간도 굉장히 빨리 나오고, C언어든 C++이든 윈도우는 리눅스든 다 가능하다는게 굉장히 좋은 것 같네요. 리버싱 ctf문제를 풀 때 유용할 것 같은데 조금 더 깊게 사용법을 알아가야겠습니다.
https://github.com/jakespringer/angr_ctf/tree/master
GitHub - jakespringer/angr_ctf
Contribute to jakespringer/angr_ctf development by creating an account on GitHub.
github.com
해당 주소에 들어가니까 angr를 이용해 문제를 풀 수 있도록 ctf가 제작되어있습니다.
각 단계별로 angr의 특정 기능을 익힐 수 있는데 가장 기초인 특정 출력 찾기부터, 힙 메모리를 다룰때, DLL 주입 등 여러 고급 기법들까지 사용이 가능합니다.
힙익스플로잇계의 how2heap느낌이네요. 다음 포스팅때 한번 살펴봐야 되겠습니다.
태생은 리버싱 도구인데 포너블 툴 느낌이 강하네요.
읽어주셔서 감사하고 다음포스팅때 뵙겠습니다.!
'취약점분석 > Reversing' 카테고리의 다른 글
| [Reversing] angr CTF 문제 풀이 - 2 (04 ~ 07) (0) | 2026.01.02 |
|---|---|
| [Reversing] angr CTF 문제 풀이 - 1 (00 ~ 03) (0) | 2026.01.01 |
| [Reversing] Hooking - 9 (notepad.exe 후킹 ④) (4) | 2025.08.11 |
| [Reversing] Hooking - 8 ( notepad.exe 후킹 ③ ) (4) | 2025.08.08 |
| [Reversing] Hooking - 7 (notepad.exe 후킹 ②) (7) | 2025.07.30 |