'리버싱'에 해당되는 글 2건

  1. 2011.07.16 Codegate 2011 Bin 500 풀이 [ VM ] (1)
  2. 2009.11.03 플레그는 아무하고나 만나주지 않는다.

2011.07.16 21:45 WarGame

[+] Introduction

 


 이번에 소개할 문제는 VM 관련 문제 입니다.

VM을 이용한 기술은 VCP [ Virtualized Code Protection ] 이 있으며 해당 기술은 Protector 에 많이 쓰이곤 합니다.

언뜻 봐도 " Code를 Virtualize 하여 Protect 한다 " 라는것을 의미한다는거라고 쉽게 추측 할 수 있죠.

상용 Packer 인 Themida에 실제로 쓰이고 있는 기술이기도 하구요.

물론 Themida에는 VCP 뿐만 아니라 여러가지 Anti Reversing 기술들이 들어가있습니다만..

 이 포스팅에서는 VM만을 다루도록 하겠습니다.

 * VM이 적용되면 Reversing 하는 입장에서는 상당히 귀찮아집니다.

뒤에서 설명하겠지만 Assembly Language 로 한줄씩 실행되기 떄문에 

전체적인 구조를 살펴보려면 Script를 짜거나 손으로 직접 모아야 하기 떄문이죠.

디버거로 열어봤는데...


<Fig 0. VM이 딱 ! >


 <Fig0-1. 5초후에 빡침이 딱!>



[+] Problem


<Fig1. Bin500을 실행 시켰을 때 >

 

먼저 Debugger로 문자열을 확인해보면

key File로 짐작되는 문자열 'codegate.key' 를 찾을 수 있습니다.

적당히 아무내용이나 집어넣어서 재실행 시켜보면

역시 유효하지 않은 파일이라고 하는군요.

<Fig2. 해당 파일이 올바르지 않을 때 메세지> 

이런 류의 문제라면 해당 Key 파일을 검사하는 부분을 우회해도 

암호가 나오지 않고 그 파일을 가지고 연산을 하기 떄문에

해당 파일의 내용을 정확히 복구를 해야 합니다.

 파일을 열어 Verify 하는 부분을 찾으려 해도 도통 보이지 않는군요.

이 문제의 핵심인 Verify 부분이 VM에 의해 실행되기 때문이죠.


[+] Analysis

 
그렇다면 Debugger로 확인을 해봅시다. 

<Fig3. VirtualAlloc을 통해 특정 메모리 영역을 할당받는 모습>

 해당 영역을 확인해보면 아시겠지만

애초에 VM 영역에서 시작하네요.

후에 VirtualAlloc 을 호출하고 특정 영역에  RWE 권한을 모두 줍니다.

계속 따라가보면 VirtualAlloc을 한번 더 호출하고 .. [ 같은 크기, 다른 위치에 RWE 권한을 줍니다 ] 

이 할당한 영역에 특정 명령어를 수행하는 것을 확인 할 수 있습니다.

<Fig4. VirtualAlloc에 할당된 영역에서 코드를 실행하고 있는 모습>
 
그 후에 다시 0x40ABBD로 Return해서 특정 루틴을 반복합니다.


<Fig5. Code를 가져와 Decrypt하는 부분>

0x0040A94A를 호출함으로써 실행할 Code를 복호화 하는데

해당 함수를 살펴보면 다음과 같습니다.


<Fig6. Fig5의 Decrypt함수 내부>

 

사실 위 함수가 문제를 푸는데 큰 도움이 되는 건 아닙니다.

CPU가 해석 할 수 있는 일반 기계어로 복호화 하는 과정이지

Key 파일의 내용을 확인하는 과정이 아니기 떄문이죠.

하지만 VM의 동작 방식은 확인을 하고 갑시다.

대략적인 VM의 구현은 다음과 같습니다.

<Fig7. VM의 동작 방식>

실제 문제 파일을 Debugging 해보면 아시겠지만

text 영역과 VM 영역이 번갈아 가며 실행되는 것을 볼 수 있습니다.

Sensitive 한 곳은 VM 처리가 되어있는거죠.



즉 우리가 원하는 것은 ' Codegate.key 파일을 입력 받고 난 후 실패 메세지가 나오기 전까지의 연산 과정 ' 입니다.

이것은 1Byte 씩 검사하여 다르면 바로 실패로 return 하는 과정일수도 있고

전체 내용을 모두 검사한 후 최종적으로 실패를 판단할 수도 있습니다.

