2015. 4. 1. 15:10 :: 운영체제

1. syslog의 개념

솔라리스에서는 시스템의 활동이나 이벤트를 추적하기 위해서 로깅 기능을 제공합니다.

syslog는 시스템에서 발생한 다양한 이벤트를 추적하고 메시지를 생성하고,

이러한 메시지를 통해 시스템의 이상 유무를 판단할 수 있습니다.

syslog 기능은 아래와 같이 구성되어 있습니다.

- syslog 함수

- syslogd 데몬

- syslog.conf 구성파일

 

2. /etc/syslog.conf

syslog.conf 파일은 syslog 설정 파일로서 특정 이벤트가 발새알 때 해당 메시지를 어디로 전달할 것인지를 정의하는

중요한 구성 파일입니다. 아래는 기본적인 syslog.conf 파일의 내용입니다.

syslog.conf 파일은 각각 라인 단위로 처리가 되며,

한 라인은 Selector 필드와 Action필드로 구분됩니다.

Selector 필드는 facility와 level로 나뉘며, facility와 level은 점(.)으로 구분합니다.

반드시 Selector 필드와 Action 필드는 두 개의 탭으로 띄워야 합니다.

facility는 이벤트 메시지를 생성하는 주 분류이며 level은 이벤트 메시지의 위험 수준에 따른 중요도를 나타냅니다.

Selector 필드는 이런 메시지의 분류와 중요도를 나타내며,

Action 필드는 메시지를 어디로 전달할지 결정하게 됩니다.

 

1) Selector 필드

 

2) level의 종류

 

3) Action 필드

 

3. 기본 syslog.conf 파일 구성

/etc/syslog.conf 파일에 기본적으로 메시지를 남기도록 되어 있는 라인은 아래와 같습니다.

1) *.err;kern.notice;auto.notice    /dev/sysmsg

- 모든 facility의 err 레벨 이상

- kern facility의 notice 레벨 이상

- auth facility의 notice 레벨 이상

/dev/sysmsg 파일에 메시지를 저장합니다.

2) *.err;kern.debug;daemon.notice;mail.crit    /var/adm/messages

- 모든 facility의 err 레벨 이상

- kern facility의 debug 레벨 이상

- daemon의 notice 레벨 이상

- mail의 crit 레벨 이상

/var/adm/messages 파일에 메시지를 저장합니다.

3) *.alert;kern.err;daemon.err    operator

- 모든 alert 레벨 이상

- kern의 err 레벨 이상

- daemon의 err 레벨 이상

이벤트를 operator 사용자에게 메시지를 전송합니다.

4) *.alert    root

모든 alert 레벨 이상의 이벤트 메시지는 root 사용자에게 메시지를 전송합니다.

5) *.emerg    *

모든 emerg 레벨 이상의 이벤트는 접속된 모든 사용자에게 메시지를 전송합니다.

6) mail.debug    ifdef('LOGHOST', /var/log/syslog, @loghost)

- 로컬 시스템이 loghost인 경우 : mail facility의 debug 레벨 이상의 이벤트는 /var/log/syslog 파일에 저장되고,

- 로컬  시스템이 loghost가 아닌 경우 : loghost 호스트로 메시지가 전송됩니다.

7) ifdef('LOGHOST',,

user.err        /dev/sysmsg

user.err       /var/adm/messages

user.alert    'root.operator'

user.emerg    *

)

- 로컬 시스템이 loghost인 경우 : 아무것도 하지 않음

- loghost가 아닌 경우 : user.err, user.alert, user.msg 이벤트 메시지를 특정 파일이나 사용자에게 전송

 

3. syslog 데몬의 시작 및 정지

syslogd 데몬 중지/시작/재시작 예시입니다.

주의할 점은 syslogd 데몬은 데몬 시작시에만 /etc/syslog.conf 파일의 구성 정보를 읽습니다.

syslogd 데몬이 동작 중일 때에는 syslog.conf 파일을 수정하더라도,

syslogd 데몬이 이를 알지 못합니다. syslogd.conf 파일을 수정하였다면

반드시 syslogd 데몬을 중지 시켰다가 재시작하거나 데몬을 새로고침(refresh) 해야합니다.

 

4. syslog  메시징 구성

logger 명령을 이용하여 임의로 메시지를 만들어 syslogd 데몬에게 전송할 수 있습니다.

logger 몀령어의 옵션은 아래와 같습니다.

1) syslog.conf 파일에 local0.err를 /var/log/local0.log 파일에 저장하도록 설정합니다.

2) /var/log/local0.log 파일을 생성합니다.

3) syslogd 데몬을 재시작합니다.

4) logger 명령을 통해 아래와 같이 전달할 메시지를 입력합니다.

5) /var/log/local0.log 파일을 열어서 확인합니다.

syslog.conf 파일에 local0.err 레벨 이상의 메시지만 전달하도록 설정하였기 때문에

local.notice 메시지는 전달되지 않을것을 확인할 수 있습니다.

 

5. 로그 실시간 모니터링

로그 파일을 실시간으로 모니터링 하기 위해 tail 명령과 -f 옵션을 같이 사용합니다.

터미널을 하나 더 실행시킨 후 로그를 남겨보겠습니다.

이상으로 syslog에 대한 포스팅을 마칩니다! ^^

 

# 참고서적 : 유닉스 관리자를 위한 엔터프라이즈 솔라리스 핵심 운영 가이드2 / 김석, 박찬주, 장성균 공저

 

 

posted by Red_Message
2015. 4. 1. 11:26 :: 운영체제

사용자 계정의 관리

솔라리스 10 환경에서는 명령어 기반으로 사용자와 그룹을 추가, 수정, 삭제할 수 있습니다.

해당하는 명령어들은 아래와 같습니다.

- useradd

- userod

- userdel

- groupadd

- groupmod

- groupdel

 

1. 사용자의 생성

1) 사용자 생성 - 직접 옵션 입력하기

사용자의 생성은 useradd 명령어를 이용하여 수행할 수 있습니다.

이 명령어를 사용하면 /etc/passwd와 /etc/shadow 파일에 변경사항이 업데이트 됩니다.

사용자들이 생성될 때 해당 명령어를 이용하여 생성하면 /etc/skel 내의 모든 파일이 사용자의

홈 디렉토리에 복사됩니다.

useradd의 주요 옵션은 아래와 같습니다.

위 옵션을 적절하게 사용하여 유저를 생성합니다.

여기서는 uid 1000으로 사용자를 생성하되, 그룹은 10번에 속하도록 생성하며,

홈 디렉토리는 /export/home/user01로 지정하고,

만약 디렉토리가 존재하지 않는다면 생성하도록 -m 옵션을 적용하고 있습니다.

또, 기본 쉘을 콘 쉘(/bin/ksh)로 지정했으며,

-c 옵션을 통해 user01 사용자가 일반 사용자임을 표시합니다.

이를 확인해보면 아래와 같습니다.

 

2) 사용자 생성 - 기본값 이용하기

사용자를 생성할 때마다 매번 이렇게 번거롭게 생성하지 않아도 -D 옵션을 이용하여

기본값을 이용한 사용자 생성이 가능합니다.

이러한 기본값은 /usr/sadm/defadduser 파일에 기록되어 있으며,

기본적으로 존재하는 파일이 아니므로 반드시 useradd 명령어가 한번 이상 실행되어야 생성되는 파일입니다.

userass -D 명령어를 1회 이상 실행하게 되면 아래와 같이 파일이 생성됩니다.

 

해당 파일을 살펴보면 아래와 같은 내용이 있습니다.

위의 파일과 같이 일반 사용자를 옵션 없이 미지정하면 사용자들의 홈 디렉토리는 /home으로,

쉘은 /bin/sh로 지정되도록 되어 있습니다.

이런 기본값들을 변경하고자 할 때는 파일을 직접 수정하거나, -D 와 별도의 옵션을 이용하면 됩니다.

예를들어 홈 디렉토리를 /home 에서 /export/home으로 변경하고자 한다면 아래와 같이 수행합니다.

 

 

2. 사용자의 변경

시스템 내의 사용자 변경은 usermod 명령어로 가능하며, 주요 옵션은 아래와 같습니다.

위 옵션을 가지고 변경을 해보고, 적용이 되었는지 확인해보겠습니다.

 

 

3. 사용자의 삭제

사용자를 제거하는 방법으로 userdel 명령어를 사용합니다.

-r 옵션을 사용하면 사용자의 로컬 파일 시스템 상에 존재하는 홈 디렉토리도 함께 삭제됩니다.

단, 반드시 해당 디렉토리가 존재해야 합니다.

아래는 위에서 생성한 userA 사용자와 디렉토리를 같이 삭제하는 예 입니다.

 

 

4. 그룹의 생성

그룹을 생성하려면 groupadd 명령어를 사용하며, 해당 명령어를 사용할 경우

/etc/group 파일을 업데이트합니다. 옵션은 아래와 같습니다.

위 옵션을 가지고 그룹을 생성합니다.

 

 

5. 그룹의 수정

그룹의 정보를 수정하려면 groupmod 명령을 사용하며, 옵션은 아래와 같습니다. 

위에서 생성한 sysop의 gid를 200으로, 이름을 sysop2로 바꾸는 예제입니다.

 

 

6. 그룹의 삭제

그룹의 삭제는 groupdel 명령어로 가능하며, 위에서 변경한 sysop2 그룹을 삭제하는 예제입니다.

 

 

7. /etc/skel

/etc/skel 디렉토리는 사용자 템플릿 디렉토리입니다.

skel은 skeleton(뼈대) 라는 의미로, 사용자들을 위한 기본적인 초기화 파일을 담고 있습니다.

일반적으로 사용자를 생성하게 되면 홈 디렉토리에 몇 가지 파일들이 존재함을 알 수 있습니다.

해당파일들이 바로 /etc/skel 디렉토리 안에 있는 파일들입니다. 아래는 skel 디렉토리에 파일 a, b, c, 를 만들고

새로운 유저를 생성한 뒤 해당 디렉토리를 살펴본 예제입니다.

 

# 참고서적 : 유닉스 관리자를 위한 엔터프라이즈 솔라리스 핵심 운영 가이드 / 김석, 장성균

 

posted by Red_Message
2015. 4. 1. 09:20 :: 운영체제

유닉스에서 사용자 관리는 관리자의 기본 업무이자, 보안상 가장 중요한 업무입니다.

