[reversing.kr] replace

취미/WarGame 2013. 10. 25. 13:29 |


이 프로그램은 우리에게서 숫자를 입력받는다.


허나, 프로그램이 정상적으로 실행되지 않을것이다.


그러니 디버거로 분석을 해보자.




프로그램을 F9 로 실행하면 위 명령에서 익셉션이 발생해 프로그램이 정지한다.


mov ptr [eax] , 90 


eax 레지스터가 가리키는 값에 90을 대입해라. 


이 패치가 오류의 원인이다. 





먼저 오류의 원인인 해당 명령을 NOP 으로 패치해보자 .


이렇게 코드를 바꾸니 일단 실행은 정상적으로 잘 이루어 졌다. 


그리고 나서 나오는 핵심 코드 





NOP으로 넘겨서 돌아온곳은 다름아닌 JMP문이다. 


그것도 가장 중요한 correct 메시지를 출력하는 루틴을 건너뛰어버리는 JMP문! 


이 코드는 대체 왜 있는걸까 ? 


이 코드가 있는 한 어떠한 값을 입력해도 절대로 correct 메시지를 볼 수 없다. 


어거지로 이 JMP문을 NOP으로 채워보면  어떠한 값을 입력해도 correct메시지를 볼 수 있지만, 우리가 원하는 건 이런 메시지를 보는 것이 아니다. 

correct가 실행될 수 있는 입력값을 찾는 것이다. 




다시 여기로 돌아와 코드를 유심히 살펴보자.

eax 는 우리가 입력한 값에 특정 연산이 더해진 값이다. 즉 우리가 입력하는 값에 따라 그 수치가 바뀐다.


그리고, 그곳에 90이라는 데이터를 입력하려고 한다.


바로 이곳이 유요하지 않은 메모리를 가리킬 경우 메모리 참조 오류를 내면서 프로그램이 뻗어버리는 것인데 .


유효한 메모리공간으로 바꾸어 버리면 어떻게 될까?


예를 들어 코드영역내의 어느부분을 가리키도록 말이다.


90은 어셈 NOP명령의 OP코드이다.


즉, eax 가 특정 메모리를 가리키도록 만들면 이 명령에 의해 해당 메모리가 NOP명령어로 패치된다는 얘기다.


이정도면 거의 정답을 다 말한셈이다.


그래도 모르겠다면 프로그램을 몇 번이고 다시 분석해보길 권한다.






답 :



우리가 위에서 확인한 correct 를 건너 뛰어버리는 JMP문 그곳을 eax 가 가리키도록 해서 NOP으로 패치시키면 correct 메세지를 볼 수 있고


그 값이 바로 답이다.


여기까지 왔다면 계산기를 조금 두들겨 답이 무엇인지 알 수 있을것이다.


'취미 > WarGame' 카테고리의 다른 글

[reversing.kr] ImagePrc  (0) 2015.02.21
[reversing.kr] Position  (0) 2013.10.30
[reversing.kr] Easy_UnpackMe  (0) 2013.10.24
[reversing.kr] 2번 문제 Easy_keygen  (0) 2013.10.23
[reversing.kr] 1번 문제 Easy CrackMe  (0) 2013.10.18
Posted by $Zero
:

정말 파일명 그대로 쉬운 언팩 파일. 


분석이랄 것도 없고 그냥 OllyDBG 의 F8만 쭈우우욱 누르다가 OEP에 도달했다. 


딱봐도 언팩된 코드를 다시 푸는 XOR연산들이 연이어 나오는데 


이 루프들을 다 BP로 건너 뛰고 하다보면 금방 OEP에 도달한다. 


무엇보다 F8을 쭉 누르고 있으면 IAT를 복구하는 루틴도 볼 수 있으니 쉽게 이게 언팩 루프라는 걸 알아낼 수 있었다. 

'취미 > WarGame' 카테고리의 다른 글

[reversing.kr] ImagePrc  (0) 2015.02.21
[reversing.kr] Position  (0) 2013.10.30
[reversing.kr] replace  (0) 2013.10.25
[reversing.kr] 2번 문제 Easy_keygen  (0) 2013.10.23
[reversing.kr] 1번 문제 Easy CrackMe  (0) 2013.10.18
Posted by $Zero
:




이 부분의 알고리즘과 사용되는 버퍼를 자세히 살펴보면


입력받은 name으로 serial key를 생성하고 있다.


버퍼에 10, 20, 30 값을 입력하고


루프를 돌면서 name 버퍼의 하나하나에 순서대로 xor 연산을 수행한다.


5B 13 49 77 13 5E 7D 13


즉, 이러한 serial key 를 만드는 이름을 찾는건


해당 시리얼을 위 알고리즘처럼 순서대로 10 , 20 , 30


을 다시 XOR연산해서 원래의 값으로 되돌리면 된다.


원래 값으로 되돌리는 데에 사용된 소스 코드



#include <stdio.h>

int main(void)
{
   int i=0;
   int j = 0x10;
   int str_count=0;
   char passwd[100] = {0x5B,0x13, 0x49, 0x77, 0x13, 0x5E, 0x7D, 0x13};


   //scanf("%s", passwd);


   for(i=0; i<100; i++)
   {
      if(passwd[i]==0)
     break;

      str_count++;
   }


   for(i=0; i<str_count; i++)
   {
      if(j == 0x40)
     j = 0x10;
      passwd[i] ^= j;
      j += 0x10;

      printf("%c ", passwd[i]);
   }






   return 0;
}



'취미 > WarGame' 카테고리의 다른 글

[reversing.kr] ImagePrc  (0) 2015.02.21
[reversing.kr] Position  (0) 2013.10.30
[reversing.kr] replace  (0) 2013.10.25
[reversing.kr] Easy_UnpackMe  (0) 2013.10.24
[reversing.kr] 1번 문제 Easy CrackMe  (0) 2013.10.18
Posted by $Zero
:





한줄 한줄 StubCode를 트레이싱해서 넘어가면 


CALL 401000 명령으로 메인 함수를 호출한다.




내부로 들어가면 입력을 받을 수 있는 DialogBox가 뜨고 


이 때부터 이벤트 핸들러에 BP를 걸어서 디버깅을 진행하면 된다. 


처음에 Easy CrackMe를 실행했을 때 작동하는 API를 유추해서 


MessageBox 에 BP를 거는 것도 좋은 방법이다. 





ollydbg 의 name in module 


에서 messagebox를 찾아보면 코드에서 호출하는 MessageBoxA 함수가 보인다. 


엔터를 쳐보면 어느주소에서 호출하는지 나오는데 모두 BP를 걸면 되겠다. 


그리고 아무값이나 입력해보면 해당 이벤트 핸들러에서 정지! 


이제부터 이 이벤트핸들러를 분석하면 password를 찾아낼 수 있다. 





이벤트 핸들러를 보면 GetDlgItemTextA 를 이용해서 어느곳에선가 문자열을 읽어오고 있다.






 

GetDlgItemTextA가 호출되면 우리가 입력한 값으로 버퍼에 데이터가 채워진다. 


이 CrackMe는 이렇게 우리가 입력한 문자열과 내부 문자열을 비교하는 프로그램 같다.


GetDlgItemTextA API의 바로 밑을 보자 


CMP BYTE PTR SS:[ESP+5],61


명령어가 있다. ESP+5의 값과 61(a)를 비교하고 있는데 


ESP+5는 바로 우리가 입력한 버퍼의 2번째 데이터이다. Index(1) 


즉, 2번째 데이터가 a 인지 검사하고 있다. 만약 여기서 a가 아니라면 Zero Flag 가 0이되고 

그 밑에 오는 JNZ명령에 의해 비밀번호가 틀렸다는 MessageBoxA로 흐름이 넘어가 버린다. 


값을 다시 입력해서 2번째에 a가 오도록 아무값이나 입력하고 이 루틴을 넘어가보자. 

(메모리를 고쳐서 넘어가도 된다.) 


그 다음에 등장하는 5y  문자.




ECX에 우리가 입력한 3번째 값부터 나머지값을 넣고 

5y라는 문자열과 함께 CALL 401150 서브루틴 함수를 호출하고있다. 

