Tag Archive

#pwn

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

25
posts
BLOG2026-08-05
[🥇 Gold 3] 스택에 가짜 청크를 세워 malloc을 속인다 — DreamHack house_of_spirit 풀이
blog

[🥇 Gold 3] 스택에 가짜 청크를 세워 malloc을 속인다 — DreamHack house_of_spirit 풀이

임의 주소 free 하나로 스택 조각을 tcache 에 밀어 넣고, 같은 크기 malloc 으로 그 스택 주소를 되돌려받아 main 의 리턴 주소를 덮는다. glibc 2.27 의 tcache free 경로가 청크 size 자체만 보고 다음 청크는 보지 않는다는 점이 전부다. gdb 로 tcache 항목이 스택을 가리키는 순간까지 확인했다.
#dreamhack#ctf#pwn+5
 
BLOG2026-07-31
[🥇 Gold 4] FILE 구조체를 위조해 프로그램이 제 손으로 flag를 뱉게 만들기 — DreamHack _IO_FILE Arbitrary Address Read 풀이
blog

[🥇 Gold 4] FILE 구조체를 위조해 프로그램이 제 손으로 flag를 뱉게 만들기 — DreamHack _IO_FILE Arbitrary Address Read 풀이

fopen이 돌려준 FILE 포인터가 그대로 read()의 버퍼로 넘어간다. 구조체를 통째로 위조해 fwrite가 내려가는 write(2)의 인자 셋 — fd·소스 주소·길이 — 을 전부 우리 값으로 바꾼다. _fileno=1, _IO_write_base=flag_buf로 두면 프로그램이 자기 flag를 소켓에 써 준다.
#dreamhack#ctf#pwn+5
 
BLOG2026-07-28
[🥉 Bronze 2] GOT가 잠겨 있으면 ld.so를 본다 — DreamHack Overwrite _rtld_global 풀이
blog

[🥉 Bronze 2] GOT가 잠겨 있으면 ld.so를 본다 — DreamHack Overwrite _rtld_global 풀이

Full RELRO에 PIE까지 걸린 바이너리가 임의 주소 쓰기를 무제한으로 내준다. GOT는 읽기 전용이라 손댈 수 없지만, ld.so의 전역 구조체 _rtld_global은 RELRO 밖에 있고 프로그램이 종료될 때 _dl_fini가 그 안의 함수 포인터를 부른다. 인자와 호출 대상이 같은 구조체 안에 있어서 쓰기 두 번이면 셸이 뜬다.
#dreamhack#ctf#pwn+6
 
BLOG2026-07-20
[🥉 Bronze 4] 80바이트 버퍼에 128바이트를 채워 tmp_fd를 훔친다 — DreamHack awesome-basics 풀이
blog

[🥉 Bronze 4] 80바이트 버퍼에 128바이트를 채워 tmp_fd를 훔친다 — DreamHack awesome-basics 풀이

chall.c 는 flag 를 읽어 지역변수 tmp_fd 가 가리키는 파일에 쓰기만 할 뿐, 그 결과를 우리에게 보여주는 코드가 없다. 그런데 바로 위에서 read(stdin, buf, 0x80) 이 buf[80] 보다 48바이트 더 받는다. 이 여유분으로 buf 바로 뒤에 붙어 있는 tmp_fd 를 표준출력(1)으로 덮으면, 뒤이은 write(tmp_fd, flag, 0x45) 가 그대로 우리 화면에 flag 를 찍어준다.
#dreamhack#ctf#pwn+4
 
BLOG2026-07-07
[🥉 Bronze 3] 반환 주소 대신 "열 파일 이름"을 덮는다 — DreamHack bof 풀이
blog

[🥉 Bronze 3] 반환 주소 대신 "열 파일 이름"을 덮는다 — DreamHack bof 풀이