기존의 대부분의 유닉스에서와 마찬가지로 솔라리스 10에서도 기본적으로 아래의 3가지 파일에

사용자와 그룹의 정보를 담아두고 있습니다. 또한 4번째 파일을 이용하여 패스워드를 제어합니다.

- /etc/passwd

- /etc/shadow

- /etc/group

- /etc/default/passwd

 

1. /etc/passwd

해당 파일은 사용자 정보를 담고 있는 기본 파일입니다.

편집기를 이용하여 직접 편집할 수 있으나 권장되지 않습니다. 이는 시스템에 문제를 야기할 수 있습니다.

가급적이면 명령어나 smc(sun management console)같은 도구 이용을 권장합니다.

/etc/passwd는 총 7개의 필드로 구성되어 있으며, 콜론(:)을 이용하여 각 필드를 구분합니다.

 

2. /etc/shadow

해당 파일은 사용자의 기본 파일인 /etc/passwd 파일의 두번째 필드인 x의 암호화된 패스워드가

저장되는 파일입니다. roor 사용자가 편집기를 이용하여 직접 편집할 수 있으나, 권장되지 않습니다.

/etc/shadow파일은 오직 root만이 읽기가 가능하기 때문에 사용자 패스워드 보안을 위해 사용됩니다.

/etc/shadow 파일은 총 9개의 필드로 구성되어 있습니다.

패스워드뿐만 아니라 패스워드의 사용 기간 등을 제한할 수 있습니다.

 

3. /etc/group

해당 파일은 기본 시스템의 그룹을 정의합니다. 새로운 그룹을 추가하거나 삭제할 때 사용됩니다.

솔라리스 시스템 내의 각 사용자들은 최소한 한 그룹에 속해야 하며,

사용자들은 사용자의 주 그룹을 참조하게 됩니다.

사용자의 주 그룹은 /etc/passwd에 GID필드에서 정의되어지며,

보조그룹은 최대 15개의 그룹에 속할 수 있습니다.

/etc/group 파일은 총 4개 필드로 구성되어 있습니다.

 

4. /etc/default/passwd

해당 파일은 시스템 내의 모든 사용자 패스워드 제어를 위한 몇 가지 파라미터를 설정할 수 있는 파일입니다.

또한 시스템 전역적으로 패스워드 에이징을 설정할 수 있습니다.

모든 사용자들에게 공통적으로 적용되며, 개별 사용자별로 /etc/shadow에 설정하지 않았다면

이 파일의 영향을 받습니다.

/etc/shadow 파일과 내용이 상출될 경우 /etc/shadow파일의 내용을 우선합니다.

# 참고서적 : 유닉스 관리자를 위한 엔터프라이즈 솔라리스 핵심 운영 가이드 / 김석, 장성균

 

posted by Red_Message

안녕하세요~ Message 입니다. 오늘은~

<실전 악성코드와 멀웨어 분석> 책의 실습 문제 3-2 을 분석해보고자 합니다.

분석환경은 동일하게  Windows XP / Vmware 9.0.3 build 입니다.

 

1. 악성코드가 설치된 방법

분석할 파일은 Lab03-02.dll 입니다. 실행파일이 아닌 DLL이 어떻게 설치되었을까요?

일단 주어진 유일한 파일이 DLL파일이니, 분석해보면 답이 나올 것 같습니다.

DWalker로 어떤 Export 함수가 있는지 확인해볼까요.

우리가 알아내야 하는 [어떻게 설치가 되었는가]에 대한 실마리가 보입니다.

바로 Install과 installA 두개의 함수인데요(대소문자주의),

installA 함수를 살펴보니 별다른 기능은 없이 Install 함수를 호출하는군요.(아래 스샷 참고)

따라서 CMD창에서 아래와같이 입력해주면 설치가 되는데요(Rundll32.exe를 이용하여 설치하는 방법은 교재에 나와있습니다.)

대소문자 주의해주세요.(installA 맨앞에 i가 소문자임)

아, 그리고 실행하기 전에 RegShot, ProcMon, SystemExplorer, WinAlysis와 같은 스냅샷 기능의 툴을 실행시켜줍니다.

이렇게해서 악성코드가 설치가 되었습니다. 다음으로 슝슝~

 

2 . 설치후 실행방법과 동작 프로세스

어떻게 실행이 되는지 알아봅시다. 설치전에 시켜놓은 스냅샷을 비교해볼까요?

아래 스샷은 SystemExplorer를 이용하여 스냅샷을 비교한 내용인데요,

스냅샷을 비교해보니 HKLM 하이브(루트키) 내부의 Service 레지스트리 키값 아래로 

폴더모양의 IPRIP 레지스트리 키(Key)가 새로 생성되었고

IPRIP 키 안에 10개의 값들이 추가된것을 확인할 수 있었습니다.

아마도 실행시키면 레지스트리 서비스(IPRIP) 내부에 본인을 설치하는 악성코드인 모양입니다.

그런데 왜 악성코드는 레지스트리 서비스에 본인을 설치했을까요?

그것에 대한 힌트는 ImagePath 값에 있는 "svchost.exe -k netsvcs" 부분입니다.(아래 svchost.exe 세부내용 참조)

 

[svchost.exe에 대하여...]

우리가 흔히 보았던 svchost.exe 프로그램은 윈도우즈 서비스를 백그라운드로 구동하는 프로세스입니다.

원래 윈도우즈 서비스는  WinLogon.exe가 호출하는 Services.exe(SCM:서비스제어관리자)에 의해 관리되지만

예외적으로 DLL 기반의 서비스는 svchost.exe에 의해 관리됩니다.

(우리가 실습중인 악성코드는 DLL 기반이기 때문에 svchost에 의해 실행되겠네요 ^^) 

svchost.exe는 윈도우 부팅시 윈도우 레지스트리의 Service항목을 검색하여 목록을 만듭니다.

HKLM\System\CurrentControlSet\Services 내부에 등록된 서비스에서 Parameters 키 내부에 ServiceDLL값이

들어 있는 서비스들을 묶어 실행시킬 목록을 형성합니다. 이해가 안간다면 우리가 분석중인 악성코드를 통해 이해해봅시다.

아니나 다를까, IPRIP 서비스이름 아래 Parameters 키가 뙇!! 그 안에 ServiceDll이 뙇!!

따라서 이 악성코드는 이제 svchost.exe가 자동으로 실행시켜주는 서비스 기반의 악성코드로 판별이 되었습니다.

재부팅후 ProcessExplorer 메뉴탭에서 [Find] - [Find Handle or DLL] 기능을 이용하여 Lab03-02.dll을 검색하면

svchost.exe가 로드하여 사용하고 있음을 볼 수 있습니다.

(교재에서는 재부팅을 하지 않기 위해 CMD 에서 서비스를 구동할 수 있는 net 명령어로 실행시킵니다 : net start IPRIP)

 

[조금더..분석 - 정적분석]

우리가 분석중인 악성코드의 경우 서비스에 등록이 되어 시스템으로부터 호출이 되는 형식으로 분석이 되었는데요,

그렇다면 우리가 설치하기 위해 사용했던 InstallA 함수 이외에, 운영체제가 접근하는 콜백함수가 있어야합니다.

( *콜백함수 : 호출되는 함수를 알려주어 특정 이벤트 발생시 모듈이나 OS 등에서 함수를 호출할 수 있도록 하는 방법)

DLL을 이용하여 서비스에 등록을 하기 위해서는 main()에서 서비스 관리를 위한 ServiceMain()을 콜백으로 등록해야합니다.

우리 악성코드는 어떻게 등록되어 있을까요? 아래 스샷을 참조해봅시다.

 

 

 

3. 호스트기반 표시자 및 정보를 수집하는 ProcMon을 사용하기 위해 사용한 필터

악성코드를 실행하기 전 ProcMon을 실행시켜 놓았다면 ProcessExplorer에서 수집한 PID를 이용하여

필터를 적용할 수 있습니다.  위에서는 재부팅을 통하여 svchost가 악성코드를 실행시키는것을 관찰했지만

ProcMon을 통한 관찰을 위해 교재에서 사용한 CMD - net 명령어를 이용하겠습니다.

실행이 되면 아래와 같이 우리가 레지스트리 편집기에서 발견하였던 익숙한 텍스트가 우리를 반겨줍니다 (ㅋㅋ)

이후에 ProcessExplorer를 이용하여 알아낸 PID를 ProcMon 필터에 적용해줍니다.(PID는 시스템 or 재부팅시 다를 수 있음.)

 

5. 악성코드에서 유용한 네트워크 기반 시그니처

ApateDNS를 이용하면 아래와 같이 악성코드의 DNS 요청을 확인할 수 있습니다.

또한 NetCat을 이용하여 아래와 같은 네트워크 시그니처를 관찰할 수 있습니다.

 

posted by Red_Message

안녕하세요~ Message 입니다. 오늘은~

<실전 악성코드와 멀웨어 분석> 책의 실습 문제 3-1 을 분석해보고자 합니다.

(책에서도 1-4 다음에 바로 3-1로 넘어갑니다)

분석환경은 동일하게  Windows XP / Vmware 9.0.3 build 이며,

사용되는 툴은 Dependency Walker, Strings, ProcMon, Process Explore, ApateDNS, NetCat 입니다.

 

1. 악성코드의 임포트 함수와 문자열 확인하기

DWalker를 통하여 임포트된 DLL을 살펴보겠습니다.

근데..임포트 함수라고는 딸랑 ExitProcess 하나뿐이군요. 정상적인 기능을 수행하는 프로그램이라면 이런 경우는 거의 없지요.

아마 패킹이 되어 있는것 같습니다.

PEID로 확인해본 결과 PEncrypt 3.1 Final -> junkcode 로 패킹이 되어있네요.

패킹을 해제하기 전까지는 정적분석이 안될것 같습니다.

마지막으로 Strings를 이용하여 문자열을 확인해봅시다.

......(중략).....

패킹이 되어있기 때문에 문자열에서 많은 기대를 할 순 없지만, 마지막 부분에서

WinVMX32, VideoDriver, vmx32to64.exe, 레지스트리 위치, 도메인명 등의 주요 단서들이 발견되었습니다.

동적분석 & 정적분석을 통해 이 단서들이 어떻게 사용되는지 알아봅시다.

 

