본문 바로가기

Reversing

09. Anti-Debugging

1. Anti-Debugging?


안티디버깅이란 말 그대로 디버깅을 힘들게 하는 기술을 말한다. 그렇다면 디버깅을 힘들게해서 무엇을 얻어낼 것인가?

프로그램의 코드를 올리디버거등의 도구를 이용한 디버깅하는 것을 방해하는 것이다.

상용 프로그램에서 사용되며, 악성코드에서 사용되기도 한다. 악성코드에서 사용될 경우, 분석가의 입장에서는 악성코드의 행위를 파악하기 힘들어진다는 점이 존재한다. 따라서, 안티디버깅을 효율적으로 해제하여 악성코드의 악성행위를 파악하는 것이 분석가의 자질로서 필요하다.


안티디버깅의 유형에는 Static 유형과 Dynamic 유형이 존재한다.


2. Static Anti-Debugging


Static 안티디버깅의 특징은 프로그램의 첫 실행에서 디버거를 탐지하는 것이다. 그 후에는 더이상 탐지하지 않는다.

즉, 안티 디버깅을 수행하는 함수가 호출 되는 시점에서만 디버거의 존재를 확인하고 그 뒤에는 탐지하지 않는다.

목적은 대부분이 디버거의 탐지이다.

Static 안티디버깅의 해제 방법에는 호출되는 API에 후킹을 걸어서, 원래 API의 루틴이 아닌 다른 루틴을 수행하도록 하거나, 플러그인을 사용해 자동으로 해제하는 방법 혹은 실제 수동으로 해당 안티디버깅을 풀어내는 방법이 있다.


3. Dynamic Anti-Debugging


Dynamic 안티디버깅의 특징은 프로그램이 실행되는 중간중간 계속해서 디버거를 탐지하는 것이다. 프로그램의 실행과 함께 계속해서 실행되며, 중간중간 디버거의 유무를 확인하기 때문에 해제의 경우에도 지속적으로 해제가 필요하다.

목적은 내부 코드와 데이터를 숨기는 것, 디버거를 탐지하는 것에 있다.

Dynamic 안티디버깅의 해제 방법에는 Static과 동일하게 호출되는 API를 후킹하거나, 플러그인을 사용해 자동으로 해제하는 방법이 있다. 또는 프로그램(툴)을 이용해 자동적으로 풀어내거나, 디버거의 설정에 따라 해제되는 종류도 있다. 정말 힘들게 수동으로 해당 안티디버깅을 풀어내는 방법도 있다.


2. Anti Static Anti-Debugging


이제부터 실제 몇몇 종류의 안티디버거의 해제를 알아보겠다.

아래 사진은 Static 안티디버깅의 한 종류인 IsDebuggerPresent() 함수를 호출하는 코드이다.



해당 코드를 실행하게되면 프로그램에 디버거가 붙어있지 않을때에는 "[+] No Degging.......!!" 을 출력하고 디버거가 붙어있는 경우에는 "[+] Debugging......!!" 을 출력한다.


아래 사진은 프로그램에 디버거가 붙어있지 않을 때 "[+] No Degging.......!!"을 출력하는 모습이다.



아래 사진은 프로그램에 디버거가 붙어있을 때 "[+] Debugging......!!"를 출력하는 모습이다.



해당 안티디버깅을 해제하는 방법에는 함수의 Call 부분에서 넘어가는 인자 값을 조작해주면 된다.

해당 함수를 호출하게되면 eax에 디버거가 붙어있다는 의미로 인자값이 들어가게되는데, 넘어간 후 해당 eax값을 0으로 바꿔주게되면 디버거가 붙어있지 않은 것으로 인식하게 된다.

혹은 해당 함수 자체를 NOP 처리 해버리는 것으로 해제할 수 있다.


아래 사진은 NtGlobal 플래그를 이용한 안티디버깅을 수행하는 코드이다.




위 사진은 메인 함수의 부분이고 실제 안티디버깅의 경우 Check() 함수 내에서 일어나게 된다.

아래 사진은 Check() 함수의 코드이다.



위 코드에서 안티디버깅이 일어나게 되고 프로그램에 디버거거 붙어있지 않을때에는 "[+] No Degging.......!!"을 출력하고 프로그램에 디버거가 붙어있을 때에는 "[+] Debugging......!!"을 출력하게 된다.

아래 사진은 디버거가 붙어있지 않을 때 "[+] No Degging.......!!"을 출력하는 모습이다.



아래 사진은 디버거가 붙어있을 때 "[+] Debugging......!!"을 출력하는 모습이다.



