저번에 배웠던 MessageBox 코드 삽입을 이용해서 이번에는 기본 프로그램인 노트패드를 이용해 보자.

테스트 환경은 Windows 7 SP1에서 테스트 하였다.

윈도우7에서는 기본적으로 ASLR을 사용하므로 기본 베이스 주소가 랜덤하게 바뀐다. 그렇기 때문에 일단, 이 옵션을 제거하고 시작한다. 제거하는 방법은 http://www.reversecore.com/69 여기를 참고하였다.


노트패드의 원본 DLL Characteristics 값은 8140이다. 이 값을 8100 으로 만들어 줘야 ASLR 기능을 끌 수 있다.

Hex Editor를 이용해서 파일 오프셋 13E 위치에서 8140 값을 8100으로 바꿔주면 된다.

그다음 Impoart Address Table을 참고해서 MessageBox 주소를 얻으면 된다.


MessabeBox 함수는 USER32.dll 에 있고 user32.dll 이 로드되는 메모리의 위치는 11A8이 된다. 노트패드의 ImageBase 주소는
0x01000000 가 되므로 user32.dll 이 로드되는 위치는 0x010011A8 이 된다.

user32.dll 의 맨 첫번째 함수는 SetActiveWindow 의 파일 오프셋 주소는 5A8이 되고, MessageBoxW의 파일오프셋은 668이다.

이걸로 계산해보면 MessageBoxW의 위치는 0x01001268 이 된다는 것을 알 수 있다. MessageBoxW함수는 유니코드를 입력받는 함수이므로 텍스트 문자열을 유니코드로 맞춰줘야 한다. 유니코드로 만든 문자열을 추가하고 앞서 소개했던 코드를 넣으면 메모장이 실행되기 전에 먼저 원하는 메세지 박스를 먼저 띄울 수 있다.



컴퓨터 바이러스란 의미가 요즘은 악성코드 혹은 시스템을 파괴하는 형태로 쓰여지고 있다. 근데 왜 바이러스라고 부를까...

초창기 바이러스 프로그램은 실제 의학에서 쓰는 바이러스 용어처럼 숙주가 없인 살수 없고 숙주 프로그램에 묻어서 실행되는 코드를 의미한다. 즉, 원래의 실행 파일에 바이러스 코드가 삽입되서 실행된 후, 원래의 코드로 다시 점프해서 바이러스 자신을 숨길 수 있다. 이런 바이러스 코드들은 어셈블러와 OS 로더 구조를 조금만 이해하면 금방 짤 수 있다.

이번 시간에는, 실제 바이러스코드가 아닌 원래의 프로그램에 원하는 코드를 넣어 실행한 후 오리지널 프로그램으로 점프하는 방법을 소개한다.

테스트는 Windows 7 SP1 에서 테스트 되었으며, 프로그램은 비주얼 스튜디오 2008 SP1 에서 컴파일 되었다.

1. 간단한 메세지 박스 출력 프로그램
MessageBox 함수를 이용하여 Hello, World를 출력하는 간단한 프로그램을 만들어 보자.

#pragma comment(linker,"/ENTRY:main /FILEALIGN:0x200 /MERGE:.data=.text /MERGE:.rdata=.text /SECTION:.text,EWR /IGNORE:4078")
#include 
int main(int argc, char *argv[])
{
    MessageBox(NULL, "Hello, World", "waintman.tistory.com", MB_OK);
    ExitProcess(0);
}

링커 옵션은 분석을 쉽게 하기위해 텍스트 영역과 데이터 영역을 통합하기위해 사용되었다.
프로그램을 실행하면 "Hello, World" 를 출력하는 메세지 박스가 뜬다.

코드를 분석하기 위해 올리디버거를 이용한다.

올리디비거로 위에서 만든 프로그램을 열어보면, 아래 그림과 같다.