2. 악성코드임을 의미하는 호스트 기반 표시자 확인하기

악성코드 실행전 동적 분석을 위한 툴을 실행시킵니다. : ProcMon, Process Explore

 

[Process Explorer]

악성코드를 실행시키고 Process Explorer로 체크합니다.

Lab03-01를 클릭하고 메뉴탭에서 [View] - [Lower Pane View] - [Handles]를 선택합니다.

이 뷰를 통하여 해당 악성코드가 뮤텍스(Mutant)를 생성하는 것을 알 수 있습니다.

숙련된 리버서 혹은 악성코드 분석가라면 뮤텍스를 생성을 바로 알아차리겠지만 공부하는 입장에서는

별다른게 없네~ 하고 그냥 넘어갈 확률이 높겠네요 ^^; (ㅜㅜ)

다음으로  [View] - [Lower Pane View] - [DLLs]를 선택합니다.

지난 실습에서 보았던 ws2_32.dll과 같은 네트워크에 관련된 DLL이 보입니다만...

DLL이 한두개도 아니고...매의 눈으로 관찰해야 보이겠녜요.

아마 경험이 쌓이면 익숙해지겠죠?

 

[ProcMon]

ProcMon을 실행시킨 후 악성코드를 실행시키면 Lab03-01.exe의 행동이 기록됩니다.

하지만 다른 프로세스들의 결과와 함께 나올 뿐만 아니라 Lab03-01.exe의 데이터만 해도 너무 많은 리스트가 나와

눈을 어지럽힙니다. 따라서 우리가 원하는 부분만을 보려면 FIlter 기능을 이용해야합니다.

메뉴탭에서 [Filter] - [Filter]에 들어가서 아래와 같이 설정합니다.

(다른 필터들을 전부 [Remove] 시켰더니 아예 결과값이 안나오더군요, 기존 필터는 그대로 유지하고, 추가 필터 3개만 [Add] 합니다.)

- Process Name : Lab03-01.exe

- Operation : WriteFIle

- Operation : RegSetValue

이렇게 필터를 만든 후 [Apply]를 누르면 총 10개의 결과값을 볼 수 있습니다.

 그중 주요 항목은 아래와 같습니다.

- WriteFile : C:\WINDOWS\system32\vmx32to64.exe (악성코드의 복사)

- RegSetValue : HKLM\SOFT\Microsoft\Windows\CurrentVersion\Run\VideoDriver (윈도우 부팅시 자동실행)

 

3. 악성코드를 인식할 수 있는 네트워크 기반의 시그니처 확인하기

 악성코드 실행전 동적 분석을 위한 툴을 실행시킵니다. : ApateDNS, NetCat

 

[ApateDNS]

ApateDNS를 실행시킨 후 [Start Server]를 클릭하면 악성코드가 주기적으로

www.prac..(중략)..anlysis.com 으로 DNS 요청을 하는것을 볼 수 있습니다.

(혹시 Vmware XP에서 초기화 오류가 발생하는경우 MicroSoft Frame Work가 설치 안됐을 가능성이 높습니다.)

 

[NetCat]

ApteDNS에서 DNS Reply IP를 이용하면 NetCat을 이용하여 리다이렉션된 내용을 볼 수 있습니다.

(요거 설정 안된줄도 모르고 한참을...ㅠ)

리스닝모드로(-l) 포트443번(-p)을 모니터링하게되면 전송되는 256바이트의 무작위 데이터를 볼 수 있습니다.

 

 

 

posted by Red_Message
2014. 1. 29. 16:13 :: 리버싱

안녕하세요. Seek입니다.

오늘은 디버깅할 때 큰 도움이 되는 내용을 공부하고자 합니다.

바로 스택 프레임입니다. 스택 프레임을 이해하고 나면 스택에 저장된 함수 변수와 함수 로컬 변수 등이 쉽게 파악되기 때문에 디버깅에 큰 도움이 됩니다.

 

1. 스택 프레임

 

   스택 프레임이란 ESP(스택 포인터)가 아닌 EBP(베이스 포인터)레지스터를 사용하여 스택 내의 로컬 변수, 파라미터, 복귀 주소에 접근하는 기법을 말합니다.

 

   ESP 레지스터는 스택 포인터 역할을 하고, EBP 레지스터는 베이스 포인터 역할을 합니다. ESP 레지스터의 값은 프로그램 안에서 수시로 변경되기 때문에 스택에 저장된 변수, 파라미터에 접근하고자 할 때 ESP 값을 기준으로 하면 프로그램을 만들기 어려워집니다. 따라서 어떤 기준 시점(함수 시작)의 ESP 값을 EBP에 저장하고 이를 함수 내에서 유지해주면, ESP 값이 아무리 바뀌어도 EBP를 기준으로 안전하게 해당 함수의 변수, 파라미터, 복귀 주소에 접근할 수 있게 되는 것입니다. 이러한 역할을 하는것이 EBP레지스터입니다.

 

<스택 프레임의 구조>

스택 프레임을 어셈블리 언어로 보았을때 이런 구조로 되어 있습니다. 

 PUSH EBP

; 함수시작(EBP를 사용하기 전에 초기 값을 스택에 저장) 

 MOV EBP, ESP  

; 현재의 ESP를 EBP에 저장 

 

 

 ...

; 함수의 본체 

; 여기서 ESP가 변경되더라도 EBP가 변경되지 않으므로 

; 안전하게 로컬변수와 파라미터를 엑세스할 수 있음 

 

 

 MOV ESP, EBP 

; ESP를 정리(함수가 시작했을 때의 초기값으로 복원) 

 POP EBP

; 리턴되기 전에 저장해 놓았떤 원래 EBP 값으로 복원

 RETN

; 함수 종료 

이런 식으로 스택 프레임을 이용해서 관리를 한다면 아무리 함수 호출이 복잡해져도 스택을 완벽하게 관리할 수 있게 됩니다.

 

 

 

2. Stackframe.exe(실습 예제)

 

   실습예제를 통해 실제 어떻게 동작하는지 보도록 하겠습니다.

 

<C언어 실습 코드>

 

<OllyDbg로 실습 프로그램 실행> / 잘안보이시면 클릭해서 확대해서 보세요~

 

실행시키시고 Ctrl + G로 40100 주소로 간 화면입니다. 그곳에 add()함수와 main()함수가 있기 때문입니다.

 

2-1. main()함수 시작 & 스택 프레임 생성

  

C언어코드에서 아래 코드 부분에 해당되는 내용입니다.

 

 

<main()>

 

main()함수 401020에 BP(Break Point)를 설치[F2]한 후 실행[F9]하시면 BP에서 멈출 것입니다.

 

<main() 함수 시작 시 스택의 상태> 

 

위 사진에서 ESP=18FF44, EBP=18FF88 입니다. 특히 ESP(18FF44)에 저장된 값 401250은 main()함수의 실행 이 끝난 후 돌아갈 리턴 주소입니다.

 

이제 한줄씩 알아보도록 하겠습니다.

 

[ 00401020    PUSH EBP ]

   main()함수는 시작하자마자 스택 프레임을 생성합니다. PUSH EBP는 'EBP 값을 스택에 집어널어라'입니다. main()함수에서 EBP가 베이스 포인터 역할을 하게 되니 EBP가 이전에 가지고 있던 값을 스택에 백업을 한다고 생각하시면 됩니다.

(main()함수 마지막에 함수가 종료되기 전에 이 값을 회복시키는것을 보실수 있을겁니다.)

 

[ 00401021     MOV EBP, ESP ]

   'ESP의 값을 EBP로 옮겨라'라는 의미입니다. 이 명령 이후에는 EBP는 ESP와 같은 값을 가지게 됩니다. 그리고 main()함수 끝날 때까지 EBP는 값이 고정됩니다. 이 의미는 스택에 저장된 함수 파라미터와 로컬 변수들을 EBP를 통하여 접근하겠다는 것입니다.

   401020과 401021의 주소에 두 명령어에 의해서 main()함수의 대한 스택 프레임이 생성되었습니다.

 

   OllyDbg에서 오른쪽 아래 스택창에서 우클릭후 'Address'에서 'Relative to EBP'를 선택해 주시면 이제부터 EBP의 위치를 확일하여 어떻게 이동하고 변동되는지 확인하실 수 있습니다.(EBP 기준으로 표시되기 때문에 좀 더 이해에 도움이 될것입니다.)

 

<main()에서 스택프레임 생성 후 초기 값>

 

 

위 사진을 보시면 ESP, EBP는 18FF40으로 동일해졌습니다. 이유는 처음 EBP인 18FF88 주소를 PUSH 명령어로 스택 18FF40에 저장하면서 ESP가 18FF40을 가르키게되었고 사진에서 보시면 18FF40에 18FF88이 들어가 있는것을 확인하실 수 있습니다. 그리고 MOV명령어로 ESP(18FF40)를 EBP에 옮겼기 때문에 두개의 값이 같아 졌습니다. (18FF88은 EBP가 함수 시작할 때 가지고 있던 초기값입니다.)

 

2-2. 로컬 변수 세팅

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다. 

 

스택에 두 로컨 변수인 a, b를 위한 공간을 만들고 값을 입력하는 부분입니다.

 

[ 401023     SUB ESP, 8 ]

    SUB는 빼기 명령어 입니다. 'ESP 값에서 8을 빼라' 라는 의미입니다. 현재 ESP는 18FF40입니다.( 사진<main()에서 스택프레임 생성 후 초기 값> 참고) 왜 ESP에서 8을 빼는 빼냐면 함수의 로컬 변수는 스택에 저장된다고 했습니다. 그리고 a, b는 long 타입으로 각각 4바이트 크기이고 2개의 변수를 스택에 저장해야되기 때문에 8바이트가 필요함으로 8을 빼서 두 변수에게 필요한 메모리 공간을 미리 확보해두는 의미입니다. 이렇게 미리 확보 해두었기 때문에 이제부턴 ESP 값이 변해도 a, b의 메모리 공간은 훼손되지 않게 됩니다.

 

[ 401026     MOV DWORD PTR SS:[EBP-4], 1] ; a

[ 40102D     MOV DWORD PTR SS:[EBP-8], 2] ; b

   위 명령어를 해석하면 'EBP-4 주소에서 4바이트 크기의 메모리에 1을 넣고, EBP-8 주소에서 4바이트 크기의 메모리에 2를 넣어라' 가 됩니다. 즉, EBP-4는 a를 의미하게되고 EBP-8은 b를 의미하게 됩니다. 여기까지 실행 결과 사진이 아래의 사진입니다. 

 

