Tag Archive

#reversing

이 태그가 포함된 글을 최신순으로 모아봤어요.

113
posts
BLOG2026-08-22
[🥈 Silver 1] main 없는 ELF — 빈 스텁 256개 중 하나가 복귀주소를 덮는다 — DreamHack No Main 풀이
blog

[🥈 Silver 1] main 없는 ELF — 빈 스텁 256개 중 하나가 복귀주소를 덮는다 — DreamHack No Main 풀이

main 도 libc 시동 코드도 없는 26KB ELF. _start 가 자기 자신을 mmap 해 .init_array 와 .fini_array 의 주소를 직접 찾아 부르고, 생성자 안 빈 스텁 256개 중 딱 하나가 스택의 복귀주소를 덮어써 진짜 검사기로 점프한다. 마지막 관문은 덧셈과 XOR 이 섞인 10개의 식 — 캐리를 상태로 든 LSB 비트 탐색으로 0.02초에 되돌렸다.
#dreamhack#ctf#reversing+5
 
BLOG2026-08-20
[💠 Platinum 3] 한글 격자가 통째로 2D 바이트코드 — DreamHack 나랏말싸미 풀이
blog

[💠 Platinum 3] 한글 격자가 통째로 2D 바이트코드 — DreamHack 나랏말싸미 풀이

한글만 빽빽한 텍스트 파일과 C++ 바이너리 하나가 배포된다. 바이너리는 그 파일을 53행 34열 격자로 읽어 초성을 명령, 중성을 이동 방향, 종성을 피연산자로 해석하는 2D VM 이었다. 격자를 디스어셈블해 보니 좌상단 6x6 이 소수 스도쿠 퍼즐판이고, 빈칸 17개가 곧 입력 17바이트였다.
#dreamhack#ctf#reversing+6
 
BLOG2026-08-20
[🥇 Gold 4] MP3 프레임의 안 쓰는 1비트에 플래그를 숨기다 — DreamHack Dance in the Light 풀이
blog

[🥇 Gold 4] MP3 프레임의 안 쓰는 1비트에 플래그를 숨기다 — DreamHack Dance in the Light 풀이

배포본 main 은 복호기가 아니라 삽입기였다. MP3 프레임 헤더의 private bit 하나와 프레임 본문의 XOR 패리티를 짝지어 한 프레임에 1비트씩 심는 구조라, 복호식은 parity(body) XOR private 하나로 끝난다. 배포본을 오라클로 삼아 재삽입 결과가 output.mp3 와 바이트 단위로 같음을 확인했다.
#dreamhack#ctf#reversing+5
 
BLOG2026-08-19
[🥈 Silver 1] MixColumns 하나가 빠지면 AES 는 XOR 표로 무너진다 — DreamHack locked present 풀이
blog

[🥈 Silver 1] MixColumns 하나가 빠지면 AES 는 XOR 표로 무너진다 — DreamHack locked present 풀이

25바이트 비밀번호를 검사하는 stripped ELF 안에 5x5 블록 AES 변형이 통째로 들어 있다. S-box 도 Rcon 도 진짜 AES 인데 MixColumns 만 없다. 그 하나가 빠지자 확산이 사라져 평문 바이트 하나가 암호문 바이트 하나로 그대로 대응했고, 라운드가 10 이라 열 회전마저 상쇄돼 자리 순열까지 항등이 됐다. 재구현으로 되감아 비밀번호를 복구하고, 키를 전혀 모르는 상태에서도 같은 값이 나오는 두 가지 경로를 따로 실증했다.
#dreamhack#ctf#reversing+7
 
BLOG2026-08-19
[🥉 Bronze 2] .rodata 247바이트가 스택 VM 바이트코드였다 — DreamHack SelfStatus 풀이
blog

[🥉 Bronze 2] .rodata 247바이트가 스택 VM 바이트코드였다 — DreamHack SelfStatus 풀이

프로그램이 자기 /proc/self/status 를 읽어 TracerPid 로 디버거를 알아채고 조용히 죽는다. 진짜 검사는 .rodata 에 박힌 247바이트를 도는 스택 VM 이 맡는데, 그 VM 은 사용자 입력을 아예 쳐다보지 않고 정답 문자열을 스스로 조립한다. 그래서 역산할 것이 없다 — VM 만 옮겨 적으면 끝난다.
#dreamhack#ctf#reversing+6
 
BLOG2026-08-19
[🥈 Silver 3] 2MB 바이너리에서 코드는 509바이트뿐이었다 — DreamHack Branches and Leaves 풀이
blog

[🥈 Silver 3] 2MB 바이너리에서 코드는 509바이트뿐이었다 — DreamHack Branches and Leaves 풀이

문제 설명이 시 네 줄뿐인 리버싱. 2.1MB짜리 stripped ELF를 열어 보면 코드는 509바이트고 나머지 전부가 배열로 접은 깊이 16 이진트리다. 입력 4글자가 16비트로 펴져 트리를 16번 내려가고, 그 리프 65536개가 서로 겹치지 않아서 기대값마다 역상이 정확히 하나로 정해진다.
#dreamhack#ctf#reversing+5
 
BLOG2026-08-19
[🥇 Gold 1] 세그폴트가 곧 정답이다 — DreamHack Nonsense 풀이
blog

[🥇 Gold 1] 세그폴트가 곧 정답이다 — DreamHack Nonsense 풀이

main 은 해시 결과인 32비트 값을 포인터로 역참조해 매번 SIGSEGV 를 낸다. .init_array 가 걸어 둔 핸들러가 폴트 주소를 정답표와 대조하고 레지스터를 고쳐 재개하므로, 판정은 main 이 아니라 폴트 24번의 주소열이 한다. 해시는 입력이 2바이트뿐이라 바이너리 자체를 dlopen 해 오라클로 쓰고 전수조사로 48글자를 복원했다.
#dreamhack#ctf#reversing+7
 
BLOG2026-08-18
[💠 Platinum 4] 한 프로세스에서 32비트와 64비트가 번갈아 돈다 — DreamHack conquergent 풀이
blog

[💠 Platinum 4] 한 프로세스에서 32비트와 64비트가 번갈아 돈다 — DreamHack conquergent 풀이

배포 파일은 17KB짜리 stripped ELF 하나. 32비트 i386 바이너리인데 .text 안에 retf 가 32개 박혀 있다. CS 셀렉터를 0x33 으로 밀어 넣고 far return 하는 Heaven's Gate 로, 같은 프로세스 안에서 64비트 가젯 14종을 굴려 입력 64바이트를 qword 8개 단위로 변환한다. objdump 가 dec eax 로 보여 주는 0x48 은 REX.W 접두사였다. 가젯 규약과 상수 테이블을 도구로 뽑아 세 단계 파이프라인을 복원하고, 곱셈 상수가 전부 홀수라는 점을 이용해 mod 2^64 역원으로 되감아 플래그를 복원했다.
#dreamhack#ctf#reversing+6
 
BLOG2026-08-18
[💠 Platinum 4] 0의 런렝스를 감마 코드로 되감기 — DreamHack Run 풀이
blog

[💠 Platinum 4] 0의 런렝스를 감마 코드로 되감기 — DreamHack Run 풀이

배포 파일은 10KB 짜리 stripped ELF 와 160KB 짜리 flag.enc 두 개뿐이다. 바이너리는 복호기가 아니라 인코더였고, 0 의 런렝스를 Elias 감마 유사 코드로 바꿔 쓰는 1810바이트짜리 루틴 하나가 전부였다. 분기 하나를 반대로 읽으면 복원본이 통째로 비트 반전된다. objdump 로 인코더를 읽고 gdb 로 못 박은 뒤 역함수를 써서 10000x10000 PNG 를 되살렸고, 복원본을 문제 바이너리에 재투입해 sha256 일치로 검증했다.
#dreamhack#ctf#reversing+6
 
BLOG2026-08-17
[💠 Platinum 4] 1099줄짜리 컨트랙트에서 진짜 코드는 40줄 — DreamHack dreamthereum 풀이
blog

[💠 Platinum 4] 1099줄짜리 컨트랙트에서 진짜 코드는 40줄 — DreamHack dreamthereum 풀이

드림핵 dreamthereum(플래티넘 4, 리버싱)은 256개의 더미 함수로 채운 스마트컨트랙트다. flagChecker가 flag[i+2] = f(flag[i], flag[i+1])을 강제하는 사슬 구조라 앞 두 글자만 정하면 31글자가 전부 따라 나오고, f는 "dummy<A>(<타입>)" 시그니처의 keccak256 앞 4바이트를 16으로 나눈 나머지였다. 후보 256개를 전수로 돌려 flag를 복원하고 foundry로 띄운 실제 EVM에서 flagChecker()가 true를 반환하는 것까지 확인했다.
#dreamhack#ctf#reversing+7
 
