티스토리 뷰

Fortinet 에서 공개한 metamorphic Code 가 들어있는 Ransomeware (Nabucur)


관련자료1 : https://blog.fortinet.com/2016/01/26/metamorphic-code-in-ransomware

관련자료2 : https://blog.fortinet.com/2016/06/07/real-time-polymorphic-code-in-ransomware


※ Metamorphic : http://www.dbguide.net/knowledge.db?mobile&cmd=view&boardConfigUid=21&boardUid=152658


※ 이번 포스팅에서는 악성코드의 동작에 대해서만 다룬다.


개요

Nabucur는 이미지 파일을 암호화 하고 화면을 잠그는 랜섬웨어의 한 종류로 코드영역에 원본데이터와 복호화 로직을 가지고 있다. 복호화를 위해 간단한연산을 귀찮을 정도로 많이 반복하게 되는데... 자세히 그리긴 힘들지만 대략 이런식이다...



꽤나 재미있는 샘플인거 같아 분석을 진행해보려한다. (분명 시작할땐 이마음으로 시작했다.)

악성코드 분석은 API추적만 있는것이 아니다. Assembly의 매력에 빠져보자.


지금부터 각 스테이지가 어떻게 진행되는지에 분석해보자.


Stage 1

EP부터 코드들이 범상치 않다. 특정메모리에 값을 쓰는 코드인 Mov DWORD PTR:[R32_1],R32_2 인 코드가 존재하는걸 보니 어딘가에 데이터를 쓰나보다 라고 생각된다. 최초로 실행되는 특정메모리에 값을 쓰는 코드를 확인해 보니 R32_10x00401000이라는걸 알수 있다. 그럼 뭐 저기서 부터 데이터 덮어 씌워 지겠지...


뭔가 복잡해 보이지만 값을 쓰는 부분만 모니터링 해보면 데이터의 의미를 알 수 있다.

EAX : 복호화된 데이터 

EDX : 복호화 될 주소 (반복적으로 + 4)


복호화를 수행하다 보면 0x00401000위치의 값이 변화된걸 알 수 있다.


[복호화 전/복호화 후]



위 과정들을 계속 반복하며 내려가다보면 CALL R32를 만날 수 있다. 첫 스테이지에서 데이터를 복호화한 그 위치이다. (CALL되는 R32의 값은 0x00401000이다) 


Stage1의 끝


Stage1 이후 복호화된 데이터 (Size : 0x250)


Stage 2 (0x00401000 ~ 0x0040124F)

복호화된 데이터중 사용하지 않는 코드나 더미코드가 엄청 많다. 코드가 많다고 겁내지 말고 JMP를 따라 잘 반복되는 부분을 찾아 잘 분석해보면...


쓸데없이 용량만 잡아먹는 의미없는 코드가...


무시할 부분 무시하고 Stage 2 의 메인 코드들만 확인해보자.



실행되는 코드는 요정도.. 실제 몇줄 안된다. 그나마도 더미코드가 포함되어 있으니 유의미한 코드만 뽑아보자. 더미코드는 RDTSC, NOP, XOR 로 구성되어 뭔가 수행하는거 같아 보이지만 그 코드들 사이를 PUSHAD, POPAD(모든 레지스터값 스택에 저장/레지스터로 복구)로 감싸고 있기 때문에 실제론 별 의미가 없다. (NOP는 당연히 무시한다.)


$ ==>    00401000    B8 AFBCF97E     MOV EAX,7EF9BCAF                  ; Stage3 Dec Key

$+6      00401006    E9 80000000     JMP <Chrysant.Stage2_Second>

$+94     00401094    E9 80000000     JMP <Chrysant.Stage2_Third>

$+119    00401119    33C9            XOR ECX,ECX

$+11B    0040111B    8D35 50124000   LEA ESI,DWORD PTR DS:[401250]

$+128    00401128    E9 80000000     JMP <Chrysant.Stage2_Fourth>

$+1AD    004011AD    3106            XOR DWORD PTR DS:[ESI],EAX