위 사진을 보시면 EBP는 계속 18FF40을 유지하고 있는것을 확인하시면서 넘어가시면 되겠습니다. 그리고 EBP-4에 1이 EBP-8에 2가 저장되어 있는 것을 확인하실수 있습니다.

  

2-3. add()함수 파라미터 입력 및 add()함수 호출

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다. 

 

 

[ 401034     MOV EAX, DWORD PTR SS:[EBP-8] ] ; b

[ 401037     PUSH EAX ]

[ 401038     MOV ECX, DWORD PTR SS:[EBP-4] ] ; a

[ 40103B     PUSH ECX ]

[ 40103C     CALL 00401000]

 

위에 5줄의 코드는 a, b를 스택에 넣어주고 add()함수를 호출하는 코드입니다. 한줄식 살펴보도록 하겠습니다. 401034는 EBP-8에 있는 b를 EAX 레지스터에 넣어주라는 의미입니다. 그다음줄 401037에서는 b가 들어가있는 EAX를 스택에 넣어주게 됩니다. 401038에서는 a(EBP-4)를 ECX에 넣어주게 되며 4번쨰 줄은 역시 a가 들어가있는 ECX를 스택에 넣게 됩니다. 마지막 40103C에서는 401000을 불러오라고 되있는데 401000은 add()함수를 의미합니다.

 

여기서 보시면 a를 먼저 꺼내서 스택에 넣는것이 아니라 b를 먼저 꺼내서 스택에 넣으라고 되어있습니다. C언어와는 반대로 저장이 되는데 이것을

'함수 파라미터의 역순 저장'이라고 합니다. 이렇게 거꾸로 넣어야지 나중에 다시 꺼낼때 C언어와 동일한 순서로 불러올수 있게 되는것입니다.

 

아래 사진은 여기까지 실행한 결과입니다. 

 

이제는 add()함수 401000 으로 가보겠습니다.

 

 

가기전에 CALL 명령어가 실행되어서 다른 함수로 가기전에 다시 돌아와야할 주소(복귀주소:return address)를 CPU는 무조건 스택에 저장합니다. 위 사진을 보시면 빨강색 표시된 부분을 보시면 40103C에서 CALL 401000 이 되어서 add()함수로 가라고 되어있습니다. 그리고 다음줄 401041주소가 add()함수가 끝나고 돌아와야할 복귀주소입니다.

 

그래서 아래의 사진은 CALL 명령이 실행되고 난 후 스택의 모습입니다. 

 

EBP-14에 복귀주소인 401041 주소가 들어가어 있는것을 보실 수 있습니다.

 

2-4. add()함수 시작 & 스택 프레임 생성

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다.  

 

 

add()함수에서도 역시 함수가 시작될때 자신만의 스택프레임을 생성하게 됩니다.

[ 401000     PUSH EBP ]

[ 401001     MOV EBP, ESP ]

 

 

위 2둘을 실행하고 add()함수에서 스택프레임이 생성된 결과입니다. 보시면 main()에서 사용하던 18FF40 의 값이 백업되어서 스택에 새롭게 저장이되고 이것을 가르키고 있는 새로운 주소 18FF28으로 EBP와 ESP가 다시 세팅된것을 보실수 있습니다.

 

2-5. add() 함수의 로컬 변수(x, y) 세팅

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다. 

 

이제 main함수에서 넘어온 a, b를 x, y에 대입하게 됩니다.

 

[ 401003     SUB ESP, 8 ]

 

변수 x, y에 대한 스택에서 메모리를 확보하기 위한 코드입니다.

 

[ 401006     MOV EAX, DWORD PTR SS:[EBP+8] ]

[ 401009     MOV DWORD PTR SS:[EBP-8], EAX ]

[ 40100C    MOV ECX, DWORD PTR SS:[EBP+C] ]

[ 40100F    MOV DWORD PTR SS:[EBP-4], ECX ]

 

add() 함수에서 새롭게 스택프레임이 생성되면서 EBP 값이 변했습니다. EBP+8 은 a를 가리키고, EBP+C는 b를 가리킵니다.

그리고 EBP-8은 x, EBP-4는 ,y를 의미합니다. 아래 사진은 여기까지 실행시킨 스택의 모습입니다.

 

 

위 사진에서 보시면 설명이 이해가 가실껍니다. EBP+8 는 1이 저장되어있는 a, EBP+C는 2가 저장되어있는 b를 의미합니다. 그리고 add()함수 시작 할 때 새롭게 지정된 EBP 기준으로 위로 EBP-8이 x, EBP-4가 ,y 가 됩니다.

 

2-6. ADD연산

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다. 

 

[ 401012     MOV EAX, DWORD PTR SS:[EBP-8] ]

EAX에 변수 x의 값(EBP-8 = 1)을 넣습니다.

 

[ 401015     ADD EAX, DWORD PTR SS:[EBP-4] ]

의미는 'EAX에 변수 y(EBP-4 = 2)를 더하라'입니다. 그러면 이 윗줄에서 이미 EAX에 1을 넣었고 거기에 방금 2를 더했으니 EAX에는 3이란 값이 들어있게 됩니다.

 

참고로 EAX는 레지스터를 예전에 설명할때 산술 연산에 사용되며 다른 특수 용도로는 리턴 값으로 사용된다고 설명하였습니다. 지금 처럼 리턴 직전에 EAX에 값이 입력되어 있다면 그대로 리턴 값으로 출력되게 됩니다.

 

지금까지의 실행시키고 스택의 변화는 없습니다. 이유는 방금 2줄의 코드에서는 스택을 변경한것은 없기때문에 EAX의 변화만 있었고 스택의 변화는 없습니다.

 

2-7. add() 함수의 스택 프레임 해제 & 함수 종료(리턴)

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다. 

 

위 2-6이랑 같은 코드 부분입니다. 이제 더하기 연산은 종료가 되어 최종적인 값인 3이 EAX에 저장되어있습니다. 다시 설명드리면 EAX는 리턴 값으로 사용됩니다.

 

이제 리턴되기 전에 스택프레임을 해제해야 합니다.

 

[ 401018     MOV ESP, EBP ]

 

함수 시작당시의 값으로 ESP를 원래 상태로 돌려 놓는것입니다. 함수 초반에 EBP를 스택에 저장하여 백업하고 ESP를 EBP 넣었었습니다. 이제 다시 복구하는 것이죠. 그리고 EBP가 ESP로 들어가면서 x, y 변수를 만들때 사용된 [SUB ESP, 8] 이란 명령어의 효과는 사라집니다. ESP는 현재의 스택의 끝을 나타내니까 다시 떙겨졌다고 생각하면 되겠습니다. 아래 사진을 보시면 이해되실거 같습니다.

 

 

검정색 표시된 부분이 ESP입니다. 그리고 그위에 두줄이 아까 지정하고 add 연산에 사용된 변수 x, y의 스택 메모리 공간입니다. 방금 명령으로 인해 아래로 댕겨졌으니 저 두 변수의 공간은 무효해진것이라 생각하시면 됩니다.

 

[ 40101A     POP EBP ]

 

마지막으로 add() 시작 시에 백업했던 EBP의 값을 스택에서 꺼내서 복원합니다. 복원 값은 18FF40이 되겠네요.

< POP EBP 명령 실행 전 > 

< POP EBP 명령 실행 후 > 

 

 

 

 

 

아래 사진은 스택 프레임을 해제난 후에 결과 사진입니다. 

 

ESP = 18FF2C 에 저장되어 있는 값인 401041 은 앞에서 main()함수에서 CALL 401000 명령으로 CPU가 스택에 입력한 복귀 주소입니다. EBP는 백업했던 값으로 돌아온것을 확인 할 수 있습니다. 즉, add()함수가 호출되기 직전의 상태로 돌아온것입니다.

 

[ 40101B     RETN ]

 

위 명령이 실행되면 저장되어있던 복귀 주소 401041 로 돌아오게 됩니다. 아래 사진은 그 결과입니다. 

 

이 사진과 add()함수에 들어가기 직전인 CALL 명령이 실행되기 전의 사진(2-3 부분을 보시면 있는 사진)과 비교해보세요. 스택의 값이 동일합니다.

 

프로그램들은 이런 식으로 스택을 관리하기 때문에 함수 호출등이 계속 충첩되더라도 스택이 겹치거나 깨지지 않고 잘 유지 됩니다.

 

2-8. add() 함수 파라미터 제거(스택 정리)

 

이제 RETN 명령으로 인하여 main()함수로 돌아왔습니다.

 

[ 401041     ADD ESP, 8 ]

 

위쪽에 401041으로 돌아온 결과 사진을 보시면 EBP-10, EBP-C 가 있습니다. 이는 add()함수로 넘겨준 a, b입니다. 이제는 add()함수가 완전히 종료되었기 때문에 필요가 없습니다. 그래서 현재 ESP인 18FF30에 8을 더하여 두변수를 제거하면서 스택을 정리해주는 것입니다.(a, b 파라미터는 각각 long 타입으로 4바이트이기 때문에 8바이트입니다.)

 

여기까지 실행 후 스택의 결과사진입니다. 

 

EBP-10, EBP-C가 당겨진 모습입니다. 두 변수가 사라진것이죠.

 

2-9. printf() 함수 호출

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다. 

 

 

[ 401044     PUSH EAX ]

[ 401045     PUSH 0040B384 ]

[ 40104A    CALL 00401067 ]

[ 40104F    ADD ESP, 8 ]

 

첫번째 명령 PUSH EAX 는 add()함수에서 넘어온 리턴값인 "3"입니다. 즉, 의미는 'EAX인 3을 스택에 넣어라'가 되겠죠. 그 다음줄은 위에 C언어 코드를 보시면 printf 함수에 파라미터가 2개인것을 보실 수 있습니다. "%d\n" 와 add(a,b) 이렇게 2개입니다.

첫번째 명령인 EAX를 스택에 넣는 것은 printf 에서 add(a, b) 인자가 되겠지요. 그리고 다음중 PUSH 0040B384 은 %d\n 를 의미한다고 알아두시면 될거 같습니다.

 