물론 전자가 훨씬 까다로워집니다. 실행중에 비교 값을 바꿔야하니까요.

ReadFile에 BP를 걸고 해당 파일을 읽어 온 후에 Buffer에 접근 하는 부분을 잡아보면

최종 Code가 실행되는 곳을 잡을 수 있습니다.

이곳을 보면 Fig4에서 할당한 곳과는 다른 공간임을 알 수 있습니다.

즉, 복호화된 Code가 실행 되는 부분은 2군데이며 

1. 일반 적인 Code가 실행되는 부분

2. Key 파일의 내용을 검사하는 부분


이 되겠습니다.

두 부분을 모두 BP 걸고 확인해보면 아시겠지만 Call , JMP 문을 찾아 볼 수 없다는걸 알 수 있습니다.

Call이나 JMP문이 가고자 하는 곳은 VM에서는 외부 , 즉 원본 프로그램의 text 영역입니다.

이 부분으로 가려면 해당 주소에 맞는 연산이 또 별도로 필요하죠.

문제 풀이엔 별로 중요한 내용이 아닙니다만,

'VM은 이런식으로 구현되고 있다' 라는 것은 염두해두시는게 좋습니다.



[+] Solution



이 다음부턴 딱히 설명 드릴게 없습니다.

Script를 짜거나 손으로 직접 코드를 바꾸시면서 진행을 하셔야 합니다.

운이 나쁘게도 앞서 설명드린 1Byte 씩 검사하고 아닐 경우 실패로 간주하는 타입이군요.

Script로 모으기 짜증 + 1  이 되었습니다.

아무튼 해당 Code를 모으면 다음과 같은 모습입니다.

Pydbg의 HardWare BP를 이용한건데 가변길이라 그런지 

다음 명령어까지 받아오더군요.. 쩝..


<Fig8. Xor, Cmp 부분 중 일부를 긁어온 모습>

이 부분을 예로 들자면

al 로 가져온 값을 xor 0x46 하고 이 값을 cmp 합니다.

xor  [ Data  ] , 0x46

cmp ( [ Data ] ^ 0x 46 )  , 0xf0

즉 Data 의 값은 0x46 과 Xor를 했을 때 0xF0 가 나오는 0xB6 이 되겠네요.

이런 식으로 Codegate.key 값을 하나씩 복원합니다.

Codegate key 값이 총 100 Byte 니 벼..별거 아니군요.

Xor 부분과 cmp 부분을 parsing 매 Byte마다 바꿔주면

다음과 같은 원본 데이터를 얻을 수 있습니다.

<Fig9. 원본 Key File >

이를 codegate.key 에 저장하면 


<Fig10. 해독된 Key File>

이게 정답은 아니겠죠?

100Byte의 16진수가 뭘 의미 할까요.

흠 분포도를 보면 문자를 의미하는건 아닌거 같습니다.

그리고 저 출력 방식도 의심해볼만 하죠

4 * 25 형식입니다.

또, 0x80 0x00 이 유독 눈에 많이 띄는걸 볼 수 있는데..

일단 이를 binary 값으로 바꿔보았습니다.


<Fig11. Binary 값으로 바꾼 모습>



<Fig11-1. !?>
왜 4 * 25 형식으로 출력했는지 알겠네요.

0x80이 binary 값으로 1000 0000 이니까

뒤의 0을 떼버리면 25 * 25 형식의 값이 완성됩니다.

여기에 모서리 부분을 보면 1과 0이 규칙적으로 나열되어있는데

전 이걸 QR 코드라고 판단했고 이를 만들어보면..



QR Code Reader로 읽어보니

I_am_in_C0d3gat3_2011!!

이라는 답이 나오는군요.















Posted by LinkC

2009.11.03 23:59 System


우리가 cpu에서 플레그 레지스터라는 이야기를 많이 들어보셨으리라 생각합니다. 일단 어셈블리를 공부하시기로 한 이상 플레그 레지스터를 모르고 넘어간다는 일은 수동기어를 장착한 차량에서 기어바꿀줄 모르고 1단으로만 기어를 놓고 달릴줄만 아는 상황과 비슷하다고 말씀 드릴 수 있습니다. (좀 표현이 이상했나요? ^^a)

플레그레지스터는 CPU내부의 명령에 따른 결과나 명령수행방식을 설정하거나 설정되는 레지스터를 말합니다.

이 레지스터는 다음과 같은 항목으로 구성되어 있습니다. 


