[CodeEngn Basic 15] obj. KNOW BOLRAND & Routine

2017. 8. 23. 01:050x02 Reverse Engineer/0x01. CodeEngn

728x90

 

문제 : Find the Serial when the name is CodeEngn


바이너리 실행 : 


 

 [그림 1] 바이너리 실행



 

 [그림 2] Borland Delphi


바이너리 컴파일 한 언어는 볼랜드였다.  볼랜드도 C기반이라고 들었던 것 같은데.. 

검색을 해보고, 링크를 걸어두었다.


https://en.wikipedia.org/wiki/Borland_C%2B%2B


올리디버거로 까보자.


볼랜드도 엔트리포인트는 4번대 주소였다. 그럼 c언어를 모토로 삼는 언어는 다들 유저 메모리 영역이 4번대인가보다.(exe)


 


 [그림 3] 처음 보는 Borland 구조 구경중~ 



win32api와 다른건 없다. 똑같이 모듈을 얻어오는 GetModuleHandle,  윈도우 창을 세팅해주는 SetWindow, GetDC, ReleaseDC 이런게 다 있다. 


대신 트레이싱으로 분석한 바로는 보통 바이너리를 열면 그 위치에 윈도우 함수 세팅이 있는데, 볼랜드의 경우는 함수를 하나 더 진입해야 윈도우 관련 세팅 함수가 존재했다. 이건 최적화의 유무일수도 있지만, 이때까지 분석한 바이너리와 비교했을 때는 분명히 다른 조건이기 때문에 메모하는 차원에서 끄적여본다.


문제 크랙만 해결하는 것도 중요하지만, 한 줄 한 줄 독서하듯이 어셈을 읽어가면서 숨겨진 의미를 찾는 것도 리버스 엔지니어링이 아닐까 하고 생각해본당 ㅋㅋㅋ

 그냥 내 생각임....                           어쩔? This is my way ~ 




0x458B14에서 Step over 하니까 바이너리가 dll을 주르륵 만나면서 풀려버린다. 

그럼 0x458B14에 Step into 해서 놀아봐야지?


그럼 00458B14   .  E8 1B87FEFF   call 15.00441234

0x00441234가 OEP임을 알 수가 있다.




 


 [그림 4] 실제 바이너리가 실행되는 위치(메인) 



모든 그림체에도 순서가 있듯이 윈도우 화면이 그려지는대도 순서가 있다는 것을 알 수 있는 아주 아주 기막힌 증상을 찍었다.


 

 [그림 5] 크랙 분석과는 아무 연관이 없는 삽질 (하지만, 모르고 지나치면 아쉬울 수 있는 부분)



 

 [그림 6] GetMessage 처럼 자원이 안들어오면 계속 무한루프다.... 

무한루프임 ! 데드락 아님!!!!!!!!! 


je -> jne로 패치하여 강제로 빠져나가보자. (난, 다음 흐름이 궁금하지 이 루틴에서 놀고 싶지가 않아...)


쭉쭉 흐름따라 구름따라 가게 되면 이런 구문이 나온다. 딱봐도 에러란다. 

잘못된 트레이싱을 해서 벌 받았다! 

004036FE  |.  BA 24904500   mov edx,15.00459024              ;  ASCII "Runtime error     at 00000000"

00403703  |.  B8 18B24500   mov eax,15.0045B218

00403708  |.  E8 D31C0000   call 15.004053E0

0040370D  |.  E8 511C0000   call 15.00405363

00403712  |.  EB 13         jmp short 15.00403727

00403714  |>  6A 00         push 0x0                         ; /Style = MB_OK|MB_APPLMODAL

00403716  |.  68 44904500   push 15.00459044                 ; |Title = "Error"

0040371B  |.  68 24904500   push 15.00459024                 ; |Text = "Runtime error     at 00000000"

00403720  |.  6A 00         push 0x0                         ; |hOwner = NULL