즉, 파라미터가 2개이니까 두번 PUSH를 한다. 그중에 하나는 저희가 알고 있고 지금까지 계산해온 EAX를 넣는다. 이정도가 되겠습니다. 

PUSH EAX, PUSH 40B384 이 2개 명령을 실행 한 결과 사진입니다. 

printf 함수가 호출 되기전의 스택의 모습에서 추가가 된것을 보실수 있습니다.

EBP-C 에 EAX가 들어갔으니 3이 들어있는 모습

EBP-10 에 %d\n이 들어간 모습 입니다.

 

3번째 줄은 CALL 401067 으로 바로 printf 함수를 불러오는 것입니다. 2개의 파라미터를 앞에서 스택에 넣어줬으니 마지막에 함수호출을 하여 넣어둔 2개의 파라미터를 이용하면 되겠지요?

 

여기서 401067 은 Visual C++ 에서 생성한 C표준 라이브러리 입니다. 이부분은 이정도만 코멘트하고 넘어 가겠습니다.

 

그리고 마지막 4번째 줄에서 스택을 다시 정리하는 명령인것을 알수 있습니다.

3번째 줄에서 printf 함수를 호출하고 종료되고 돌아왔기 때문에 사용 완료된 스택을 정리하는 것입니다.

 

2-10. 리턴 값 세팅

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다. 

 

main() 함수의 리턴 값을 0으로 초기화 세팅해줍니다.

 

[ 401052     XOR EAX, EAX ]

 

XOR 명령은 Exclusive OR bit 연산입니다. 연산 방법은 같은 값을 XOR 하면 0이 되는 방식입니다.

여기까지 실행 한 스택 결과 사진입니다.

 

 

보시면 스택을 변동하는 명령어는 없기때문에 그대로이고 EAX가 0으로 바뀐것을 보실수 있습니다. 위에서 리턴값은 EAX의 값이라고 했습니다. 목적 역시 main()함수까지도 끝났으니 리턴값을 초기화 0으로 세팅한다고 했으니 원하는대로 0으로 세팅된것을 확인할 수 있습니다.

 

2-11. main() 함수 스택 프레임 해제 & main() 함수 종료

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다.  

 

main()함수가 종료되는 부분입니다. 위에서 했던 add()함수 종료시에 했던 스택 프레임 해제와 동일합니다.

 

[ 401054     MOV ESP, EBP ]

[ 401056     POP EBP ]

 

위 두함수로 인하여 스택프레임은 해제 되었습니다. 동시에 main()함수의 변수인 a, b 역시 더이상 무효하게 되었습니다. 여기까지 실행한 후 스택의 결과 사진입니다.

어디서 많이 본 스택의 모습이지 않습니까? 기억 못하시겠지만 main()함수 시작할 때 스택의 모습입니다.

2-1<main() 함수 시작 시 스택의 상태> 사진을 보시면 동일하다는 것을 확인할 수 있습니다.

기억은 못하시더라도 이런식으로 main() 함수가 종료되면서 스택프레임을 해제하면 초기의 스택모습으로 복원 된다는것을 계속해서 인식하면 될것입니다.

 

[ 401057     RETN ]

 

이제 main()함수가 종료되면서 리턴주소인 401250 으로 돌아가게(점프) 됩니다. 이러함으로써 프로그램이 종료되게 됩니다.

 

3. 마무리

 

스택 프레임을 간단히 설명하자면 수시로 변경되는 ESP 레지스터 대신 EBP 레지스터를 사용하여 로컨변수, 파라미터, 복귀 주소 등을 관리하는 방법입니다. 아마 여기까지 잘 이해하셨다면 디버깅에 대해 조금 자신감(?)이 생겼을거라 생각됩니다. 저도 이부분을 공부하면서 함수의 로컬변수, 파라미터, 복귀주소, 리턴 값 등이 어떻게 처리되고 어떻게 동작하는 원리를 이해했기 때문입니다. 많은 도움 되셨기를 바랍니다.

 

 

참고서적 : 리버싱 핵심 원리 / 저자 : 이승원

 

':: 리버싱' 카테고리의 다른 글

UPack PE 헤더 분석 + 디버깅  (1) 2016.12.31
[정보 보안 개론] Chapter 06 악성코드  (0) 2016.11.07
[Red_Seek] abex' crackme #1 (크랙미 #1)  (0) 2014.01.23
[Red_Seek] 스택  (0) 2014.01.22
[Red_Seek] IA-32 Register  (0) 2014.01.14
posted by Red_Seek
2014. 1. 23. 15:45 :: 리버싱

드디어 처음으로 하는 실습입니다ㅎㅎ

제목처럼 해당 파일을 크랙해보라고 도발(?)하는 이름이네요...

크랙미는 크랙 연습을 목적으로 작성되어 공개된 프로그램입니다. 그리고 매우 간단해서 초보자에게 알맞습니다. (저같은..)

그럼 이제 그 도발에 넘어가서 크랙을 해보도록 하겠습니다.

 

 

1. abex' crackme #1

 

   크랙을 하기전에 디버깅을 하기전에 어떤 프로그램인지 알아보록하겠습니다.

 

위 사진이 실행되며 확인 버튼을 눌러보면 아래와 같은 문구가 나옵니다.

 

에러메시지가 뜨네요. 저희가 크랙해야될 내용이 보입니다. HD(하드디스크)가 CD-ROM으로 인식되게 하면 되겠네요.

디버깅을 해보도록 하겠습니다.

 

실행시켜보니 이제까지 디버깅해왔던 HelloWorld.exe와는 코드길이 엄청나게 차이가 나네요. 이유는 지금 하고있는 abex crackme는 어셈블리언어로 만들어진파일이라 코드 길이가 짧습니다. HelloWorld는 C언어등의 언어로 작성된것을 어셈블리로 변환된것이라 긴것입니다.(잘안보이시면 사진을 클릭하여 크게 보세요~)

 

 

본론으로 돌아와서 코드분석을 해보겠습니다. (아래 사진 잘 안보이시면 클릭하여 크게 보세요~)

위 사진처럼 분석해보았습니다.

 

 

참고. abex' crackme #1에서 사용된 어셈블리 명령어 

 명령어

 설명 

PUSH 

스택에 값을 입력 

CALL 

지정된 주소의 함수를 호출 

INC 

값을 1 증가 

DEC 

값을 1 감소 

JMP 

지정된 주소로 점프 

CMP 

주어진 두 개의 값을 비교

(SUB 명령어와 동일하나 값이 변경되지 않고 EFLAGS 레지스터만 변경됨,

두 값이 동일하다면 SUB 결과는 0이고 ZF = 1 로 세팅됨) 

JE 

조건 분기(Jump if Equal)

(ZF = 1 이면 점프) 

 

 

2. 크랙

 

   이제 코드를 패치해서 크랙을 해보도록 하겠습니다. 위에서 말씀드렸듯이 HD를 CD-ROM으로 인식하게 하면 되겠네요.

 

 

위 조건분기 코드를 보시면 CMP EAX, ESI를 비교를 먼저 합니다. 그 다음 JE 명령어에서 위에서 비교된 결과값을 가지고 해당 주소 40103D로 점프하라고 되어있습니다.(명령어의 설명을 위쪽 크랙미1에서 사용된 어셈블리 명령어 표를 보시면 됩니다.)

 

코드분석을 해놓은 그림을 보시면 아시겠지만 EAX, ESI는 같지 않습니다. 그러므로 CMP에서도 불일치하다는 결과값을 출력하기 때문에 JE명령어에서 일치할때 점프하라고 되어있는 주소로 점프하지 못하고 바로 다음줄로 이동하게 됩니다.

 

바로 다음줄은 조건 불일치 시 라고 명시를 해둔 곳인데 결과적으로 HD를 CD-ROM으로 인식하지 못했다는 메시지박스를 출력하게 되는겁니다.

 

저희는 이제 조건분기 부분에서 JE명령어 부분을 크랙해서 CMP에서 비교한 값이 어느값이 나오든지 바로 HD를 CD-ROM으로 인식했다는 결과가 출력되도록 해보겠습니다.

 

"JE SHOR 0040103D" 라는 명령어를 "JMP SHORT 0040103D"로 바꾸시면 되겠습니다. 바꾸시는 방법은 JE명령어 코드를 선택하시고 [Space]버튼 또는 더블클릭해서 바꾸시면 되겠습니다. 그다음 바뀐 프로그램을 새롭게 저장해야되는데요. 먼저 코드를 바꾸신 다음에 작은 팝업창을 닫으시고(JE명령어 코드만 바꾸세요...) 아무코드에서나 마우스 우클릭후에 'Copy to executable' 메뉴에서 'All modifications' 선택하세요. 그 다음 'Copy all'을 누르시면 새로운 코드파일이 생성됩니다. 거기서 마우스 우클릭 후에 'save file'를 선택하여 새롭게 저장하시고 생성되는 exe 프로그램을 실해보시면 HD를 CD-ROM으로 인식했다는 메시지박스를 보실수 있을것입니다.

 

JE 명령어 코드를 바꾸는것 이외에도 다른 부분을 패치해서 하는 방법도 있습니다. 참고하시면 되겠습니다.

 

 

3. 스택에 변수를 전달하는 방법

 

   크랙미#1을 마무리하면서 스택에 변수를 전달하는 방법에 대해 알아보고 마무리하겠습니다.

 

 

1번에서 크랙미#1 코드분석을 보시면 MessageBoxA() 함수가 3개 나옵니다. 그리고 바로 위 코드를 보시면 MessageBoxA() 함수가 실행되기전에 4번의 PUSH 명령어를 사용하여 필요한 변수를 역순으로 입력하고 있습니다.

 

위 코드를 C언어로 변형하면

[ MessageBox(NULL, "Make me think your HD is a CD-ROM.", "abex' 1st crackme", MB_OK:MB_APPLMODAL); ]

이렇게 됩니다. C언어에서는 함수에 넘기는 변수의 순서가 어셈블리 언어에서는 역순으로 넘어가는 것을 알고계세요. 이유는 스택은 FILO(First In Last Out) 구조이기 때문입니다.

 

디버거를 EIP=0040100E 시점까지 f4 키를 통해 진행한 다음 해당 스택을 보시면 위 사진처럼 보실수 있습니다.