CF - carry flag(케리플레그) 바로 전 연산에서 자리를 넘치거나 쉬프트등으로

        밖으로 밀려난 비트값이 1일경우 1로 설정되는 플레그 

PF - Parity flag(패리티 플레그) 바로전에 연산된 값의 결과에 1인 비트수가 짝수일경우 1로 설정됨. 

AF - Adjust flag(보정 플레그) BCD 연산을 위하여 보정에 사용되는 플레그 

ZF - Zero flag(제로 플레그) 바로 위의 연산결과값이 0이 나오거나 비교시 두 값이 같으면 1로 설정 

SF - Sign flag(사인 플레그) 연산결과의 부호를 나타내며 결과값이 양수일때는 0으로 음수일때는 1로 설정 

OF - Overflow flag(오버플로우 플레그) 연산결과가 양의 정수값이나 음의 정수값 범위를 넘칠경우 설정되는 플레그, 케리플레그와 동작이 유사하여 케리플레그와 값이 일치하는 경우도 종종 있다.

이외에도 시스탬종작을 위한 IF , NT , RF , VM , IOPL 등의 플레그가 있지만 오늘 강좌하려는 범위에서

넘어가므로 생략하기로 합니다. 


 그럼 이 플레그들과 명령들과의 관계를 알아보기로 합니다. 이제부터 필요한 준비물이 있습니다. 어셈블리

책 중 CPU명령어가 자세히 나온 책을 하나 선정하여 영향받는 플레그와 영향주는 플레그항목까지 기록된

자료를 준비해 주시기 바랍니다. 어차피 어셈블리 프로그래밍을 한다면 이정도의 준비물은 있어야 합니다. 


 우리가 플레그하면 조건분기명령과 많이 연관을 짓습니다. 만약 ax 레지스터 값이 5인지를 알려면 다음과

같이 합니다. 


cmp ax , 5 
je xxx ;     ax값이 5일경우 점프할곳 
jne yyy ;   ax값이 5가 아닐경우 점프할곳 

이정도 부분까지는 대충 이혜하실수 있어야 다음 부분을 보셔도 이혜가 가시게 됩니다.

위의 명령의 경우 다음과 같이 바꿀수 있습니다.

sub ax , 5 

je xxx ;      ax값이 5일경우 점프할곳 
jne yyy ;    ax값이 5가 아닐경우 점프할곳 

대부분 어셈블리 명령을 여타언어에서의 "if"명령과 많이 연관시켜 생각하기 때문에 "if"와 매우 유사한

기능을 하는 cmp명령이 비교명령으로 생각하여 cmp 명령으로 비교연산을 하는 경우가 생깁니다.

하지만 이는 어셈블리 명령으로 다른 언어를 흉내 내려할때에 사용하는것이지 어셈블리를 무엇때문에

사용하려 하는지에 대하여 고찰해 보아야 할 필요가 있음을 느끼게 하는 대목입니다. 

일단은 먼저 명령과 플레그에 관하여 상관관계를 알아봐야 합니다. 우리거 명령표를 볼때 (좀 자세히 나온것)

영향받는 플레그와 영향주는 플레그가 표기되어 있을것입니다.

예를 들어서,

dec 명령의 경우 명령이 실행에 영향받는 플레그 - 없음

명령이 실행후에 영향주는 플레그                      - OF, SF, ZF, AF, PF 


이 뜻은 dec 명령은 실행시 실행특성에 영향을 주는 플레그는 존재 안하며 실행후 결과연산특성이

적용되는 플레그들은 OF, SF, ZF, AF, PF 가 있다는 뜻입니다. 즉 dec 명령 수행후 결과값이 음수

일경우 SF 가 1로 설정이 되고 (음수이므로) ZF는 0이 되며(0이 아니므로) PF는 결과값의 내부

패리티를 채크하여 1이 짝수개이면 1이 설정되고 홀수개면 0이 설정되게 되어 있다는 뜻입니다.

그러므로 dec 연산을 한후 사용할수 있는 조건분기명령은

dec ax 

jz xxxx ;    결과가 0이면 점프 
js xxxx ;    결과가 음수일경우 
jp xxx ;      결과가 짝수 패리티일경우 

등이 될수 있습니다. 따라서 이러한 사실들로 우리는 cmp 명령이 결과값에 영향을 안주고

플레그에만 영향을 주는 sub명령임을 알 수 있습니다. 여기서 원래라면 플레그와 음수/양수와

부호없는 수의 크기비교에 따른 플레그값의 설정을 설명해야 하지만 지면의 크기와 여러분의

