본문 바로가기

해킹/System

[Windows_System] Shellcoding 쉘코딩법 #1

반응형

============================================================================

사진날아가서 재작성 합니다~  실행 환경이 달라져서 일부 수정되었습니다!

윈도우7+VS2018 => 윈도우10+VS2017

===========================================================================

시스템 분야의 첫 글은 윈도우 쉘코딩으로 하고자 합니다.
쉘코드란? 시스템 함수를 구동하기 위한 코드로서 아키텍처 수준에서 작성된 코드입니다.
즉 공격 시스템의 아키텍쳐에서 시스템 함수를 불러오게 해주는 기계어 코드라고 저는 이해 하고 있습니다.
시스템 해킹에서 취약점이 터졌다면 그 취약점을 이용해 이 쉘코드를 해당 시스템에 올리므로서 시스템 함수를 이용해 원하는 프로그램,함수를 실행할 수 있기에 해당 시스템을 해커가 장악할 수 있는 것입니다.
이 쉘코드를 작성하기 위한 과정은
1.쉘코드에서 사용할 시스템 함수 찾기;
2.고수준언어(ex.C)로 시스템 함수를 불러오는 프로그램 작성;
3.해당 아키텍쳐에서 사용되는 어셈블리 추출;
4.기계어 추출
로 이루어져 있습니다.
일단 윈도우에서 사용가능한 시스템함수(프로세스 실행함수)들은 많습니다. winexec(), createprocess(), shellexecute()등등등.
각 함수별 특징,장점,단점들이 여러가지 있어 상황별로 사용하시면 됩니다. 저는 저는 사용이 간단한 winexec()함수를 사용할 것입니다.
이제 c언어에서 해당 함수를 실행해주는 프로그램을 작성해줍시다.

#include <stdio.h>
#include <windows.h>

int main() {
	char buf[5] = { 'c','a','l','c','\x0' };
	WinExec(buf,1);
	exit(1);
}
//r3dzone
//shellcode.c

shellcode.c


5번 라인을 보면 winexec함수에 인자로 들어갈 문자열을 배열에 저장해줍니다. 이는 나중에 프로그램을 어셈블리로 추출할 때 배열이기때문에 연속된 메모리주소를 가지기 때문에 사용 및 가독성에 좋습니다. 또한 마지막에 '\x0'으로 널바이트를 너어주므로서 문자열 인식을 끊기도록 해줍니다.
6번 라인에서 해당배열을 인자로 winexec함수를 동작시킵니다. 두번째 인자는 창표시를 킨다는 의미입니다.
7번은 해당 프로세스를 종료해줍니다. 
작성해준 프로그램이 잘 되는지 컴파일 하고 동작시켜봅니다.

실행시 정상적으로 계산기를 실행시킨다!

이제 프로그램이 만들어졌으므로 어셈블리로 추출해줍니다. 추출은 비주얼 스튜디오를 통해서 간단하게 할 수 있습니다.

BP!

적당한 곳에 브레이크 포인트를 걸어주고 디버그해줍니다.

어셈블리!

BP에 걸렸으면 ALT + G로 디스어셈블리로 이동할 수 있습니다.

비주얼스튜디오 옵션

위와 같이 설정해주시면 디스어셈블러가 친절하게 소스를 어셈블리로 뱉어내줍니다

이제 적당한 메모장이나 텍스트 에디터에 긁어와 줍시다!

#include <stdio.h>
#include <windows.h>

int main() {
 push        ebp  
 mov         ebp,esp  
 sub         esp,48h  
 push        ebx  
 push        esi  
 push        edi  
	char buf[5] = { 'c','a','l','c','\x0' };
 mov         byte ptr [buf],63h  
 mov         byte ptr [ebp-7],61h  
 mov         byte ptr [ebp-6],6Ch  
 mov         byte ptr [ebp-5],63h  
 mov         byte ptr [ebp-4],0  
	WinExec(buf,1);
 push        1  
 lea         eax,[buf]  
 push        eax  
 call        dword ptr [__imp__WinExec@8 (0C79000h)]  
	exit(1);
 push        1  
 call        dword ptr [__imp__exit (0C7914Ch)]  
}
 pop         edi  
 pop         esi  
 pop         ebx  
 mov         esp,ebp  
 pop         ebp  
 ret  