예전에 공부했던 스택부분으로 기억하시면서 보시면 거꾸로 저장되는것을 확인할수 있습니다. 그렇게 저장을 다하고 맨마지막에 MessageBoxA()가 실행되면서 스택에 있는 변수를 꺼내게 되는데 스택의 구조는 FILO, LIFO(Last In First Out)으로 마지막에 들어간것이 처음으로 나오게 되면서 MessageBoxA()입장에서는 변수들이 순서대로 꺼내는 입장이 되게 됩니다.

 

 

이상으로 크랙미#1에 대한 공부를 마치도록 하겠습니다. 이처럼 크랙에 대해 자세히 분석해보았고 실제 실습을 해보면서 공부하며 핵심 원리를 파악하시고 많이 연습하시면 좋을거 같습니다.

 

참고서적 : 리버싱 핵심 원리 / 저자 : 이승원

 

 

':: 리버싱' 카테고리의 다른 글

[정보 보안 개론] Chapter 06 악성코드  (0) 2016.11.07
Red_Seek :: 스택 프레임  (0) 2014.01.29
[Red_Seek] 스택  (0) 2014.01.22
[Red_Seek] IA-32 Register  (0) 2014.01.14
[Red_Seek] 엔디언 표기법  (0) 2014.01.09
posted by Red_Seek
2014. 1. 22. 16:01 :: 리버싱

안녕하세요.

Seek 입니다~

 

오늘은 스택에 대해서 공부하도록 하겠습니다.

스택은 컬 변수 저장, 함수 파라미터 전달, 복귀 주소 저장 등 다양한 용도로 사용됩니다. 디버깅을 할 때 스택 메모리를 확인하는 일이 많기 때문에 스택의 동작 원리를 잘 알아두면 디버깅 실력 향상에 크게 도움이 될것입니다.

 

 

1. 스택

 

   프로세서에서 스택 메모리의 역할은 다음 세가지입니다.

   

    1) 함수 내의 로컬 변수 임시 저장

    2) 함수 호출 시 파라미터 전달

    3) 복귀 주소(return address) 저장

 

   스택의 FILO(First In Last Out) 구조가 위와 같은 역할을 수행하기에는 아주 좋습니다.

 

2. 스택의 특징

 

   프로세스에서 스택 포인터(ESP)의 초기값은 Stack Bottom쪽(스택메모리의 바닥쪽)에 가깝습니다. PUSH 명령에 의해서 Stack에 값이 추가되면 바닥부터 메모리가 저장되므로 스택 포인터는 Stack Top을 향해 바닥에서 위쪽으로 움직이고, POP 명령에 의해 스택에서 값이 제거되면 다시 스택 포인터는 Stack Bottom을 향해 아래쪽(바닥쪽)으로 움직입니다. 즉, 높은 주소에서 낮은 주소 방향으로 스택이 자라납니다. 스택 메모리에서 바닥에서 위쪽으로 값이 저장되면서 윗 방향으로 스택이 자랍니다. 이러한 스택의 특성 때문에 '스택이 거꾸로 자란다.'라는 표현을 쓰기도합니다. 이렇게 표현하는 이유는 스택이라는 단어는 뭔가를 쌓는다는 뜻이기 때문에 쌓을수록 위로 올라오는 것이 더 직관적이기 때문입니다.

 

3. 스택 동작 예제

 

   실제로 스택이 어떤 식으로 동작하는지 이해를 돕기 위해 예제를 실습해보겠습니다.

 

위 그림은 초기상태의 스택입니다. 스택포인터(ESP) 값은 18FF8C입니다. 그아래 스택 창을 보면 ESP가 가리키는 주소와 그 값을 보여줍니다. 이제 왼쪽에 PUSH 100명령을 실행해보겠습니다.

 

 

위 사진은 PUSH 100을 실행 시킨 결과입니다. 스택포인터(ESP)를 보시면 18FF8C 에서 18FF88로 4바이트만큼 줄어들어서 값이 위로 올라가신것을 보실수 있습니다. 그리고 그 아래 스택 창을 보시면 18FF88에 100이 PUSH명령에 의해 저장되어 있는 것을 보실수 있습니다.

 

즉, 스택에 값을 집어 넣었더니 ESP가 위쪽 방향으로 이동한 것을 볼 수 있습니다. (ESP값이 4만큼 줄었습니다.) 다음에는 POP EAX를 실행해서 값을 다시 꺼내보도록 하겠습니다.

 

 

위 사진은 POP EAX 명령으로 다시 스택에서 값을 꺼낸 결과를 보여주는 사진입니다. 다시 ESP는 4바이트만큼 증가하여 18FF8C로 되었고, 그 아래 스택 창을 보시면 초기 상태와 같이 바꼈습니다.

 

즉, 스택에서 값을 꺼냈더니 ESP는 아래쪽 방향으로 이동하였습니다.

 

결과입니다.

"스택에 값을 입력(PUSH)하면 스택 포인터(ESP)는 감소(위로 이동)하고, 스택에서 값을 꺼내면(POP) 스택 포인터(ESP)는 증가(아래로 이동)합니다."

 

추가로 스택 포인터의 초기 값은 스택 메로리의 맨 아래쪽에 있음을 다시 알아두기 바랍니다.

 

 

 

 

 

오늘 공부한 간단한 예제는 스택에 값이 입/출력 될 때 스택포인터의 변화를 잘 보여주고 있습니다. 응용 프로그램의 디버깅을 위하여 스택과 스택포인터의 변화를 눈여겨 보시면서 공부하시길 바랍니다.

 

 

참고서적 : 리버싱 핵심 원리 / 저자 : 이승원

 

':: 리버싱' 카테고리의 다른 글

Red_Seek :: 스택 프레임  (0) 2014.01.29
[Red_Seek] abex' crackme #1 (크랙미 #1)  (0) 2014.01.23
[Red_Seek] IA-32 Register  (0) 2014.01.14
[Red_Seek] 엔디언 표기법  (0) 2014.01.09
[Red_Seek] 리버싱(Reversing) 이란?  (0) 2014.01.08
posted by Red_Seek
2014. 1. 14. 16:17 :: 리버싱

안녕하세요~ Seek 입니다.

오늘은 IA-32 레지스터에 대해 공부해보겠습니다.

(여기서 IA-32는 Intel Architecture 32비트를 의미합니다.)

 

1. CPU 레지스터

 

  레지스터란 CPU 내부에 존재하는 다목적 저장 공간입니다. 일반적으로 메모리라고 하는 RAM과는 조금 성격이 다릅니다. CPU가 RAM에 있는 데이터를 접근하기 위해서는 물리적으로 먼 길을 돌아가기 때문에 시간이 오래 걸리니다. 하지만 레지스터는 CPU내부에 있는 저장 공간이기 때문에 고속으로 데이터를 처리할 수 있습니다.

 

  리버싱 초급 단계에서 애플리케이션 디버깅을 잘 하려면 디버거가 해석해주는(디스어셈) 어셈블리 명령어를 공부해야 합니다. 어셈블리 명령어의 대부분은 레지스터를 조작하고 그 내용을 검사하는 것들이기 때문에 정작 레지스터를 모르면 명령어 자체도 이해하기 힘들기 때문에 레지스터를 공부해야합니다. 다들 화이팅 합시다! 저도 화이팅 중입니다 ㅎ

 

2. IA-32 레지스터

 

  IA-32 레지스터는 지원하는 기능도 많고 그만큼 수도 많이 있습니다. 애플리케이션 디버깅의 초급 단계에서는 Basic program execution register에 대해서 알아두어야 합니다. 디버깅할 때 가장 많이 보게 될 레지스터입니다.

 

 2-1. Basic program execution registers

위 그림처럼 크게 4가지의 종류가 있습니다.

   - General Purpose Registers (32비트 - 8개)

   - Segment Registers (16비트 - 6개)

   - Program Status and Control Register (32비트 - 1개)

   - Instruction Pointer (32비트 - 1개)

 

(참고 : 레지스터 이름 앞에 E(Extended)가 붙은 경우는 예전 16비트 CPU인 IA-16 시절부터 존재하던 16비트크기의 레지스터들을 32비트로 확장시켰다는 뜻입니다.)

 

 

   1) 범용 레지스터(General Purpose Registers)

 

      범용 레지스터는 이름처럼 범용적으로 사용됩니다. '막'쓰는' 막'레지스터들이라고 생각하세요. 32비트(4바이트)이며, 보통 상수/주소 등을 저장할 때 주로 사용되며, 특정 어셈블리 명령어에서는 특정 레지스터를 조작하기도 합니다.

 

위 사진은 범용 레지스터입니다.

 - EAX : 0~31(32비트)

 - AX : 0~15(16비트)

 - AH : 8~15(8비트)

 - AL : 0~7(8비트)

EAX를 기준으로 간단히 설명을 드리면, 4바이트(32비트)를 다 사용하고 있을 때는 EAX를 사용하고, 2바이트(16비트)만 사용할 때는 EAX의 하위 16비트 부분인 AX를 사용하면 됩니다. AX는 다시 상위 1바이트(8비트)인 AH와 하위 1바이트(8비트)인 AL로 나뉘어집니다. 이런식으로 32비트 레지스터를 상황에 맞게 크기에 맞게 나누어 알뜰하게 사용할 수 있습니다.

 

각 레지스터의 이름입니다.

 - EAX : Accumulator for operands and results data

 - EBX : Pointer to data in the DS segment

 - ECX : Counter for string and loop operations

 - EDX : I/O pointer

위 4개의 레지스터들은 주로 산술연산(ADD, SUB, XOR, OR등) 명령어에서 상수/변수 값의 저장 용도로 많이 사용됩니다. ECX와 EAX는 특수한 용도로도 사용됩니다. ECX는 반복문 명령어(LOOP)에서 반복 카운트(loop count)로 사용됩니다.(루프 돌 때마다 1씩 감소) EAX는 일반적으로 함수 리턴 값에 사용됩니다. 모든 Win32 API함수들은 리턴 값을 EAX에 저장한 후 리턴합니다.

(참고 : Win32 API 함수들은 내부에서 ECX와 EDX를 사용합니다. 이런 API가 호출되면 ECX, EDX의 값이 변경되기 때문에 ECX, EDX에 중요한 값이 저장되어 있다면 API 호철 전에 다른 레지스터나 스택에 백업해야 됩니다.)

 

