보통 악성스크립트는 난독화가 돼있는 상태로 인터넷을 돌아다닌다.

그림1 악성스크립트 일부

위 사진은 악성 파일의 일부로 vD 함수에 의해 디코딩된다.

 

이러한 난독화는 일반적으로 윈도우에서 제공하는 WScript.echo를 사용하여 변수를 출력하는 방식으로 난독화를 해제한다.

 

하지만 윈도우에서는 기본적으로 GUI를 사용하는 wscript가 default값이기 때문에 그림2와 같이 msgbox 형식으로 출력이 되기 때문에 복사하기에 어려움이 있다.

그림2 wscript를 사용한 복호화

파일로 만들기 쉬운 방법 중 하나는 cscript를 사용하는 것이다. 

cscript를 사용하면 그림3과 같이 파이프라인으로 쉽게 복호화를 할 수 있다.

 

그림3 cscript를 사용한 복호화

그림4는 복호화된 js 파일이다. 위의 3라인만 지우면 깔끔하게 복호화가 된 것이다.(다만 eval 사용으로 몇번의 복호화를 거쳐야할 것 같다.)

그림4 복호화된 js파일

마지막으로 cscript를 default로 세팅하는 명령어가 존재한다.

그림5 처럼 wscript.exe //H: CScript 또는 wscript.exe //H: WScript 명령어로 기본설정을 변경할 수 있다. 

그림5 cscript를 기본스크립트로 설정

 

Posted by 회색세계

1. PE 파일이란?

 PE(Portable Executable)파일은 Windows 운영체제에서 사용되는 실행 파일의 형식.

 PE 파일은 헤더 + 바디로 구성.

  헤더에는 파일이 실행되기 위해 필요한 정보가 저장됨(메모리 적재 방식, EP 주소 등).

  바디에는 명령어(코드), 데이터, 리소스 등이 저장됨.

 (Linux는 ELF(Executable and Linkable Format)를 실행파일의 형식으로 사용)

 

2. PE 파일의 종류

 실행계열 : EXE, SCR

 라이브러리 계열 : DLL, OCX, CPL, DRV

 드라이버 계열 : SYS, VXD

 오브젝트 파일 계열 : OBJ

 

3. PE 기본 구조

 PE 파일은 헤더 + 바디로 구성.

  헤더에는 파일이 실행되기 위해 필요한 정보가 저장됨(메모리 적재 방식, EP 주소 등).

  바디에는 명령어(코드), 데이터, 리소스 등이 저장됨.

PE 파일의 메모리 로딩

섹션의 헤더에 각 Section에 대한 파일/메모리에서의 크기, 위치, 속성등이 정의됨.

PE 헤더의 끝부분과 각 섹션의 끝에는 NULL padding 영역이 존재하며 이는 파일, 메모리, 패킷 등을 처리할 때 효율을 높이기 위해 기본 단위 개념으로 사용.

 

4. VA & RVA

 VA(Virtual Address) : 프로세스 가상 메모리의 절대주소.

 RVA(Relative VA) : 어느 기준 위치에서부터의 상대주소.

   ImageBase + RVA = VA

   RVA는 이미 그 위치에 다른 PE 파일이 로딩된 경우 재배치를 효율적으로 할 수 있기때문에 사용.

 