$+23B    0040123B    83C6 04         ADD ESI,4                          ; Stage3 Address

$+23E    0040123E    83C1 04         ADD ECX,4                          ; Counter

$+241    00401241    81F9 F3040000   CMP ECX,4F3                        ; Size

$+247    00401247    0F8C DBFEFFFF   JL Chrysant.00401128


그나마 위에꺼에서도 점프문 제거하고나면 몇 코드 안남는다. 이정도면 간단하니까 쉽게 분석가능하겠지. 


$ ==>     00401000   B8 AFBCF97E     MOV EAX,7EF9BCAF                ; Stage3 Dec Key

$+119    00401119    33C9            XOR ECX,ECX

$+11B    0040111B    8D35 50124000   LEA ESI,DWORD PTR DS:[401250]

$+1AD    004011AD    3106            XOR DWORD PTR DS:[ESI],EAX

$+23B    0040123B    83C6 04         ADD ESI,4                       ; Stage3 Address

$+23E    0040123E    83C1 04         ADD ECX,4                       ; Counter

$+241    00401241    81F9 F3040000   CMP ECX,4F3                     ; Size

$+247    00401247    0F8C DBFEFFFF   JL Chrysant.00401128//AD


변환해 보자면..
EAX = 0x7EF9BCAF //Xor Key
ESI = 0x00401250 //Dec Area

for( ECX = 0 ; ECX < 0x4F3 ; ECX+=4)
{
  *(ESI+ECX) ^= EAX
}
대략 요런 간단한 코드다. 그럼 연산 후 다음 스테이지로 넘어가자.

Stage3 코드 복호화 전/후

Stage3 (0x00401250 ~ 0x00401743)
Stage3가 시작되면 시작하자마자 비교문과 JMP문이 보이는데 CMP,JE는 같을리 없으니 무시하고 JMP문을 보면된다. JMP문이 해석하던 도중 끊켜서 나오지 않는데 이는 더미코드 0x00 때문이니 NOP처리를 해주면 아름답게 보이므로 이쁘게 정리해주도록 하자. (그냥 점프문에서 ENTER 쳐서 봐도 된다.)

Dummy Byte 제거 전
Dummy Byte 제거 후

분석을 진행하기 전에 Stage3의 코드들을 모아보자. (의미있는 코드들만 BOLD 처리 함)

00401250 <Stage 3> 813D 62124000 3>CMP DWORD PTR DS:[401262],Chrysant.004FFD3C
0040125A           0F84 51010000   JE Chrysant.004013B1
00401260           EB 04           JMP SHORT Chrysant.00401266
00401266           BA 20D865FC     MOV EDX,FC65D820
0040126B           BB B03602FE     MOV EBX,FE0236B0
00401270           BA 870451FF     MOV EDX,FF510487
00401275           E8 F2000000     CALL Chrysant.0040136C
0040127A           81F2 8ED79BFB   XOR EDX,FB9BD78E
00401280           BB 169687FB     MOV EBX,FB879616
00401285           81F2 426503FE   XOR EDX,FE036542
0040128B           BA 34F9F5FB     MOV EDX,FBF5F934
00401290           BB 78353900     MOV EBX,393578
00401295           3D 4182FF64     CMP EAX,64FF8241            //<- EAX = KEY
0040129A           0F85 F8030000   JNZ Chrysant.00401698
004012A0           E9 B0000000     JMP Chrysant.00401355
00401355           C705 62124000 3>MOV DWORD PTR DS:[401262],Chrysant.004FFD3C
0040135F           BB 541C8ABF     MOV EBX,BF8A1C54
00401364           EB DA           JMP SHORT Chrysant.00401340
00401340           E8 66FFFFFF     CALL Chrysant.004012AB       //Stage4 Code Decrypt
00401345           EB 90           JMP SHORT Chrysant.004012D7
004012D7           E9 D5000000     JMP <Chrysant.Stage4>