scanf("%144s") 로 스택 버퍼를 넘치는 전형적인 오버플로 문제. 그런데 덮을 것은 반환 주소가 아니다. 버퍼 바로 아래에 read_cat() 이 open() 할 파일명 "./cat" 이 놓여 있어서, 그 자리를 "flag" 로 덮으면 프로그램이 알아서 flag 파일을 열어 출력한다. 버퍼와 파일명 사이 거리 0x80 만 맞추면 끝.
#dreamhack#ctf#pwn+5
 
BLOG2026-07-06
[🌱 새싹] strncmp 는 앞 8바이트만 본다 — DreamHack cmd_center 풀이
blog

[🌱 새싹] strncmp 는 앞 8바이트만 본다 — DreamHack cmd_center 풀이

"ifconfig" 로 초기화된 명령 버퍼를 그대로 system() 에 넘기는 바이너리. 바로 옆 24바이트 지역 변수에 read 로 100바이트를 받아주는 탓에, 그 버퍼를 넘쳐서 명령 문자열 뒷부분에 세미콜론으로 원하는 명령을 이어붙일 수 있다. strncmp 가 앞 8글자("ifconfig")만 검사한다는 게 이 문제의 전부다.
#dreamhack#ctf#pwn+5
 
BLOG2026-06-30
[🥉 Bronze 1] 카나리 흘리고 system 으로 되돌아가기 — DreamHack Return to Library 풀이
blog

[🥉 Bronze 1] 카나리 흘리고 system 으로 되돌아가기 — DreamHack Return to Library 풀이

NX 가 켜져 스택 셸코드를 못 쓰는 BOF 문제. 첫 read 뒤 printf("%s") 가 카나리 LSB(\x00) 만 덮으면 상위 7바이트를 흘려준다. 그걸로 카나리를 복원하고, 두 번째 read 오버플로우로 pop rdi → "/bin/sh" → system@plt 로 이어지는 ret2plt 체인을 올려 셸을 띄운다. 바이너리가 처음에 system 을 한 번 호출해 줘서 PLT 가 이미 채워져 있는 게 포인트.
#dreamhack#ctf#pwn+6
 
BLOG2026-06-27
[🥉 Bronze 2] OOB read 로 카나리 흘려보고 BOF 로 셸 — DreamHack ssp_001 풀이
blog

[🥉 Bronze 2] OOB read 로 카나리 흘려보고 BOF 로 셸 — DreamHack ssp_001 풀이

스택 카나리가 켜진 32비트 BOF 문제. (P)rint 메뉴의 box[idx] 가 인덱스 검사를 안 해 OOB read 가 되고, box 기준 +0x80 위치에 있는 카나리 4바이트를 그대로 읽어올 수 있다. 카나리를 복원한 뒤 (E)xit 메뉴의 read(name, name_len) 길이 무제한 오버플로우로 카나리를 그대로 채우고 반환 주소를 get_shell 로 덮어 셸을 잡는다.
#dreamhack#ctf#pwn+6
 
BLOG2026-06-26
[🥉 Bronze 3] 카나리 흘려보고 스택에 셸코드 심기 — DreamHack Return to Shellcode 풀이
blog

[🥉 Bronze 3] 카나리 흘려보고 스택에 셸코드 심기 — DreamHack Return to Shellcode 풀이

NX 가 꺼져(스택 RWX) 있지만 스택 카나리가 켜진 BOF 문제. read 뒤의 printf("%s") 가 카나리의 LSB(\x00) 를 덮으면 상위 7바이트를 흘려주고, 그걸로 카나리를 복원한다. 그다음 gets() 무제한 오버플로우로 스택에 셸코드를 올리고 카나리를 그대로 채운 뒤 반환 주소를 buf 로 돌려 ret2shellcode 로 셸을 띄운다.
#dreamhack#ctf#pwn+6
 
BLOG2026-06-25
[🥉 Bronze 4] 반환 주소를 get_shell 로 덮는다 — DreamHack Return Address Overwrite 풀이
blog