5. PE 헤더

 5.1 DOS Header

  PE파일의 가장 앞 부분에 존재하며 제일 앞 부분에는 MZ 시그니처가 존재.

  IMAGE_DOS_HEADER 구조체의 크기는 40이며, 가장 중요한 멤버는 e_magic과 e_lfanew 멤버임.

   e_magic : 앞에서 말한 DOS Signature (4D5A => "MZ")

   e_lfanew : NT header의 옵셋을 표시

 

 5.2 DOS Stub

  DOS Stub의 존재 여부는 옵션이며 현재는 잘 사용되지 않음.

 

 5.3 NT Header

  3개의 멤버를 가지며, 제일 첫 번째 멤버는 Signature로 "PE" 값을 가짐.

  나머지는 File Header와 Optional Header 구조체 멤버가 존재.

 

  File Header

   파일의 개략적인 속성을 나타냄. 아래는 4개의 중요한 멤버를 순서대로 나타냄.

   #1. Machine : Machine 넘버는 CPU별로 고유한 값을 가지며 32비트 Intel x86 호환칩은 14C의 값을 가짐.

   #2. NumberOfSection : 섹션의 수를 나타내며, 반드시 0보다 커야함. 정의된 섹션 개수와 다를 시 에러 발생.

   #3. SizeOfOptionalHeader : NT Header의 마지막 멤버 IMAGE_OPTIONAL_HEADER32의 크기를 나타냄.

                                       위의 구조체는 크기가 고정돼 있지만 32비트와 64비트의 크기가 달라 크기를 명시함.

   #4. Characteristics : 파일의 속성을 나타내는 값으로, 실행이 가능한 형태인지, DLL 파일인지의 정보를 bit or 로 조합.

 

 Optional Header

  PE 헤더 중 가장 크기가 크며, 잘못 세팅되면 파일이 정상 실행 되지 않음. 아래는 중요 멤버를 순서대로 나타냄.

  #1. Magic : 32비트의 경우 10B, 64비트의 경우 20B의 값을 가진다. IMAGE_OPTIONAL_HEADER의 종류에 따라 달라짐.

  #2. AddressOfEntryPoint : RVA 값을 가짐. 프로그램에서 최초로 실행되는 코드의 시작 주소로 매우 중요함.

  #3. ImageBase : PE파일이 로딩되는 시작 주소를 나타냄. 주로 exe는 00400000, dll은 10000000값이며 EIP= ImageBase + AddressOfEntryPoint 값으로 세팅됨.

  #4. SectionAlignment, FileAlignment : 각각 메모리와, 파일을 관리하는 최소 단위이며 각각의 섹션의 크기는 이 멤버의 배수가 되어야 함.

  #5. SizeOfImage : PE 파일이 메모리에 로딩되었을 때 가상 메모리에서 PE Image가 차지하는 크기를 나타냄.

  #6. SizeOfHeader : PE 헤더의 전체 크기를 나타내며, 이 값 역시 FileAlignment의 배수여야 함.

  #7. Subsystem : 이 값을 보고 시스템 드라이버 파일인지, 일반 실행파일 인지 구분할 수 있음.

                       ex) 1, Driver file    2, GUI file    3, CUI file

  #8. NumberOfRvaAndSize : IMAGE_OPTIONAL_HEADER32 구조체의 마지막인 DataDirectory 배열의 개수를 나타냄.

  #9. DataDirectory : IMAGE_DATA_DIRECTORY 구조체의 배열로 각 항목마다 정의된 값을 가짐.

       DataDriectory[0] = EXPORT Directory

       DataDriectory[1] = IMPORT Directory

       DataDriectory[2] = RESOURCE Directory

       DataDriectory[3] = EXCEPTION Directory

       DataDriectory[4] = SECURITY Directory

       DataDriectory[5] = BASERELOC Directory

       DataDriectory[6] = DEBUG Directory

       DataDriectory[7] = COPYRIGHT Directory

       DataDriectory[8] = GLOBALPTR Directory

       DataDriectory[9] = TLS Directory

       DataDriectory[A] = LOAD_CONFIG Directory

       DataDriectory[B] = BOUND_IMPORT Directory

       DataDriectory[C] = IAT Directory

       DataDriectory[D] = DELAY Directory

       DataDriectory[E] = COM_DESCRIPTOR Directory

       DataDriectory[F] = Reserved Directory

 

5.4 섹션 헤더

 각 섹션의 속성을 정의한 것.

 프로그램의 안정성을 위함임(ex. Code 영역과 Data영역이 하나로 되어있으면 overflow 시 코드를 덮게됨).

 섹션마다 다른 액세스 권한을 가짐.

  code : 실행, 읽기 권한

  data : 비실행, 읽기, 쓰기 권한

  resource : 비실행, 읽기 권한

 

5.4.1 IMGAE_SECTION_HEADER

 섹션 헤더는 각 섹션 별 IMAGE_SECTION_HEADER 구조체 배열로 되어 있음.

 해당 구조체에서 중요 멤버

  VirtualSize : 메모리에서 섹션이 차지하는 크기

  VirtualAddress : 메모리에서 섹션의 시작 주소(RVA)

  SizeOfRawData : 파일에서 섹션이 차지하는 크기

  PointerToRawData : 파일에서 섹션의 시작 위치

  Characteristics : 섹션의 속석(bit OR)

 

5.5 RVA to Raw

 PE 파일이 메모리에 로딩되었을 때 각 섹션에서 메모리의 주소와 파일 옵셋을 매핑하는 방법.

 RAW - PointerToRawData = RVA - VirtualAddress

                            RAW = RVA - VirtualAddress + PointerToRawData

 

5.6 IAT

 Windows 운영체제의 핵심 개념인 process, memory, DLL 구조 등에 대한 내용이 함축되어 있음.

 프로그램이 어떤 라이브러이에서 어떤 함수를 사용하고 있는지를 기술한 테이블.

 

5.6.1 DLL

 여러 프로그램에서 동시에 사용할 수 있는 코드와 데이터를 포함하는 라이브러리. 

 메모리를 적게 사용하며, 모듈식 프로그램을 손 쉽게 개발할 수 있으며, 또한 배포가 용이.

 