크 이쁘게 함수 프롤로그 부터 에필로그 까지 깔끔하게 잘 뽑혔습니다.

특히 배열 부분이 EBP를 기준으로 예쁘게 정렬되있어서 쉘코드 작성시 용이합니다.

배열에 따로 넣었을때

이전에 테스트겸 짤때 배열 선언이랑 같이 문자열 삽입을 안하고 저런식으로 코딩했을때는 보안 기법이 걸린건지

아님 그냥 프로세서 처리가 그런건진 의문입니다만 어셈을 깠을때 eax,ecx를 이용해서 간단한 연산후에 문자열을 버퍼에 넣어줬습니다.

이렇게 되면 쉘코딩시에 수정도 불편하고 쉘코드 크기도 커져서 좋지 않습니다. 

어셈블리!

위와 같이 깔끔하고 간결하게 어셈블리를 뽑아내는걸 목표로 코딩합시다!

#include <stdio.h>
#include <windows.h>

int main() {
 push        ebp  
 mov         ebp,esp  
 sub         esp,48h  
 push        ebx  
 push        esi  
 push        edi  
	char buf[5] = { 'c','a','l','c','\x0' };
 mov         byte ptr [buf],63h  
 mov         byte ptr [ebp-7],61h  
 mov         byte ptr [ebp-6],6Ch  
 mov         byte ptr [ebp-5],63h  
 mov         byte ptr [ebp-4],0  
	WinExec(buf,1);
 push        1  
 lea         eax,[buf]  
 push        eax  
 call        dword ptr [__imp__WinExec@8 (0C79000h)]  
	exit(1);
 push        1  
 call        dword ptr [__imp__exit (0C7914Ch)]  
}
 pop         edi  
 pop         esi  
 pop         ebx  
 mov         esp,ebp  
 pop         ebp  
 ret  

이제 필요한 부분은 함수를 불러오는 부분입니다. 저 상태로 동작시에는 제대로 제대로 프로그램이 동작하지 않으므로 winexec의 주소와 exit의 주소를 알아올 필요가 있습니다.

저는 두 함수의 주소를 알아내기 위해 immunity 디버거를 사용할 것입니다. 올리디버거로도 같은 방식으로 찾아내실 수 있습니다.

일단 원리를 설명하자면 저 두 디버거에는 분석을 위하여 로딩된 dll에 있는 모든 API를 보여줍니다. 그리고 윈도우 프로그램은 kenerl32.dll을 반드시 로딩하게 되어있고 이 dll에는 윈도우 시스템 함수들이 저장되어 있습니다.

따라서 아무 프로그램이나 디버거에 넣어줍시다.(위에 작성한 프로그램이아니라 아무 프로그램이나 상관없습니다 ^^)

저는 적당히 굴러다니던 PEID로 ㅋㅋ

위와 같이 디버거에서 우클릭후 serch for에서 Name in all modules를 눌러주시면 이름순으로 불러온 모듈을 표시해줍니다.

해당 창을 클릭후 검색할 함수를 타이핑해주면 창에 해당함수가 검색됩니다.

winexec는 0x76643A10 의 주소를 갖고있는 것을 알 수 있습니다. 같은 방법으로 exit의 주소는 0x75852D20임을 알아냈습니다.

#char buf[5] = { 'c','a','l','c','\x0' };
 mov         byte ptr [buf],63h   //buf에 calc0 차곡차곡 저장~ ebp 기준으로
 mov         byte ptr [ebp-7],61h  
 mov         byte ptr [ebp-6],6Ch  
 mov         byte ptr [ebp-5],63h  
 mov         byte ptr [ebp-4],0  
#WinExec(buf,1);
 push        1 			//스택에 인자인 1 넣기
 lea         eax,[buf]  //인자로 buf 주기
 push        eax 		//스택에 buf주소 쌓기
 mov		 eax , 0x76643A10 //WinExec() 주소
 call        eax  //함수 실행!
#exit(1);
 push        1  		//스택에 1 쌓기!
 mov		 eax , 0x75852D20 //exit() 주소!
 call        eax  //함수 실행!

위와 같이 어셈블리를 수정해줍시다.

 필요한 부분만 뽑아왔는데 설명은 위 코드블럭에 첨부해놨습니다!