[🥉 Bronze 4] 반환 주소를 get_shell 로 덮는다 — DreamHack Return Address Overwrite 풀이

scanf("%s") 로 스택 버퍼를 넘쳐 반환 주소를 덮는, pwnable 입문의 정석. NX 가 켜져 있어 스택 셸코드는 못 올리지만 바이너리 안에 execve("/bin/sh") 를 부르는 get_shell 함수가 이미 있다. 반환 주소를 그 함수 주소로 덮어 흐름을 가져오는 코드 재사용(ret2win) 기법으로 셸을 띄운다.
#dreamhack#ctf#pwn+5
 
BLOG2026-06-25
[🥉 Bronze 2] scanf 가 셸코드를 잘라먹는다 — DreamHack basic_exploitation_000 풀이
blog

[🥉 Bronze 2] scanf 가 셸코드를 잘라먹는다 — DreamHack basic_exploitation_000 풀이

buf 주소를 친절히 누출해 주고 scanf 로 스택을 넘치게 하는 전형적인 스택 셸코드 문제. 그런데 교과서 execve 셸코드를 그대로 쓰면 동작하지 않는다. scanf("%s") 가 셸코드 안의 0x0b(SYS_execve) 를 공백으로 보고 거기서 잘라먹기 때문. 0x0b 바이트 없이 11을 만들어 우회하고, 누출된 buf 주소로 점프해 셸을 띄운다.
#dreamhack#ctf#pwn+5
 
BLOG2026-06-23
[🥉 Bronze 4] 접속만 하면 바이너리가 플래그를 읽어 준다 — DreamHack welcome 풀이
blog

[🥉 Bronze 4] 접속만 하면 바이너리가 플래그를 읽어 준다 — DreamHack welcome 풀이

드림핵 포너블 탭의 첫 연습 문제. 익스플로잇은 없고, 서버에서 도는 welcome 바이너리가 스스로 /flag 를 열어 출력해 준다. 우리가 할 일은 nc 로 그 서비스에 붙어 출력을 받는 것뿐이다. 서버형 포너블이 어떻게 굴러가는지, 왜 바이너리를 내 컴퓨터에서 돌리면 segfault 가 나는지, 같은 일을 pwntools 로 스크립트화하면 어떻게 되는지까지 입문자 눈높이로 짚는다.
#dreamhack#ctf#pwn+4
 
BLOG2026-06-08
[💎 Diamond 1] Math.exp가 NaN을 잊을 때 (5) — read에서 flag까지: 불가능이라던 부트스트랩을 뒤집고 라이브 서버를 뚫다 — DreamHack EXP-NaN
blog

[💎 Diamond 1] Math.exp가 NaN을 잊을 때 (5) — read에서 flag까지: 불가능이라던 부트스트랩을 뒤집고 라이브 서버를 뚫다 — DreamHack EXP-NaN

4편은 ".at() 무가드 OOB read는 확정, 그런데 write는 여전히 막혀 있고 read→write 다리는 addrof/fakeobj뿐"으로 끝났다. 5편은 그 다리를 건너 라이브 flag까지 가는 기록이다 — 그리고 솔직히 3일이 걸렸다. addrof/fakeobj 부트스트랩이 "nursery monoculture로 불가능"이라던 결론을 뒤집고(진짜 원인은 globalThis 저장이 fold를 deopt시킨 것), fake FixedDoubleArray로 첫 forward 케이지 R/W를 열고, 그게 GC 신뢰성에서 이틀을 막았다가 "오프셋 가정을 런타임 leak으로 바꾸는" 한 줄로 5/5가 됐다. 거기서 fake Float64Array의 base_pointer를 갈아끼워 data_ptr을 절대주소로 지정하는 양방향 R/W로 backward 인스턴스까지 읽고, V12 Revenge에서 쓴 imported_function_targets 덮기로 절대주소 누출 없이 execve("/bin/sh")까지. 익스는 서버 한도(2000B)에 맞춰 1999바이트로 깎았고, 로컬 10/10이 서버에선 0/12로 죽길래 진단을 보냈더니 mapArr 하나만 환경마다 달랐다(0x18d7b9 vs 0x10d7b9) — 그걸 런타임 leak으로 적응시켜 host3.dreamhack.games에서 flag를 회수했다.
#dreamhack#ctf#pwn+8
 