이 함수 내부를 따라들어가면 우리가 입력한 버퍼의 3번째값부터 

5y 문자열을 1byte씩 비교한다. 


두번째,

3,4번째 문자를 5y로 변경하라. 


알아낸 정보 


?a5y??????


이제 거의 다왔다. 


R3versing이라는 문자열의 등장. 


언뜻보기엔 이녀석이 패스워드같다. 


하지만 아니다, 조금 더 파헤쳐보자 





가장 중요한 루틴이다. 


우선 EAX에 우리가 입력한 버퍼의 Index(4)의 값을 대입한다. 


즉 ?a5y를 입력했다면 이 부분엔 아직 데이터가 없다. 


?a5y???? 입력했다면 ????4개가 들어가있었을터. 


이 루틴은 반복문인데 1번 반복할때 우리가 입력한 버퍼와 

R3versing 문자열이 들어있는 ESI값을 

1BYTE씩 계속 비교한다. 


이 값 역시 완전히 일치해야한다. 

여기까지 진행하면 우리가 얻은 데이터는 이러하다 


?a5yR3versing 


거의 다왔다. 


이제 앞의 ?값이 무엇인지만 알아내면 된다. 





바로 이 부분! 


ESP+4 와 45를 비교하고있다. 


직접 스택을 확인하면 알겠지만 저건 우리가 입력한 버퍼의 시작주소이다. 

즉 첫번째 바이트의 주소이다. 

그 바이트부분이 45인지 검사하고 있다. 


45는 E


Clear.

 



'취미 > WarGame' 카테고리의 다른 글

[reversing.kr] ImagePrc  (0) 2015.02.21
[reversing.kr] Position  (0) 2013.10.30
[reversing.kr] replace  (0) 2013.10.25
[reversing.kr] Easy_UnpackMe  (0) 2013.10.24
[reversing.kr] 2번 문제 Easy_keygen  (0) 2013.10.23
Posted by $Zero
:





win XP의 CreateRemoteThread 실행 과정


kernel32!CreateRemoteThread() -> ntdll!ZwCreateThread()


windows7 의 CreateRemoteThread 실행 과정


kernel32!CreateRemoteThread() -> kernelbase!CreateRemoteThreadEx() -> ntdll!ZwCreateThreadEx()


kernelbase는 Vista 이상부터 추가된 dll 파일 . 


zwCreateThreadEx() native API를 호출하는 코드루틴을 보면 


Suspend 모드로 쓰레드를 생성하고 


Resume API를 호출하기전에 어떠한 변수값을 비교한 후 점프를 함 .


만약 그 변수값이 틀릴 경우 Resume을 호출하지 않고 그대로 ExitThread API함수로 건너 뜀 


Resume 이 실행되야 Suspend모드에서 빠져나올 수 있으므로 


해당 CMP 명령어에서 변수값을 똑같도록 맞춰주면 DLL인젝션이 성공적으로 이루어짐

'레퍼런스 > Reversing' 카테고리의 다른 글

ADM 64 Linux 호출 규약  (0) 2018.05.18
[리버싱] 어셈블리어 정리  (1) 2014.05.13
Posted by $Zero
:
BOOL WINAPI ReadProcessMemory(
  _In_   HANDLE hProcess,
  _In_   LPCVOID lpBaseAddress,
  _Out_  LPVOID lpBuffer,
  _In_   SIZE_T nSize,
  _Out_  SIZE_T *lpNumberOfBytesRead
);

Process의 메모리 데이터를 읽어오는 함수.


hProcess : 메모리에서 데이터를 읽어 올 프로세스의 핸들. 이 핸들에대한 PROCESS_VM_READ 권한이 있어야 메모리에 액세스 할 수 있다. (디버깅 모드로 접근시 모든 메모리에대해 R/W 권한이 있다.)

lpBaseAddress : 읽어올 데이터가 있는 메모리의 주소. 이 메모리의 주소는 hProcess내의 공간이어야 하며, 시스템은 이 메모리에 접근해 데이터를 전송하기전에 이 메모리를 읽을 권한이 있는지 검사하고 혹 권한이 없다면 함수를 종료한다.

