본문 바로가기

Write Up

[2016 CAT HolyShield] Reversing - Who Am I?


이번 CAT HolyShield에서 푼 리버싱 문제인데 가상화 문제였습니다. 풀 때 꽤 재미있었던 문제라서 좀 더 분석을 해 보고 이렇게 풀이를 올립니다. 먼저 실행 모습입니다. 

파일은 exe파일 하나와 dll파일 하나가 주어집니다. exe를 먼저 IDA로 열어서 보면 

이러한 부분이 존재하는데 분석하면 주어진 dll파일을 열어서 0x1E00영역에 입력한 값으로 덮어 씌웁니다. 헥스에디터로 원래 0x1E00부분을 보면 

Nice_To_Meet_You~~!!라는 문자열이 존재하는데 이를 입력 값으로 덮어 씌웁니다. 그리고 로드한 dll에서 checkfunc함수의 주소를 가지고 와서 인자로 넘겨주고 호출합니다. 그럼 이제 dll을 보겠습니다. IDA로 까 보면

이게 전부입니다. 처음에는 가상화 일 것이라고 생각을 못했었으나 분석을 하다보니 가상화임을 깨달았습니다.. 이 부분이 vcpu입니다. ebp-0x28에 있는 값이 가상 레지스터 veip이고, 그 veip에서 한 바이트 가상 opcode를 cl 레지스터에 가져와서 그 핸들러를 호출해 줍니다. 올리디버거에서 exe파일을 통해 dll파일을 로드할 때 디버깅 해 보려고 했는데 자꾸만 LoadLibraryW함수를 호출하고 나서 계속 Readable하지 않은 곳을 읽고 있다고 하면서 오류가 나고 프로세스가 죽었습니다..(혹시 이유를 아시는 분 계시면 댓글 남겨주세요!) 여기서 한 3시간동안 갈피를 잡지 못하고 헤메다가 그냥 직접 코드를 짜서 dll을 로드시켜보니 잘 로딩이 되더군요. 그래서 제가 직접 코드를 짜서 분석했습니다. 코드는 간단히 dll을 로드하고 checkfunc함수의 주소만 얻는 부분만 넣었으므로 소스는 생략하고 직접 디버깅 해 보겠습니다. 

올리디버거에서 Break in new module(DLL)옵션에 체크를 해 주시고 dll를 로드해 보겠습니다. 

잘 로딩이 되었고 이제 DllEntry주소에 브레이크 포인트를 걸고 실행하면 dll디버깅이 가능하게 됩니다. 

vcpu입니다.

가상화된 vcode입니다. 지금 vcode가 어떻게 이루어져 있는지 분석하기에는 분량이 너무 많고 다 분석을 한 부분만 설명하겠습니다.

먼저 vcode의 구조를 보면 첫 바이트는 opcode, 두 번째 바이트는 operand의 정보를 담고있는 operandinfo이고, 그 뒤는 opcode가 무엇이냐에 따라 각 operand의 개수와 크기가 가변적입니다. 

분석된 각 opcode의 기능들을 인텔 명령어에 비교하여 설명해 보면

01 - PUSH
    00 - 
PUSH reg[operand1]
    01 - PUSH reg[operand1]
    04 - PUSH reg[2]([ebp+8])
    08 - PUSH operand1
02 - MOV
    00 - MOV reg[operand1], reg[operand2]
    01 - MOV reg[operand1], DWORD:[reg[operand2]]
    21 - MOV reg[operand1], BYTE:[reg[operand2]]
    24 - MOV 
BYTE:[reg[operand1]], reg[operand1]

03 - SUB
    02 - SUB reg[operand1], DWORD:[operand2]
04 - MOV
    00 - MOV reg
05 - CALL
    04 - CALL reg[operand1]
06 - XOR

    00 - XOR 
reg[operand1], reg[operand2]
    24 - XOR BYTE:[reg[operand1]], reg[operand2]
07 - ADD
    01 - ADD reg[operand1], DWORD:[reg[operand2]]
    02 - ADD reg[operand1], operand2
    06 - ADD DWORD:[reg[operand1]], operand2
08 - XCHG
    00 - XCHG reg[operand1], reg[operand2]
09 - CMP
    00 - CMP reg[operand1], reg[operand2]
    01 - CMP reg[operand1], DWORD:[reg[operand2]]
    02 - CMP reg[operand1], operand2
0A - JZ
    08 - 
jmp veip + operand1
0B - INC
    00 - INC reg[operand1]
0C - JL
    08 - jmp veip + operand1
0D - JLE
    08 - 
jmp veip + operand1
0E - Exit
    00 - Exit vcpu to Dll Entry
0F - TEST
    00 - TEST reg[operand1], reg[operand2]
10 - JG
    08 - 
jmp veip + operand1
11 - POP
    00 - POP reg[operand1]
12 - JMP
    08 - jmp veip + operand1
13 - Exit Virtual mode

이렇게 됩니다. 01~13은 opcode이고 그 밑에 탭으로 띄어져 있는 것은 각 operandinfo에 따른 연산 방식의 차이입니다. 01 opcode(PUSH)에서 operandinfo 00과 01의 차이를 잘 모르겠고, 04의 역할이 애매하네요.. 

그리고 EBP를 기준으로 밑으로 가는 부분이 가상 스택, 위에 있는 부분이 레지스터 부분입니다. EBP+4는 EBP를 기준으로 VESP가 상대적인 값으로 들어있고(1, 2, 3과 같은 값들) EBP+24는 VEAX이며 EBP+4와 EBP+24 사이의 값들은 가상 범용 레지스터로써 쓰입니다. EBP+28은 VEIP이고 EBP+C는 veflags로 값이 0일 때는 비교한 앞의 값이 더 클 때, 1일 때는 같은 때, 2일 때는 더 작을 때 세팅이 됩니다. 그리고 10007028 주소에 LoadLibrary, GetProcAddress, VirtualProtect API의 실제 주소가 들어 있으며 vcpu가 Call을 할 때 이 값을 참조하여 호출합니다. 

이제 vcode가 어떤 식으로 진행이 되는지를 알아보겠습니다. 먼저 VIrtualProtect함수를 호출하여 암호화 되어 있는 Dll 소스들을 복호화 합니다. 그 후에 데이터 섹션에 있는 값들 역시 연산을 통해 복호화를 하는데 복호화를 하면서 아까 덮어씌운 입력 값 역시 함께 복호화가 되고 그 값을 checkfunc에서 미리 정해져 있는 테이블과 비교합니다. 

복호화 과정은 단순한데 먼저 네 글자 단위로 쪼개서 각 자리를 바꿉니다. ABCD 였다면 BDAC이렇게 자리를 바꾼 후에 세 번째 글자로 첫 번째, 두 번째, 네 번째 글자와 xor연산을 해 줍니다. 이렇게 4바이트 단위로 끊어서 연산을 한 뒤에 첫 번째 글자로 나머지 모든 문자를 xor해 줍니다. 그리고 그 값을 정해져 있는 Table과 비교합니다. Table은 

이렇게 되어 있으며, 역연산 코드는 

이렇게 짤 수 있고 실행해 보면

이렇게 키가 나오게 됩니다. 



KEY : He110_My_N@me_1s_KY!


가상화에 관한 자료는 http://ezbeat.tistory.com/338 여기서 볼 수 있으며 저도 이 자료로 공부했습니다.