나머지 레지스터들의 이름입니다.

 - EBP : Pointer to data on the stack (in the SS segment)

 - ESI : Source pointer for string operations

 - EDI : Destination pointer for string operations

 - ESP : Stack pointer (in the SS segment)

위 4개의 레지스터들은 주로 메모리 주소를 저장하는 포인터로 사용됩니다. ESP는 스택 메모리 주소를 가리킵니다. 스택 메모리 관리는 프로그램에서 매우 중요하기 때문에 ESP를 다른 용도로 사용하지 말아야 합니다. EBP는 함수가 호출되었을 때 그 순간의 ESP를 저장하고 있다가, 함수가 리턴하기 직전에 다시 ESP에 값을 되돌려줘서 스택이 깨지지 않도록 합니다. 이것을 Stack Frame 기법이라고 합니다. ESI와 EDI는 특정 명령어들(LODS, STOS, REP MOVS등)과 함께 주로 메모리 복사에 사용됩니다.

 

 

   2) 세그먼트 레지스터(Segment Registers)

 

    세그먼트 레지스터를 이해하기 위해 세그먼트에 대해 먼저 설명하겠습니다. 세그먼트는 IA-32의 메모리 관리 모델에서 나오는 용어입니다. IA-32보호모드에서 세그먼트메모리를 조각내어 각 조각마다 시작주소, 범위, 접근권한 등을 부여해서 메모리를 보호하는 기법을 말합니다. 또한 세그먼트는 페이징 기법과 함께 가상 메모리를 실제 물리 메모리로 변경할 때 사용됩니다.

 

세그먼트 메모리는 Segment Descriptor Table(SDT)이라는 곳에 기술되어 있습니다. 세그먼트 레지스터는 바로 이 SDT의 index를 가지고 있는것입니다.

 

세그먼트 레지스터는 총 6개(CS, SS, DS, ES, FE, GS)이며 각각의 크기는 16비트(2바이트)입니다.

 - CS : Code Segment

 - SS : Stack Segment

 - DS : Data Segment

 - ES : Extra(Data) Segment

 - FS : Data Segment

 - GS : Data Segment

 

CS는 프로그램 코드 세그먼트를 나타내며, SS는 스택 세그먼트, DS는 데이터 세그먼트를 나타냅니다. ES, FS, GS 세그먼트는 추가적인 데이터 세그먼트입니다.

 

 

   3) 프로그램 상태와 컨트롤 레지스터(Program Status and Control Register)

 

    - EFLAGS : Flag Register

 

    플래그 레지스터의 이름은 EFLAGS이며 32비트(4바이트) 크기입니다. (이역시 앞에 'E'가 붙어있어서 기존 16비트에서 32비트로 확장된 형태를 의미합니다.)

 

<EFLAGS Register>

 

 

위 그림은 EFLAGS 레지스터이며 각각의 비트마다 의미를 가지고 있습니다. 각 비트는 1 또는 0의 값을 가지는데, 이는 On/Off 혹은 True/False를 의미합니다. 일부 비트는 시스템에서 직접 세팅하고, 일부 비트는 프로그램에서 사용된 명령의 수행 결과에 따라 세팅되기도 합니다.

 

리버싱 입문단계에서는 애플리케이션 디버깅에 필요한 3가지 Flag(ZF, OF, CF)에 대해서만 잘 이해하시면 됩니다.

 

 - Zero Flag(ZF)

   연산 명령 후에 결과 값이 0이 되면 ZF가 1(True)로 세팅됩니다.

 

 - Overflow Flag(OF)

   부호 있는 수(Signed integer)의 오버플로가 발생했을 때 1로 세팅됩니다. 그리고 MSB(Most Significant Bit)가 변경되었을 때도 1로 세팅됩니다.

 

 - Carry Flag(CF)

   부호 없는 수(unsigned integer)의 오버플로가 발생했을 때 1로 세팅됩니다.

 

 

   4) Instruction Pointer

 

    - EIP : Instruction Pointer

 

    Instruction Pointer는 CPU가 처리할 명령어의 주소를 나타내는 레지스터이며, 크기는 32비트(4바이트)입니다. (이역시 16비트에서 32비트로 확장 되었습니다.) CPU는 EIP에 저장된 메모리 주소의 명령어(instruction)를 하나 처리하고 난 후 자동으로 그 명령어 길이만큼 EIP를 증가시킵니다. 이런 식으로 계속 명령어를 처리해 나갑니다.

 

범용레지스터와는 다르게 EIP는 그 값을 직접 변경할 수 없도록 되어 있어서 다른 명령어를 통하여 간접적으로 변경해야 합니다. EIP를 변경하고 싶을 때에는 특정 명령어(JMP, Jcc, CALL, RET)를 사용하거나 인터럽트(interrupt), 예외(exception)를 발생 시켜야 합니다.

 

 

 

이렇게 간단하게 레지스터들에 대해서 간략히 살펴보았습니다. 레지스터는 디버깅에 기초가 되는 어셈블러 명령어에 의해 조작되기 때문에 레지스터를 잘 알면 디버깅을 하는 데 크게 도움이 될 것 입니다.

 

 

참고서적 : 리버싱 핵심 원리 / 저자 : 이승원

':: 리버싱' 카테고리의 다른 글

Red_Seek :: 스택 프레임  (0) 2014.01.29
[Red_Seek] abex' crackme #1 (크랙미 #1)  (0) 2014.01.23
[Red_Seek] 스택  (0) 2014.01.22
[Red_Seek] 엔디언 표기법  (0) 2014.01.09
[Red_Seek] 리버싱(Reversing) 이란?  (0) 2014.01.08
posted by Red_Seek

안녕하세요~ Message 입니다. 오늘은~

<실전 악성코드와 멀웨어 분석> 책의 실습 문제 1-4 을 분석해보고자 합니다.

드디어 1번 문제를 벗어날 수 있는 마지막 문제입니다 ㅎㅎ 하지만 약간 난이도 있어보입니다.(ㅜㅜ)

분석환경은 동일하게  Windows XP / Vmware 9.0.3 build 입니다.

 

1. 바이러스 토탈에서 악성코드 여부 확인하기

분석하고자 하는 파일은 책에서 제공하는 실습용 악성코드 Lab01-04.exe 입니다.

37군데에서 악성코드라고 진단하였습니다.

\

 

2. 패킹되거나 난독화된 징후 찾기, 패킹이 되어있다면 언패킹하기

PEView로 보았을 때는 각 섹션의 영역이 정상적으로 있고,

Virtual Size와 Size of Raw Data(실제크기)가 약간의 차이가 있지만 정상으로 보입니다.

 

PEID로도 확인해본 결과 아무런 패킹이 되어 있지 않네요.

 

3. 이 프로그램이 컴파일 된 시각

컴파일 된 시각을 PEVIEW로 확인해본 결과

2019년 8월 30일로 나오는군요...임의로 변경된 시간이므로 신뢰할 수 없을 것 같습니다.

 

4. 임포트를 보고 악성코드의 기능 알아내기

DWalker를 통하여 임포트된 DLL을 살펴보겠습니다.

KERNEL32.DLL, MSVCRT.DLL, ADVAPI32.DLL 총 3개의 DLL이 임포트 되었네요.

그중 ADVAPI32.DLL 이 가장 의심스럽습니다. (실습 1-2에서는 해당 DLL이 CreateServiceA 를 호출하고

부팅 시 자동으로 실행되는 윈도우 서비스를 등록하는 악성코드였죠.)

ADVAPI32.DLL이 호출하는 API 함수들을 살펴볼까요? (자세한 내용은 MSDN에서 확인하세요)

OpenProcessToken

특정 프로세스의 액세스 토큰을 가져오는 API입니다. 성공하게 되면 넣어준 인자값안에 해당 프로세스의

액세스 토큰 핸들값을 넣어줍니다. 이 토큰을 활용하여 특정행위에 대한 권한 획득이 가능합니다.

(여기서 access token은 로그인 계정에 따라 달라지겠죠? Admin, Guest 등..)

LookupPrivilegeValueA

액세스 토큰의 권한 리스트에 특정 권한이 있는지 검색해주는 API입니다.

예를들어 시스템을 무한 재부팅 시키는 악성코드라면 시스템종료를 시킬 수 있는 권한을 가져야겠죠.

아마 악성코드가 필요한 권한이 있는데, 그 권한이 해당 액세스 토큰에 있는지

알아보는데 사용될거라 예상됩니다.

AdjustTokenPrivileges

액세스 토큰에 할당되어 있는 권한을 활성/비활성화 시키는데 사용하는 API입니다.

새로운 권한을 추가하는 것은 불가능하며 해당 토큰이 가지고 있는 권한 내에서만 

활성/비활성 여부만 조정할 수 있습니다. 

 

5. 감염된 시스템에서 악성코드를 인식하는데 어떤 호스트 기반이나 네트워크 기반을 사용했는지(스압)

Strings 툴을 이용하여 해당 파일의 모든 문자열을 뽑아보았습니다.

위에서 발견한 임포트 함수들 외 악성행위를 할 것으로 의심되는 부분을 뽑아볼까요?

- GetWindowsDirectoryA

- WinExec

- GeTempPathA

- URLDownloadToFileA

- http://www.practical...(생략)...anlysis.com/updater.exe

- systemwupdmgrd.exe

이건뭐...뽑다보니 전부다 의심되는것들 투성이네요 ㅎㅎ(아래 스샷참고)

위와 같은 API들이 사용되는것을 대략적으로 파악했습니다.

 

[권한획득]

이제 임포트 함수애서 파악했던 권한에 관련된 API들부터 시작하여 차근차근 분석해볼까요, 

API들의 기능으로 보아, OpenProcessToken > LookupPrivilegeValueA > AdjustTokenPrivileges

순으로 진행이 될듯 한데요, 실제로 확인해본 결과 예측한 그대로 흘러가더군요.

올리디버그의 [우클릭 - Search for - found intermodular calls] 기능을 이용하여 해당 API들을 추적합니다.

먼저 OpenProcessToken 함수를 살펴볼까요? (아래 스샷 참조)

hProcess

프로세스의 핸들값, GetCurrentProcess를 이용하여 현재 프로세스의 핸들을 구하고 리턴값(EAX)을 넣어주네요.

DesiredAccess

원하는 접근 유형입니다. TOKEN_QUERYTOKEN_ADJUST_PRIVILEGES을 인자값으로 넣었네요.