00403722  |.  E8 11DBFFFF   call <jmp.&user32.MessageBoxA>   ; \MessageBoxA


재 분석 들어간다.


해결하고 싶었는데, 너무 많은 시간을 투자하기엔 오늘 할 게 아직 많아서 패스.. (귀찮아서 일걸?) 


0x00458B14에 BP걸고 Step over하니 정상적으로 바이너리가 풀린다. 

난 그 풀리는 전체 과정이 궁금했는데, 시간되면 꼭 다시 할거다. 


지금 Name과 Serial에 뭘 넣든 간에 더이상의 루틴을 디버깅 하면으로 보지 못하고 상태 값이 Terminated가 될 것이다. 이유는 네임과 시리얼의 정답 유무를 체크하는 루틴을 아직 찾지 않았고, 관련 사항에 bp를 걸지 않았기 때문이다. 


오잉, 틀렸습니다. ㅋㅋㅋㅋ 

바이너리가 GUI환경이고, Check it 이라는 button을 클릭하고 새로운 서브 윈도우가 생기기 때문에, 서브윈도우와 메인 윈도우가 꺼지지 않는다면 Terminated가 안되게 코드를 짜두었나보다. 


그럼, 노답이니.... bp 찾아 떠나자.



하 여행가고싶다..비행기 1달 뒤면 타러간다 ㅋㅋㅋㅋ 


다시 분석으로 돌아오자. 오늘 뭔가 기분이 좋은지 헛소리가 많은 포스팅이다. 


버튼클릭 시 출력되는 메시지박스의 lptext들에 대해 구경하자.


 


 [그림 7] 버튼 이벤트 발생 시 나오는 출력문들



저 문구들이 보이는 곳에 반드시!! 분기에 도달하기 위한 CMP가 존재할 것이다.

여윽시 존재한다... 


 

 [그림 8] 성공과 실패에 접근할 수 있는 cmp 구문 발견 


상기 사진에서 EAX는 어떻게 받아올까 에 대한 답변을 해보자.


CMP위의 명령은 

0045882C  |.  E8 43EFFAFF   call 15.00407774

이것인데, CALL 0x00407774 이놈은 누가 봐도 함수다. 


우리의 함수는 반환형을 가지거나, 반환형을 가지지 않거나로 나뉘어진다. 

지금 eax와 [0x45B844]를 비교하는 것으로 보아 

0x45B844주소 안의 값과 EAX를 비교한다는 말과 일맥상통하게 되는데 

EAX가 정수이기때문에 가능한 일이다. 


그렇다면 우리는 call의 함수를 다음과 같이 슈도코드로 정의할 수 있겠다.


int 00407774 ( ) // 슈도코드라서 가능하다 함수나 변수의 맨 앞에는 숫자가 올 수 없다.  하지만, 컴파일 될 때는 다음과 같은 값이 가능하다. 왜냐? 주소이기 때문이다. 

우린 지금 함수명을 기입하고 있기 때문에 숫자가 되면 컴파일이 되지 않게 되는 것이다.

{

   return EAX;

}


아직 우리는 0x00407774 함수에 접근하지 않았으므로 구체적인 로직은 궁금해하지 않아도 된다. 궁금해하지말아요~~ (이수 노래 좋다) 


그럼 다시 코드로 돌아와서 


00458831  |.  3B05 44B84500 cmp eax,dword ptr [0x45B844]

00458837  |.  75 1B         jnz short 15.00458854

00458839  |.  B8 88884500   mov eax,15.00458888              ;  ASCII "You cracked the UBC CrackMe#1 ! Please send your solution to ubcrackers@hotmail.com !"

0045883E  |.  E8 29C1FEFF   call 15.0044496C

00458843  |.  BA E8884500   mov edx,15.004588E8              ;  ASCII "CRACKED"

00458848  |.  A1 3CB84500   mov eax,dword ptr [0x45B83C]

0045884D  |.  E8 9ECDFCFF   call 15.004255F0