5.6.2 IMAGE_IMPORT_DESCRIPTOR

 PE 파일은 자신이 어떤 라이브러리를 임포트하고 있는지 IMAGE_IMPORT_DESCRIPTOR 구조체에 명시.

 구조체의 중요 멤버

  OriginalFirstThunk : INT(Import Name Table)의 주소(RVA)

  Name : Library 이름 문자열의 주소(RVA)

  FirstThunk : IAT(Import Address Table)의 주소(RVA)

 

 PE 로더가 임포트 함수 주소를 IAT에 입력하는 순서:

  1. IID의 Name 멤버를 읽어 라이브러리의 이름 문자열(kernel32.dll)을 얻음.

  2. 해당 라이브러리를 로딩. => LoadLibrary("kernel32.dll")

  3. IID의 OriginalFirstThunk 멤버를 읽어 INT 주소를 얻음.

  4. INT에서 배열의 값을 하나씩 읽어 해당 IMAGE_IMPORT_BY_NAME 주소(RVA)를 얻음.

  5. IMAGE_IMPORT_BY_NAME의 Hint(ordinal) 또는 Name 항목을 이용하여 해당 함수의 시작 주소를 얻음.

     => GetProcAddress("GetCurrentThreadId")

  6. IID의 FirstThunk(IAT) 멤버를 읽어서 IAT 주소를 얻음.

  7. 해당 IAT 배열 값에 위에서 구한 함수 주소를 입력.

  8. INT가 끝날 때까지 위 4~7 과정을 반복.

 

5.7 EAT

 라이브러리 파일에서 제공하는 함수를 다른 프로그램에서 가져다 사용할 수 있도록 해주는 핵심 메커니즘.

 EAT를 통해서만 해당 라이브러리에서 익스포트하는 함수의 시작 주소를 정확히 구할 수 있음.

 

5.7.1 IMAGE_EXPORT_DIRECTORY

 임포트와 마찬가지로 어떤 라이브러리르 임포트하고 있는지를 해당 구조체에 명시.

 구조체의 중요 멤버

  NumberOfFunctions : 실제 Export 함수 개수

  NumberOfNames : Export 함수 중에서 이름을 가지는 함수 개수 (<= NumberOfFunctions)

  AddressOfFunction : Export 함수 주소 배열(배열의 원소 개수 = NumberOfFunctions)

  AddressOfNames : 함수 이름 주소 배열(배열의 원소 개수 = NumberOfNames)

  AddressOfNameOrdinals : Ordinal 배열(배열의 원소 개수 = NumberOfNames)

 

5.7.2 GetProcAddress()

 라이브러리에서 함수 주소를 얻는 API. 동작 원리는 아래와 같음

  1. AddressOfNames 멤버를 이용해 함수 이름 배열로 감.

  2. 함수 이름 배열은 문자열 주소가 저장되어 있으며, 문자열 비교를 통하여 원하는 함수 이름을 찾음.(name_index)

  3. AddressOfNameOrdinals 멤버를 이용해 ordinal 배열로 감.

  4. ordinal 배열에서 name_index로 해당 ordinal 값을 찾음.

  5. AddressOfFunctions 멤버를 이용해 함수 주소 배열(EAT)로 감.

  6. EAT에서 아까 구한 ordinal을 배열 인덱스로 하여 원하는 함수의 주소를 얻음.

Posted by 회색세계

1. Windows에서의 유니코드

 문자셋(Character Sets)의 종류와 특성

  아스키코드 : 1바이트(7비트 + 1비트)를 사용. 256 개의 문자를 사용할 수 있음.

  유니코드    : 2바이트를 사용. 65536개의 문자를 사용할 수 있음.

 

 문자셋의 3가지 종류

  SBCS(Single Byte Character Set) : 1바이트만을 사용하는 방식(아스키코드 기반 문자열)

  MBCS(Multi Byte Character Set) : 1바이트와 2바이트를 혼용해서 사용하는 방식(아스키코드 + 유니코드)

  WBCS(Wide Byte Character Set) : 2바이트만을 사용하는 방식(유니코드 기반 문자열)

 

 WBCS 기반의 프로그래밍

  char 자료형 대신 wchar_t 사용 : wchar_t는 2바이트를 사용(typedef unsigned short wchar_t)

  "ABC"를 대신하는 L"ABC"       : wchar_t str[] = L"ABC" 방식으로 사용

  strlen을 대신하는 wcslen        : 유니코드 기반의 문자열 함수를 사용

    ex) strlen : size_t wcslen(const wchar_t* string)

         strcpy : wchar_t* wcscpy(wchar_t* dest, const wchar_t* src)

         strncpy : wchar_t* wcsncpy(wchar_t* dest, const wchar_t* src, size_t cnt)

         strcat : wchar_t* wcscat(wchar_t* dest, const wchar_t* src)

         strncat : wchar_t* wcscat(wchar_t* dest, const wchar_t* src, size_t cnt)

         strcmp : int wcscmp(const wchar_t* s1, const wchar_t* s2, size_t cnt)

         strncmp : int wcsncmp(const wchar_t* s1, const wchar_t* s2, size_t cnt, size_t cnt)

         printf : int wprintf(const wchar_t* format [, arguemt]...)

         scanf : int wscanf(const wchar_t* format [, arguemt]...)

         fgets : wchar_t* fgetws(wchar_t* string, int n, FILE* stream)

         fputs : int fputws (const wchar_t* string, FILE* stream)

    size_t : typedef unsigned int size_t;

 