잠시후 보게될 AdjustTokenPrivileges 함수에서는 토큰 권한을 수정할때 TOKEN_ADJUST_PRIVILEGES가 필요하며,

해당함수의 다섯번째 인수인 PreviousState 매개변수가 NULL일 경우 TOKEN_QUERY까지 필요합니다.

TokenHandle

반환되는 토큰의 핸들값을 받을 인자입니다.

 

다음은  LookupPrivilegeValue 함수를 살펴보겠습니다.

lpSystemName

시스템의 이름입니다.

lpName

어떤 권한이 있는지 확인하는 인자입니다. 이 인자값에서 악성코드 제작자의 의도를 알 수 있겠죠.

여기서는 "SeDebugPrivilege" 가 들어갔음을 볼 수 있는데요, 디버그 권한을 가진 프로그램은

프로세스 핸들을 이용하여 OpenProcess 나 OpenThread API를 사용할때 보안검사를 통과할 수 있게됩니다.

이 악성코드의 흐름상 권한을 획득한 뒤 바로 파일 생성 및 수정을 위해 OpenProcess가 사용됩니다.

아마 이를 위한 사전절차가 되겠네요.

lpLuid

반환되는 LUID(Locally Unique Identifier) 식별자를 받을 인자입니다. 시스템에서는 특권을 LUID로 구분합니다.

프로세스 핸들의 개념과 동일하게 보시면 되겠네요.

 

다음은  AdjustTokenPrivileges 함수를 살펴보겠습니다.

TokenHandle

권한을 변경하고자 하는 토큰의 핸들입니다.

DisableAllPrivileges

TRUE일경우 모든 권한을 disable로 설정합니다. TRUE일경우 아래의 Newstate값의 영향을 받습니다.

NewState

권한에 대한 정보를 담고 있는 TOKEN_PRIVILEGES 구조체 포인터가 들어가는 자리입니다.(아래 스샷 참조)

여기서 또한번의 구조체 포인터가 들어가게 되는데요, LUID_AND_ATTRIBUTES 구조체 입니다.

해당 구조체가 특권에 대한 Enable/Disable을 결정하는 플래그인 Attributes 플래그를 가지고 있습니다.(아래 스샷 참조)

언급은 안했지만 위에서 보았던 LookupPrivilegeValue 에서 알아낸 LUID를 이 구조체 안에 이미 담아두었답니다.

정리하면, LookupPribilegeVlaue API를 이용하여 알아낸 "SeDebugPriviliges" 의 LUID를 이용하여

해당 Attributes의 플래그가 Enable로 셋팅 되었다면 정상적으로 권한을 얻을 수 있다는것이죠!

(함수 실행전 구조체안의 변수들은(Attributes 플래그 포함) 이미 제작자가 세팅해 놓은 상태입니다.)

 

[윈도우 보안(WFP) 해제]

악성코드는 SeDebugPriviliges 특권을 얻은후 WFP를 무력화 시킵니다.

sfc_os.dll 안의 Ordinal #2 익스포트 함수를 LoadLibraryA + GetProcAddress를 이용하여 호출하는데요,

sfc_os.dll의 ordinal #2 익스포트 함수는 windows에서 시스템을 보호하기 위해 사용하는 WFP를

무력화시키기 위해 사용하는 함수입니다.

반대로 ordinal #1 함수는 WFP를 활성화 시키는 함수입니다.

(WFP는 우리가 Ctrl+Alt+Del 키를 눌렀을때 흔히 볼 수 있었던 winlogon.exe를 통해 활성화 됩니다.)

sfc_os.dll의 #1 익스포트 함수인 SfcInitProt가 호출되면 SFC Watcher Thread가 생성됩니다.

이 쓰레드가 실제 보호하고자 하는 프로세스의 이벤트를 감시합니다.

하지만 sfc_os.dll #2 익스포트함수는 SFC Watcher Thread를 제거하는 SfcTerminateWatcherThread 입니다.

(SFC Watcher Thread가 종료되면 재부팅 되기 전에는 다시 생성되지 않는다고 하네요.)

이러한 SfcTerminateWatcher를 호출하기 위해서는 "SeDebugPrivilege" 권한이 필요하며, SFC Watcher Thread를 생성한

winlogon.exe에서 직접 실행되어야 합니다.

따라서 악성코드 제작자는 SeDebugPrivilege를 얻어야만 했을것이고, 실제로 위에서 해당 작업을 수행했습니다.

정리하면, 악성코드는 

- CreateRemoteThread 함수를 이용하여 winlogon.exe를 실행시키고

- SfcTerminateWatcherThread를 호출하여 WFP를 무력화 시킵니다.

 

그 과정을 진행하기 위해서는 CreateRemoteThread 함수를 호출해야 하며,

안에 들어가는 주요 매개변수 값 2가지를 알아내야합니다.

첫번째로 GetProcAddress 함수를 사용하여 sfc_os.dll의 SfcTerminateWatcherThread의 주소값을 알아내야 하는것과

winlogon.exe의 프로세스 핸들값을 알아내야 합니다.

 

아래는 CreateRemoteTHread 함수입니다.

따라서 GetProcAddress 함수를 호출하여 SfcTerminateWatcherThread의 주소값을 얻은 후

OpenProcess 함수를 호출하여 winlogon.exe의 프로세스 핸들값을 얻어냅니다.

(OpenProcess를 하기위해 winlogon의 PID값을 알아내기위한 별도의 과정이 있으나 내용이 너무 길어져 생략합니다.)

(아래 스샷 참조)

두가지의 매개변수를 알아낸 후 바로 CreateRemoteThread 함수를 호출하여 WFP를 무력화시킵니다.

 

[시스템 파일 이동(백업)]

악성코드는 윈도우 WFP를 해제시킨 이후 system32 폴더 안에 있는 주요 파일을 임시폴더로 옮깁니다.

일단, GetWindowsDirectory 함수는 운영체제에 설치되어 있는 경로를 구하는 함수입니다.

lpBuffer에 uSize만큼의 경로를 담아오게 됩니다. (아래스샷참조)

Snprintf 함수는 원하는 포멧으로 버퍼에 문자열을 입력할 수 있는 함수입니다.

GetWindowsDIrectory 함수를 호출하여 받아온 경로 C:\Windows 값에 \system32\vupdmgr.exe 문자열을

합치는 역할입니다. 따라서 포맷은 %s%s(C:\Windows + \system32\vupdmgr.exe )입니다.

이후 GetTempPathA 함수를 호출하여 임시파일 디렉토리를 얻은 후

아까와 같은 방식인 %s%s 포맷으로 임시폴더 디렉토리와 파일명(\winup.exe) 문자열을 합칩니다.

이후 moveFIleA 함수를 호출하여 시스템 파일을 원하는 이름으로 변경한 뒤 임시폴더로 이동시킵니다.

 

[파일생성 및 실행]

시스템 파일을 이동시킨 후 악성코드는 새로운 파일을 생성 및 실행합니다.

먼저 시스템 파일을 백업할때처럼 wupdmgr.exe의 디렉토리 주소를 알아낸후 GetMoudleHandle 함수를 호출하여

실행중인 모듈의 핸들을 얻습니다. (아래스샷 참조)

이후 FindResourceA 라는 함수를 호출하는데요, 이 함수는 실행모듈이 가진 리소스를 검색하는데 사용됩니다.

아마 악성코드 안에 추가적인 악성코드 파일이 있는것 같습니다. 흔히 이런 악성코드를 드로퍼(Dropper)라고 지칭합니다.

hModule : 리소스를 얻고자하는 모듈의 핸들입니다. 위에서 GetModuleHandle 함수를 호출한 이유가 되겠지요

lpName : 리소스의 이름을 포함하는 NULL로 끝나는 문자열 포인터입니다.

lpType : 리소스의 타입을 의미하는 문자열 포인터입니다.

 

FindResourceA 함수를 호출하여 얻어낸 리소스의 핸들을 이용하여

LoadResource 함수를 호출하는데요, 이 함수는 해당 리소스를 메모리 적재를 수행합니다.

SizeofResource를 이용하여 적재된 리소스의 사이즈를 알아낸 후 다음 작업을 진행합니다.

그 이후 바로 CreateFIleA 함수를 호출하는데요, 이 함수는 파일을 오브젝트를 생성 또는 오픈할 수 있는 함수입니다.

lpFileName : 파일을 생성하거나 열 경로입니다.

dwDesiredAccess : 파일에 대한 액세스 권한을 지정합니다.

dwShareMode : 파일 공유모드를 지정합니다.

lpSecurityAttributes : SECURITY_ATTRIBUTES 구조체의 포인터입니다. 사용하지 않을 경우 NULL

dwCreationDisposition : 파일의 존재 유무에 따른 행동입니다. 플래그에 따라 결정됩니다.(ex 파일 없을 경우 생성, 항상 생성, 파일 열기 등)

dwFlagsAndAttributes :  파일의 속성을 지정합니다(ex 읽기전용, 숨김, 보관 기능 등)

hTmplateFile : GENERIC_READ 액세스 권한을 가진 템플릿 파일의 유효한 핸들입니다. 사용하지 않을 경우 NULL

system32 폴더에 wupdmgr.exe 파일 생성을 성공한 후 WriteFile 함수를 호출하여 파일 안의 내용을 채웁니다. (아직은 빈파일이니까요)

파일 내용은 FindResource + LoadResource로 적재해준것으로 채워지겠죠!

이후 CloseHandle로 핸들을 닫고 WInExec으로 생성한 파일을 실행합니다. 이것으로 악성코드의 주요 행동이 분석되었습니다.

 

 

6. Resource Hacker를 이용하여 리소스를 점검하고 추출하기

정적분석에서 이미 파악한 내용이지만, Resorce Hacker를 이용하여 리소스를 점검하고 추출해보겠습니다.

리소스가 발견되었습니다. 정적분석시 발견되었던 BIN 타입의 101 이름을 가진 리소스군요.

이것으로 분석 1-4를 마치겠습니다. 책만 봤을 때는 그냥 간단하겠구나 싶었는데

막상 정적분석을 하니 내용이 많이 길어졌네요.

읽어주셔서 감사합니다. 혹시 틀린점이 있다면 댓글 남겨주세요.

지금까지 Message 였습니다. 모두 즐거운 하루 되세요!

 

posted by Red_Message