00458852  |.  EB 0A         jmp short 15.0045885E

00458854  |>  B8 F8884500   mov eax,15.004588F8              ;  ASCII "Try Again !"

00458859  |.  E8 0EC1FEFF   call 15.0044496C


cmp의 destination과 source의 차가 0이 아니면 0x00458854로 점프 



자 이제 결과 루틴은 봤으니 결과를 제공하는 루틴을 살펴봐야 한다. 


 

 [그림 9] 분석하러 들어가자 고고 


 


 [그림 10] 분석을 위한 바이너리 값 




00407774  /$  55            push ebp

00407775  |.  8BEC          mov ebp,esp

00407777  |.  83C4 F0       add esp,-0x10


늘상 sub esp 만 봤찌만, 이놈도 esp의 위치에서 인덱스를 정하기 때문에 프롤로그로 봐도 무방할 것 같다. 


0040777E  |.  8955 F8       mov [local.2],edx

00407781  |.  8BD8          mov ebx,eax

00407783  |.  33C0          xor eax,eax

 eax에 내가 입력한 aaaaa가 있다. 


00407794  |.  8BC3          mov eax,ebx

다시 eax로 옮긴다. 


00407796  |.  E8 D9B1FFFF   call 15.00402974


그닥, 의미 있는 루틴이라고 생각되지는 않는다.

 

 [그림 11] ... ?? 


 


 [그림 12] 잘못된 값일 경우, 그 값을 lptext에 대입하여 출력시킴


 


 [그림 13] 더 자세히 분석하기 위해 코드 패치 


cmp eax, dword ptr 부분으로 리턴 될 수 있게 되었다.


이 문제는 특별한 루틴이 존재하지 않는걸까? 단순한 하드코딩과 주소 내의 값을 비교하는 거였는가..?? 


[0x45B844] 가 *flag 가 되는거고 

flag를 따라간 주소 공간의 값이 6061이 되는거??  

너무 허무하지않나... 



고친 jmp를 je로 다시 바꾸고 루틴을 run하면 0x458831로 안가길래 

이 주소를 hardware bp를 걸어보았다. 


그래도 여전히 에러가 뜬다. 

여기서 놓친 부분이 있다. 힌트가 존재했던 것이다!! 


'aaaaa' is not a valid integer value. 


이 부분은 reference text strings 에 잡히지 않았다.

너무 궁금해서 아는 형에게 물어봤는데 '유니코드' 라고 딱 말해주셨다.. 


오우 눈썰미를 키워야겠다 ㅋㅋ 그리고 메모리 윈도우에 들어가서 유니코드와 아스키코드를 둘 다 찾을 수 있는 법을 알려주었다 

alt+m -> ctrl + b 


 


 [그림 14] memory window -> dump 



aaaaa는 문자열이기 때문에 10진수가 아니었단 말임. 


그럼 십진수로 테스트를 진행하자.


 


 [그림 15] 개발자가 요구하는 십진수를 입력했다~~~~ 



bp 걸어둔 함수로 진입해서 처음 하는 일은 test 명령어를 만나서 아스키코드 12345가 12345로 그대로 유지 되어 je를 만족하지 못하고 그 다음 명령어로 내려간다.


0040297A  |.  85C0          test eax,eax

0040297C  |.  74 73         je short 15.004029F1             ;  test 연산 결과 0이 아니므로 점프 x


0040297E  |.  31C0          xor eax,eax

00402980  |.  31DB          xor ebx,ebx

여기서 다 0으로 바꿈 


[esi]는 12345이다


00402987  |> /8A1E          /mov bl,byte ptr [esi]

00402989  |. |46            |inc esi

0040298A  |. |80FB 20       |cmp bl,0x20

0040298D  |.^\74 F8         \je short 15.00402987


byte만큼 bl에 옮기므로 [0]~[4] 를 비교하는것이고, 


만약 bl이 0x20 (' ')이면 00402987로 점프 



