ZINO
HomeResearchBlogTagsPlaygroundStackPortfolio
⌘K

레드팀 · AI 자동화 · 시스템 엔지니어링

직접 만들고, 실험하고, 기록한다

개인정보처리방침·이용약관

© 2026 ZINO LAB

← 태그 목록

Tag Archive

#gdb

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

22
posts
BLOG2026-08-01
[🥇 Gold 2] 화면에 한 자리씩 뜨는 실행파일 — DreamHack captain-hook 풀이
blog

[🥇 Gold 2] 화면에 한 자리씩 뜨는 실행파일 — DreamHack captain-hook 풀이

클릭할 때마다 16진수 한 자리를 그리는 창. 그 18,432자리를 다 모으면 UPX로 패킹된 또 하나의 실행파일이 나온다. 풀어서 실행하면 flag가 뜨는데 굵기 10.0 펜으로 그은 가림막이 글자를 덮는다. 상수 하나를 0.3으로 바꾸면 덮개가 걷힌다.
#dreamhack#ctf#reversing+5
 
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-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
[🥈 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-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] srand() 없는 rand()는 이미 예언된 미래다 — DreamHack randzzz 풀이
blog

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

"난수를 맞혀보라"는 리버싱 문제. 그런데 바이너리를 뜯어보면 어디에도 srand()가 없다 — glibc의 rand()는 시드를 명시적으로 주지 않으면 항상 똑같은 순서로 값을 뱉는 결정론적 함수이므로, 이 프로그램이 뭘 물어볼지는 실행하기도 전에 전부 계산할 수 있다. 문제는 그 다음이다. 두 번의 검증이 서로 다른 정답을 요구하는데 입력은 한 번뿐이라, 한 번의 정상 실행으로는 절대 flag 전체를 볼 수 없다. GDB로 실행 중인 프로세스의 스택 메모리를 직접 고쳐써서 두 검증을 동시에 통과시킨 끝에야 완전한 flag가 나왔다.
#dreamhack#ctf#reversing+5
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-19
[🥈 Bronze 2] 미끼인 줄 알았던 디코드가 사실 정답이었다 — DreamHack 아 문제 이름 뭘로하지 풀이
blog

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

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

[🥉 Bronze 4] strncmp(18) 부분비교 + 시간기반 토큰 — DreamHack angry_darim 풀이

time()으로 매초 바뀌는 "동적 토큰"을 strncmp로 딱 18바이트만 비교하는 바이너리. 로컬에서는 실행 순간의 epoch를 그대로 넣어 우회하지만, 정작 DreamHack에 내는 flag는 그 시간 요소를 통째로 들어내야 하는 정적 문자열이었다.
#dreamhack#ctf#pwnable+5
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 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-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-15
[🥈 Silver 4] add·rotate·xor 블록암호를 정적 역산 — DreamHack My ARX Cipher 풀이
blog

[🥈 Silver 4] add·rotate·xor 블록암호를 정적 역산 — DreamHack My ARX Cipher 풀이

stripped ELF 하나가 12바이트 key로 flag.enc를 만들어 놓았다. 블록 함수를 objdump로 뜯어 4바이트(16bit 워드 v0·v1)에 rotate-add-xor-swap을 3라운드 도는 ARX 구조임을 밝히고, 각 연산을 역으로 되감아 flag를 복원한다. gdb 런타임과 재암호화 라운드트립으로 이중 검증.
#dreamhack#ctf#reversing+5
BLOG2026-07-13
[🥉 Bronze 4] 만들어 놓고 버린 반환값에서 플래그를 낚아채다 — DreamHack Check Return Value 풀이
blog

[🥉 Bronze 4] 만들어 놓고 버린 반환값에서 플래그를 낚아채다 — DreamHack Check Return Value 풀이

main이 플래그를 버퍼에 조립해 반환하지만, 그 반환값을 쓰지 않고 곧바로 다른 문자열로 덮어써 출력하지 않는 리버싱 문제. 반환값을 덮어쓰기 직전 지점에 gdb 브레이크포인트를 걸고 $rax를 문자열로 읽으면 플래그가 그대로 나온다.
#dreamhack#ctf#reversing+3
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-06
[🌱 새싹] cmp 명령어 하나가 곧 플래그 — DreamHack Simple Crack Me 풀이
blog

[🌱 새싹] cmp 명령어 하나가 곧 플래그 — DreamHack Simple Crack Me 풀이

정적 링크·strip된 ELF 한 개. scanf로 정수 하나를 받아 상수와 비교하는 게 로직의 전부다. objdump로 main을 까면 cmp eax,0x13371337 한 줄이 곧바로 나온다 — 그 16진수를 10진수로 바꿔 DH{}로 감싸면 플래그. gdb로 그 비교가 실행 시점에 실제로 어떻게 일어나는지까지 확인했다.
#dreamhack#ctf#reversing+4