BLOG2026-08-17
[🥈 Silver 1] 키스트림 1비트로 되돌린 SNOW 3G — DreamHack snow 풀이
blog

[🥈 Silver 1] 키스트림 1비트로 되돌린 SNOW 3G — DreamHack snow 풀이

20KB 짜리 스트립트 ELF 안에 3GPP 표준 스트림 암호 SNOW 3G 가 통째로 들어 있었다. 키는 "Merry-Christmas!" 로 고정이고 IV 는 우리가 넣는 입력의 앞 16바이트 — 즉 복원해야 할 값이 자기 자신을 암호화한다. 워드마다 필요한 정보가 키스트림의 최하위 1비트뿐이라는 점을 이용해 256가지만 확인하고 32글자를 되돌렸다.
#dreamhack#ctf#reversing+6
 
BLOG2026-08-17
[🥇 Gold 4] 파일 커서를 656번 흩뿌린 검증기 되감기 — DreamHack fverify 풀이
blog

[🥇 Gold 4] 파일 커서를 656번 흩뿌린 검증기 되감기 — DreamHack fverify 풀이

fverify 는 fseek(SEEK_CUR) 4949번과 1바이트 fread 656번으로만 이루어진 직선 코드다. 파일 오프셋이 코드 어디에도 안 적혀 있어 objdump 출력만으로는 무엇을 검사하는지 알 수 없다. 커서 이동을 그대로 재생해 슬롯과 파일 오프셋을 다시 이어 붙이면 656바이트 정답 파일이 통째로 복원된다.
#dreamhack#ctf#reversing+5
 
BLOG2026-08-17
[🥈 Silver 2] 시리얼 검증 루프를 거꾸로 돌리기 — DreamHack babycmp 풀이
blog

[🥈 Silver 2] 시리얼 검증 루프를 거꾸로 돌리기 — DreamHack babycmp 풀이

SERIAL 한 줄만 받는 MFC 다이얼로그 앱. 입력 24글자를 int32 배열로 부풀린 뒤 .data 에 박아 둔 값 24개와 통째로 비교한다. 변환이 ror32 → XOR → ADD → +30000 넉 줄뿐이라 자리마다 후보가 하나로 떨어지고, 키는 .rdata 가 아니라 코드 즉치값으로 숨어 있다. 역산본은 바이너리의 루프 기계어를 Unicorn 으로 그대로 돌려 교차검증했다.
#dreamhack#ctf#reversing+6
 
BLOG2026-08-17
[🥉 Bronze 2] 암호도 플래그도 .data 에 누워 있다 — DreamHack Gyul Brix Calculator 풀이
blog

[🥉 Bronze 2] 암호도 플래그도 .data 에 누워 있다 — DreamHack Gyul Brix Calculator 풀이

당도를 입력받는 척하는 32비트 PE 안에, 관리자 암호와 플래그가 각각 단일바이트 XOR 한 겹만 쓴 채 .data 에 나란히 누워 있다. 키가 코드 안 즉치값이라 strings 로는 아무것도 안 잡히고, 두 블롭을 키·암호문 쌍으로 넘겨짚으면 더 헤맨다. 디스어셈블리에서 0x55 와 0x77 을 읽어내면 끝난다.
#dreamhack#ctf#reversing+6
 
BLOG2026-08-17
[🥇 Gold 4] 5바이트 스텁 97개로 부순 건 코드가 아니라 디스어셈블러였다 — DreamHack Vernichtet 풀이
blog

[🥇 Gold 4] 5바이트 스텁 97개로 부순 건 코드가 아니라 디스어셈블러였다 — DreamHack Vernichtet 풀이

.text 전체에 `eb ff c1 ff c9` 가 97개 박혀 있다. jmp 가 자기 오퍼랜드로 뛰어드는 겹침 명령이라 선형 스윕 디스어셈블러는 실행되지도 않는 유령 명령을 보여 준다. 길이를 유지한 채 NOP 으로 덮어 코드를 되살리고, 그 안에 숨어 있던 15×15 Hidato 퍼즐을 풀어 답안 파일의 sha256 을 플래그로 받아냈다.
#dreamhack#ctf#reversing+7
 
BLOG2026-08-17
[🥇 Gold 3] 게임을 켜지 않고 게임 화면을 읽는다 — DreamHack My_First_Game_v0.1 풀이
blog

[🥇 Gold 3] 게임을 켜지 않고 게임 화면을 읽는다 — DreamHack My_First_Game_v0.1 풀이

Direct3D 9 로 만든 창 하나짜리 게임. 실행해 보면 배경 텍스처만 보이는데, 사실 68개 메시가 전부 두께 없는 평면이라 카메라와 같은 평면에 놓여 옆에서 보이고 있었다. 장면 데이터가 통째로 .data 에 하드코딩돼 있어 D3DXCreateMeshFVF 루프가 읽는 네 개의 배열을 직접 파싱하면 게임을 켜지 않고 68글자를 그대로 복원할 수 있다.
#dreamhack#ctf#reversing+5
 
BLOG2026-08-16
[🥇 Gold 1] 함수 125개짜리 4진 트라이를 되감다 — DreamHack Function Network 풀이
blog

[🥇 Gold 1] 함수 125개짜리 4진 트라이를 되감다 — DreamHack Function Network 풀이

stripped 바이너리가 64바이트 입력을 10000번 변형해 고정된 목표값과 비교한다. 변형의 종류는 .data 에 박힌 64비트 key 가 정하는데, 그 key 는 함수 포인터가 아니라 디스패처 125개로 짜인 4진 트라이를 2비트씩 32번 타고 내려가는 경로였다. 도착지인 리프 4개가 전부 전단사라 목표값에서 10000스텝을 거꾸로 감으면 입력이 그대로 나온다.
#dreamhack#ctf#reversing+6
 
BLOG2026-08-11
[🥉 Bronze 2] XOR 0x42 뒤에 숨은 명령 한 개 — DreamHack flag printer 풀이
blog

[🥉 Bronze 2] XOR 0x42 뒤에 숨은 명령 한 개 — DreamHack flag printer 풀이

print/id/help 세 개만 받는다던 프로그램이, 사실은 네 번째 명령을 몰래 받고 있었다. 모르는 토큰을 strdup 해서 XOR 0x42 한 뒤 스택에 조립해 둔 네 바이트와 비교하는 코드가 있고, 그걸 통과하면 print 가 art 대신 flag 를 연다. objdump 로 비교값을 읽고 gdb 로 런타임까지 확인해 한 줄로 flag 를 뽑았다.
#dreamhack#ctf#reversing+5
 
BLOG2026-08-10
[🥇 Gold 4] 하루 넘게 걸리는 정렬을 0.2초로 건너뛰기 — DreamHack Slooooow 풀이
blog

[🥇 Gold 4] 하루 넘게 걸리는 정렬을 0.2초로 건너뛰기 — DreamHack Slooooow 풀이

배포본은 stripped PIE 하나뿐이고 코드는 1,136바이트, 데이터는 400KB다. main 이 하는 일은 그 400KB를 Stooge sort 로 정렬하고 SHA-256 을 찍는 것뿐인데, 마스터 정리를 세워 보면 10만 원소에 하루가 넘게 걸린다. 정렬 알고리즘을 실행할 필요는 없다. 비교 함수가 쓰는 비트 순열만 그대로 복원하면 결과는 파이썬 한 줄로 나온다.
#dreamhack#ctf#reversing+7
 
BLOG2026-08-09
[🥇 Gold 1] 브라우저 안 wasm 한 덩어리 되돌리기 — DreamHack passcode 풀이
blog

[🥇 Gold 1] 브라우저 안 wasm 한 덩어리 되돌리기 — DreamHack passcode 풀이

HTML 한 장과 JavaScript 한 장이 전부인 문제. main.js 첫 줄에 박힌 1,984바이트 WebAssembly 모듈이 25글자 passcode를 검사한다. wabt로 뜯어 보니 256회 루프가 바이트를 전혀 섞지 않아, 25개 위치가 각자 독립된 치환 사슬을 탄다. 위치마다 256개 후보를 정방향으로 돌려 역상을 하나씩 확정했다.
#dreamhack#ctf#reversing+5
 
BLOG2026-08-07
[🥈 Silver 3] strncmp 는 8바이트를 다 보지 않는다 — DreamHack Broken Password 풀이
blog