해당 안티디버깅을 해제하려면 Check() 코드를 유심히 살펴보아야 한다.

Check() 코드는

mov eax, fs:[30h]

mov eax, [eax+68h]

and eax, 0x70

test eax, eax

의 어셈블리어로 구성되어 있는데, 해당 어셈블리어는 현재 디버거가 붙어있는지 나타내주는 flag 값을 가져오고 그 값과 0x70을 and 연산하여 디버거가 붙어있는지 아닌지를 검사하는 것이다.

따라서 해제 방법은, 해당 플래그 자체를 0으로 설정해줌으로써 디버거의 존재를 플래그 자체에서 삭제하거나

해당 루틴을 수행하며 eax의 값을 조작해주는 방법으로 해제해주면 된다.

혹은 eax를 가져오고 검사하는 부분을 NOP 처리해줌으로써 해제할 수도 있다.


아래 사진은 CheckRemoteDebuggerPresent() 함수를 이용한 안티디버깅을 수행하는 코드이다.



해당 코드를 실행하게되면 프로그램에 디버거가 붙어있지 않을 때에는 "[+] No Degging.......!!"를 출력하고 프로그램에 디버거가 붙어있을 때에는 "[+] Debugging......!!"를 출력하게 된다.

아래 사진은 프로그램에 디버거가 붙어있지 않을 때 "[+] No Degging.......!!"를 출력하는 모습이다.




아래 사진은 프로그램에 디버거가 붙어있을 때 "[+] Debugging......!!"를 출력하는 모습이다.



해당 안티디버깅을 해제하는 방법은 IsDebuggerPresent() 함수를 이용한 안티디버깅을 해제하는 방법과 매우 유사하다.

해당 안티디버깅을 수행하게되면 Call CheckRemoteDebuggerPresent() 를 진행하게 되는데, 해당 진행 과정에서 코드 조작으로 해당 함수를 호출하지 못하게 하거나 해당 함수를 호출 한 후 인자로 넘어오는 eax 값을 0으로 바꿔주면 된다.

혹은 해당 함수 호출 자체를 NOP값으로 바꾸어서 호출 자체를 없애버리는 방법도 있다.

또는 해당함수에서 실제 안티디버깅을 수행하는 부분은 함수 안 NtQueryInformationProcess 부분인데, 그 부분의 인자값을 조작해주거나 없애주면 된다.


2. Anti Dynamic Anti-Debugging


한 종류의 Dynamic 안티 디버깅을 소개하고 해제하는 방법을 소개하겠다.
해당 안티디버깅은 SEH 핸들러를 이용한 안티디버깅이며, 프로그램의 실행 중간중간 디버거를 탐지해 안티디버깅을 수행한다.

아래 사진은 SEH 핸들러를 이용한 안티디버깅의 메인 코드이다.



아래 사진은 seh_handle() 함수의 코드이다.



해당 안티디버깅은 SEH 핸들러를 이용해 인터럽트를 걸고, 디버거가 붙어있는지 아닌지를 검사한다.

아래 사진은 프로그램에 디버거가 붙어있지 않을 때 "[+] No Degging.......!!" 을 출력하는 모습이다.



아래 사진은 프로그램에 디버거가 붙어있을 때 "[+] Debugging......!!"를 출력하는 모습이다.



해당 안티디버깅을 우회하는 가장 간단한 방법은 설정을 바꿔주는 방법이다.

아래는 올리디버거에서 기본적으로 설정되어있는 디버깅 옵션이다.



위와 같이 설정되어 있는 디버깅 옵션을 아래와 같이 바꿔주고 실행하게되면 자동으로 안티디버깅을 해제하고 실행하게 된다.



혹은 프로그램(툴)을 이용해 안티디버깅을 해제하거나, 해당 안티디버깅에 해당되는 플러그인을 디버거에 접목시켜 풀어내거나, 안티디버깅이 발생할 때마다 수동으로 안티디버깅을 해제하며 프로그램의 실행을 진행하는 방법도 있다.

수동 해제는 추천하지 않는다...


이상으로 안티디버깅에 대해 간단히! 알아보았다.

반응형

'Reversing' 카테고리의 다른 글

11. LOTL (Living Off The Land)  (0) 2024.03.07
10. FUD(Fully UnDetectable)  (0) 2023.10.05
08. angr  (0) 2017.11.01
07. arm 어셈블리어 정리  (0) 2017.10.27
06. gdb 명령어 정리  (0) 2017.09.29