[🥉 브론즈 3] 출력되지 않는 flag를 함수 인자에서 훔치다 — DreamHack Check Function Argument 풀이 — ZINO
2026-07-13·1분 읽기·
[🥉 브론즈 3] 출력되지 않는 flag를 함수 인자에서 훔치다 — DreamHack Check Function Argument 풀이
main이 어떤 함수를 flag 문자열 주소를 인자로 넘겨 호출하지만, 그 함수는 flag를 출력하지 않는다. 게다가 flag는 바이너리에 평문으로 없고 생성자가 런타임에 조립한다. x86-64 호출 규약상 첫 인자는 rdi이므로, 그 함수 호출 지점에 GDB 브레이크를 걸고 rdi를 읽어 flag를 그대로 가로챘다.
그러니 첫 번째 인자는 언제나 rdi 다. 함수를 call 하기 직전(또는 함수 진입 직후)에 rdi를 읽으면 그 함수의 첫 인자를 그대로 볼 수 있다.
이 문제는 flag를 어떤 함수에 인자로 넘기기만 하고 출력하지 않으므로, 정적으로 소스를 읽는 것만으론 값을 못 본다. 대신 동적 디버깅으로 프로그램을 실제로 돌리다가 그 함수 호출 순간에 멈춰 rdi를 들여다보면 flag가 메모리에 통째로 들어 있다. 이 바이너리는 non-PIE라 함수 주소가 고정(0x4015e2)이어서 브레이크포인트를 걸기도 쉽다.
file/strings — stripped non-PIE, 문자열엔 "takes the flag as an argument :)"와 "Can you see that?" 힌트만
"Below function takes the flag as an argument :)", "Can you see that?" — 정확히 문제 설명대로다. 실제로 실행하면 이 두 줄만 찍고 끝난다.
정상 실행 — 안내 메시지 두 줄만 출력되고 flag는 어디에도 없다
flag는 화면에 없다. 그럼 어디 있나? 정적 분석으로 flag가 어디로 흘러가는지 따라가 본다.
🧭 정적 분석 — flag를 인자로 받는 함수 찾기
main을 디스어셈블하면 흐름이 짧고 명확하다.
puts("...takes the flag as an argument :)")rdi = [0x4040d0] ; 전역에 저장된 flag 문자열 주소call 0x4015e2 ; 이 함수가 flag를 인자(rdi)로 받는다puts("Can you see that?")
main 디스어셈블 — 전역 [0x4040d0]의 flag 주소를 rdi에 싣고 0x4015e2 함수를 호출
main은 안내 메시지를 찍은 뒤, 전역 변수 0x4040d0에 담긴 포인터를 rdi에 싣고 0x4015e2 함수를 호출한다. 이 rdi가 바로 flag 주소다. 그 함수는 flag를 받아 무언가 하지만 출력은 안 한다.
▶ 삽질: strings로 flag를 찾으려다 허탕, flag는 런타임에 조립된다
처음엔 습관적으로 strings | grep DH{로 flag를 찾으려 했다. 결과는 0건. flag가 바이너리에 평문으로 박혀 있지 않다는 뜻이다.
strings에 DH 문자열이 없고, .init_array에 생성자 두 개가 등록돼 있음
이유는 .init_array에 있었다. 생성자(main보다 먼저 실행되는 초기화 함수)가 두 개 등록돼 있고, 이들이 바이너리에 임베드된 데이터를 XOR(반복 키)·덧셈으로 변환해 flag 문자열을 런타임에 조립한다. 그래서 정적으로 파일을 뒤져선 flag가 안 보이고, 프로그램을 실제로 실행해 조립이 끝난 뒤 메모리를 봐야 한다. 결국 동적 디버깅이 정답이라는 걸 이 삽질로 확인한 셈이다.
💣 핵심 — GDB로 인자 읽기
0x4015e2 호출 시점의 rdi가 flag 주소다. non-PIE라 주소가 고정이니 그 함수에 브레이크를 걸고 rdi를 문자열로 읽는다.
0x4015e2는 main이 flag 주소를 rdi에 넣고 호출하는 함수의 주소다. 그 지점에서 rdi를 읽으면 조립이 끝난 flag 문자열이 나온다.
ooh you figured out me :) Flag is DH{63db030352ca9f9f5e6b8a59c0527bee}
📝 결론
화면에 안 나오는 값도 함수 인자에서 가로챌 수 있다.
이 문제의 요점은 "출력되지 않는 데이터를 어떻게 볼 것인가"였다. 답은 그 데이터가 반드시 거치는 지점 — 함수 호출의 인자 레지스터 — 를 잡는 것이다. x86-64에서 첫 인자는 rdi이므로, 호출 직전/직후에 rdi만 읽으면 된다. 이는 실전 리버싱에서 복호화 루틴에 넘어가는 평문, 네트워크로 나가기 직전의 데이터, 로그에 안 찍히는 비밀값 등을 관찰하는 기본 기술이다.
정적 분석과 동적 분석은 역할이 다르다.
strings로 flag를 못 찾은 건 flag가 런타임에 조립되기 때문이었다. 정적 분석은 "어디를 봐야 하는지"(어느 함수가 flag를 받는지)를 알려주고, 동적 분석은 "그 순간 실제 값이 무엇인지"를 보여준다. 둘을 함께 써야 한다 — 정적으로 목표 지점을 찾고, 동적으로 그 지점의 값을 확인하는 흐름이 이 문제의 정석 풀이다.
Comments
댓글
댓글을 남기려면 로그인이 필요해요. (네이버 · 구글 계정)
댓글 불러오는 중…