초기 CMP, JE 두 코드는 같을 일이 없으므로 패스한다.
JMP 이후 MOV CALL JMP XOR 등이 존재하지만 무시하고 CMP 문으로간다. (메모리에 별다른 행위를 수행하지 않아 무시하고 진행함)
CALL문 에서 EAX값을 만들어내 비교(CMP)하는데 이는 KEY 값으로 사용된다. 그러므로 굳이 키값을 만들어 내는 로직을 분석하지 않아도 CMP문의 상수값으로 키값이 확인 가능하다. (키값으로 사용되는건 이후 코드에서 확인한다.)

0x00401340 에 있는 CALL문이  Stage4에서 사용될 코드를 복호화 하는 로직이며 복호화 후 JMP문을 통해 Stage 4 의 EP로 이동하게된다. (마지막 JMP문)
CALL문 내부의 코드를 보기좋게 모아보면 다음과 같다. (코드 정리하다 성격 나빠지겠다..)




복잡해 보이지만 실제로 의미있는 코드는 몇개 없다. EBX, EDX의 값은 코드가 진행되면서 MOV 연산으로 연속적으로 값이 덮어쓰게 되니 실질적으론 Dummy CODE라 볼수 있으니 패스하도록 한다. 다른 레지스터들에서 의미있는 값들을 보자면 아래와 같다.

EAX : KEY - 0x64FF8241
ECX : Size - 0xEB
ESI : Stage4 Addr -  0x004013B1

또 다시 변환해 보자면..
EAX = 0x64FF8241    //KEY
ESI = 0x004013B1    //ADDR


for( ECX = 0xEB ; ECX >= 5 ; ECX-=4)
{
  *ESI ^=EAX;
  ESI = ESI + 4;
}
if(ECX == 1)
 return;
else 
 for( ; ECX >= 1 ; ECX--)
  {
    *ESI ^= AL
     ESI++
   }

대략 요렇게 표현할 수 있겠다.... (맞을꺼다 아마...) 복호화 후엔 JMP문을 통해 Stage4로 이동하게된다.

Stage 4 코드 복호화 전/후


Stage 4 (0x004013B1 ~ 0x0040149B)



복호화된 코드를 다시 깔끔하게 꺼내보면 위 그림과 같다. 여러번의 삽질 끝에 실제 원본데이터 복구 위치는 세번째 콜이라는걸 알아냈다. (이전 CALL 들은 다른 악성행위를 수행하는데.. 본 포스팅에서는 원본 데이터 복구쪽에만 치중하므로 이부분은 무시한다. 세번째 CALL이 원본 데이터 복호화 루틴이라는걸 알아내는데는 수많은 삽질이 필요했다. ) 물론 세번째 콜 들어가기전에 위에 있는 코드들도 중요한데...처음 두개의 CALL문,CMP, JNZ 이후 코드들이 중요하다. 이부분을 눈여겨 보도록하자. 레지스터 별로 나눠보면 아래와 같은데...


004013F5         A1 1B174000     MOV EAX,DWORD PTR DS:[40171B]

004013FA         0305 1F174000   ADD EAX,DWORD PTR DS:[40171F]


00401400         8D3D 00104000   LEA EDI,DWORD PTR DS:[<Stage2 Start>] // Section Start

00401406         033D 17174000   ADD EDI,DWORD PTR DS:[401717]

0040140C         033D 0F174000   ADD EDI,DWORD PTR DS:[40170F]

00401412         83C7 08         ADD EDI,8


00401415         8B1D 2B174000   MOV EBX,DWORD PTR DS:[40172B]


0040141B         A3 03174000     MOV DWORD PTR DS:[401703],EAX

00401420         893D 07174000   MOV DWORD PTR DS:[401707],EDI

00401426         891D 0B174000   MOV DWORD PTR DS:[40170B],EBX


EAX = *0x0040171B + *0x0040171F

EDI = 0x00401000 + *0x00401717 + *0x0040171F + 8

EBX = *0x0040172B


연산된 값은 다시 아래와 같이 저장하는데.. 뭐 기억만 하고 넘어가도록 하자 (후에 요긴하게 쓰인다.)

0x00401703 = EAX