[🥈 Silver 3] strncmp 는 8바이트를 다 보지 않는다 — DreamHack Broken Password 풀이

/dev/urandom 으로 만든 8바이트 비밀번호를 strncmp 로 검사한다. 그런데 strncmp 는 NUL 을 만나면 거기서 멈추고 같다고 답한다. 첫 바이트가 0x00 으로 나오는 1/256 만 노리면 개행 한 글자로 통과한다. 더 그럴싸해 보이는 임의 주소 쓰기 경로는 주소 유출이 없어 쓸 수 없는 미끼였다.
#dreamhack#ctf#misc+6
 
BLOG2026-08-04
[💠 Platinum 3] 실행 중에 자기 바이트코드를 갈아끼우는 .pyc — DreamHack pyc 풀이
blog

[💠 Platinum 3] 실행 중에 자기 바이트코드를 갈아끼우는 .pyc — DreamHack pyc 풀이

dis.dis 로 열면 평범한 XOR 검사처럼 보이는 .pyc 인데, 정작 그 코드는 한 번도 실행되지 않는다. ctypes 로 code object 의 co_code 버퍼를 제자리에서 XOR 해 진짜 검사 루틴을 꺼내 쓰기 때문이다. 바이트코드를 손으로 디코드해 복원하고, 4바이트 창 ROTR·XOR 변환을 뒤에서 앞으로 되감아 플래그를 뽑았다.
#dreamhack#ctf#reversing+5
 
BLOG2026-08-04
[💠 Platinum 3] 니블 교환·AES S-box·std::sort 로 짠 로그인 검사 되돌리기 — DreamHack rev-login 풀이
blog

[💠 Platinum 3] 니블 교환·AES S-box·std::sort 로 짠 로그인 검사 되돌리기 — DreamHack rev-login 풀이

아이디와 비밀번호를 세 단계로 검사하는 C++ 바이너리. 아이디 검사는 글자를 니블 단위로 뒤집어 옆 글자와 엮은 뒤 AES S-box 를 통과시키고, 비밀번호 검사는 S-box 를 한 번 더 누산한 다음 std::sort 로 자리 정보를 지워 버린다. 플래그는 어디에도 없고 정답 문자열의 SHA-1 이 곧 키다.
#dreamhack#ctf#reversing+6
 
BLOG2026-07-28
[🥈 Silver 1] 상쇄되는 연산 14개와 미끼 키 2개 — DreamHack Ransom 풀이
blog

[🥈 Silver 1] 상쇄되는 연산 14개와 미끼 키 2개 — DreamHack Ransom 풀이

UPX 로 포장된 32비트 PE 가 flag.txt 를 두 번 암호화한다. 진짜 main 은 암호화 함수를 둘 부르는데, 하나만 보고 뒤집으면 아무것도 안 나온다. 2단계는 고정 인덱스 연산이 15개나 붙어 있지만 14개가 역연산 짝이라 상쇄되고 실질은 하나뿐이고, .rdata 의 키처럼 생긴 문자열 셋 중 둘은 스택에 복사만 되고 읽히지 않는 미끼다. 키가 파일보다 한 바이트 짧아 마지막 바이트는 초기화 안 된 스택을 읽는다.
#dreamhack#ctf#reversing+7
 
BLOG2026-07-28
[🥉 Bronze 3] 리크도 셸도 없이 strcmp 하나를 오라클로 바꾸기 — DreamHack checkflag 풀이
blog

[🥉 Bronze 3] 리크도 셸도 없이 strcmp 하나를 오라클로 바꾸기 — DreamHack checkflag 풀이

플래그를 맞히면 Correct 를, 틀리면 Failed 를 돌려주는 것이 전부인 바이너리다. 리크할 포인터도 덮을 리턴 주소도 없다. 그런데 main 이 flag 를 담는 버퍼와 우리가 입력하는 버퍼를 같은 스택 영역에 0x40 간격으로 나란히 놓고, read 는 그보다 훨씬 긴 0xc8 바이트를 받는다. 0x40+d 바이트를 보내면 flag 앞 d 글자가 내 바이트로 덮이고, 그 상태의 strcmp 는 "flag 의 남은 뒷부분이 이거 맞아?" 라는 접미사 오라클이 된다. 뒤에서부터 한 글자씩 물어보면 16글자가 16초 만에 나온다.
#dreamhack#ctf#pwnable+7
 
BLOG2026-07-27
[🥇 Gold 4] RC4인 줄 알았던 암호와, 파일 하나에 숨어 있던 세 조각 — DreamHack Enc-JPG 풀이
blog

[🥇 Gold 4] RC4인 줄 알았던 암호와, 파일 하나에 숨어 있던 세 조각 — DreamHack Enc-JPG 풀이

배포물은 윈도우 실행파일 Enc 와 깨진 flag.jpg 둘뿐이다. Enc 를 뜯어보면 RC4 처럼 생긴 루틴이 나오는데 표준과 세 군데가 다르다 — 키 확장이 나머지가 아니라 AND, 키스트림이 S 박스를 한 번 더 타고, 무엇보다 0x400 청크마다 S 박스를 새로 만든다. 마지막 하나를 놓치면 JPEG 헤더만 살아나고 나머지가 전부 깨진다. 제대로 복호하면 파일 한 개 안에 JPEG 둘과 평문과 PNG 가 이어붙어 있고, 플래그는 그 셋에 나뉘어 들어 있다. 마지막 조각은 PNG 의 IDAT 길이 필드 자리에 덮여 있어서 그림으로는 영영 안 보인다.
#dreamhack#ctf#reversing+7
 
BLOG2026-07-26
[🥈 Silver 2] Rust 크랙미의 비트 치환을 거꾸로 돌려 64글자 입력 복원 — DreamHack CrabME 풀이
blog

[🥈 Silver 2] Rust 크랙미의 비트 치환을 거꾸로 돌려 64글자 입력 복원 — DreamHack CrabME 풀이

Rust 로 빌드된 크랙미. 입력은 64글자 소문자 hex 로 고정돼 있고, 두 글자씩 묶은 32바이트를 flagchecker 가 "비트 치환 → XOR 0x63 → +0x22" 세 단계로 바꿔 .rodata 에 박힌 상수 32개와 비교한다. 세 단계가 전부 바이트 단위 전단사라 브루트포스 없이 그대로 역산되고, 나온 64글자가 곧 플래그 본문이다. objdump 로 치환표를 유도하고 gdb 로 런타임에서 같은 값이 나오는지 확인했다.
#dreamhack#ctf#reversing+6
 
BLOG2026-07-25
[🥈 Silver 1] main 이라는 이름의 거짓말 — DreamHack Honest 풀이
blog

[🥈 Silver 1] main 이라는 이름의 거짓말 — DreamHack Honest 풀이

바이너리 하나만 주는 리버싱 문제. 64개 함수가 전역 counter 를 하나씩 올리며 입력을 한 글자씩 검사하는 단순한 구조인데, 그대로 역산하면 Wrong! 이 나온다. ELF 진입점을 따라가 보면 _start 가 __libc_start_main 에 넘기는 함수가 main 심볼이 아니라 verify_func_28 이다. 시작 칸이 55칸 어긋나 정답 문자열이 통째로 회전해 있었다.
#dreamhack#ctf#reversing+5
 
BLOG2026-07-25
[🥉 Bronze 2] 버튼 8.9억 번 대신 APK 속 SEED-128 CBC 를 직접 푼다 — DreamHack [CodeEngn] MobileApp L03 풀이
blog

[🥉 Bronze 2] 버튼 8.9억 번 대신 APK 속 SEED-128 CBC 를 직접 푼다 — DreamHack [CodeEngn] MobileApp L03 풀이

SmartApp L03.apk 는 버튼을 randomRange(44444) 번 눌러야 키값을 보여준다. 평균 8.9억 번이라 UI 로는 절대 못 본다. jadx 로 뜯어보면 그 값은 입력과 무관하게 Security.DecryptStr() 이 하드코딩 hex 를 SEED-128 CBC 로 복호한 결과였고, key 와 IV 는 dex 의 fill-array-data 페이로드에 상수로 박혀 있다. apktool 없이 classes.dex 에서 상수를 직접 긁어내 openssl seed-cbc 로 복호하면 키값 CodeEngn_4_Ever 가 나온다.
#dreamhack#ctf#reversing+9
 
BLOG2026-07-25
[🥈 Silver 2] 해킹당한 VM 이미지에서 채굴기가 삼킨 플래그 되찾기 — DreamHack Infested Terran 풀이
blog

[🥈 Silver 2] 해킹당한 VM 이미지에서 채굴기가 삼킨 플래그 되찾기 — DreamHack Infested Terran 풀이

