본문 바로가기

My Study/Reversing

Stack Based Obfuscation ( Binary Code )

이번에 해볼 것은 바이너리 코드 난독화입니다.
보통 자바,닷넷..등등 이런 언어들은 특정 플랫폼에서 작동을 하며 바이트코드 기반 언어입니다.
그렇기 때문에 exe파일을 디컴파일러로 볼 경우 원본 소스코드가 거의 복원이 됩니다.

해당 언어만 안다면.. 뭐 말다했죠. 
그렇기 때문에 예전부터 이번 언어들은 배포하기전에 대부분 난독화를 시켜서 배포를 하게 됩니다.

하지만 C나 C++로 만들어진 프로그램들은 바이트코드 기반 언어가 아니기 때문에 디버거로 분석을 하더라도
어셈블리언어로 분석을 해야하며 안티리버싱 기법들도 많이 나와서 각종 기법들..그리고 패킹까지..;;

그렇다고 해서 프로그램이 안전하냐?? 그런건 아닙니다. 역시나 패킹된것도 언패킹되기 마련이고.. 안티디버깅 기법도
다 우회되기 마련입니다. 그러면 어떻게 프로그램을 보호하냐??.. 그게 이번에 설명해볼 바이너리 난독화 입니다.

리버서들이 분석을 하는데 있어서 시간을 질질 끌게하고.. 
그렇게 함으로써 리버서를 지치게해서 분석을 포기하게 만드는 것이지요.

여러 난독화 방법이 있지만 이번엔 스택을 사용하는 난독화 방법을 보겠습니다.

대단한것도 아닌데 앞에 말을 너무 많이 했군요. 코드들은 간단하며 그냥 보면 이해하실수 있습니다.
아~! 참고로 난독화라는 것은 원래의 기능을 유지하면서 원래의 명령어를 다르게 표현하는 것입니다.

1. PUSH Instruction Obfuscations
PUSH 명령어는 다들 아시다싶이 뒤에 인자로 오는 값/레지스터 를 스택에 넣는 것입니다.
PUSH 명령어 대신 다른 표현 방식을 보시겠습니다.
MOV DWORD PTR SS:[ESP-4],<value/register>
SUB ESP,4

또는

SUB ESP,4
MOV DWORD PTR SS:[ESP],<value/register>
코드만 봐도 쉽게 이해가 되실 것입니다. 
PUSH로 넣는게 아니라 MOV로 값을 넣어주고 그만큼 스택포인터를 조정해주는 것입니다.

2. POP Instruction Obfuscations
POP은 ESP가 가리키고 있는 값을 스택에서 꺼내어 POP뒤의 레지스터 또는 메모리에 넣어주는 것입니다.
다른 표현 방식을 보시겠습니다.
MOV <register>,DWORD PTR SS:[ESP]
ADD ESP,4

또는

POP <[memory]>
MOV <register>,<[memory]>
이번에도 MOV로 스택에 있는 값을 레지스터에 넣어주고 스택을 조정해 줬습니다.
그 아래는 그냥 POP을 하지않고 특정 메모리에 값을 넣고 그 값을 레지스터에 넣어주는 것입니다.

3. CALL Instruction Obfuscations
CALL명령어는 실행이 되면 오버랜드로 있는 값으로 점프를 하면서 
함수가 끝나고 실행되어야할 위치의 주소를 ESP에 PUSH해줍니다.
PUSH EIP+5
JMP <address>
간단히 CALL이 하는 역할을 그대로 표현한것 입니다.
EIP에 5를 더한 이유는 보통 함수는 CALL <address>하면 opcode 5Byte이고 API함수 같은 경우는 6Byte입니다.
( 그래서 API후킹에서.. 내가 만든 함수 주소 구할 때 (MyFunc - APIFunc - 5) 이렇게 해주는건가요.. :-D )
그러면 
PUSH EIP  <-- 맞는 명령어 일까요??
아닙니다. 일반적으로 코드상에선 EIP에 직접적로는 접근 할 수 없습니다.
그렇기 때문에 EIP를 가져오는 함수를 하나 만들어도 괜찮지요.
CALL <Get EIP>
       ↓
POP EAX
PUSH EAX
RETN
위 처럼 하게되면 일단 CALL명령어가 실행되면 함수가 끝나고 돌아갈 위치의 EIP주소를 리턴 값으로 얻는 함수를 만든거죠.

위 코드처럼 EIP를 가져오면 됩니다. 그러면 저 위의 PUSH EIP+5 명령어가 해결됬군요.

그리고 CALL의 또 다른 표현방법입니다. 직접적으로 함수를 호출하는게 아니라
함수의 주소를 메모리/레지스터에 넣고 그 메모리/레지스터를 CALL하는 것입니다.
MOV <[memory]/register>,<address>
CALL <[memory]/register> 
위와 같이 말이죠.

4. RETN Instruction Obfuscations
RETN명령어는 ESP가 가리키고 있는 값을 EIP로 POP을 하고 그 주소로 점프를 하게 됩니다.
이것도 말 그대로를 코드로 옮겨보겠습니다.
POP <register/[memory]>
JMP <register/[memory]>
크게 설명할건 없군요.

5. MOV Instruction Obfuscations
MOV명령어는 오른쪽 오퍼랜드에 있는 값을 왼쪽 오퍼랜드로 복사하는 것입니다.
MOV 대신 사용할수 있는 코드를 보시겠습니다.
PUSH <register/[memory]/value>
POP <register/[memory]>
간단하게 스택을 이용하는 것입니다. PUSH를 이용해 해당 값을 스택에 넣고
POP을 사용해 원하는 목적지에 값을 넣는 것입니다.

6. JMP Instruction Obfuscations
JMP명령어는 뒤에오는 주소로 무조건 점프입니다. JMP의 다른 표현 방법입니다.
PUSH <address>
RETN

또는

CALL <address>
POP <register>
ADD ESP,4
점프할 주소를 스택에 PUSH하고 RETN 해버리는 것입니다. 명령어만 아시면 쉽게 이해될 것입니다.
그 아래는 그 주소로 CALL하면 해당 주소로 가면서 CALL다음 위치의 명령어 주소를 스택에 PUSH하는데
진짜 함수를 CALL하는게 아니므로 내부엔 RETN명령어가 없습니다. 그렇기 때문에 RETN을 대신해줄 명령어를 써준것입니다. PUSH 된 값을 다시 POP하고 스택을 정리해준 것입니다. 원래는 JMP명령어 이므로 POP와 ADD는 무조건 실행되는것이 아닙니다. 안돌아올수도 있죠.. :-)

더욱 많은 명령어들이 있지만 일단 스택을 사용해서 난독화를 할수 있는 방법을 봐보았습니다.
어떻게 보면 이런 생각을 할수도 있습니다. 저런거야 뭐 난독화 하나마나 아니겠어..?? 라고 생각하실수도 있습니다.
하지만 리버싱을 해보신 분이라면 PUSH, POP, CALL, JMP.. 등등 이런 명령어들이 한 프로그램에 엄청나게 많다는 것을
아실 것 입니다. 그 많은 명령어들이 전부 위와같은 식으로 바뀐다면... 코드를 분석하는데 더 많은 시간과 인내가 필요하겠죠?? 난독화는 그것을 노린 것 입니다.

그리고 눈치채셨을 탠데.. 이렇게 난독화를 하면 보통 프로그램의 크기가 커질수 밖에 없습니다.. X-(