BLOG2026-06-07
[💠 Platinum 1] V8을 V12로 — array-shift 길이 언더플로로 OOB부터 V8 샌드박스 탈출·RCE까지 — DreamHack V12 Revenge
blog

[💠 Platinum 1] V8을 V12로 — array-shift 길이 언더플로로 OOB부터 V8 샌드박스 탈출·RCE까지 — DreamHack V12 Revenge

"V8이 너무 느리다, V12로 갈아타자 (* 내부 함수 없이 *)" — array-shift Torque 빌트인에서 빈 배열 가드 한 줄을 주석 처리한 게 전부인 버그다. 길이 0 배열에 .shift()를 부르면 newLength = 0-1 = -1, 그게 그대로 length 필드로 들어가 length(0xFFFFFFFF) ≫ capacity 인 OOB 배열이 만들어진다. 이 글은 그 OOB에서 케이지 임의 R/W·addrof를 세우고 — 한 번은 "V8 샌드박스가 절대주소 누출을 막아 코드 실행이 불가능하다"고 잘못 결론 내렸다가 — 출제자가 의도한 정석 기법을 찾아 뒤집은 기록이다. 핵심은 WasmInstanceObject의 imported_function_targets: 임포트 함수의 호출 타깃을 담은 "케이지 안 ByteArray"가 실은 raw(샌드박스 밖) 포인터라, 케이지 쓰기만으로 import 호출을 임의 주소로 보낼 수 있다. 거기에 jump_table_start로 RWX를 직접 읽고, 셸코드 실행 순간 살아있는 r14(케이지 베이스)로 wasm 메모리를 가리켜 execve("flag_reader") — 절대주소 누출 없이 샌드박스를 빠져나와 라이브 flag까지 회수했다.
#dreamhack#ctf#pwn+6
 
BLOG2026-06-07
[💎 Diamond 1] Math.exp가 NaN을 잊을 때 (4) — 근데 .at()이 열려 있었다: 놓친 비-CheckBounds sink — DreamHack EXP-NaN
blog

[💎 Diamond 1] Math.exp가 NaN을 잊을 때 (4) — 근데 .at()이 열려 있었다: 놓친 비-CheckBounds sink — DreamHack EXP-NaN

3편은 "남은 출구를 전수로 두드렸지만 다 닫혔다"로 끝났다. 4편은 그 결론을 내 손으로 뒤집는 기록이다 — 3편이 본 sink는 전부 bracket 접근(`a[i]`)과 CheckBounds 계열이었는데, 딱 하나 안 본 게 있었다. `Array.prototype.at()`. 이건 별도 reducer(js-call-reducer.cc)라 경계검사를 CheckBounds가 아니라 fold 가능한 NumberLessThan 두 개 + bare LoadElement 로 한다. 패치가 그 파일을 안 건드려서 그대로 살아있고, reshape로 typer를 속이면 두 가드가 접혀 무가드 OOB read가 진짜로 열린다(자연 티어업, exit0, 결정적). 다만 store 짝이 없어 write는 여전히 막혀 있고(bracket=trap, TypedArray/fill/with 전멸, a.length는 치유), read→write 다리는 addrof/fakeobj뿐이다. 포인터 압축이 왜 reader 타입을 가르는지, 그리고 그 fakeobj 부트스트랩(타입-매칭 인접)이 왜 nursery monoculture에 막히는지 — V8 reducer 구조부터 쉘 스샷까지 한 단계씩.
#dreamhack#ctf#pwn+7
 