bzImage + rootfs.ext2 두 파일만 주는 문제. 이미지를 마운트하지 않고 열어보면 buildroot 바이너리들 사이에 glibc로 빌드된 두 개가 섞여 있다. /home/ubuntu/.helper는 표준입력으로 받은 값을 argv[0]="[kworker]"로 위장해 /usr/bin/sl에 넘기고, sl은 입력 36글자를 힙에 흩뿌린 뒤 누적합을 하드코딩된 값과 비교한다. 검증식이 삼각행렬이라 이웃한 두 항을 빼는 것만으로 한 글자씩 그대로 복원된다.
#dreamhack#ctf#reversing+7
 
BLOG2026-07-24
[🥈 Silver 2] 리버싱 대신 메모리를 읽는다 — DreamHack just read memory 풀이
blog

[🥈 Silver 2] 리버싱 대신 메모리를 읽는다 — DreamHack just read memory 풀이

입력을 한 글자씩 연결리스트 노드와 비교하고, 다 맞으면 플래그를 뱉는 바이너리. 노드 값은 루프 인덱스가 아니라 세 헬퍼 함수가 공유하는 초기화 안 된 스택 슬롯과 전역 cnt 로 만든 숨은 점화식(cnt = 27*cnt + 103)에서 나온다. 그 점화식을 파이썬으로 접어 정답 입력을 복원할 수도 있지만, 문제 이름대로 gdb 로 리스트 생성 직후에 멈춰 힙을 그대로 읽어내는 게 더 빠르다.
#dreamhack#ctf#reversing+5
 
BLOG2026-07-24
[🥈 Silver 4] 3×10¹⁷번 세제곱을 안 기다리고 접기 — DreamHack power cube 풀이
blog

[🥈 Silver 4] 3×10¹⁷번 세제곱을 안 기다리고 접기 — DreamHack power cube 풀이

실행하면 "You've waited so long...."만 남기고 영원히 안 끝나는 바이너리 하나. 하는 일은 64비트 정수를 세제곱만 3.1×10¹⁷번 반복한 뒤 그 결과를 SHA256해서 플래그로 찍는 게 전부다. 세제곱을 쌓으면 지수가 3의 거듭제곱으로 자란다는 것과, 2⁶⁴ 곱셈군의 지수가 2⁶²라는 카마이클 정리 하나로 그 반복을 한 줄 pow로 접는다.
#dreamhack#ctf#reversing+5
 
BLOG2026-07-22
[🥉 Bronze 2] 3중으로 막아둔 안드로이드 버튼, 그래도 정적분석으로 뚫는다 — DreamHack [CodeEngn] MobileApp L02 풀이
blog

[🥉 Bronze 2] 3중으로 막아둔 안드로이드 버튼, 그래도 정적분석으로 뚫는다 — DreamHack [CodeEngn] MobileApp L02 풀이

L01과 같은 SEED-128 CBC + Java BigInteger 부호 인코딩 구조지만, 이번엔 유효 블록이 2개라 CBC 체이닝까지 풀어야 한다. onCreate()의 실행 조건은 참조비교(==)·하드코딩 상수· 불가능한 과거 시각 3중으로 막혀 있어, 앱을 실행해서는 절대 답이 안 나오게 설계됐다.
#dreamhack#ctf#reversing+6
 
BLOG2026-07-21
[🥇 Gold 2] .rela.tivity — 재배치 테이블이 곧 암호문이었다, DreamHack Theori of Relativity 풀이 (2번 보류 끝에 해결)
blog

[🥇 Gold 2] .rela.tivity — 재배치 테이블이 곧 암호문이었다, DreamHack Theori of Relativity 풀이 (2번 보류 끝에 해결)

키 문자열을 입력하면 맞는지 틀렸는지만 알려주는 짧은 리버싱 문제. 검증에 쓰이는 104바이트 "키 블록"이 파일에 적힌 값과 실행 중인 값이 완전히 다른데, 그 값을 바꾸는 함수를 아무리 찾아도 안 나온다 — 지난 두 세션은 여기서 막혔다. 진짜 범인은 함수가 아니라 ".rela.tivity"라는 이름의 가짜 재배치 섹션과 그걸 진짜로 처리해버리는 ELF 로더 자신이었다. 게다가 마지막 관문에서는 "디버거로 관찰하는 행위 자체가 관찰 대상을 바꿔버리는" 진짜 물리학 농담 같은 함정까지 기다리고 있었다.
#dreamhack#ctf#reversing+5
 
BLOG2026-07-21
[🥈 Silver 4] 10²²년짜리 잠을 0.3초로 줄이기 — DreamHack Long Sleep 풀이 (2번 보류 끝에 해결)
blog

[🥈 Silver 4] 10²²년짜리 잠을 0.3초로 줄이기 — DreamHack Long Sleep 풀이 (2번 보류 끝에 해결)

실행하면 "flag를 생성 중"이라며 뜸을 들이다가 점점 더 길게 잠드는 바이너리. sleep()이 아니라 libc 심볼도 없는 raw syscall이라 LD_PRELOAD 후킹이 안 통하고, 파일을 패치하거나 소프트웨어 브레이크포인트를 걸면 곧바로 "integrity_check(): file has been corrupted"가 뜬다. 지난 두 세션은 이 두 방어가 서로 "같은 것"을 검사한다고 착각해 막혔다. 사실은 완전히 다른 두 층이었다 — 하나는 코드 바이트 자체의 무결성, 하나는 그저 실행 속도. 코드는 그대로 두고 syscall 인자값만 바꾸면 두 방어 다 건드리지 않고 지나간다.
#dreamhack#ctf#reversing+4
 
BLOG2026-07-21
[🥇 Gold 1] 27455번 반복되는 누산도 결국 선형이다 — DreamHack multipoint 풀이 (2번 보류 끝에 해결)
blog

[🥇 Gold 1] 27455번 반복되는 누산도 결국 선형이다 — DreamHack multipoint 풀이 (2번 보류 끝에 해결)

38바이트 목표값과 memcmp하는 39바이트 입력 검증기. 검증 알고리즘 자체는 단순한 곱셈-누산 루프인데, 몇 개의 레코드를 도는지가 파일 안의 데이터로 결정되는 구조라 정적으로 훑다가 "말이 안 되게 크다"는 판단을 내려 두 세션 연속 보류했던 문제다. 사실은 판단이 틀렸던 것뿐이었다 — 정말로 27455번 반복하는 게 맞았고, 그 반복이 아무리 많아도 각 항의 계수가 전부 컴파일타임 상수라는 사실을 놓치지 않으면 전체가 GF(251) 위의 선형 방정식 하나로 정리된다.
#dreamhack#ctf#reversing+4
 
BLOG2026-07-21
[🥇 Gold 2] 코드가 스스로를 만들어낼 땐, 만드는 코드부터 읽어야 한다 — DreamHack You shall not pass 풀이
blog

[🥇 Gold 2] 코드가 스스로를 만들어낼 땐, 만드는 코드부터 읽어야 한다 — DreamHack You shall not pass 풀이

.NET/Mono 어셈블리 하나가 주어지는 리버싱 문제. ikdasm으로 Main()을 열어봐도 정작 flag를 검증하는 로직이 안 보인다 — 이 프로그램은 System.Reflection.Emit으로 검증 메서드를 실행 도중에 직접 조립해서 리플렉션으로 호출한다. "OpCode를 스택에 올리고 Emit을 부르는" 패턴이 100번 가까이 반복되는 걸 정규식 파서로 순서대로 추출해 실제 만들어질 메서드의 명령 시퀀스를 복원했다. 복원한 로직은 인접한 두 값의 순환 합을 32개의 목표값과 비교하는 방정식 시스템이었고, 이를 Z3로 직접 풀어 얻은 flag를 실제 mono 런타임에 넣어 "correct!"까지 확인했다.
#dreamhack#ctf#reversing+5
 
BLOG2026-07-21
[🥈 Silver 4] "L번 도니 한 번씩만 더해졌겠지"는 틀린 직관이었다 — DreamHack pybycodetes 풀이
blog

[🥈 Silver 4] "L번 도니 한 번씩만 더해졌겠지"는 틀린 직관이었다 — DreamHack pybycodetes 풀이