공부를 위하여 여러분께 숙제로 남겨드리기로 하겠습니다. 

(헉.. 돌이 날라오는군요) 

그럼 다음으로 플레그의 용법이 조건분기에만 한하지 않음을 예로 들어보이겠습니다.

이 예중 대표적인 예가 adc 명령입니다.  우리가 64비트수치에다가 어떤 수를 더한다고

생각합니다. 하지만 우리가 쓰는 cpu는 32비트 이므로 64비트 수치를 바로 계산할수

있는 명령은 없습니다. 따라서 먼저 64비트중 하위 32비트를 덧셈을 한 후 더한값이

자리를 넘칠 경우 상위 32비트를 더할때 1을 더 더해주면 됩니다. 바로 이럴때 사용되는

것이 carry flag 입니다. 위에서 설명했듯이 케리플레그는 연산의 자리를 넘치거나 했을때

설정되는 플레그입니다. add 명령은 결과값에 대하여 carry 플레그에 영향을 주는 명령입니다.

따라서 x 라는 64비트 변수를 y라는 64비트 변수로 더한다고 할때 그 연산루틴은 다음과 같이

설정할수 있습니다. 


mov eax , dword ptr y[0] ;  먼저 y의 하위 32비트 값을 얻어서 
add dword ptr x[0] , eax ;   x의 하위 32비트에 더한다음 
mov eax , dword ptr y[4] ;  다음 y의 상위 32비특밧을 얻어서 
adc dword ptr x[4] , eax ;   이전하위 32비트의 더한 넘친자리 수 까지 더하여 x의 상위 32비트에 더한다 

여기서 질문 2가지를 하실수 있게 되실겁니다. 
Q1) adc 는 어떤 일을 하는데 하위에서 자리넘침이 있는지를 알수 있나요? 

A1) 옙, 그건 먼저 add로 연산했을 경우 add 명령은 CF (carry flag)에 결과값 영향을 끼칩니다.

carry는 연산결과가 변수타입이 표시할 수 있는 범위를 넘쳤을 경우 1로 설정되므로 두 덧셈의

결과가 dword (더블워드,32비트)를 넘쳤을 경우 1로 설정됩니다. 후에 밑에 쓰인 adc 명령은

덧셈을 하면서 CF값까지 같이 더하는 명령입니다. 즉, 바로 위쪽의 add 명령으로 인하여

자리넘침이 생겼을 경우 케리플레그값이 1이 되므로 상위 32비트를 더하면서 밑에 32비트 덧셈

결과의 자리넘침 결과(=CF)값가지 더해지게 되는 것이지요. 이해를 위하여 다음 예제를 넣겠습니다. 


ex1) 
mov eax , 0 ;eax 를 0으로 만듬 
clc ;케리플레그(CF) 를 0으로 만듬,또는 클리어한다라고 
adc eax , 0 ;표현, 그런후 adc 명령으로 eax + 0 + CF를 연산 
------------- 
결과 eax = 0 

ex2) 
mov eax , 0 ;        eax 를 0으로 만듬 
stc ;                    케리플레그(CF) 를 1로 만듬,또는 설정한다라고 
adc eax , 0 ;        표현, 그런후 adc 명령으로 eax + 0 + CF를 연산 
------------- 
결과 eax = 1 

이제 adc 명령이 이혜가 가시는지요 ^^; 

Q2) 위의 소스를 보면 하위 32비트 덧셈 명령인 add 명령과 케리플레그 의 형향을 받는

덧셈인 adc 명령사이에 mov 라는 명령이 쓰였습니다. 그렇다면 이 mov 명령의 영향에

의해서 CF값이 바뀌어 adc 명령의 수행에 이상을 주지 않을까요? 

A2) 위의 설명을 좀더 이혜할 수 있는 기회입니다. 이 설명의 제일 앞 중에 영향을 주는

플레그리스트가 각 명령별로 틀리다고 말씀 드렸을 것입니다. 다행히도 mov 명령은

CF 플레그에 영향을 받지도 주지도 않는 명령어 입니다. 따라서 mov 명령은 CF 값을

변화시키거나 하지 않습니다. 참고로 mov 명령은 영향받는 플레그도 없고 주는 플레그도 없는

명령입니다. 그러므로 add 와 adc 사이에는 CF에 영향을 주지 않는 명령이라면 얼마든지

써도 되는 것입니다. ^^ 


이번에는 어셈블리를 이용한 최적화된 조건분기를 보여드리겠습니다.