wmain 함수에 대한 이해

 

왼쪽의 main에서는 MBCS 기반으로 구성되어 인자로 전달된다.

오른쪽의 wmain에서는 WBCS 기반(유니코드)으로 구성되어 인자가 전달된다.

 

참고: 뇌를 자극하는 윈도우즈 시스템 프로그래밍. 윤성우 저

Posted by 회색세계

1. 시스템 프로그래밍의 이해와 접근

 시스템 프로그래밍이란?

  - 컴퓨터 시스템을 동작시키는 프로그램

  - 하드웨어를 사용할 수 있도록 도와주는 프로그램

 

 컴퓨터 시스템의 주요 구성요소

  CPU <=> 캐쉬(Cache) <=> 메인 메모리 <=> 하드 디스크

      [ 컴퓨터 구조]                         [ 운영 체제 ]

 

2. 컴퓨터 하드웨어의 구성

 

컴퓨터 하드웨어의 구성

CPU(Central Processing Unit)    : 중앙처리장치로 연산을 담당. 

메인 메모리(Main Memory)      : 램(RAM)이라는 저장장치로 구성. 컴파일이 완료된 프로그램 코드가 실행되는 영역

입 출력 버스(Input/Output Bus) : 컴퓨터를 구성하는 구성 요소 사이에서 데이터를 주고 받기 위해 사용되는 경로

 - Address Bus : 주소 값을 이동하기 위해 필요한 버스.

 - Data Bus     :  데이터를 이동하기 위해 필요한 버스.

 - Control Bus  :  데이터를 보내거나 받는 것을 알리기 위한 버스.

 

3. CPU에 대한 이해

ALU : CPU 내에서 실제 연산을 수행하는 장치. 덧셈이나 뺄셈과 같은 산술 연산과 AND나 OR와 같은 논리 연산을 수행.

컨트롤 유닛 : CPU에 들어어온 명령어를 해석하고, 해석된 결과를 바탕으로 ALU가 이를 처리하게 함.

레지스터 : CPU 내에서 사용하는 아주 작은 메모리로 명령어 및 데이터를 저장하는 저장장치. 

버스 인터페이스 : CPU 내에서 I/O 버스의 프로토콜을 이해하는 장치.

클럭 신호(Clock Pulse) : CPU를 구성하는 요소는 아니지만 동기를 맞추기 위해 사용.

                               동기가 맞지 않을 시 버퍼를 덮어 쓰는 등의 문제가 발생. 

                               동기는 느린 장치의 속도에 맞춤.

 

4. 프로그램의 실행과정

 전처리기 => 컴파일러 => 어셈블러 => 링커

  단계 1: 전처리기에 의한 치환 작업

      #include, #define과 같이 #으로 시작하는 지시자의 지시를 따라서 소스코드를 적절히 변경하는 작업

  단계 2: 컴파일러에 의한 번역

      소스코드는 컴파일러에 의해 어셈블리 코드로 번역된다. 어셈블리 코드는 이진 데이터를 조합해서 만들어진

      프로그램 코드를 의미

  단계 3: 어셈블러에 의한 바이너리 코드 생성

      바이너리 코드 : 1과 0으로만 구성되는 코드.

      컴퓨터는 오로지 1과 0만을 이해하기 때문에 어셈블리 코드는 바이너리코드로 번역되어야 함.

  단계 4: 링커에 의한 연결과 결합

      프로그램 내에서 참조하는 함수나 라이브러리들을 하나로 묶는 작업. 이 과정이 끝나면 실제 실행 가능한 파일 생성

 

프로그램의 실행 과정

Fetch : 메모리상에 존재하는 명령어를 CPU로 가져오는 작업 (레지스터에 저장)

Decode : 가져다 놓은 명령어를 CPU가 해석하는 단계 (컨트롤 유닛)

Execution : 해석된 명령어의 명령대로 CPU가 실행하는 단계 (ALU)

 

출처: 뇌를 자극하는 윈도우즈 시스템 프로그래밍. 윤성우 저

Posted by 회색세계
이전버튼 1 2 이전버튼

블로그 이미지
회색세계

공지사항

Yesterday
Today
Total

달력

 « |  » 2025.12
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

최근에 올라온 글

최근에 달린 댓글

글 보관함