실행 파일이 아니라 파이썬 dis.dis() 바이트코드 덤프 두 장(main.txt, hard2rev.txt)과 출력 결과(output.txt)만 주어지는 리버싱 문제. opcode를 그대로 읽어 파이썬 소스로 재구성하면, flag를 바이트 XOR 한 뒤 값 하나에 +36을 하고 배열 전체를 j//2번 뒤집는 걸 반복하는 함수가 나온다. "루프가 L번 도니까 각 원소에 정확히 한 번씩만 더해졌겠지"라는 첫 직관은 틀렸다 — 반복되는 반전 때문에 같은 값이 여러 번 걸리기도, 아예 안 걸리기도 한다. 실제 값 대신 "위치 라벨" 배열을 똑같은 알고리즘으로 굴려 정확한 횟수와 최종 위치를 추적하고, 그 결과를 역산해 flag를 복원했다.
#dreamhack#ctf#reversing+3
 
BLOG2026-07-21
[🥈 Silver 4] ctype 비트 하나를 잘못 짚으면 퍼뮤테이션 전체가 어긋난다 — DreamHack Permpkin 풀이
blog

[🥈 Silver 4] ctype 비트 하나를 잘못 짚으면 퍼뮤테이션 전체가 어긋난다 — DreamHack Permpkin 풀이

chall 바이너리는 입력을 받지 않는다. 대신 미리 정해둔 sample 문자열을 스스로 뒤섞어 rev1.txt/rev2.txt로 보여줄 뿐이다. 진짜 풀어야 할 대상은 같은 방식으로 미리 변환돼 주어진 flag1.txt/flag2.txt. 바이너리 안의 23자리 hex 문자열을 ctype 기반으로 숫자 배열(퍼뮤테이션+XOR 키)로 바꾸는데, 그 변환 도중 하필 값이 0이 되는 지점이 있어서 이후 strlen()이 배열 길이를 23에서 13으로 끊어버린다. 이 함정을 처음엔 못 보고 엉뚱한 ctype 비트를 가정해 틀렸다가, gdb로 실제 프로세스의 중간 메모리 상태를 직접 찍어보고 나서야 알고리즘을 100% 검증하고 뒤집을 수 있었다.
#dreamhack#ctf#reversing+4
 
BLOG2026-07-21
[🥈 Silver 4] 화면에 아무것도 안 뜨는 바이너리에서 flag 뽑아내기 — DreamHack fake 풀이
blog

[🥈 Silver 4] 화면에 아무것도 안 뜨는 바이너리에서 flag 뽑아내기 — DreamHack fake 풀이

실행해도 아무것도 출력하지 않는 ELF 바이너리 하나. strace로 확인해보니 read/write 계열 syscall이 단 한 번도 없다 — 임포트된 함수조차 malloc/free뿐이다. objdump로 뜯어보니 main()은 스택에 movabs 즉시값으로 23바이트 시드 버퍼를 조립해 malloc한 뒤, reverse → 인접 바이트 교환 → XOR라는 세 단계 변환만 거쳐 곧바로 free해버린다. 화면에 찍히는 건 없지만, 그 변환 결과를 파이썬으로 그대로 재현하고 gdb로 free() 직전 힙 버퍼를 직접 들여다보는 것으로 flag를 확인했다.
#dreamhack#ctf#reversing+4
 
BLOG2026-07-21
[🥈 Silver 4] 프로그램이 정말로 자기 이름에게 물었다 — DreamHack What is your name? 풀이
blog

[🥈 Silver 4] 프로그램이 정말로 자기 이름에게 물었다 — DreamHack What is your name? 풀이

zip 안에 exe가 11개, 전부 파일명이 무작위 3글자로 뒤섞여 있다. 실행해도 콘솔에 아무것도 안 뜨고, sha256도 전부 다르다. cmp -l로 두 파일을 겹쳐보니 123392바이트 중 딱 2바이트만 다르다는 걸 발견하면서 실마리가 풀렸다. 각 exe는 GetModuleFileNameA로 자기 자신의 파일명을 읽어 한 글자씩 밀어서 출력할 뿐이었고, 그 결과를 순서대로 이어붙이면 그대로 flag가 된다 — "What is your name?"이 은유가 아니라, 프로그램이 진짜로 자기 자신의 이름을 묻는 질문이었다.
#dreamhack#ctf#reversing+4
 
BLOG2026-07-21
[🥈 Silver 2] 반복문을 없애도 제약은 사라지지 않는다 — DreamHack Call more functions 풀이
blog

[🥈 Silver 2] 반복문을 없애도 제약은 사라지지 않는다 — DreamHack Call more functions 풀이

64바이트 입력을 검증하는 함수 하나를 열어보니 반복문도, 분기문도 없다. 대신 똑같은 헬퍼 함수 네 개(push, peek, xor, compare)를 319번이나 이어서 호출하는, 완전히 펼쳐진(unrolled) 코드가 전부다. 사람 눈으로 319개의 호출을 하나하나 손으로 추적하는 대신, objdump 출력을 그대로 파싱해서 이 호출 순서를 Z3 SMT 솔버로 "재생"시켰다. 어떤 값이 정답인지 스스로 계산할 필요 없이, 검증 로직 자체를 제약 조건 덩어리로 옮겨놓기만 하면 풀린다.
#dreamhack#ctf#reversing+5
 
BLOG2026-07-21
[🥈 Silver 4] srand() 없는 rand()는 이미 예언된 미래다 — DreamHack randzzz 풀이
blog

[🥈 Silver 4] srand() 없는 rand()는 이미 예언된 미래다 — DreamHack randzzz 풀이

"난수를 맞혀보라"는 리버싱 문제. 그런데 바이너리를 뜯어보면 어디에도 srand()가 없다 — glibc의 rand()는 시드를 명시적으로 주지 않으면 항상 똑같은 순서로 값을 뱉는 결정론적 함수이므로, 이 프로그램이 뭘 물어볼지는 실행하기도 전에 전부 계산할 수 있다. 문제는 그 다음이다. 두 번의 검증이 서로 다른 정답을 요구하는데 입력은 한 번뿐이라, 한 번의 정상 실행으로는 절대 flag 전체를 볼 수 없다. GDB로 실행 중인 프로세스의 스택 메모리를 직접 고쳐써서 두 검증을 동시에 통과시킨 끝에야 완전한 flag가 나왔다.
#dreamhack#ctf#reversing+5
 
BLOG2026-07-21
[🥈 Silver 3] 시리얼 검증 함수 5개는 사실 연립방정식 5개였다 — DreamHack verify 풀이
blog

[🥈 Silver 3] 시리얼 검증 함수 5개는 사실 연립방정식 5개였다 — DreamHack verify 풀이

스트립된 keygen류 리버싱 바이너리. "DH 뒤에 8자리-10자리-10자리-8자리 형식의 hex 세그먼트"를 가진 시리얼을 입력받아 형식을 검사하고, 4조각을 각각 64비트 정수로 바꾼 뒤 5개의 검증 함수를 체인으로 통과시킨다. 함수를 하나씩 디스어셈블해보면 그 5개가 전부 덧셈과 XOR로 이루어진 식이라는 게 드러난다 — a+b, b+c, c+d, a+d, b^c^d. 겉보기엔 복잡한 검증 로직이지만 결국 미지수 4개짜리 연립방정식 5개일 뿐이고, numpy로 66초 만에 유일해를 찾아낸다.
#dreamhack#ctf#reversing+5
 
BLOG2026-07-21
[🥈 Silver 2] MD5를 잘게 썰면 "충돌 저항성"이라는 말이 무색해진다 — DreamHack hash-browns 풀이
blog

[🥈 Silver 2] MD5를 잘게 썰면 "충돌 저항성"이라는 말이 무색해진다 — DreamHack hash-browns 풀이

스트립된 리버싱 바이너리 하나가 전부다. 입력을 받아 27바이트로 자르고, 그걸 다시 3바이트씩 9조각으로 쪼갠 뒤 각 조각의 MD5를 계산해 바이너리 안에 박혀 있는 16바이트 값과 비교한다. 27바이트 전체를 한 번에 맞히려 들면 무차별대입은 우주 나이보다 오래 걸리지만, 3바이트짜리 조각 하나의 MD5 역상은 파이썬으로 0.2초면 찾는다. 해시 함수 자체는 멀쩡한데, "작은 조각으로 쪼개 각각 검증한다"는 설계 하나가 전체 키공간을 9개의 훨씬 작은 문제로 무너뜨린다.
#dreamhack#ctf#reversing+5
 
BLOG2026-07-20
[🌱 Sprout] 문제 만든 사람이 엔디언을 깜빡했다 — DreamHack littlevsbig 풀이
blog

[🌱 Sprout] 문제 만든 사람이 엔디언을 깜빡했다 — DreamHack littlevsbig 풀이

