[조조전 Assem] - 항상 명중 코드 분석하기
[조조전 Assem] - 항상 명중 코드 분석하기
여기서는 Assem 기본 명령어를 알고 있다는 가정하에 글을 씁니다.
제가 생각하는 기본적인 명령어 : Add, Sub, Div, Imul, Push, Pop, Mov, Call, Cmp, Jmp 등.
제가 모든 글에서 명령어를 실행해보라는 말은 F8을 한번 누르라는 것이고,
함수로 진입하라는 말은 F7을 한번 누르라는 뜻입니다.
EBP, ESP 값은 컴퓨터에 따라 다를 수 있습니다.
--------------------------------------------------------------------
'아직 2번째 강좌인데 벌써 분석이야?' 하시는 분들이 있으실 것 같은데
딱히 다룰 주제가 없어서 분석을 주제로 잡았습니다.
분석이라고 해서 어려워하실 필요 없습니다.
전 명령어 하나 하나씩 자세하게 설명드릴테니까요.
단지, 위에서 말씀드린 것과 같이 기본적인 명령어들은 익히고 계셔야합니다.
이제부터 코드 분석을 시작할텐데 먼저 제일 무난한 '항상 명중' 부터 분석할 예정입니다.
먼저 코드 총정리 글에 가셔서 항상 명중을 적용시켜야합니다. 그렇지 않으면 분석도 불가능하니까요.
OllyDbg를 실행하시고 항상 명중을 적용시킨 exe 파일을 열어주세요.
그리고 Ctrl + G 를 눌러 위의 그림과 같이 496E34 를 적고 OK 를 눌러주세요.
위의 사진처럼 496E34로 이동합니다. (참고로, 아이템 번호, 병종 번호, 인물 번호는 다를 수 있습니다.)
여기서 F2를 눌러서 Breakpoint를 걸어줍니다.
그러면 주소 부분이 빨갛게 변할 것입니다. Breakpoint가 걸렸다는 표시입니다.
F5를 눌러 게임을 실행합니다. 그리고 전투로 가서 누구든지 상관없이 책략이 아닌 공격으로 커서를 적군에 갖다대보세요. 갖다대는 순간 OllyDbg로 돌아와서 496E34에서 Breakpoint 때문에 걸리게됩니다.
위와 같은 상황에서 명령어를 실행해봅시다.
Stack 창을 보면 Push 명령어로 Stack에 값이 추가됐습니다.
이 값이 무엇을 뜻하는지, 어디에 쓰이는지는 아직 모릅니다.
그리고 ESP 의 변화가 보입니다. 22FD1C -> 22FD18로 값이 변했습니다.
ESP는 Stack 의 Top을 나타내는 것이니 Stack에 Push 로 추가된 값의 주소를 가르키게됩니다.
다시 명령어를 실행하세요.
그러면 MOV ECX, DWORD PTR SS:[EBP-20] 명령어 실행으로 ECX의 값이 바뀝니다.
여기서 DWORD PTR SS:[EBP-20] 에 대해 알아보겠습니다.
DWORD 자리에 들어갈 단어는 DWORD, WORD, BYTE가 있습니다.
DWORD 는 4Byte를 뜻하는 것이고, WORD 는 2Byte, BYTE는 당연히 1Byte입니다.
PTR 은 Pointer를 뜻하는 것 같습니다. [] 안에 있는 주소의 값을 읽는다는 뜻이죠.
제 생각에는 SS:, DS: 는 신경쓸 필요가 없을 것 같습니다. 명령어를 쓸 때는 SS:, DS:를 넣지 않아도
Assem에서 자동으로 넣어줍니다.
가장 중요한 것은 [EBP-20]입니다. EBP 는 스택이 꼬이지 않게 하기 위한 임시 스택 베이스라고 보시면
됩니다. 현재 EBP 는 22FD4C입니다.
Stack 은 위로 갈수록 주소가 4씩 작아지고(-4), 아래로 갈수록 주소가 4씩 커집니다(+4).
즉, 위의 사진처럼 [EBP-20]은 22FD2C가 되는겁니다.
결과적으로 MOV ECX, DWORD PTR SS:[EBP-20]은 '22FD2C(EBP-20)의 값을 4Byte 읽고,
그 값을 ECX에 이동시켜라.' 라는 말이 됩니다.
아직 명령어 실행하시지 마시고 CALL 407DF9 에 대해 설명드리겠습니다.
407DF9 는 아이템 장착 검사를 하는 함수입니다.
정확히 말하자면 CALL 407DF9 전에 PUSH 된 아이템 효과 번호를 가진 아이템이 장착됐는지 검사하는겁니다.
즉, 13은 아이템 효과 번호가 되는 것이고, PUSH 13 은 아이템 효과 번호를 Stack 넣어두는겁니다.
잘 보시면 CALL 407DF9 가 실행되기 전에 Stack에서 아까 PUSH 된 값 옆에 Arg1가 생겼습니다.
이 것은 저 값이 함수의 인수(매개변수)가 된다는 뜻입니다. 즉, Arg 라는 것이 뜨면 그 함수와 그 값은 밀접한
관련이 있다고 생각하시면 됩니다.
ECX 의 값도 407DF9 함수에 쓰입니다. ECX 값은 메모리 주소인데 메모리 주소에 관한 것은 첨부 파일에
올려놨으니 참고하시면 됩니다. (4AC2E0 부분을 보시면 됩니다.)
407DF9를 자세히 파고들려고는 생각했으나 막상 함수 살펴보니 저도 햇갈리네요. -_-;
그래서 나중에 살펴보기로 하고....
명령어를 실행하면 00 혹은 01로 EAX 값이 바뀔 것입니다.
407DF9 는 아이템을 장착했으면 01, 안했으면 00을 반환합니다.
그럼 이제 EAX가 0인지 1인지 검사를 해봐야할텐데.....
명령어를 실행하면 TEST EAX, EAX 명령어가 실행됩니다.
TEST 명령어는 첫 번째 대상과 두 번째 대상을 AND 하여 플래그를 SET하는 명령어입니다.
플래그를 SET한다고 하니 CMP 명령어랑 비슷하다고 생각되지 않나요?
※ CMP : 첫 번째 대상과 두 번째 대상을 비교하여 플래그로 SET하는 명령어
CMP 명령어와 비슷한 개념이라고 보시면 됩니다만... 비교가 아니라 AND 연산자라는 것이 다르죠.
지금은 간단히 CMP AL, 0 와 같은 뜻으로 알고 계시면 됩니다.
TEST 명령어 바로 밑에 JE 명령어가 있는데 EAX가 0이면 점프하고, EAX 가 0이 아니면 점프하지 않고
그대로 진행합니다. 이런 것을 보면 CMP AL, 0 과 흡사하다고 볼 수 있죠.
명령어를 실행하기 전에 Register에 있는 EAX를 더블클릭합시다.
위와 같은 창이 뜨는데 Hexadecimal을 0으로 만들고 OK를 누릅시다.
그리고 명령어 실행~
그러면 점프가 되면서
위와 같은 주소에 커서가 잡힙니다. 명령어를 실행하시면
MOV ECX, DWORD PTR SS:[EBP-14]에 의해 ECX의 값이 변할텐데 이것도 역시
위에 나왔던 MOV ECX, DWORD PTR SS:[EBP-20] 과 같은 원리이니 다시 설명하지는 않겠습니다.
CALL 41C700 이 있는데 41C700 은 인물 번호를 구하는 함수입니다.
역시 ECX 가 함수에 쓰이는데요. 이 ECX도 메모리 주소입니다.
근데 407DF9 에 쓰이는 메모리 주소와 같을 수도 있고 다를 수도 있습니다.
같다면(4AC2E0) 반드시 CALL 후에 AND AH, 0F 명령어가 있어야하며, 다르다면(4BD3C8) 없어도 됩니다.
항상 명중은 407DF9 에 쓰이는 메모리 주소와 다르기 때문에 AND AH, 0F를 넣지 않았습니다.
명령어를 실행하면 EAX가 공격하려는 인물의 번호로 바뀔 것입니다.
그런데 어떻게 인물의 번호를 불러오는지 궁금하다고요?
그럼 41C700를 분석해보록 하겠습니다.
0041C700 /$ 55 PUSH EBP
0041C701 |. 8BEC MOV EBP,ESP
0041C703 |. 51 PUSH ECX
0041C704 |. 894D FC MOV DWORD PTR SS:[EBP-4],ECX
0041C707 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
0041C70A 8B00 MOV EAX,DWORD PTR DS:[EAX]
0041C70C 8BE5 MOV ESP,EBP
0041C70E |. 5D POP EBP
0041C70F \. C3 RETN
41C700 함수는 이렇게 이루어져 있습니다.
기본적인 함수 구조는
PUSH EBP
MOV EBP, ESP
~~~
~~~
MOV ESP, EBP
POP EBP
RETN
으로 이루어져 있습니다. 기본적인 함수 구조일뿐, 꼭 이렇게 하지 않아도 됩니다.
하지만 가끔 가다 RETN X 와 같이 RETN 옆에 숫자가 있는 걸 보실텐데요.
ADD ESP, X
RETN
과 같은 뜻인데... 지금은 자세히 알 필요는 없을 듯합니다.
기본적인 함수 구조를 제외한 나머지 명령어들을 분석해보겠습니다.
PUSH ECX
단지 스택을 늘리려는 의도만 있는 것 같습니다. SUB ESP, 4와 같은 뜻입니다.
MOV DWORD PTR SS:[EBP-4], ECX
[EBP-4] 주소에 4Byte ECX를 넣는다.
MOV EAX,DWORD PTR SS:[EBP-4]
[EBP-4] 에 있는 4Byte 값을 EAX에 넣는다.
MOV EAX,DWORD PTR DS:[EAX]
EAX 주소에 있는 4Byte 값을 EAX에 넣는다. 즉, 인물 번호를 EAX에 넣는 것이겠죠.
해석하면 이런 식입니다.
이 함수를 간추린다면
PUSH EBP
MOV EBP, ESP
MOV EAX, DWORD PTR DS:[ECX]
MOV ESP, EBP
POP EBP
RETN
입니다. 결국 41C700을 보면 EAX = ECX 이고, ECX 메모리 주소의 값을 EAX에 넣은거니...
더 간추릴 수 있을까요? 대답은 "Yes" 입니다.
MOV EAX, DWORD PTR DS:[ECX]
RETN
으로만 이루어져 있어도 아무 문제는 없습니다. 함수의 기본 구조는 기본 구조일뿐이지 무시해도 되는 것이니까요.
하지만 함수의 기본 구조를 무시하게 되면 Stack을 다룰 때 힘들어집니다. 뭐 Stack을 다루지 않는 함수라면
위처럼 만들어도 상관은 없습니다.
그럼 41C700 에 대해 알아봤으니 다시 돌아와서 CMP AX, 0FFFF(인물번호는 다를 수 있습니다.)를 봅시다.
CMP AX, 0FFFF 는 인물 번호를 비교하는 작업입니다. 인물번호는 최대 2Byte기 때문에 AX로만 비교해도 문제 없습니다.
명령어를 실행하기전에 여기서 위에서 했던 것과 같이 EAX를 더블클릭해서 인물번호와 다르게 값을 설정하시고,
명령어를 실행하세요. 그러면 JNZ 명령어에 커서가 잡힙니다. 다시 명령어 실행하면 점프할겁니다.
위와 같은 주소에 커서가 잡힙니다. 명령어를 실행하면 ECX 의 값이 바뀝니다.
이 메모리 주소는 407DF9의 메모리 주소(EBP-20)와 같습니다.
CALL 406970은 병종 번호를 불러오는 함수입니다.
함수의 구조는 대체적으로 41C700과 아주 비슷합니다. 단지 EAX가 아닌 AL에 병종 번호가 넣어집니다.
즉, EAX로 비교하면 절대 안되고 AL로 비교해야합니다. EAX로 비교할거면 AND EAX, 0FF를 넣으시면 되는데
AL 로 비교하는 것보다 비효율적이므로 그냥 AL로 비교하시는 것이 좋습니다.
명령어를 실행하시면 AL에 공격하려는 인물의 병종 번호가 넣어집니다.
CMP AL, 0FF 는 병종 번호를 비교하는 작업입니다.
이제 비교를 해야하는데... 명령어 실행 전에 또 EAX 값을 바꾸는데 이번에는 설정한 병종 번호와 같게합니다.
그러면 JE에서 점프가 될것입니다.
XOR EAX, EAX 는 사실상 MOV EAX, 0 과 같습니다. XOR EAX, EAX가 Byte를 더 적게 먹을뿐이죠.
XOR 연산자는 2개의 비트가 모두 1일 때만 0으로 만듭니다. 그러니 같은 대상을 XOR 하게 되면 당연히 0이
되는거죠.
MOV AL, 64 는 그냥 그대로 해석하면 AL에 0x64를 넣는다는건데 이 EAX가 명중률을 뜻합니다.
그러니까 명중률 100%(항상명중) 라는 뜻이죠.
근데 굳이 MOV EAX, 64 를 안하고 두 개의 명령어를 썼을까?
MOV EAX, 64 : B8 64 00 00 00 (5Byte)
XOR EAX, EAX
MOV AL, 64
-> 33 C0 B0 64 (4Byte)
1Byte라도 아끼자는 정성이 담겨있음을 볼 수 있습니다. -_-;
명령어를 실행해서 JMP 까지 하면 43D848로 이동합니다.
바로 함수 끝부분이군요. 원래 43D848이 포함된 함수는 명중률을 계산하는 함수인데
이미 명중률이 100%로 결정되버렸으니 계산할 필요는 없어지고 그냥 함수를 끝내야되는 것입니다.
Ctrl + F2 키를 누르고 F5를 눌러 다시 실행합니다. 그리고
이 부분으로 같은 방법으로 이동하고 이번엔 비교하기 전에 EAX 를 병종번호와 다르게 설정해주세요.
그러면 이번에는
PUSH 27
MOV ECX, DWORD PTR [EBP-20]
이 부분은 사실상 JMP 코드로 지워진 복구 코드입니다.
그리고 모든 조건이 맞지 않았을 때 점프하는 곳으로 항상 명중 효과는 발휘하지 않는 부분이기도 하죠.
JMP 43D7C1
코드를 복구한 다음에 다시 그 복구 다음 코드로 JMP 하는 것입니다.
명령어를 실행해서 JMP를 해보면
CALL 407DF9 가 보이고 그 위에는 JMP 496E34 가 보입니다.
원래 JMP 496E34 대신에 PUSH 27, MOV ECX, DWORD PTR [EBP-20] 이 있었던거죠.
그리고 항상 명중은 없던걸로 인식되고 명중률 계산을 진행하는 겁니다.
아... 힘들군요.
일일이 한 명령어씩 분석하는 것도 쉽지 않은 일이군요.
항상 명중 갖고 힘들어하는 저를 보니, 다른 코드들은 어떻게 할지 고민되네요. -_-;
만약 이해 안되는 부분이 있다거나 설명 부분이 부족한 부분이 있다면 댓글로 말씀해주세요.