0x20이 아니므로 다음 명령 ㄱㄱ 


0040298F  |.  B5 00         mov ch,0x0

00402991  |.  80FB 2D       cmp bl,0x2D

00402994  |.  74 69         je short 15.004029FF

00402996  |.  80FB 2B       cmp bl,0x2B

00402999  |.  74 66         je short 15.00402A01

0040299B  |.  80FB 24       cmp bl,0x24

0040299E  |.  74 66         je short 15.00402A06

004029A0  |.  80FB 78       cmp bl,0x78

004029A3  |.  74 61         je short 15.00402A06

004029A5  |.  80FB 58       cmp bl,0x58

004029A8  |.  74 5C         je short 15.00402A06

004029AA  |.  80FB 30       cmp bl,0x30

004029AD  |.  75 13         jnz short 15.004029C2


왠지

00402987  |> /8A1E          /mov bl,byte ptr [esi]

00402989  |. |46            |inc esi

0040298A  |. |80FB 20       |cmp bl,0x20 

0040298D  |.^\74 F8         \je short 15.00402987 

얜 배열 초기화 였던 것 같다. 아님말구

flag[ ] = {0}; 이랄까 

flag = (int *) calloc(갯수 * sizeof(int)) 이랄까  


그냥 스페이스 들어있는지 확인하는거일걸?


다시 명령어로 돌아오면 

내가 입력한 값 flag[0] = 0x31 ('1') 과 개발자가 세팅한 0x2d와 비교를 한다. 

if (flag[0] == '-') 면 0x4029FF로 점프하는데 다르니까 다음 명령어 쭉쭉쭉 내려간다. 


00402991  |.  80FB 2D       cmp bl,0x2D

00402994  |.  74 69         je short 15.004029FF

00402996  |.  80FB 2B       cmp bl,0x2B

00402999  |.  74 66         je short 15.00402A01

이까지는 다른 주소로 점프인데 


0040299B  |.  80FB 24       cmp bl,0x24

0040299E  |.  74 66         je short 15.00402A06

004029A0  |.  80FB 78       cmp bl,0x78

004029A3  |.  74 61         je short 15.00402A06

004029A5  |.  80FB 58       cmp bl,0x58

004029A8  |.  74 5C         je short 15.00402A06

얘는 같다. 


왜 그런지 볼까? 


 "2D2B2478587858".decode("hex")

'-+$xXxX' 


부호와 특수문자 알파벳의 소문자 대문자가 보인다. 


내가 찾아야 하는 건, 숫자다. 


아스키 코드표를 잠시 참조 해보겠다.


 




00402A06  |> \BF FFFFFF0F   mov edi,0xFFFFFFF

00402A0B  |.  8A1E          mov bl,byte ptr [esi]

00402A0D  |.  46            inc esi

00402A0E  |.  84DB          test bl,bl

00402A10  |.^ 74 DF         je short 15.004029F1

00402A12  |>  80FB 61       /cmp bl,0x61

00402A15  |.  72 03         |jb short 15.00402A1A

00402A17  |.  80EB 20       |sub bl,0x20

00402A1A  |>  80EB 30       |sub bl,0x30                     ;  Switch (cases 30..46)

00402A1D  |.  80FB 09       |cmp bl,0x9

00402A20  |.  76 0B         |jbe short 15.00402A2D

00402A22  |.  80EB 11       |sub bl,0x11

00402A25  |.  80FB 05       |cmp bl,0x5

00402A28  |.^ 77 D0         |ja short 15.004029FA

00402A2A  |.  80C3 0A       |add bl,0xA                      ;  Cases 41 ('A'),42 ('B'),43 ('C'),44 ('D'),45 ('E'),46 ('F') of switch 00402A1A

00402A2D  |>  39F8          |cmp eax,edi                     ;  Cases 30 ('0'),31 ('1'),32 ('2'),33 ('3'),34 ('4'),35 ('5'),36 ('6'),37 ('7'),38 ('8'),39 ('9') of switch 00402A1A