BLOG2026-06-07
[💎 Diamond 1] Math.exp가 NaN을 잊을 때 (3) — 남은 출구를 전부 두드리다: 의도된 sink마저 막혀 있었다 — DreamHack EXP-NaN
blog

[💎 Diamond 1] Math.exp가 NaN을 잊을 때 (3) — 남은 출구를 전부 두드리다: 의도된 sink마저 막혀 있었다 — DreamHack EXP-NaN

2편은 "표준 값-range 경로(CheckBounds 제거)는 11.2에서 닫혔다"까지였다. 3편은 그 뒤 — CheckBounds를 거치지 않는 *비표준 출구*를 하나하나 전부 두드린 기록이다. fill/copyWithin, foldable 빌트인, TypedArray·DataView, 요소종류 전이, 표현형 변환까지 후보 sink를 전수로 d8에 때려봤고 전부 정화/트랩/in-range로 죽었다. 그리고 가장 뜻밖의 발견 — 이 버그류의 *의도된* 해법인 new Array(len) length-construction마저 이 빌드에선 막혀 있다. 패치가 typer.cc만 건드리고 js-create-lowering의 길이 보호는 안 풀어서다. 어긋남은 살아있는데(자연 티어업에서 INT_MIN으로 발산) 받아줄 sink가 없다. 끝으로 남은 한 수 — 배포 바이너리 직접 부검.
#dreamhack#ctf#pwn+7
 
BLOG2026-06-07
[💎 Diamond 1] Math.exp가 NaN을 잊을 때 (2) — 이 버그를 죽인 V8 11.2 하드닝, 소스 부검 — DreamHack EXP-NaN
blog

[💎 Diamond 1] Math.exp가 NaN을 잊을 때 (2) — 이 버그를 죽인 V8 11.2 하드닝, 소스 부검 — DreamHack EXP-NaN

1편에서 타입혼동을 인덱스까지 살려 보냈지만 거기서 트랩으로 끊겼다. 2편은 "왜 트랩인가"를 V8 11.2 소스로 부검한다. CheckBounds가 기계어가 되는 경로, typer가 인덱스를 in-bounds로 증명하면 검사를 제거가 아니라 abort로 바꾸는 무조건 하드닝(kAbortOnOutOfBounds), 그리고 doar-e식 우회를 정조준해 막아둔 element-access 빌더의 두 번째 CheckBounds까지 — 소스 주석째로 들고 본다. 끝으로, 같은 시기 하드닝 켜진 V8을 실제로 뚫은 익스(구글 v8CTF·openECSC)가 왜 값-range typer 버그가 아니라 length/element-kind를 직접 손상시키는 다른 버그 클래스를 쓰는지 대조한다.
#dreamhack#ctf#pwn+7
 
BLOG2026-06-06
[💎 Diamond 1] Math.exp가 NaN을 잊을 때 (1) — 트리거는 됐는데 OOB가 안 자란다 — DreamHack EXP-NaN
blog

[💎 Diamond 1] Math.exp가 NaN을 잊을 때 (1) — 트리거는 됐는데 OOB가 안 자란다 — DreamHack EXP-NaN

d8 패치 한 줄이 TurboFan에게 "Math.exp는 절대 NaN을 만들지 않는다"고 거짓말을 시킨다. 하지만 Math.exp(NaN)은 NaN이다. 타입혼동 트리거까지는 깔끔하게 된다. 문제는 그 어긋남을 OOB로 키우는 단계 — NaN은 인덱스로 가는 모든 길에서 정화되고, Object.is 불리언은 폴딩되거나 Boolean으로 남고, 자연 티어업에선 어긋남이 인덱스까지 살아 도달하는데 거기서 11.2가 무조건 박아둔 kAbortOnOutOfBounds 하드닝이 트랩으로 끊어버린다. 이 글(1편)은 버그·트리거와, 공개된 typer-OOB 기법이 V8 11.2 하드닝에 어떻게 막히는지를 실측으로 끝까지 추적한다.
#dreamhack#ctf#pwn+6