소스에는 int_arr[0] == 0x64726d68, int_arr[1] == 0x636b3a29라는 비교식이 그대로 노출돼 있다. 16진수를 사람 눈으로 그대로 읽으면 "drmhck:)" — "dreamhack :)"을 줄인 문자열처럼 보인다. 그런데 그 문자열을 그대로 입력하면 프로그램은 거부한다. x86은 little-endian이라 4바이트 정수를 메모리에 거꾸로 저장하기 때문에, 실제로 입력해야 하는 바이트열은 각 4바이트 워드 단위로 뒤집힌 "hmrd):kc"다. GDB로 스택 위 바이트를 직접 찍어 그 반전을 눈으로 확인했다.
#dreamhack#ctf#misc+3
 
BLOG2026-07-20
[🥉 Bronze 1] 두 번의 XOR은 상쇄되지 않았다 — DreamHack ez_rev 풀이
blog

[🥉 Bronze 1] 두 번의 XOR은 상쇄되지 않았다 — DreamHack ez_rev 풀이

"함수가 너무 많아요"라는 문제 설명과 달리 실제로 입력을 건드리는 함수는 셋뿐이다. 회전 두 번과 XOR 두 번이 교차로 걸려 있는데, 같은 키로 XOR을 두 번 하니 그냥 상쇄될 거라 생각하고 지름길을 탔다가 완전히 다른 문자열이 나왔다. 원인은 두 XOR 사이에 낀 회전 하나가 인덱스 정렬 자체를 바꿔버린다는 점이었다.
#dreamhack#ctf#reversing+2
 
BLOG2026-07-20
[🥉 Bronze 1] 키는 GOT 뒤에 숨어 있었다 — DreamHack basic_CrackMe 풀이
blog

[🥉 Bronze 1] 키는 GOT 뒤에 숨어 있었다 — DreamHack basic_CrackMe 풀이

32비트 PIE ELF와 Windows PE가 세트로 나오는 리버싱 문제. 겉보기엔 흔한 XOR 크랙미인데 막상 열어보면 XOR 한 번이 아니라 자릿수마다 제곱·뺄셈·덧셈이 섞인 int64 배열 연산이라 손으로 따라가면 금방 헷갈린다. 진짜 함정은 알고리즘이 아니라 키 위치 — 키 문자열은 코드 어디에도 안 보이고 GOT 슬롯에 꽂힌 포인터 하나가 .rodata를 가리키고 있었다.
#dreamhack#ctf#reversing+3
 
BLOG2026-07-20
[🥉 Bronze 1] 위치 무관 바이트 변환은 256개짜리 표 하나로 뒤집힌다 — DreamHack ezmix 풀이
blog

[🥉 Bronze 1] 위치 무관 바이트 변환은 256개짜리 표 하나로 뒤집힌다 — DreamHack ezmix 풀이

stripped 바이너리가 program.bin이라는 2바이트짜리 opcode 프로그램을 해석해 우리가 입력한 문자열을 ADD/XOR/ROR 256번 연속으로 뒤섞는다. 언뜻 복잡해 보이지만 세 연산 전부 버퍼의 모든 바이트에 같은 인자를 똑같이 적용할 뿐이라, 입력 위치와 무관하게 0~255 전체에 대해 한 번만 정방향 표를 만들면 역표는 자동으로 나온다. output.bin 36바이트에 역표를 그대로 대입해 flag를 즉시 복원했다.
#dreamhack#ctf#reversing+3
 
BLOG2026-07-20
[🥉 Bronze 1] 64바이트를 6개 함수가 나눠 검증하는 crackme — DreamHack mix-compare 풀이
blog

[🥉 Bronze 1] 64바이트를 6개 함수가 나눠 검증하는 crackme — DreamHack mix-compare 풀이

64바이트 입력을 check → check_not → check_add → check_dec → check_mul → check_la 여섯 함수가 10바이트 안팎씩 나눠 맡아 각자 다른 산술 변환(NOT·덧셈·뺄셈·곱셈·역방향 덧셈)으로 검증한다. 비교 대상 배열은 .data에 평문 정수로 그대로 박혀 있어서, 각 구간의 변환을 거꾸로 풀기만 하면 64바이트 전체가 그대로 복원된다.
#dreamhack#ctf#reversing+3
 
BLOG2026-07-19
[🥈 Bronze 2] 함수 주소일까, 호출 지점일까 — DreamHack [CodeEngn] Malware L08 풀이
blog

[🥈 Bronze 2] 함수 주소일까, 호출 지점일까 — DreamHack [CodeEngn] Malware L08 풀이

다른 악성코드를 실행해 추가 감염을 일으키는 함수의 주소를 찾으라는 문제. 그래프 전체가 ShellExecuteExA로 대상 파일을 실행하는 하나의 함수인데, 그 함수 자체의 시작 주소를 냈다가 오답을 받았다. 함수 안에 ShellExecuteExA 호출이 두 곳이라는 걸 알고도 어느 쪽인지 헷갈렸는데, 결국 "함수의 주소"가 가리키는 건 실행 API를 호출하는 그 지점이었다.
#dreamhack#ctf#reversing+5
 
BLOG2026-07-19
[🥈 Bronze 2] 미끼인 줄 알았던 디코드가 사실 정답이었다 — DreamHack 아 문제 이름 뭘로하지 풀이
blog

[🥈 Bronze 2] 미끼인 줄 알았던 디코드가 사실 정답이었다 — DreamHack 아 문제 이름 뭘로하지 풀이

39바이트 암호문을 XOR 0xAA로 풀면 그럴듯한 DH{} 문자열이 바로 나온다. 너무 쉬워서 미끼라고 판단하고, 사용자 입력을 변형하는 복잡한 커스텀 인코딩 함수를 통째로 역산했다. GDB로 대조하다 32비트 시프트 카운트 마스킹 때문에 특정 자리가 항상 0이 되는 버그를 발견했고, 그 버그를 쫓아가다 결국 처음 봤던 그 디코드 문자열이 진짜 정답이었다는 걸 알게 됐다.
#dreamhack#ctf#reversing+4
 
BLOG2026-07-19
[🥈 Bronze 2] 난독화된 전역 변수를 GDB로 그대로 읽어내기 — DreamHack Stop before stops! 풀이
blog

[🥈 Bronze 2] 난독화된 전역 변수를 GDB로 그대로 읽어내기 — DreamHack Stop before stops! 풀이

C++ 바이너리인데 함수·전역변수 이름이 전부 무작위 문자열로 바뀌어 있다. 정적 분석으로 로직은 다 읽히는데(24자 비밀번호를 비교해 맞으면 flag를 출력), 실제 비교 대상 문자열은 실행 시점에 조용히 채워지는 전역 std::string이라 정적 디스어셈블리만으론 값을 알 수 없다. GDB로 static initializer가 끝난 직후 raw 메모리를 읽어 값을 그대로 꺼냈다.
#dreamhack#ctf#reversing+5
 
BLOG2026-07-19
[🥉 Bronze 3] 뮤텍스가 두 개인 악성코드에서 Thread Mutex 골라내기 — DreamHack [CodeEngn] Malware L06 풀이
blog

[🥉 Bronze 3] 뮤텍스가 두 개인 악성코드에서 Thread Mutex 골라내기 — DreamHack [CodeEngn] Malware L06 풀이

같은 악성코드(bagla downloader) 그래프를 다루는 CodeEngn 시리즈의 또 다른 문제. 이번엔 뮤텍스가 두 개 나온다 — 프로그램 시작 시 만드는 프로세스 단일실행 가드용, 그리고 CreateThread 직후 만드는 두 번째 뮤텍스. 문제가 묻는 "Thread Mutex"는 스레드 생성과 바로 붙어 있는 후자다.
#dreamhack#ctf#reversing+5
 
BLOG2026-07-19
[🥉 Bronze 4] 가짜 함수 199개 속에서 flag 찾기 — DreamHack please, please, please 풀이
blog

[🥉 Bronze 4] 가짜 함수 199개 속에서 flag 찾기 — DreamHack please, please, please 풀이

stripped PIE 바이너리 안에 거의 동일한 함수가 199개 박혀 있다. 전부 "please~"만 출력하는 죽은 코드고, 실제 main도 puts 한 줄만 부르고 끝난다. objdump로 199개 중 유일하게 다른 함수를 찾아내면 "Hhu,"라는 또 다른 미끼가 나오는데, 정작 flag는 strings 한 번이면 바로 보이는 DH{NOGADA}였다.
#dreamhack#ctf#reversing+5
 
BLOG2026-07-19
[🥉 Bronze 4] IDA 그래프 한 장으로 끝내는 악성코드 재접속 주기 — DreamHack [CodeEngn] Malware L07 풀이
blog

