Level 38 이번레벨에서는이전과다른패커를언패킹하면서 DLL & TLS(Thread Local Storage Callback function) 을이용하여디버거를탐지하는것에대한우회방법을다룰것이다. 악성코드에서사용하는패커 / 프로텍터들은리버싱을어렵게하기위해자체적은방어코드를가지고있기때문에단순히분기문을패치하는식으로는해결되지않는다. 내부적으로호출되는함수들을계속 "Step-In" 하면서분석해야한다. 이전과마찬가지로순수올리디버거 ( 플러그인사용 X) 를이용할것이다. * 분석바이너리는 NT 기반의시스템이아니라면대부분작동되지않을 것이다. (NT 시스템인데도불구하고작동되지않으면, 어쩔수없다 ) 올리디버거에서해당바이너리를열면 "System EP" 에서멈춰있는것을 볼수있다. 레벨 3 를이해한사람이라면뭔가느낌이올것이다. 메모리맵창 (Alt+E) 에서바이너리의 PE header 를보면위사진에서보듯이 몇가지항목은프로텍터에서 0 으로변조한것을볼수있다. 그러나 ImageBase 처럼중요한항목은원래값으로유지된다.
EP의주소값이 "RVA : 00000000" 인것을볼수있다. 당연히올리디버거에서는해당 EP 값을이상하다고판단하여처음에 "System EP" 에서멈춘것이다. * ImageBase + RVA EP == VA EP 400000 + 0 == 400000 현재바이너리에적용된프로텍터는언패킹루틴을트레이싱하면서어떤코드가실행되는지알수있게해준다. ( 왜냐하면프로텍터가무료이기때문에코드자체가간단하게이루어져있기때문이다 ). 늘그래왔던것처럼이번에도 F8 트레이싱을하다가어느함수에서프로그램을실행시키면해당함수내부를 F7 트레이싱을하는방식으로하면된다. 다만프로텍터가난독화코드 ( 분기문다음에 opcode 'CD" 존재 ) 를사용하기때문에우선가독성을위하여난독화부터해제할것이다. 이를위해서 "Analyze This!" 플러그인을사용할것이다. ( 사용하면좌측 => 우측사진처럼약간변한다 )
"400007 JMP 400195" 명령을통해 DOS 헤더를벗어나고있다. 이전에 말했던것처럼분기문과실제코드사이에난독화코드 - "opcode CD" 가 존재한다. Step-Over 트레이싱을하면위주소의명령어에서 nag 창이발생하고있다. 따라서해당함수에서 Step-In / 내부진입후분석을해야한다. 재시작후 4001FB 함수내부진입후트레이싱을하면위사진의코드루틴을볼수있다. "40020E INT 2E == Interrupt 2E" 명령어는내부적으로 "ZwSetInformationThread" 함수를호출하고이때올리디버거나일반적인자동언패커는멈추게된다. 이런기법은 "Ring3" 레벨의디버거를무력화시킬수있다. 해당명령어를실행하면디버거는계속실행되지만분석을진행할수없기때문에우회해야한다.
간단하게 INT 2E 명령어다음주소 (400210) 주소에 New origin here 기능을이용하면우회된다. 400219 CALL 400221 에서 nag 창이호출되기때문에 Step-In 한다. 내부진입후쭉쭉트레이싱을하다보면레지스터에몇가지값이세팅된뒤 "400355" 함수를호출하고있는것을볼수있다. 해당함수에서언패킹루틴이완전히이루어지기때문에분석을원하면 Step-In, 건너뛰려면 Step-Over를하면된다. 지금은 Step-Over를할것이다. 해당함수를실행하면언패킹이정말끝난것일까아닐까?? * 윈도우즈로더에서메모리에패킹된바이너리를매핑시켰을때, 로더는언패킹루틴에서사용하는 DLL/API 함수를찾게된다. ( 원본바이너리에서사용하는것을찾는것이아니다 ). 따라서 OEP로분기하기전 / 그리고바이너리의명령어로돌아가기전에언패킹루틴은또다른작업을하게된다. 바로원본바이너리에서사용하는 DLL을메모리에매핑시키고원본바이너리의 IAT를복구해야한다. 따라서언패킹루틴은 "LoadLibraryA / GetProcMemory" 함수를사용한다.
user32.dll 파일을 LoadLibraryA 함수인자로주고실행하면, 77CF000 을 리턴하는것을볼수있다. * LoadLibraryA 함수는특정실행가능모듈 (exe / dll) 파일을호출하는프로세스의주소공간에매핑시킨다. 참고로해당함수의파라미터인 "lplibfilename" 은모듈을이름을의미한다. 만약해당모듈의경로가명시되지않고확장자가생략되었다면기본적으로 dll 이라고인식한다. 또한함수의실행이성공하면모듈의핸들값을리턴하고실패하면 NULL 값을리턴한다. * 즉, LoadLIbraryA 함수는 DLL 파일을매핑하고, "GetProcAddress" 함수에서 DLL 함수의주소를알아낼수있게모듈의핸들값을리턴한다. 위주소까지트레이싱을하고시스템스택창을보니언패킹루틴에서 "ShowCursor" API 함수를 user32.dll 내부에서찾았고 IAT 영역에해당함수의실제주소를작성하려고한다. 이를위해서 "GetProcAddress" 함수를사용할수있다. * GetProcAddress 함수는실행이성공한경우익스포트된 DLL 함수의 주소를리턴하고실패하면 NULL 값을리턴한다. 즉, 이함수는 DLL 내부의 익스포트된함수의실제주소를리턴한다.
본인의환경에서는 "ShowCursor" 함수주소가 "VA 77D0FA6E" 임을알수있다. "400321 MOV [EDX],EAX" 명령을통해이전에얻은함수의주소를 IAT 영역에작성하고있다. 따라서 IAT 영역주소는대충 "40104C" 주위일것이다. 400321 MOV [EDX],EAX 명령어를실행하면 IAT 영역 (40104C) 에이전에 GetProcAddress 함수를통해얻은 ShowCursor 주소를작성한다.
"40032F JMP 4002DD" 명령을통해루프를돌고있는듯한데어찌보면당연한것이 "user32.dll" 파일내부에서바이너리에서사용할다음 API 함수주소를찾아야하기때문이다. 이런식으로 "user32.dll" 의모든함수의주소를가져와서 IAT 영역에작성하였다면, 이번에는 "400334 JMP 40029B" 명령어를통해 "LoadLibraryA" 함수쪽으로이동하여다음 dll 파일을찾게된다. 모든 API 함수 / DLL 파일에대한루프가종료되면하드코딩된 OEP(RVA 주소 ) 값을 imagebase 값에더한뒤 "400351 JMP EAX" 명령어를통해 OEP로분기할것이다. LordLibraryA 함수와 JMP EAX 명령어에 BP 를설치한뒤실행하여어떤 DLL 파일을사용하는지볼것이다. BP를이용해서계속실행하면여러개의 dll 파일을찾고있는것을알수있다. 그런데 msvcrt.dll 을찾은뒤실행하면디버거가탐지되었다는 Nag 창이나타난다. 처음에는프로텍터가탐지한줄알았지만말이안되는게지금은언패킹루틴의마지막작업인 IAT 복구를하고있는중이다. 프로텍터코드에서탐지를하려면훨씬이전에탐지되어야한다. 따라서바이너리자체에서디버거를탐지한것이라는결론을낼수있다. 그런데아직바이너리의 OEP로분기하지도않았고, 원본코드는한줄조차실행되지않았는데도탐지된상태이기때문에이해가가지않을수있다. 원인은의외로간단한데로딩되는
모든 DLL 파일은해당 DLL 파일을이용하는바이너리에제어권을넘기기전에 자체적으로코드를실행할수있다. 따라서바이너리에서사용하는 DLL 파일의코드에서디버거를탐지한것이라는결론이좀더정확하다. Nag 창이나타나고종료해보니 "lena151.dll" 파일의주소를불러온것을알수있다. 즉, 해당 DLL 파일이로딩되는순간자신의코드를실행하면서디버거를탐지한것이다. 다행이악의적인파일이아니기때문에단순한 Nag 창만생성했지만실제악성코드라면어플리케이션자체 / 윈도우를종료시키거나데이터삭제등을수행할수도있다. 이런경우라면디버거를실행하여 DLL 파일을로드하기전에 Step-In 트레이싱을하여탐지를회피해야한다. 어떤부분이문제인지덤프를뜨면서분석할것이다. 일단 "JMP EAX (JMP OEP)" 주소까지이동한뒤, 해당명령어를실행하기전에 "lena151.dll" 파일에 대해약간알아볼것이다. "Alt+E ( 모듈창 )" 을보니문제가되는 "lena151.dll" 파일은 "Base 값, 10000000, EP 값,10001000" 으로매핑되었다. 해당파일의 EP 로이동한뒤 HW BP 를설치하여 DLL 파일의코드가실행될때분석을계속할것이다. 위사진과같이 lena151.dll 파일의 EP 주소에 HW BP 를설치한다.
이제 JMP EAX 명령을실행하여 OEP 로이동한뒤덤프를뜰것이다. ImpREC 툴에서해당바이너리를선택하고 OEP 값을이전에찾은 10C0 으로바꾼뒤 AutoSearch Get Imports 를클릭하면위사진처럼모든모듈의상태가 "valid:yes" 이기때문에완벽할듯하지만, 실제로는문제가되는부분들이존재한다. 또한 "lena151.dll" 파일은찾지못했고해당모듈의 API 함수역시임포트되지않았다. 이상태에서덤프뜬뒤 IAT를수정하면어떻게되는지볼것이다. Rebuild Import 옵션을체크해제하고덤프를뜬뒤 ImpREC 툴에서 Fix Dump 를클릭하여덤프뜬바이너리의 IAT 를수정한뒤실행하면위사진 처럼오류가발생한다.
따라서 ImpREC 툴에서기존에찾아진모듈정보를 Clear Improts 를클릭 하여삭제하고, Get API Calls 기능을이용하여다시임포트모듈을찾는다. 위사진처럼 "ImpREC" 툴에서이전과달리좀더많은 dll 파일을찾을수있었던이유는 IAT 영역을나누었기때문이다. 동일한섹션이지만한부분은 "401000" 주소영역에위치하고 "lena151.dll" 모듈과함수는 "493000" 주소영역에위치하고있다.
다시 "Fix Dump" 를하려고하면위사진처럼에러가발생한다. 리버서가실수한것이아니라어셈블러에서코딩했을때일종의강력함을보여주는코딩트릭이다. dll 파일 ( 지금은 lena151.dll) 에서 API 함수를임포트할때임포트하려는 API 함수가 "ImpREC" 를방해하고있다고할수있다. 위사진에서 Show Suspect 를누르면약간정보가맞지않을만한함수들이 보여진다. 가장마지막함수주소를보니 "RVA : 0FC05069" 이다. 주소자체가바이너리 에서가질수없는주소이다. 해당주소자체를 "FindWindowA" 함수로 인식하고있으니디스어셈블하여분석할것이다.
"FindWindowA" 함수가맞긴맞다. 다만이함수를 IAT 영역에서삭제해야한다. 삭제를하여도 "lena151.dll" 파일에서바이너리에서사용할수있게해당함수를임포트할것이고, 이것자체로 API 함수의코드는 dll 파일을가리키게될것이다. * 확신이가지않으면 "CommandBar" 플러그인을이용하여 "? FindWindowA" 를입력하고엔터키를눌러서보거나, "Address & Function Convert" 등과같은툴을이용할수도있다. 문제가되는함수를삭제한뒤덤프를뜬바이너리를대상으로다시 Fix Dump 를하여 IAT를수정한다음올리디버거를종료한뒤이전에덤프 / 수정된파일을실행하면정상적으로동작할것이다. 이제디버거우회를위하여새롭게덤프 / 수정된파일을디버거에서분석할것이다.
새로운바이너리를디버거에서열면이전에설치한 "lena151.dll 파일의 HW BP" 에서멈춘다. 만약안멈추면다시세팅하면된다. 처음에는 FindWindowA 함수를이용하여현재실행되는프로그램중 OLLYDBG 가있으면분기가일어난다. 분기되는곳을보니디버거탐지 nag 창과관련된메시지박스함수코드이다. FindWindowA 함수가실행후임의로 Zero Flag 값을변경하면다음에는 IsDebuggerPresent 함수를이용하여디버거를탐지하고, 탐지되면 nag 창코드로분기시켜버린다. FindWindowA / IsDebuggerPresent 함수실행후임의로 Zero Flag 값을 변경하면이번에는 ZwQueryInformationProcess 함수를이용하여디버거를 탐지한다.
탐지되면이번에는 Internal OLLYDBG error 라는메시지박스창을띄우는 코드로분기시킨다. 얼핏보기에는올리디버거에러지만실제로는안티 디버깅기법이다. FindWindowA / IsDebuggerPresent / ZwQueryInformationPrecess 총 3개의디버거탐지루틴다음에는리턴된느것으로보아이제슬슬패치를하면된다. 이전에보았던분기문을패치해도되지만그냥 dll 파일의 EP 부분을 "RETN" 으로패치하면코드는실행되지않지만임포트는정상적으로될것이다. 패치하면 DLL 파일을저장해야하는데기본적으로 system32 폴더가선택되어있다. 윈도우즈에서는 dll 파일경로가명시되지않은경우 system32 폴더에서찾기때문에해당폴더에저장해야한다. 이전의 "lena151.dll" 파일을패치한것으로덮어씌운뒤바이너리를실행하면이제디버거에탐지되지않을것이다.
탐지되지않는다고하면좋겠지만디버거에탐지된다. 분명히프로텍터탐지는아니고, dll 탐지도전부우회하였다. 이런경우에는 "TLS callback function" 에서탐지했을확률이높다. 이번에도바이너리의 EP에도달하기전에탐지되었다. 만약처음실행되는라인의코드가 OEP 코드라면다시생각해야한다. * Thread Local Storage (TLS) 프로세스의모든쓰레드들은가상주소공간과해당프로세스의전역변수를공유한다. 쓰레드함수의지역변수는함수를실행하는각각의쓰레드에대하여독립적이다. 다만해당함수에서사용하는정적 / 전역변수는모든쓰레드를위하여동일한값을가지게된다. 이때 TLS를사용하여각각의쓰레드에서사용하는변수를복사할수있다. 간단하게말하면, TLS 는쓰레드별로독립된변수저장공간으로써, 쓰레드내에서프로세스의정적 / 전역변수를마치지역변수처럼 독립적으로사용하고싶을때이용할수있다. NAG 창을종료해보니디버거가탐지된다음에 OEP 에도달하고있다. 이제 "PETools" 를이용하여 TLS 에대해알아볼것이다.
TLS callback 함수를무력화하는쉬운방법중하나는 "TLS Directory" 의 "RVA/SIZE" 값을전부 0으로세팅하는것이다. 다만디버거탐지말고바이너리실행을위해필요한코드가있을수있기때문에우선코드부터알아봐야한다. ( 우측의... 버튼클릭 )
"Address of callbacks" 항목의값은 TLS callback 함수코드의시작주소를 의미한다. TLS callback 함수시작주소를덤프창에서보면 0049313D 값이존재한다. 해당주소로이동하면 TLS 코드가있을것이다. 이전과비슷하게 FindWindowA / IsDebuggerPresent 함수를이용하여 디버거를탐지하고있다. DLL 파일패치때와비슷하게 EP 명령어를그냥 RETN 으로패치해버리면 디버거탐지가되지않는다.
패치한다음새로운바이너리로저장하고, 패치된바이너리를열면 정상적인 OEP 에서멈춘다. 그런데아직도디버거를탐지한다. 1. 프로텍터탐지아님 2. DLL 탐지우회 3. TLS 탐지우회결론은, 드디어원본바이너리자체에서탐지한다고생각할수있다. nag 창이떠있는상태에서 일시정지 (F12) - user code 까지실행 (Alt+F9) 를 클릭하여 Back to user mode 로세팅하여어느함수에서디버거탐지 nag 창을호출하는지보면된다.
어느주소에서 CALL JMP 401632 명령을실행했을것이다. 이함수역시이전과비슷하게디버거탐지를하고 Messagebox 함수를호출한다. 단해당함수는그냥 nag창만띄운뒤메인프로그램실행을위해 4013C8 로분기한다. 보아하니디버거탐지를하는 401632 함수는 401200 주소에서호출하고있다. 해당주소로이동하면처음에는디버거탐지를위해 401632 주소로분기시키고있다. 이전에설명했듯이 nag 창이호출되면 4013C8 주소로분기하기때문에그냥해당주소로분기되게패치하면된다. 최종패치된바이너리를저장후실행하면모든것이완벽하게동작된다.