0x00401707 = EDI

0x0040170B = EBX


0x00401703 

0x00401707 

0x0040170B 

EAX 

EDI 

EBX 


연산후 세번째 콜문 (0x0040142C : CALL 0x004014A6) 으로 이동해보자. JMP문을 타고 가면 CMP, JE, JMP가 나오고 JMP문을 선택해 보면 또다시 점프코드가 짤려있음을 알 수 있다. 위에서 처리한 방법과 똑같은 방법으로 0x00을 제거해주면...

NOP 처리 전/후



깔끔하게 출력되는데.... 어디선가 많이본 코드들 같지 않은가? 그렇다. Stage3 와 완전 동일하다. 고로. Stage4의 분석은 생략한다. (이후 Stage5까지의 코드가 완전 동일하다 절대 귀찮아서가 아니다.)

그래도 Stage5로 넘어가기전에 복호화되는 영역은 기록해놔야겠지.

EAX = 0xCECA7BAA <- Key

ECX = 0x6F <- Size

ESI = 0x00401624 <- Stage5 Area


Stage5 복호화 전/후

.

Stage5 (0x00401624 ~ 0x00401692)

드디어 복호화를 위한 최종 Stage까지 왔다.. (힘들다.....) 이번스테이지에는 버릴코드 하나없이 깔금하게 복호화 로지만 들어있다. ..자..분석해보자..

Stage5 메인코드


일단 코드를 쭉 들여다보면 특정 메모리의 값을 레지스터로 가져오는 코드가 존재한다. 모아서 확인해보자.


00401624 <Stage5> A1 03174000     MOV EAX,DWORD PTR DS:[401703]

00401629          8B3D 07174000   MOV EDI,DWORD PTR DS:[401707]            ; 0042C351

00401647          8B1D 0B174000   MOV EBX,DWORD PTR DS:[40170B]


요렇게 생긴 코드 3개인데.. 어디서 많이본것 같지않은가? Stage4에서 기억하고 넘어가자고 했던 그 코드다.

스크롤 올리기 귀찮을테니..


---------------------------------------------- Stage4 진행 중 ----------------------------------------

연산된 값은 다시 아래와 같이 저장하는데.. 뭐 기억만 하고 넘어가도록 하자 (후에 요긴하게 쓰인다.)

0x00401703 = EAX

0x00401707 = EDI

0x0040170B = EBX


0x00401703 

0x00401707 

0x0040170B 

EAX  : 0x000D6B44

EDI  : 0x0042C351

EBX : 0xE564AF92

--------------------------------------------------------------------------------------------------------------

그러하다. 위에서 연산해 저장했던 값들이다. 이 값들의 정체를 디버깅하면서 하나하나 알아보자. (코드는 짧은데 다른스테이지에 비해 연산게 많다)


자 먼저 EP에서 몇바이트만..


00401624 <Stage5> A1 03174000     MOV EAX,DWORD PTR DS:[401703]

00401629          8B3D 07174000   MOV EDI,DWORD PTR DS:[401707]                ; 042C351

0040162F          33D2                 XOR EDX,EDX

00401631          BB 04000000      MOV EBX,4

00401636          F7F3                 DIV EBX


자 먼저 연산에 주로 사용되는 EAX부터 보자. EAX는 00401703에 저장된 값(0x000D6B44)을 가져온 뒤 EBX에 4를 넣고 나눠준다. 좀더 쉽게 보자면..


EAX = 0x000D6B44 // EAX = *0x00401703

EBX = 4

EDX = EAX %EBX

EAX = EAX /EBX;


와 같아 지겠다.  다음 코드를 보자.

00401638     83FA 00          CMP EDX,0

0040163B     76 01            JBE SHORT Chrysant.0040163E

0040163D     40               INC EAX

0040163E     40               INC EAX

0040163F     8BD0             MOV EDX,EAX
00401641     42               INC EDX