[🥉 Bronze 4] IDA 그래프 한 장으로 끝내는 악성코드 재접속 주기 — DreamHack [CodeEngn] Malware L07 풀이

실행 파일 대신 IDA 그래프 캡쳐 한 장(07.pdf)만 주어진다. 악성코드가 C2 서버 접속에 실패했을 때 몇 ms 간격으로 재시도하는지를 묻는데, 그래프 안의 재시도 루프를 따라가면 `push 96000h` → `call ds:Sleep`가 답이다. 16진수 0x96000을 10진수로 환산한 614400이 flag가 된다.
#dreamhack#ctf#reversing+5
 
BLOG2026-07-17
[🥉 Bronze 4] 심볼 없는 STM32F4 펌웨어에서 진짜 키 찾기 — DreamHack Please Discover my Discovery key 풀이
blog

[🥉 Bronze 4] 심볼 없는 STM32F4 펌웨어에서 진짜 키 찾기 — DreamHack Please Discover my Discovery key 풀이

769바이트짜리 stripped Cortex-M 펌웨어 하나가 전부다. strings 를 걸면 "진짜 키"를 묻는 미끼 문자열들이 우수수 나오는데, 정작 보드가 UART 로 뱉는 값은 판독 불가능한 바이트다. Thumb 코드를 디스어셈블하고 Unicorn 으로 돌려보면 그 바이트가 두 base64 문자열의 XOR 임이 드러나고, 되돌리면 숨은 메시지가 나온다.
#dreamhack#ctf#misc+6
 
BLOG2026-07-16
[🥉 Bronze 3] stripped Rust 바이너리에서 스택 key 를 뽑아 XOR 역산 — DreamHack Rusty Gyul 풀이
blog

[🥉 Bronze 3] stripped Rust 바이너리에서 스택 key 를 뽑아 XOR 역산 — DreamHack Rusty Gyul 풀이

심볼을 모두 걷어낸 Rust ELF 하나가 "플래그를 맞춰봐"라고 한다. ltrace 에는 strcmp 하나 안 잡히고 main 은 껍데기뿐이다. 진짜 검사 루틴을 어셈블리에서 찾아 들어가 보면, 입력을 trim 한 뒤 스택에 실어둔 32바이트 key 와 한 바이트씩 XOR 해서 전부 0x55 인지만 본다. key 를 그대로 뽑아 0x55 로 되XOR 하면 플래그가 나온다.
#dreamhack#ctf#reversing+5
 
BLOG2026-07-16
[🥉 Bronze 3] 안드로이드 APK 속 SEED-128 CBC 키값 복원 — DreamHack [CodeEngn] MobileApp L01 풀이
blog

[🥉 Bronze 3] 안드로이드 APK 속 SEED-128 CBC 키값 복원 — DreamHack [CodeEngn] MobileApp L01 풀이

SmartApp L01.apk 를 jadx 로 뜯어보니 버튼을 누르면 Security.DecryptStr() 가 하드코딩된 hex 문자열을 SEED-128 CBC 로 복호해 "Key" 로 띄운다. 암호문은 Java BigInteger 의 부호 있는 2의 보수로 인코딩돼 있고, key 와 IV 가 정적 배열에 그대로 박혀 있다. openssl 로 그대로 복호해 키값 H3ll0 C0de3ngn 를 얻는다.
#dreamhack#ctf#reversing+6
 
BLOG2026-07-20
[🥉 Bronze 2] 부분 문자열로 흩어 놓은 배치 난독화 되돌리기 — DreamHack Batch Checker 풀이
blog

[🥉 Bronze 2] 부분 문자열로 흩어 놓은 배치 난독화 되돌리기 — DreamHack Batch Checker 풀이

검사 로직을 윈도우 배치 파일로 난독화한 리버싱 문제. 100자짜리 무의미한 이름의 변수 37개를 정의한 뒤 %VAR:~offset,1% 부분 문자열 문법으로 한 글자씩 뽑아 7,463자짜리 명령 한 줄을 조립한다. 이 치환을 프로그램으로 풀면 원래의 비교식이 드러나고, 비교 대상이 곧 플래그다. "실행만 하면 된다"는 문제 설명과 달리, 실행하지 않고 정적으로 풀어야 안전하다.
#dreamhack#ctf#reversing+3
 
BLOG2026-07-14
[🥉 Bronze 2] Duff's device로 펼친 위치별 XOR 되돌리기 — DreamHack legacyopt 풀이
blog

[🥉 Bronze 2] Duff's device로 펼친 위치별 XOR 되돌리기 — DreamHack legacyopt 풀이

입력을 Duff's device로 펼친 루프에서 위치별 상수와 XOR해 hex로 출력하는 인코더 문제. 각 출력 바이트가 그 자리 입력에만 달려 있어, 같은 길이의 알려진 입력을 한 번 넣어 위치별 XOR 상수를 캐낸 뒤 주어진 출력에 그대로 적용하면 플래그가 복원된다. 재인코딩이 원본과 일치함까지 확인했다.
#dreamhack#ctf#reversing+4
 
BLOG2026-07-13
[🥉 브론즈 2] 입력할 수 없는 정답 — DreamHack My Favorite Fruit 풀이
blog

[🥉 브론즈 2] 입력할 수 없는 정답 — DreamHack My Favorite Fruit 풀이

과일 이름을 물어보는 챗봇 바이너리다. 다섯 과일을 다 맞히면 전역 버퍼를 flag 로 복원해 출력하는데, scanf 포맷이 %9s 라 열 글자짜리 strawberry 는 아홉 글자로 잘려 절대 매칭되지 않는다. 그래서 런타임으로는 flag 가 나오지 않는다. 하지만 버퍼를 만드는 연산이 XOR 라 순서와 무관하므로, 다섯 과일 키를 정적으로 한꺼번에 XOR 해 flag 를 복원했다.
#dreamhack#ctf#reversing+4
 
BLOG2026-07-13
[🥉 Bronze 3] 원형 리스트 인코더를 블랙박스로 뒤집다 — DreamHack tiny-ouroboroi 풀이
blog

[🥉 Bronze 3] 원형 리스트 인코더를 블랙박스로 뒤집다 — DreamHack tiny-ouroboroi 풀이

입력 각 글자를 그 글자값 길이의 원형 연결리스트로 만들어 회전·토글한 뒤 비트를 모아 한 바이트씩 출력하는 인코더 문제. 출력의 각 바이트가 오직 그 자리 글자와 위치에만 의존하므로, 함수를 다 역산할 필요 없이 후보 글자를 통째로 넣어 위치별 매핑을 만들면 플래그가 복원된다.
#dreamhack#ctf#reversing+3
 
BLOG2026-07-13
[🥉 브론즈 3] XOR 한 줄로 잠근 랜섬웨어, 키를 거꾸로 뽑아내다 — DreamHack darimchal_001 풀이
blog

[🥉 브론즈 3] XOR 한 줄로 잠근 랜섬웨어, 키를 거꾸로 뽑아내다 — DreamHack darimchal_001 풀이

입력한 복호화 키를 고정 KEY와 XOR해 JOKER 상수와 strncmp하는 랜섬웨어 콘셉트 문제. XOR은 자기역원이라 JOKER ⊕ KEY를 그대로 계산하면 통과 비밀번호 pa55uc0_가 나온다. 마지막에 플래그를 DH{} 없이·언더바를 빼고 pa55uc0로 내야 정답으로 인정되는 함정이 있었다.
#dreamhack#ctf#crypto+4
 
BLOG2026-07-13
[🥈 실버 3] 커스텀 base64를 알려진 평문으로 복원하다 — DreamHack baseball 풀이
blog

[🥈 실버 3] 커스텀 base64를 알려진 평문으로 복원하다 — DreamHack baseball 풀이

baseball 바이너리는 table 파일의 64글자를 알파벳으로 쓰는 커스텀 base64 인코더다. 정작 table은 주어지지 않지만, 평문 text_in과 그 인코딩 text_out이라는 알려진 쌍이 있다. 표준 base64의 위치별 6비트 값에 text_out 문자를 대응시켜 알파벳을 복원하고, 같은 알파벳으로 flag_out을 복호했다.
#dreamhack#ctf#reversing+4
 
BLOG2026-07-13
[🥉 브론즈 4] 암호화된 이미지를 되살리다 — DreamHack Recover 풀이
blog

[🥉 브론즈 4] 암호화된 이미지를 되살리다 — DreamHack Recover 풀이

