매우 유명한 프로텍터 Themida™는
- 더미다?테미다? 영어쓰는애들은 데마이다 또는 더마이다라 읽을 거 같은데, 스페인 회사라 모르겠음ㅋㅋ
code virtualization으로 명성이 높습니다. 게다가 가상 opcode자체도 암호화 되어있어서
디스어셈블러를 만들려면 복호화 기능을 함께 넣어야 합니다;; ㅋㅋㅋ
가상의 instruction이 있으므로 디스어셈블러가 의미가 없지만요. 뭐 아무튼.
안티도 안티지만, 이 포스트에선 Themida™에서 사용하는 code obfuscation에 대해 보겠습니다.
좀 들여다 봤는데 가상함수의 코드가 엄청나게 꼬여있더군요. (obfuscated -> obbed라고 하겠습니다.)
code virtualization으로 명성이 높습니다. 게다가 가상 opcode자체도 암호화 되어있어서
디스어셈블러를 만들려면 복호화 기능을 함께 넣어야 합니다;; ㅋㅋㅋ
가상의 instruction이 있으므로 디스어셈블러가 의미가 없지만요. 뭐 아무튼.
안티도 안티지만, 이 포스트에선 Themida™에서 사용하는 code obfuscation에 대해 보겠습니다.
좀 들여다 봤는데 가상함수의 코드가 엄청나게 꼬여있더군요. (obfuscated -> obbed라고 하겠습니다.)
가상함수 function array
저 각 함수들이 몇백 라인씩 됩니다. 그리고 중간에 끊임없는 jmp로 또 꼬여있구요.
근데 실제로 하는일은 얼마 없습니다.
심하게 code obfuscation이 돼있는데요,
예를 들면 이런 식입니다.
push eax |
sub esp,0x4 mov dword ptr ss:[esp], eax |
mov edx,0x99999999 |
mov edx,0x12345678 add edx,0x8765435 |
sub ch,0x93 |
push ebx mov bl,0x93 sub ch,bl pop ebx |
(위는 예시일 뿐, obbing의 종류가 매우 많습니다)
이런 식으로 실제는 몇줄 안되는 코드를 몇백줄로 바꿔버립니다.
툴을 짜서, jmp 를 생략한 가상함수를 뜯어오게 했습니다.
제 샘플의 첫번째 함수인 0x4f0961의 가상함수를 보겠습니다.
push dword ptr ss:[esp]
mov eax,dword ptr ss:[esp]
push 0x164a
mov dword ptr ss:[esp],edi
mov edi,esp
push esi
mov esi,0x2bef3510
push eax
mov eax,0x486c5acd
push ecx
mov ecx,0x6342c4e
dec ecx
not ecx
xor ecx,0x9755bf6c
sub eax,ecx
mov ecx,dword ptr ss:[esp]
add esp,0x4
push edx
mov edx,0x6ea3528c
add eax,edx
pop edx
sub esp,0x4
mov dword ptr ss:[esp],edi
mov edi,0x695936bf
sub edi,0x1c5427
neg edi
add edi,0xd34d4497
and eax,edi
pop edi
sub eax,0x3c74fab3
and esi,eax
pop eax
push esi
inc dword ptr ss:[esp]
pop esi
xor esi,0xb8b0505
add edi,esi
pop esi
push eax
mov eax,0x4
add edi,eax
pop eax
xor edi,dword ptr ss:[esp]
xor dword ptr ss:[esp],edi
xor edi,dword ptr ss:[esp]
mov esp,dword ptr ss:[esp]
push ebx
mov ebx,esp
add ebx,0x4
push edx
mov edx,0x4
add ebx,edx
pop edx
xchg dword ptr ss:[esp],ebx
mov esp,dword ptr ss:[esp]
push 0x3ea4
mov dword ptr ss:[esp],ecx
push edx
mov edx,0x5df25e5b
mov ecx,0x50ed205a
xor ecx,edx
pop edx
add dword ptr ss:[esp+0x4],ecx
pop ecx
push 0xe5c
mov dword ptr ss:[esp],ebp
mov ebp,0x7fd66343
sub dword ptr ss:[esp+0x4],ebp
pop ebp
push esi
mov esi,0x462c5b2e
add dword ptr ss:[esp+0x4],esi
pop esi
push ecx
push edx
push 0x355577d2
pop edx
add edx,0x151a1b0b
add edx,0xee9ed360
mov ecx,edx
mov edx,dword ptr ss:[esp]
add esp,0x4
sub dword ptr ss:[esp+0x4],ecx
mov ecx,dword ptr ss:[esp]
add esp,0x4
add dword ptr ss:[esp],eax
push ecx
mov ecx,0x390e663d
add dword ptr ss:[esp+0x4],0x37b00094
add dword ptr ss:[esp+0x4],ecx
sub dword ptr ss:[esp+0x4],0x37b00094
pop ecx
sub dword ptr ss:[esp],0x462c5b2e
push edi
push 0x3921
mov dword ptr ss:[esp],eax
push 0x19fc5d80
pop eax
shr eax,0x3
not eax
add eax,0x8315eef4
mov edi,eax
pop eax
sub dword ptr ss:[esp+0x4],0x161e5482
add dword ptr ss:[esp+0x4],edi
add dword ptr ss:[esp+0x4],0x161e5482
pop edi
sub dword ptr ss:[esp],0xd1f7e01
lodsb
add al,0x4e
push ecx
push eax
mov al,0xd8
shr al,0x7
sub al,0x6c
mov ch,al
pop eax
add al,ch
mov ecx,dword ptr ss:[esp]
add esp,0x4
add al,bl
sub al,0x95
push ecx
mov ch,0x68
add ch,0xff
not ch
push ebx
mov bl,0x9b
sub bl,0x4f
dec bl
sub bl,0xe4
xor bl,0xf4
sub ch,bl
pop ebx
inc ch
add ch,0x1
push dx
mov dl,0xf4
xor ch,dl
pop dx
sub ch,0xa5
push edx
push eax
mov ah,0x92
shl ah,0x2
inc ah
neg ah
add ah,0xf1
mov dh,ah
mov eax,dword ptr ss:[esp]
add esp,0x4
push cx
mov ch,0xf9
xor dh,ch
pop cx
xor dh,0x72
inc dh
push cx
mov cl,0x36
sub dh,0x1e
add dh,cl
add dh,0x1e
mov cx,word ptr ss:[esp]
add esp,0x2
sub al,dh
pop edx
add al,0x4e
sub al,ch
sub al,0x4e
add al,0x5a
pop ecx
push 0x591d
mov word ptr ss:[esp],cx
push 0x3a8d
mov dword ptr ss:[esp],edx
mov dh,0xda
push eax
mov ah,0x7b
shl ah,0x4
inc ah
dec ah
shl ah,0x5
or ah,0xa4
xor ah,0x33
sub dh,ah
pop eax
push 0x7b44
mov dword ptr ss:[esp],ebx
push 0x4f68
mov dword ptr ss:[esp],eax
mov ah,dh
mov bh,ah
pop eax
mov ch,bh
pop ebx
mov edx,dword ptr ss:[esp]
add esp,0x4
xor al,ch
mov cx,word ptr ss:[esp]
push 0x458f
mov dword ptr ss:[esp],ebp
mov ebp,esp
add ebp,0x4
push ecx
mov ecx,0x4ff85c46
dec ecx
not ecx
sub ecx,0xb007a3b8
add ebp,ecx
mov ecx,dword ptr ss:[esp]
add esp,0x4
push ebp
push dword ptr ss:[esp+0x4]
push dword ptr ss:[esp]
pop ebp
add esp,0x4
pop dword ptr ss:[esp]
mov esp,dword ptr ss:[esp]
push 0x4c66
mov dword ptr ss:[esp],ecx
push 0x676d
mov dword ptr ss:[esp],eax
push edx
push ecx
mov ch,0x8b
shr ch,0x1
shl ch,0x4
shr ch,0x7
xor ch,0xb4
mov dl,ch
pop ecx
add dl,0xff
sub dl,0xe1
mov al,dl
mov edx,dword ptr ss:[esp]
add esp,0x4
push 0x2e39
mov dword ptr ss:[esp],ebx
push ecx
mov cl,0x8c
mov bh,0x65
xor bh,cl
pop ecx
dec bh
inc bh
inc bh
sub bh,0x62
xor bh,0x23
xor bh,0x92
mov cl,bh
mov ebx,dword ptr ss:[esp]
add esp,0x4
sub cl,al
pop eax
and cl,0x82
shl cl,0x6
push edx
mov dh,0x3d
xor cl,dh
pop edx
push eax
push edx
mov dl,0x92
mov ah,dl
pop edx
sub cl,0x84
add cl,ah
push 0x5a9b
mov dword ptr ss:[esp],ebx
mov bh,0x22
sub bh,0x61
add bh,0xc3
sub cl,0x77
add cl,bh
add cl,0x77
mov ebx,dword ptr ss:[esp]
add esp,0x4
pop eax
xor al,cl
pop ecx
push dx
mov dl,0x3a
push ecx
mov ch,0x52
push eax
mov ah,0xb9
add ch,ah
pop eax
add ch,0xc4
shl ch,0x4
shr ch,0x2
sub ch,0x33
add bl,0x2d
sub bl,0xcb
sub bl,ch
add bl,0xcb
sub bl,0x2d
pop ecx
sub bl,dl
add bl,0x9
pop dx
sub bl,al
push eax
mov al,0x1e
or al,0x4d
sub esp,0x4
mov dword ptr ss:[esp],edx
mov dl,0xd1
push eax
push ebx
mov bh,0x11
mov al,bh
pop ebx
not al
not al
xor al,0xe0
sub dl,al
pop eax
sub dl,0x57
sub al,dl
pop edx
not al
or al,0xfa
neg al
push edx
mov edx,esp
add edx,0x4
sub edx,0x2
xchg dword ptr ss:[esp],edx
pop esp
mov word ptr ss:[esp],dx
push 0x7954
mov dword ptr ss:[esp],eax
mov ah,0x3f
push ebx
mov bh,ah
mov dh,bh
pop ebx
mov eax,dword ptr ss:[esp]
add esp,0x4
xor al,dh
pop dx
add bl,al
pop eax
movzx eax,al
jmp dword ptr ds:[edi+eax*4]
음 350줄 약간 안됩니다.
눈치빠르신 분은 보셨겠지만, 함수 맨 밑에는 edi를 base로, al을 index 삼아 jmp하는 부분이 있습니다.
c로 표현하면 (func_array[index])(); 정도 되겠는데요
모든 가상함수는 저런식으로 호출된 후 제 할일을 하고,
다음 실행을 위해 opcode를 복호화, 다음 가상함수를 호출하는 루틴으로 jmp를 합니다.
(저 al에 로드되는 가상 opcode를 "vfunc 지시자" 라 부르고,
로딩 루틴을 "vfunc 지시자 loader"라 부릅시다.)
저 위 lodsb 부터가 그 루틴입니다.
(일부러 한줄 떼어놓은 부분입니다.)
모든 가상함수가 작업 처리 후 저 곳으로 뛰어들죠.
가상함수는 100라인 조금 넘고, 다음 instruction을 load하는 "지시자 loader"는 250라인이 좀 안되네요.
obfuscation 형식을 분석해서 deObber를 만들어서 돌려봤습니다.
심심하신 분들은 한번 obfuscation 방식을 분석하셔도 좋을겁니다.
방식은 단순하니까요^^
완전히 deobbing하고난 코드는 아래와 같아집니다.
가상함수
pop eax
add dword ptr ss:[esp],eax
지시자 loader
lodsb
add al,bl
xor al,0x0c
sub bl,al
movzx eax,al
jmp dword ptr ds:[edi+eax*4]
엄청 줄어버리죠?
100->2;;;;
240->6;;;;
obbing을 지겹게 해논겁니다.
obbing의 무서움은 분석가가 눈으로 얘가 뭐하나 알아볼 수 없게 한다는데 있겠죠?
그럼 분석가도 눈으로 대응해선 안되죠.
귀찮더라도 저렇게 노가다로 deobber를 만드는 수 밖에 없습니다. ㅋㅋ
모든 가상함수가 저런 식으로 돼있습니다.
deobbing 규칙을 여러 개 넣었는데 문제가 생기네요.
규칙끼리 충돌하는 일이 있습니다.
예를 들면 다음과 같은 obbed 코드를 deobbing시
pop eax push edx |
mov eax,dword ptr ss:[esp] add esp,0x4 sub esp,0x4 mov dword ptr ss:[esp],edx |
add esp,0x4와 그 다음 라인의
sub esp,0x4가 상쇄돼서 deobbing시 잘못하면 사라져버립니다.
mov eax,dword ptr ss:[esp]
mov dword ptr ss:[esp],edx 만 남아서 더이상 deobbing이 안되는 경우죠.
쩝 저기서 제 마인드가, 귀찮아져서(네 Themida한테 졌습니다.) 지금은 호기심도 떨어지고 진행을 안하는 상태입니다.
지금도 전체 가상함수의 한 반정도는 완전히 deobbing 된 것으로 보이는데,
지겨워서 못해먹겠습니다-_-;; 이 "지겹게 만드는 작전"은 아주 훌륭한 작전인 것 같습니다.
그럼 obfuscation 규칙이나 올려보겠습니다.
각 규칙은 16비트 버전도 있고, 다른 instruction이 들어가는 경우도 있습니다.
또 다른 규칙도 몇개 있구요.
아래 규칙만 이해해도 obfuscation engine을 만들기 좋죠.
sub esp,0x4 mov dword ptr ss:[esp],exx |
push exx |
push 0x* mov dword ptr ss:[esp],eax |
push eax |
push 0x* pop edx |
mov edx,0x* |
mov xxx,dword ptr ss:[esp] add esp,0x4 |
pop xxx |
push xxx not dword ptr ss:[esp] pop xxx |
not xxx |
mov edx,0x12345678 add edx,0x87654321 |
mov edx, 0x99999999 |
mov xxx,0x12345678 inc xxx |
mov xxx, 0x12345679 |
push ebx mov bl,0x93 sub ch,bl (add ch,0x12) pop ebx |
sub ch,0x93 (add ch,0x12) |
add ah,0x1 sub ah,cl sub ah,0x10 |
sub ah,0x9 sub ah,cl |
push edx mov dl,0x1 add bh,0x10 pop edx |
add bh,0x10 |
push dword ptr[edx] pop ebp |
mov ebp, dword ptr[edx] |
push ebp mov ebp,dword ptr ss:[esp+0x4] pop dword ptr ss:[esp] |
xchg dword ptr ss:[esp],ebp |
push ebp mov ebp,esp add ebp,0x6 xchg dword ptr ss:[esp],ebp pop esp |
add esp,0x6 sub esp,0x4 |
mov esp,dword ptr ss:[esp] | pop esp |
push ebp add dword ptr ss:[esp],0x12345678 pop eax |
mov eax,ebp add eax,0x12345678 |
xor edi,dword ptr ss:[esp] xor dword ptr ss:[esp],edi xor edi,dword ptr ss:[esp] |
xchg dword ptr ss:[esp],edi |
push ecx mov ecx,0xd1f7e01 add dword ptr ss:[esp+0x4],ecx pop ecx |
add dword ptr ss:[esp],0xd1f7e01 |
push ecx mov ecx,0x390e663d pop ecx |
x |
xchg bh,cl add bh,0x1 xchg bh,cl |
add cl,0x1 |
push esi mov esi,esp xchg dword ptr ss:[esp],esi pop esp |
sub esp,0x4 |
deobbing 에 드는 시간은 꽤 깁니다. 제가 단순 문자열 패턴을 이용해 구현해서 그런 것도 있지만
워낙 가상함수의 개수도 많고, 각 함수도 심하게 obbing 돼있어서리
몇만번 deobbing 한 뒤에야 output이 나오네요. 몇분 걸립니다;
저 위 0x4f0961 함수의 매 deobbing 과정을 텍스트 파일로 출력한 결과 첨부하겠습니다.
0000부터 하나씩 보면 deobbing 현황을 볼 수 있습니다.
(사실 디버깅용으로 만든건데, 이해하시기 좋을 것 같습니다.)
그럼 이만.