00402A2F  |.^ 77 C9         |ja short 15.004029FA

00402A31  |.  C1E0 04       |shl eax,0x4

00402A34  |.  01D8          |add eax,ebx

00402A36  |.  8A1E          |mov bl,byte ptr [esi]

00402A38  |.  46            |inc esi

00402A39  |.  84DB          |test bl,bl

00402A3B  |.^ 75 D5         \jnz short 15.00402A12

00402A3D  \.^ EB A9         jmp short 15.004029E8

00402A3F   .  C3            retn


여기로 점프하는데 일단 신경 쓰지 말아보자  


 

 저 위의 코드들은 다 je로 비교하는데 얜 왜 jnz로 비교를 하고 있을까에 대해 생각해보았다. 


004029C6  |>  80EB 30       /sub bl,0x30

004029C9  |. |80FB 09       |cmp bl,0x9

004029CC  |. |77 2C         |ja short 15.004029FA

004029CE  |. |39F8          |cmp eax,edi

004029D0  |. |77 28         |ja short 15.004029FA

004029D2  |. |8D0480        |lea eax,dword ptr [eax+eax*4]

004029D5  |. |01C0          |add eax,eax

004029D7  |. |01D8          |add eax,ebx

004029D9  |. |8A1E          |mov bl,byte ptr [esi]

004029DB  |. |46            |inc esi

004029DC  |. |84DB          |test bl,bl

004029DE  |.^\75 E6         \jnz short 15.004029C6


sub를 만나면 bl은 1이 된다. 

bl과 9를 비교하고 ja점프구문을 만나게 된다.


앞의 숫자가 더 작으므로 만족 x 


열심히 돌아 돌아 돌아 

004029D9  |.  8A1E          |mov bl,byte ptr [esi]

004029DB  |.  46            |inc esi


EBX에 0x30 ~ 0x39 까지 쭉쭉 증가하는 것을 발견했다.

 

루틴이 돌다가 bl이 0이 되면 0x4029e0로 내려온다.



아까~~ 저 위에서 말했듯이 함수의 반환 값을 EAX가 들고 쓕 나온다.


EAX는 3039


0040779B  |.  8BF0          mov esi,eax

0040779D  |.  837D FC 00    cmp [local.1],0x0

004077A1      74 23         je short 15.004077C6


ESI에 3039를 넣고, [local.1]과 0을 비교한다. 


004077C6  |> \33C0          xor eax,eax

004077C8  |.  5A            pop edx                          ;  0018F334

004077C9  |.  59            pop ecx

004077CA  |.  59            pop ecx

004077CB  |.  64:8910       mov dword ptr fs:[eax],edx

004077CE  |.  68 E3774000   push 15.004077E3

004077D3  |>  8D45 F8       lea eax,[local.2]

004077D6  |.  E8 0DC0FFFF   call 15.004037E8


xor 만나서 과감하게 0으로 바뀌는 eax....ㅠㅠ 

이제 스택에 넣어둔 아이들을 빼면서 이 함수를 불렀던 놈으로 돌아갈 준비를 하자. 

(이 함수에서 제공해준 놈을 다 놔두고 무일푼으로 돌아가자) 


004077D6  |.  E8 0DC0FFFF   call 15.004037E8


004037E8  /$  8B10          mov edx,dword ptr [eax]

004037EA  |.  85D2          test edx,edx

004037EC  |.  74 1B         je short 15.00403809

004037E8  /$  8B10          mov edx,dword ptr [eax]

004037EA  |.  85D2          test edx,edx


004037EC  |.  74 1B         je short 15.00403809


별 의미 없다 



이게 정답이다. 



6160은 HEXA DECIMAL 


입력한 값 : 12345  -> 레지스터에 보이는 값 3039(HEX)


그렇다면 정답은????? 