lpBuffer : lpBaseAddress에 있는 데이터를 받기위한 버퍼의 포인터 . 

nSize : 받을 데이터의 크기 


lpNumberOfBytesRead : 받아온 데이터의 크기를 저장할 변수의 포인터. 

(NULL로 지정시 이 파라미터는 무시된다.)


Posted by $Zero
:



Token관련 API 

Windows의 Token은 계정의 권한과 같은 개념이다. 

윈도우엔 권한이 있어야만 실행 가능한 것들이 있다.

(현재 로그인 세션의 권한을 저장하고 있는 변수라고 생각하면 된다.)


BOOL WINAPI OpenProcessToken(
  _In_   HANDLE ProcessHandle,
  _In_   DWORD DesiredAccess,
  _Out_  PHANDLE TokenHandle
);

ProcessHandle : Token을 얻어올 프로세스 핸들
DesiredAccess : 해당 프로세스에서 필요한 액세스 권한
TokenHandle : 얻어온 토큰이 저장될 핸들


BOOL WINAPI LookupPrivilegeValue(
  _In_opt_  LPCTSTR lpSystemName,
  _In_      LPCTSTR lpName,
  _Out_     PLUID lpLuid
);

*LUID = locally unique identifier의 약자로 Windows가 부팅되면서 부여되는 64비트크기의 고유한 값이다.


함수 이름 그대로 PrivilegeValue 값을 Lookup하는 함수. 
PrivilegeValue는 LUID로 구분되어 OS가 리부팅 되기전까지 가지고 있는 고유한 값이다.
(각 권한마다 매 부팅시 LUID가 부여되며 우리는 이 LUID로 권한을 disable || enable 시킬 수 있는 것이다.)


lpSystemName : Privilege값을 얻어올 시스템 이름. NULL입력시 로컬 시스템.
(MSDN에서 의미하는 System이 호스트단위인지는 잘 모르겠다. 공유가 가능한 LAN상에서 타 시스템의 Token도 가지고 올 수 있는건지 MSDN에선 Local system과 일반 system을 구분하고 있다.)

lpName : PrivilegeValue를 얻어올 PrivilegeName. 

lpLuid : PrivilegeValue를 받을 LUID자료형의 포인터. 



BOOL WINAPI AdjustTokenPrivileges(
  _In_       HANDLE TokenHandle,
  _In_       BOOL DisableAllPrivileges,
  _In_opt_   PTOKEN_PRIVILEGES NewState,
  _In_       DWORD BufferLength,
  _Out_opt_  PTOKEN_PRIVILEGES PreviousState,
  _Out_opt_  PDWORD ReturnLength
);

명시된 Access Token 의 권한을 Disable || Enable 시킬 수 있는 API함수


TokenHandle : 권한을 수정 할 Token의 핸들.

DisableAllPrivileges : 수정과는 관계없는 인자이므로 FALSE를 전달하면 된다. 이 값이 TRUE일 경우 NewState값은 무시되며 모든 권한이 Disable된다 

NewState : 수정할 토큰에대한 정보를 담은 TOKEN_PRIVILEGES 구조체의 포인터 (예제 참조)

BufferLength : 버퍼의 크기 입력. 수정되기전의 Privileges 값을 받을 때 사용되는 버퍼같은데 어떻게 사용하는지는 자세하게 알지 못하겠다. 0으로 설정하려면 PreviousState (변경되기 전의 값을 받을 포인터) 또한 NULL이어야 한다. 

PreviousState : 변경되기전 Privileges값을 받기위한 포인터 

ReturnLength : 변경되기전 Privileges 값을 리턴받기위해 필요한 바이트 크기. TOKEN_PRIVILEGES의 크기를 말하는 것 같다. 이 역시 PreviousState값이 NULL일 경우에만 NULL을 전달 할 수 있다. 




예제


int main(int argc, char *argv[])
{


if(!SetPrivilege(SE_DEBUG_NAME, TRUE))
return 1;

return 0;
}



BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
TOKEN_PRIVILEGES tp;
HANDLE hToken;
LUID luid;

if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{  
printf("OpenProcessToken error : %u\n", GetLastError());
return FALSE;
}