chall 바이너리가 flag.png를 바이트마다 암호화해 encrypted 파일로 저장한다. 암호화는 4바이트 키 deadbeef로 XOR한 뒤 0x13을 더하는 단순 가역 연산이라, 순서를 반대로 되돌리면 원본이 복구된다. 복구본의 첫 8바이트가 PNG 시그니처와 일치하는 걸 확인하고, 되살린 이미지 속 플래그를 읽었다.
#dreamhack#ctf#reversing+4
 
BLOG2026-07-13
[🥉 브론즈 3] 출력되지 않는 flag를 함수 인자에서 훔치다 — DreamHack Check Function Argument 풀이
blog

[🥉 브론즈 3] 출력되지 않는 flag를 함수 인자에서 훔치다 — DreamHack Check Function Argument 풀이

main이 어떤 함수를 flag 문자열 주소를 인자로 넘겨 호출하지만, 그 함수는 flag를 출력하지 않는다. 게다가 flag는 바이너리에 평문으로 없고 생성자가 런타임에 조립한다. x86-64 호출 규약상 첫 인자는 rdi이므로, 그 함수 호출 지점에 GDB 브레이크를 걸고 rdi를 읽어 flag를 그대로 가로챘다.
#dreamhack#ctf#reversing+4
 
BLOG2026-07-13
[🥉 브론즈 3] 세 단계 변환을 거꾸로 되돌리다 — DreamHack r-xor-t 풀이
blog

[🥉 브론즈 3] 세 단계 변환을 거꾸로 되돌리다 — DreamHack r-xor-t 풀이

입력 64자를 add·배열 뒤집기·xor 세 단계로 변환해 고정된 target과 strncmp하는 리버싱 문제. 세 연산이 모두 가역이라, target에서 xor 3을 풀고 배열을 다시 뒤집고 add 13을 되돌리면 올바른 입력이 그대로 복원된다. 그 입력이 곧 플래그 본문이고, 바이너리에 넣어 Nice!로 확인했다.
#dreamhack#ctf#reversing+4
 
BLOG2026-07-13
[🥉 Bronze 1] 죽은 코드 속 Caesar 복호기를 되살리다 — DreamHack Small Counter 풀이
blog

[🥉 Bronze 1] 죽은 코드 속 Caesar 복호기를 되살리다 — DreamHack Small Counter 풀이

바이너리는 실행해도 카운터만 세고 끝난다. flag를 만드는 flag_gen은 counter==5일 때만 불리는데 루프가 0에서 끝나 절대 도달하지 않는 죽은 코드다. flag_gen이 알파벳 테이블로 Caesar 복호를 한다는 걸 읽어내고, 암호문 IM 접두가 DH 로 풀리는 shift=5를 확정한 뒤 그 로직을 재현해 flag를 복원했다. GDB로 실제 flag_gen을 직접 호출해 결과가 정확히 일치함을 확인했다.
#dreamhack#ctf#reversing+4
 
BLOG2026-07-07
[🥈 Silver 4] 암호문 배열을 걷어내니 로직은 열 줄도 안 됐다 — DreamHack Secure Mail 풀이
blog

[🥈 Silver 4] 암호문 배열을 걷어내니 로직은 열 줄도 안 됐다 — DreamHack Secure Mail 풀이

1.1MB짜리 난독화된 JS라 겁먹었지만, 진짜 로직은 223KB짜리 암호문 배열을 걷어내면 열 줄이 안 됐다. 생년월일 후보의 MD5를 키/IV로 쓰는 AES-128-CBC로 첨부 메일을 복호화하는 구조라, 원본 코드를 그대로 브루트포스하면 한 번에 2.3초씩 걸려 하루가 넘게 걸린다. 알고리즘만 뽑아 Node 네이티브 AES로 다시 짜서 3만 5천 개 생년월일을 45초에 끝냈다.
#dreamhack#ctf#reversing+5
 
BLOG2026-07-05
[🥉 Bronze 4] 왼쪽으로 돌린 비트를 오른쪽으로 되돌리기 — DreamHack rev-basic-7 풀이
blog

[🥉 Bronze 4] 왼쪽으로 돌린 비트를 오른쪽으로 되돌리기 — DreamHack rev-basic-7 풀이

입력 바이트를 (i mod 8)만큼 왼쪽으로 회전시키고 i와 XOR한 값을 31바이트 고정 테이블과 비교하는 x86-64 PE. 회전은 반대 방향으로 같은 양만큼 돌리면 정확히 원상복구되는 연산이라, 검증식을 그대로 뒤에서부터 읽어 ROR로 되돌렸다. 디스어셈블로 검증 로직을 읽고, 표를 바이너리에서 직접 추출해 역산하고, wine으로 Correct를 확인한 과정.
#dreamhack#ctf#reversing+7
 
BLOG2026-07-03
[🥉 Bronze 4] 곱셈 치환을 모듈러 역원으로 되돌리기 — DreamHack rev-basic-8 풀이
blog

[🥉 Bronze 4] 곱셈 치환을 모듈러 역원으로 되돌리기 — DreamHack rev-basic-8 풀이

입력 각 바이트에 0xfb를 곱하고 하위 1바이트만 남긴 값을 21바이트 고정 테이블과 비교하는 x86-64 PE. 0xfb가 mod 256에서 홀수라 곱셈의 역원이 존재한다는 점을 이용해 검증 테이블을 그대로 역산했다. 마지막 21번째 비교가 사실은 문자열 종결 NUL 검증이라는 것도 함께 정리.
#dreamhack#ctf#reversing+4
 
BLOG2026-07-01
[🥉 Bronze 4] 정답을 흘리는 NULL 종단 — DreamHack rev-basic-5 풀이
blog

[🥉 Bronze 4] 정답을 흘리는 NULL 종단 — DreamHack rev-basic-5 풀이

입력의 인접한 두 바이트를 더해 .data 테이블과 비교하는 x86-64 PE. 식만 보면 미지수가 하나 남는 부정방정식이지만, 입력 버퍼가 0으로 초기화돼 문자열이 NULL로 끝난다는 점과 테이블 마지막 값이 0이라는 점이 오른쪽 끝을 고정한다. 거기서 왼쪽으로 도미노처럼 역산하면 입력이 그대로 나온다.
#dreamhack#ctf#reversing+4
 
BLOG2026-06-25
[🥈 Silver 4] 검은 가림막을 ret 한 줄로 걷어내다 — DreamHack patch 풀이
blog

[🥈 Silver 4] 검은 가림막을 ret 한 줄로 걷어내다 — DreamHack patch 풀이

GDI+ 라인 드로잉으로 flag를 그리는 Windows GUI 프로그램. flag 글자를 검정으로 그리기 전에, 같은 검정으로 두꺼운 선을 겹쳐 그려 가운데를 덮는 "가림막"을 먼저 깐다. 검정 글자가 검정 가림막 위에 얹히니 안 보일 뿐이다. 가림막을 그리는 함수의 첫 바이트를 ret(0xC3)로 패치해 가림막을 통째로 없애면 flag가 흰 배경 위에 그대로 드러난다.
#dreamhack#ctf#reversing+4
 
BLOG2026-06-22
[🌱 새싹] 호박을 만 번 깎으면 떨어지는 플래그 — DreamHack Carve Party 풀이
blog

[🌱 새싹] 호박을 만 번 깎으면 떨어지는 플래그 — DreamHack Carve Party 풀이

서버도 백엔드도 없는 HTML 한 장짜리 문제. 호박을 10000번 클릭하면 canvas에 플래그가 그려지는데, 그 플래그 바이트는 클릭 100번마다 한 번씩 XOR 변환을 거친다. 키로 쓰이는 pie 변수가 함수 밖 전역이라 배치 사이에 값이 이어진다는 점만 잡으면, 손으로 만 번 누르는 대신 100배치를 그대로 시뮬레이션해서 플래그를 꺼낼 수 있다.
#dreamhack#ctf#web+4
 
BLOG2026-05-29
[💎 Diamond 4] 난독화된 안드로이드 DRM을 통째로 빌려 쓰기 — DreamHack Dream DRM 풀이
blog

[💎 Diamond 4] 난독화된 안드로이드 DRM을 통째로 빌려 쓰기 — DreamHack Dream DRM 풀이

request_book의 id 파라미터 SQL Injection으로 서버의 임의 파일(/flag)을 암호문 상태로 읽어내고, 패킹된 native DRM 라이브러리를 NDK 하니스 + dlopen + 가짜 JNIEnv로 직접 호출해 복호화 오라클로 삼는 과정을 정리한다. 오프라인 크래킹·APK 재서명·MITM 등 막다른 길까지 전부 기록한 Theori 채용 CTF 다이아 4 풀이.
#dreamhack#ctf#web+7