위와 달라진 부분이라면 함수 프롤로그, 에필로그 삭제( 어차피 뒤에 c코드에 어셈으로 넣어줄거라 필요없습니다~)와

mov eax , 함수 주소

call eax 부분의 추가입니다!

 원래는 함수로 call 해주는 부분이었지만 저희가 구해온 함수 주소를 사용하기 위해 eax 레지스터에 넣어주고

해당 레지스터를 call 해주므로서 함수를 실행시킵니다!

꼭 eax 레지스터일 필요는 없습니다~

실패 ㅠㅠ

비주얼 스튜디오로 돌아가서 해당 어셈을 테스트해줍시다.

 정상적으로 돌아가지 않습니다! 

당연합니다 buf라는 배열을 선언하질 않았으니 당연히 어셈으로 인식을 못합니다! 얼른  ebp - 8 로 해당 배열명을 바꿔줍시다.

정상작동!

정상적으로 계산기가 실행된다면 다시 디스어셈블리를 켜주고 코드 바이트 표시 옵션을 활성화 해줍니다. 이 코드 바이트가 저희가 추출해야될 기계어 코드입니다.

코드바이트를 보면 asm이 시작되는 C6 45 부터 exit 함수를 실행시키는 FF D0까지 따오면 됩니다!

메모장에 해당 코드바이트를 복사해주고 16진수임을 표시하기위해 \x를 추가해줍시다. (복사하실때는 소스 표시를 끄시면 코드 바이트만이 보입니다~)

이 코드 바이트들이 저희가 얻고자 했던 진짜 "shellcode" 입니다~

TIP:Notepad ++ 나 한글오피스 사용하시면 alt+드래그로 블럭 지정후 삭제 가능하니까 편하게 편집하세요~

저는 저렇게 블럭 삭제하고 띄어쓰기 공란 한칸을 \x로 바꾸기해서 편집했습니다~ 

다시 비주얼 스튜디오로 돌아가서

 일단 프로젝트 설정으로가 DEP를 비활성화 해줍니다. 

DEP옵션은 스택에 올라와있는 코드가 로드되서 실행 되는걸 방지하는 보안옵션입니다. 따라서 이 옵션이 켜져있으면 쉘코드 실행시 공격으로 인지하고 프로그램을 종료하게 됩니다.

해당 옵션을 비활성화 한 후 테스트 해봅시다.  (프로젝트 속성 - 링커 - 고급)

#include <stdio.h>
#include <windows.h>

int main() {
	char shellcode[] = "\xC6\x45\xF8\x63\xC6\x45\xF9\x61\xC6\x45\xFA\x6C\xC6\x45\xFB\x63\xC6\x45\xFC\x00\x6A\x01\x8D\x45\xF8\x50\xB8\x10\x3A\x64\x76\xFF\xD0\x6A\x01\xB8\x20\x2D\x85\x75\xFF\xD0";
	int * addr = (int*)shellcode;

	__asm{
	jmp addr
	}
}
//r3dzone
//shellcode.c

코드는 위와같이 문자열 배열에 저희가 뽑아낸 쉘코드(위의 코드바이트)를 저장해주고 해당 배열의 주소로 어셈을 점프 시켜서 실행 시켜주도록 합시다!

성공!

정상적으로 동작합니다! 쉘코드 작성에 성공하였습니다.

하지만 해당 쉘코드는 실전에서 사용하기에는 무리가 있습니다.

문제점1. 널바이트(\x00)이 쉘코드 중간에 있어 쉘코드를 중간까지만 읽어올수도 있다.

문제점2. 윈도우 vista부터는 aslr이 걸려있기 때문에 해당 시스템을 재시작할시에 쉘코드를 사용할 수 없다.

 

이 두문제를 해결 하기 위한 널바이트 제거와 유니버셜 쉘코드 작성은 차후 포스팅 하도록 하겠습니다.

 

오랜만에 글을 쓰네요... 다 날아갔던 글 재작성하려니 허탈감이... 요즈음 파이썬만 썻더니 주석처리도 C에서#으로 쓰질 않나ㅋㅋ 오랜만에 쉘코딩 다시 하니 재미도 있고 금방 2부도 작성해야겠습니다~