나머지가 존재할 경우(CMP EDX, 0 ) +2를 해주고(INC EAX : 2회) 0일경우 +1을 해준다. 이후 연산된 값을 EDX로 옮겨 다시 EDX의 값을 1 증가시켜 주는데............ 내가 쓰고도 무슨말인가 싶으니 코드로 보자.

------------------------

if(EDX == 0)

  EAX++;

else

  EAX+=2;


EDX = EAX + 1;

------------------------


뭐 대강 이런식이다. 이런식으로.. EDX에 들어가 있는값은 어떤값을 4로 나눈값 + ?? 한 값이 들어가있다. (아마 반복용 카운터로 사용될듯 하다.)


EAX와 EDX에 들어가는 값은 확인했으니.. 다음은 EDI에 들어가는 값을 모니터링해보자. Stage5가 시작될 때 있던 코드를 기억하는가 ? 스크롤 올리기 귀찮은거 아니까 다시 코드를 가져와 보자.


00401624 <Stage5>   A1 03174000     MOV EAX,DWORD PTR DS:[401703]

00401629            8B3D 07174000   MOV EDI,DWORD PTR DS:[401707]       ; 042C351

0040162F            33D2            XOR EDX,EDX


Bold처리 되어있는 저부분이다. 자 EAX와  EDX의 연산이 끝나고 나면 나오는 코드는 다음과 같다.

00401642    8BF7            MOV ESI,EDI                         ; Chrysant.0042C351
00401644    83EE 08         SUB ESI,8

별로 설명할 것도 없다. EDI의 값을 ESI에 옮기고 (0x0042C351) -8을 해준다. 들어가는 값을 딱 보면 알겠지만 메모리 영역의 주소값이다. 그렇구나..EDI의 값에서 -8을 해서 ESI에 옮기는 구나.. 하고 다음코드를 보자.

00401647         8B1D 0B174000   MOV EBX,DWORD PTR DS:[40170B]
0040164D         311E            XOR DWORD PTR DS:[ESI],EBX
0040164F         8B1E            MOV EBX,DWORD PTR DS:[ESI]

EDI와 ESI를 보고 있었더니 난데없이 EBX가 튀어 나온다. 놀란가슴 진정시키고 차근차근 분석해보자.

EBX에는 0x0040170B의 값을 집어넣고 ESI위치의 값과 XOR한 후 그 값을 EBX로 다시 로드한다.

말인 즉슨, ESI의 위치의 값을 EBX의 값으로 XOR한 후 그 위치의 값을 다시 EBX로 로드 한다는 것이다.

(XOR후 가져온 값 : EBX = 0xC3CDACEA ) 

헷갈릴만하다. 글을 보는사람이 잘 모르는게 아니라 필자가 글을 못써서 그런거이니 뒤에서 다시한번 정리하겠다. 그냥 그러려니 하고 넘어가자.

자 다음코드를 보자.


00401651         83C6 04             ADD ESI,4

00401654         33FF                XOR EDI,EDI

00401656         8BCB                MOV ECX,EBX


ESI의 값을 +4한 뒤 EDI의 값을 0으로 초기화 시켰다. 그리고 EBX의 값을 ECX로 옮겼네..? 

연산하던 레지스터가 계속 바껴서 헷갈리겠지만.. 그러려니 하자. (절대 귀찮아서가 아니다.)

다음 코드들을 보자.


00401658    8B1E            MOV EBX,DWORD PTR DS:[ESI]     ; ------- loop Start -------

0040165A    33D9            XOR EBX,ECX

0040165C    D3CB            ROR EBX,CL

0040165E    891E            MOV DWORD PTR DS:[ESI],EBX

00401660    83C6 04         ADD ESI,4

00401663    47              INC EDI

00401664    3BFA            CMP EDI,EDX

00401666    75 F0           JNZ SHORT Chrysant.00401658    ; -------Loop End ---------


반복문이다. 반복문이 시작되었다.!!!

놀랄거없다 그냥 XOR하는거니까 뭐..... 

자 단순히 생각하자. ESI 위치의 값을 가져와서 ECX로 xor하고 CL의 값만큼 Rotate Reft하고  그값을 다시 ESI 위치에 저장하는거다. 그후에 ESI를 4 증가시키니 다음 위치를 가리키는 거겠지. 그리고 EDI를 증가시켜서 EDX와 비교하고. 조건에 따라서 반복.. 솔직히 내가 쓰면서도 헷갈린다. 이참에 정리한번 하고가자. (망했다 술기운 오른다)


복습시간. (마지막 스테이지의 코드도 정리하려한다.)



0x00401703 

0x00401707 

0x0040170B 

EAX  : 0x000D6B44

EDI  : 0x0042C351

EBX : 0xE564AF92

요건 베이스로 깔고.

자 Stage5 코드중 EAX연산 관련 코드만 모은거 (다른 레지스터 관련된 코드는 삭제했다, 관련 레지스터만 보면되니..)

1. EAX 관련 연산

00401624       A1 03174000     MOV EAX,DWORD PTR DS:[401703]

00401631       BB 04000000     MOV EBX,4

00401636       F7F3            DIV EBX

00401638       83FA 00         CMP EDX,0

0040163B       76 01           JBE SHORT Chrysant.0040163E

0040163D       40              INC EAX

0040163E       40              INC EAX

0040163F       8BD0            MOV EDX,EAX

00401641       42              INC EDX


EDX = *(0x00401703) % 4

EAX = *(0x00401703) /  4

EDX = EAX + 2 or 3


2. EDI 관련연산

00401629                  8B3D 07174000   MOV EDI,DWORD PTR DS:[401707]

00401642                  8BF7            MOV ESI,EDI

00401644                  83EE 08         SUB ESI,8

ESI = *(0x00401707) - 8


3. EBX와 ESI 관련 연산 

00401647                  8B1D 0B174000   MOV EBX,DWORD PTR DS:[40170B]

0040164D                  311E            XOR DWORD PTR DS:[ESI],EBX

0040164F                  8B1E            MOV EBX,DWORD PTR DS:[ESI]

00401651                  83C6 04         ADD ESI,4

00401654                  33FF            XOR EDI,EDI

00401656                  8BCB            MOV ECX,EBX


EBX = EBX ^ *(ESI)

ESI = ESI+4

EDI = 0 

ECX = EBX


대략 XOR용 키값은 ESI -8 위치의 xor 한 값(현재 ECX , MOV ECX EBX),

00401658           8B1E       MOV EBX,DWORD PTR DS:[ESI]      ; ------- loop Start -------

0040165A           33D9       XOR EBX,ECX

0040165C           D3CB       ROR EBX,CL

0040165E           891E       MOV DWORD PTR DS:[ESI],EBX

00401660           83C6 04    ADD ESI,4

00401663           47         INC EDI

00401664           3BFA       CMP EDI,EDX

00401666           75 F0      JNZ SHORT Chrysant.00401658    ; -------Loop End ---------

반복 돌면서 XOR, ROR수행 


자 복호화된 코드르 보자. 

복호화된 데이터

시작위치 (0x0042c349) 의 4바이트가 XOR을 위한 키값,  시작위치 + 4의 위치가 FileSize/4, +8위치가 FileName, FileName 이후 부터가 복호화 해야 하는 데이터 위치가 되겠다. 뭔가 많이 생략된 기분이지만 자세한걸 적으려면 Stage4로 돌아가야해서... 출근해서 다시 정리하든지 해야겠다.. (아마 하겠지...)


어영부영 넘어가는거 같지만 이런과정으로 원본 파일명과 원본 파일 데이터를 복구할 수 있다. 물론 이과정에서 ScreenLock라든가 다른 데이터를 감염시키는 루틴도 같이 존재하긴 하지만 이글의 목적은 원본데이터 복구 이므로 이정도로 마친다.  


P.S : 글 처음으로 존댓말 쓰네요. 비판은 환영하지만 비난은 거절합니다.

P.S2 : 포스팅 관련 궁금함은 댓글이나 sfkino@gmail.com으로 보내주시면 친절히 답변 드리겠습니다.



댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
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
글 보관함