윈도우7 비주얼 스튜디오 2008에서 컴파일시 기본적으로 ASLR(Address Space Layout Randomization)을 지원한다. 즉 실행될 때마다 ImageBase 값이 바뀌게 된다. 그렇기 때문에 비주얼 스튜디오에서 이 옵션을 해제한다.
(참고 사이트: http://www.reversecore.com/69)


올리디버거로 분석한 코드를 살펴보면,
MessageBox 함수는 4개의 파라미터를 입력받으므로 4개의 파라메터들을 스택에 push 한다. 스트링의 경우 0x00401018,
0x00401030에 있는 값들을 넣게 된다. 그리고 MessageBox 함수는 0x00401080에 적혀있는 주소값이 된다.

PE 헤더의 Import Address Table 을 분석하면 0x00401080에 MessageBox 함수의 주소를 적는다는 것을 알 수 있다.

이제, "virus" 라는 메세지 창을 띄우는 코드를 삽입하면 된다. 일단 메세지 박스 함수의 주소는 0x00401080이란 것을 알아내었으니, "virus" 란 글자를 데이터 영역에 쓰기만 하면 된다. 문자열은 텍스트 영역중에서 0x00401060부터 쓰고 그 뒤에 MessageBox를 실행하는 코드도 같이 적어준다. 메모리 영역 0x00401060의 주소는 파일 오프셋 0x00000260dp 에 위치한다. 파일 오프셋을 얻는 방법은 PEview 나 직접 PE 포맷을 분석하면 얻을 수 있다.

삽입된 코드는 다음과 같다.



원래의 OEP로 가기 위해 mov eax, 0x00401040; jmp eax 를 사용하였다. 그리고 PE 헤더의 AddressOfEntry 값을 0x00401066으로 고친뒤, 실행하면 virus 메세지 박스가 먼저 실행되고, Hello, World가 나오는 것을 확인할 수 있다.

이런과정을, 프로그램을 통해서 직접 수정하게 되면 기본 바이러스 프로그램을 만들 수 있을 것이다.

Hello, World 출력 Shellcode

printf를 이용하여 콘솔에 Hello, World를 출력하는 프로그램은 쉽게 짤 수 있다.


#include 
int main()
{
    printf("Hello, World");
    return 0;
}

windows 시스템에서 printf 함수는 msvcrt.dll에 포함되어 있다. Shellcode를 만들기 위해서 해당라이브러리를 로딩하는 코드를 작성해야 한다. 라이브러리 로딩함수는 LoadLibrary함수를 사용하고, 이 함수는 kernel32.dll에서는 LoadLibraryA로 정의되어 있다.

kernel32.dll은 따로 로딩할 필요가 없으므로, kernel32.dll에 있는 LoadLibraryA위치를 찾으면 된다.

함수의 위치를 찾는 코드는 다음과 같다.

#include 
#include 

/***************************************
arwin - win32 address resolution program
by steve hanna v.01
   vividmachines.com
   shanna@uiuc.edu
you are free to modify this code
but please attribute me if you
change the code. bugfixes & additions
are welcome please email me!
to compile:
you will need a win32 compiler with
the win32 SDK

this program finds the absolute address
of a function in a specified DLL.
happy shellcoding!
***************************************/


int main(int argc, char** argv)
{
        HMODULE hmod_libname;
        FARPROC fprc_func;
        
        printf("arwin - win32 address resolution program - by steve hanna - v.01n");
        if(argc < 3)
        {
                printf("%s  n",argv[0]);
                exit(-1);
        }

        hmod_libname = LoadLibrary(argv[1]);
        if(hmod_libname == NULL)
        {
                printf("Error: could not load library!n");
                exit(-1);
        }
        fprc_func = GetProcAddress(hmod_libname,argv[2]);
        
        if(fprc_func == NULL)
        {
                printf("Error: could find the function in the library!n");
                exit(-1);
        }
        printf("%s is located at 0x%08x in %sn",argv[2],(unsigned int)fprc_func,argv[1]);


}


위 코드로 얻은 kernel32.dll에 있는 LoadLibraryA의 주소는 0x771d395c 이다.
LoadLibraryA 함수는 로드할 dll을 매개변수로 받으므로, 어셈블 코드에서는 이름을 스택에 넣어주면 된다.

같은 방식으로 msvcrt.dll 에 있는 printf함수의 주소를 찾고 어셈블로 콜하면 된다.

콘솔 프로그램은 exit함수를 호출해야 정상 종료가 되므로, msvcrt.dll 에 있는 exit함수의 주소를 찾아서 콜해주면 Hello, World용 어셈블 코드를 얻을 수 있다.

[SECTION .text]

global _start

_start:

	xor eax, eax
	xor ebx, ebx
	xor ecx, ecx
	xor edx, edx
	jmp short GetLibrary
LibraryReturn:
	pop ecx
	mov [ecx + 10], dl
	mov ebx, 0x771d395c
	push ecx
	call ebx

	jmp short PrintfFunc
PrintfFuncReturn:
	pop ecx
	xor edx, edx
	mov [ecx+12], dl
	push ecx
	mov ebx, 0x76c3c5b9
	call ebx
	xor eax,eax
	push eax			
	mov eax, 0x76c336aa 		;exitprocess(exitcode);
	call eax	

GetLibrary:
	call LibraryReturn
	db 'msvcrt.dllN'
		
		
		
PrintfFunc:
	call PrintfFuncReturn
	db 'Hello, WorldN'


이것을 nasm으로 컴파일하고 objdump를 이용하여 기계어 코드를 뽑아낸다. (cygwin에서 작업 혹은 Linux 시스템에서)


nasm -f elf hello.asm; ld -o hello hello.obj; objdump -d hello

objdump 결과


hello:     file format pei-i386

Disassembly of section .text:
00401000 <_start>:
  401000: 31 c0                 xor    %eax,%eax
  401002: 31 db                 xor    %ebx,%ebx
  401004: 31 c9                 xor    %ecx,%ecx
  401006: 31 d2                 xor    %edx,%edx
  401008: eb 26                 jmp    401030 <_start+0x30>
  40100a: 59                    pop    %ecx
  40100b: 88 51 0a              mov    %dl,0xa(%ecx)
  40100e: bb 5c 39 1d 77        mov    $0x771d395c,%ebx
  401013: 51                    push   %ecx
  401014: ff d3                 call   *%ebx
  401016: eb 28                 jmp    401040 <_start+0x40>
  401018: 59                    pop    %ecx
  401019: 31 d2                 xor    %edx,%edx
  40101b: 88 51 0c              mov    %dl,0xc(%ecx)
  40101e: 51                    push   %ecx
  40101f: bb b9 c5 c3 76        mov    $0x76c3c5b9,%ebx
  401024: ff d3                 call   *%ebx
  401026: 31 c0                 xor    %eax,%eax
  401028: 50                    push   %eax
  401029: b8 aa 36 c3 76        mov    $0x76c336aa,%eax
  40102e: ff d0                 call   *%eax
  401030: e8 d5 ff ff ff        call   40100a <_start+0xa>
  401035: 6d                    insl   (%dx),%es:(%edi)
  401036: 73 76                 jae    4010ae <__DTOR_LIST__+0x54>
  401038: 63 72 74              arpl   %si,0x74(%edx)
  40103b: 2e                    cs
  40103c: 64                    fs
  40103d: 6c                    insb   (%dx),%es:(%edi)
  40103e: 6c                    insb   (%dx),%es:(%edi)
  40103f: 4e                    dec    %esi
  401040: e8 d3 ff ff ff        call   401018 <_start+0x18>
  401045: 48                    dec    %eax
  401046: 65                    gs
  401047: 6c                    insb   (%dx),%es:(%edi)
  401048: 6c                    insb   (%dx),%es:(%edi)
  401049: 6f                    outsl  %ds:(%esi),(%dx)
  40104a: 2c 20                 sub    $0x20,%al
  40104c: 57                    push   %edi
  40104d: 6f                    outsl  %ds:(%esi),(%dx)
  40104e: 72 6c                 jb     4010bc <__DTOR_LIST__+0x62>
  401050: 64                    fs
  401051: 4e                    dec    %esi
00401052 <__CTOR_LIST__>:
  401052: ff                    (bad) 
  401053: ff                    (bad) 
  401054: ff                    (bad) 
  401055: ff 00                 incl   (%eax)
  401057: 00 00                 add    %al,(%eax)
 ...
0040105a <__DTOR_LIST__>:
  40105a: ff                    (bad) 
  40105b: ff                    (bad) 
  40105c: ff                    (bad) 
  40105d: ff 00                 incl   (%eax)
  40105f: 00 00                 add    %al,(%eax)
 ...

_start 부분만 16진수로 얻은 코드를 테스트 해보면 정상적으로 실행이 된다.

 char shellcode[] = "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x26\x59\x88\x51\x0a\xbb\x5c\x39\x1d\x77\x51\xff\xd3\xeb\x28\x59\x31\xd2\x88\x51\x0c\x51\xbb\xb9\xc5\xc3\x76\xff\xd3"
 			  "\x31\xc0\x50\xb8\xaa\x36\xc3\x76\xff\xd0\xe8\xd5\xff\xff\xff\x6d\x73\x76\x63\x72\x74\x2e\x64\x6c\x6c\x4e\xe8\xd3\xff\xff\xff"
 			  "\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x4e";

int main(int argc, char **argv)
{
	int *code;
	code = (int *)shellcode;
	__asm {
		jmp code;
	}
	return 0;
}


[참고자료]
http://www.vividmachines.com/shellcode/shellcode.html

클럽박스나 피디박스 프로그램을 실행시키면 1시간마다 10점 혹은 12점의 마일리지를 적립할수 있다.

12점 마일리지를 받을 경우 자신의 네트워크 자원을 사용하므로 약간은 부담이 갈 수 있다.

그렇다고 마일리지를 얻을려고 다운받지도 않고 있는데 피디박스나 클럽박스를 켜놓기도 부담스럽다.

특히나 온라인게임을 할때 피디박스나 클럽박스가 실행되고 있으면 게임이 실행되지 않는 경우까지 발생한다.

그래서 생각해 보았다. 사실 1시간마다 주는 마일리지를 좀더 시간단축하고픈 마음이었지만...

일단 생각할수 있는 시나리오는,

1. 시간을 재는 프로그램 카운터를 빨리 진행시킨다.

이 방법을 사용하려고 프로그램을 분석해 보았지만, 실력이 짧은 관계로 카운터를 빨리 진행시키는데 실패하였다.

이런 프로그램이 있었던것 같았는데, 패치가 된 듯하다.

2. 마일리지를 받는 순간을 노린다.

1시간이 되면 거의 즉시 마일리지 적립확인이 가능하다. 프로그램상이든, 웹상이든...

여기서 착안을 하여 프로그램에서 서버로 뭔가를 전송한다고 생각되었다.

패킷 캡쳐 프로그램으로 캡쳐해보니 역시나 특정서버로 특정 문자열을 전송하는것을 확인 할 수 있었다.

혹시? 하는 생각에 계속 그 문자열을 날려 보았지만...

결과는 서버에서도 시간을 측정하였기 때문에 시간단축은 안 되는 것이었다.

처음에는 질의(query)가 매 시간마다 바뀌어서 반영이 안되는줄 알았지만, 그것은 아니었다.

이 것으로 피디박스나 클럽박스 프로그램이 실행중이 아니라도 마일리지를 얻을 수 있게 되었다.

해당서버로 매 시간마다 특정 쿼리(query)를 날려주면 알아서 그걸 반영해 준다.

한마디로 내 네트워크 자원을 안 빌려주고도 12점이라는 마일리지를 받을수 있다.

자신이 직접 프로그래밍하여 시간마다 특정 쿼리를 날리는 프로그램을 만들수도 있고...

+ Recent posts