if(!LookupPrivilegeValue(NULL, lpszPrivilege, &luid))
{
printf("LookupPrivilegeValue error : %u\n", GetLastError());
return FALSE;
}

tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if(bEnablePrivilege)
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;   
else
tp.Privileges[0].Attributes = 0;               // disable

if(!AdjustTokenPrivileges(hToken, 
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES) NULL,
(PDWORD)NULL))
{
printf("AdjustTokenPrivileges error : %u\n", GetLastError());
return FALSE;
}

if(GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
printf("The token does not have the specified privilege. \n");
return FALSE;
}

return TRUE;
}




**  TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY 에 대해 


TOKEN_ADJUST_PRIVILEGES : Token에 Access권한을 설정하는 AdjustTokenPrivileges를 호출할때 필요하다
MSDN : Required to enable or disable the privileges in an access token.


TOKEN_QUERY : 어떤 값인지 감을 못잡겠다 . 

MSDN의 내용대로라면 Lookup API를 쓰기위해 지정해주는 mask가 아닐까 한다. 

query : 질의, 문의 

MSDN : Required to query an access token.

Posted by $Zero
:

15살. 남 주인공과

27살. 띠동갑 차이의 여 주인공. 


나이도 환경도 다르지만 어딘가 어린아이 같은 두 주인공이 서로를 보완하며 보다 완벽해지는 


진부하지만, 애틋한 작품이다 . 


작화는 역시 '신카이 마코토'라는 말이 나올정도로 초속 5CM 보다도 더 진화했다고 평가하고 싶다.






















Posted by $Zero
:

GetModuleFileName 함수는 현재 실행되고 있는 프로세스의 이름을 얻어오는 함수이다. 


MSDN에 정의된 GetModuleFileName API


DWORD WINAPI GetModuleFileName(
  _In_opt_  HMODULE hModule,
  _Out_     LPTSTR lpFilename,
  _In_      DWORD nSize
);


첫 번째 인자는 이름을 얻어올 프로세스의 핸들이지만, 보통 자신의 이름을 얻기 위해 사용되므로 

NULL로 사용되는 경우가 대부분이다.  첫 번째 인자에 NULL을 넣어 사용하면 현재 자신의 이름을 얻어오고

그 외 다른 프로세스의 핸들을 전달하거나 DLL파일의 핸들을 전달하면 해당 모듈의 파일 이름을 얻어온다. 


두 번째 인자는 해당 모듈의 이름이 입력될 버퍼를 넣어주면 되고 


세 번째 인자는 버퍼의 사이즈를 넣어주면 된다. (이런 사이즈인수는 대부분 버퍼오버플로우 공격을 하지 못하도록 하기 위함이라고 보면 된다. 그외 오버 플로우로 인한 버그의 소지도 예방할 수 있음.)

Posted by $Zero
:

DLL 코드 작성


DLL 코드에는 winMain 함수처럼


DllMain 콜백 함수가 정의 된다.


DllMain을 정의하지 않더라도 컴파일이 되는데, 이는 컴파일러가 내용이 텅 빈(empty) dummy code를 삽입하기 때문이다.


반드시 필요하지만 정의하지 않을경우 컴파일러가 Default DllMain을 삽입해준다는 소리다.



DllMain 함수의 모양은 아래와 같다.


BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved)
{
    switch(dwReason)
    {
    case DLL_PROCESS_ATTACH:
        break;

    case DLL_PROCESS_DETACH:
        break;

    }

    return TRUE;
}


일단 첫 번째  인자는 winMain의 hInstance와 같은 역할을 하는 DLL자신의 인스턴스이다.


두 번째 인자 dwReason은 DLL이 호출된 이유를 담고 있다. 위 Switch 문이 그 값에 해당한다.

DLL_PROCESS_ATTACH: DLL파일이 처음 프로세스에 로딩되었을 때 (ex : LoadLibrary() 호출)

DLL_PROCESS_DETACH: DLL파일이 프로세스에서 해제 되었을 때  (ex : FreeLibrary() 호출)

이 외에도 스레드 관련 값이 2개 더 있다. (MSDN 참조)





Posted by $Zero
: