티스토리 뷰
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_1은 0x00401000이라는걸 알수 있다. 그럼 뭐 저기서 부터 데이터 덮어 씌워 지겠지...
뭔가 복잡해 보이지만 값을 쓰는 부분만 모니터링 해보면 데이터의 의미를 알 수 있다.
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
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
나머지가 존재할 경우(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
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으로 보내주시면 친절히 답변 드리겠습니다.