^,^ 



 호기심이 안드나?? 코드엔진 사이트에서 CodeEngn이라고 친절하게 Name을 알려줬는데 다른 Name일 때도 해보면, 이게 어떤 루틴이 적용되는지 알 수 있지 않겠는가????


이제 이름부분 알아보러가자.




이 함수를 유심히 보면 된다.  

00458760  /$  55            push ebp

00458761  |.  8BEC          mov ebp,esp

00458763  |.  6A 00         push 0x0

00458765  |.  53            push ebx

00458766  |.  56            push esi

00458767  |.  57            push edi

00458768  |.  BB 44B84500   mov ebx,15.0045B844                      ;  ASCII "@="

0045876D  |.  BE 48B84500   mov esi,15.0045B848

00458772  |.  BF 40B84500   mov edi,15.0045B840

00458777  |.  33C0          xor eax,eax

00458779  |.  55            push ebp

0045877A  |.  68 F3874500   push 15.004587F3

0045877F  |.  64:FF30       push dword ptr fs:[eax]

00458782  |.  64:8920       mov dword ptr fs:[eax],esp

00458785  |.  8D55 FC       lea edx,[local.1]

00458788  |.  A1 3CB84500   mov eax,dword ptr [0x45B83C]

0045878D  |.  8B80 CC020000 mov eax,dword ptr [eax+0x2CC]

00458793  |.  E8 28CEFCFF   call 15.004255C0

00458798  |.  8B55 FC       mov edx,[local.1]

0045879B  |.  8BC7          mov eax,edi

0045879D  |.  E8 9AB0FAFF   call 15.0040383C

004587A2  |.  33C0          xor eax,eax

004587A4  |.  8903          mov dword ptr [ebx],eax

004587A6  |.  8B07          mov eax,dword ptr [edi]

004587A8  |.  E8 B7B2FAFF   call 15.00403A64

004587AD  |.  85C0          test eax,eax

004587AF  |.  7E 19         jle short 15.004587CA

004587B1  |.  C706 01000000 mov dword ptr [esi],0x1

004587B7  |>  8B17          /mov edx,dword ptr [edi]

004587B9  |.  8B0E          |mov ecx,dword ptr [esi]

004587BB  |.  0FB6540A FF   |movzx edx,byte ptr [edx+ecx-0x1]

004587C0  |.  C1E2 03       |shl edx,0x3

004587C3  |.  0113          |add dword ptr [ebx],edx

004587C5  |.  FF06          |inc dword ptr [esi]

004587C7  |.  48            |dec eax

004587C8  |.^ 75 ED         \jnz short 15.004587B7

004587CA  |>  8B07          mov eax,dword ptr [edi]

004587CC  |.  E8 93B2FAFF   call 15.00403A64

004587D1  |.  C1E0 03       shl eax,0x3

004587D4  |.  0103          add dword ptr [ebx],eax

004587D6  |.  8B03          mov eax,dword ptr [ebx]

004587D8  |.  C1E0 02       shl eax,0x2

004587DB  |.  8903          mov dword ptr [ebx],eax

004587DD  |.  33C0          xor eax,eax

004587DF  |.  5A            pop edx

004587E0  |.  59            pop ecx

004587E1  |.  59            pop ecx

004587E2  |.  64:8910       mov dword ptr fs:[eax],edx

004587E5  |.  68 FA874500   push 15.004587FA

004587EA  |>  8D45 FC       lea eax,[local.1]

004587ED  |.  E8 F6AFFAFF   call 15.004037E8

004587F2  \.  C3            retn


 



'0x02 Reverse Engineer > 0x01. CodeEngn' 카테고리의 다른 글

[CodeEngn Basic 16]  (0) 2018.02.09
Basic 15번.  (0) 2018.02.09
[CodeEngn Basic 13] Find the Answer feat. C#  (0) 2017.08.20
[CodeEngn Basic 12] Can you replace?  (0) 2017.08.20
[CodeEngn Basic 11] Do you know StolenBytes?  (0) 2017.08.20