만약 x값이 y값보다 큰지, 같은지 , 작은지를 비교해야 할 경우가 생긴다고 합니다.

이럴경우 일반적으로 연산을 구현할때

if (x=y) 


        file://같을때 처리문 

else 

        if (x<y) 
       { 
                  file://x가 y보다 작을때 처리문 
        } 
        else 
       { 
                  file://x가 y보다 클때 처리문 
        } 

과 같은 비교조건문이 생기게 됩니다. 이를 컴파일하여 디스어셈블 할 경우

엄청나게 최적화되지 않은 컴파일러라면 다음과 같이 됩니다.

file://VC 6.0 디스어셈블 예

102: if (x=y) 

00402F31 mov eax,dword ptr [ebp-54h] 
00402F34 mov dword ptr [ebp-50h],eax 
00402F37 cmp dword ptr [ebp-50h],0 
00402F3B  je   CTestasmView::OnBtnAsmTest+46h (00402f46) 
103: { 
105: } 
106: else 
00402F44 jmp  CTestasmView::OnBtnAsmTest+5Eh (00402f5e) 
107: { 
108: if(x<y) 
00402F46 mov ecx,dword ptr [ebp-50h] 
00402F49 cmp ecx,dword ptr [ebp-54h] 
00402F4C jge CTestasmView::OnBtnAsmTest+57h (00402f57) 
109: { 
111: } 
112: else 
00402F55 jmp CTestasmView::OnBtnAsmTest+5Eh (00402f5e) 
113: { 
115: } 
116: } 

하지만 이를 어셈블리 코드로 고친다음 다음과 같이 할 수 있습니다. 

mov eax , y 

cmp x , eax 
jg Lvl1 ;      x가 y보다 클경우 처리 
jl Lvl2 ;       x가 y보다 작을경우 처리 
;                x와 y가 같을경우 처리 
... 
jmp CmpEnd 

Lvl1: 
... ;             x가 y보다 클경우 처리문 
jmp CmpEnd 

Lvl2: 
... ;             x가 y보다 작을경우 처리문 

CmpEnd: 


좀더 사용 명령수가 줄어들게 됩니다. 뿐만 아니라 명령처리에 대한 흐름을 원활하기

위하여 이런 분기가 일어나지 않도록 컨디션 mov 명령이 팬티엄pro부터 생겨났으므로

조건에 따라 처리해야할 변수의 갯수가 적을 경우 이런 명령을 이용하면 무분기 조건처리도

가능합니다. 물론 flag와 명령의 처리특성을 이용하신다면 무분기 조건처리에 대한 여러가지

편법 또한 이용하실수 있게 되실겁니다. 


이번 강좌또한 마치며 한말씀 드리려 합니다. 항상 기본을 하며 말씀을 드리는 것이지만

어셈블리를 사용하실때 왜 프로그램의 소스에 어셈블리를 사용하는가와 어떨때 어셈블리를

사용하고 어떨때 사용하지 않는것이 낳은지를 잘 아시는것이 좋은 프로그래머의 예라 할 수

있습니다. 아직까지도 이런 부분에서 많은 분들이 어셈블리에 대하여 많은 소음이 끊이지

않고 있습니다. 저는 따로 생각하지 않고 적어도 어셈블리는 상용프로그램에서도 많이 사용되고

있으며 무언가 필요하기 때문에 외국 고수준 프로그래머들도 사용하고 있으며 같은 로직으로

되었을때 타사제품과 좀더 경쟁력을 갖추기 위하여 나은 수행속도를 원할때 선택되는 사항이라는

점을 알아주시면 좋겠습니다. 

출처 - http://ttongfly.net/zbxe/?document_srl=45194

'System' 카테고리의 다른 글

[펌]함수 호출규약 [Calling Convention]  (0) 2010.01.01
Format String Bug  (0) 2009.12.22
Reverse Engineering Cheat-Sheet  (0) 2009.11.30
What is SVN[Subversion]?  (0) 2009.11.14
어셈블리어 명령어 정리  (3) 2009.11.06
플레그는 아무하고나 만나주지 않는다.  (0) 2009.11.03
Posted by LinkC
이전버튼 1 이전버튼

블로그 이미지
LinkC

태그목록

Tistory Cumulus Flash tag cloud by BLUEnLIVE requires Flash Player 9 or better.

공지사항

Yesterday21
Today20
Total330,163

달력

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

최근에 받은 트랙백

글 보관함


. .