Assembly Lang - 제어 흐름
어셈블리 언어에서의 제어 흐름에 대해 학습한 내용입니다.
어셈블리 언어 - 제어 흐름
- 프로그램은 특정 조건과 상태에 따라 다양한 행동들을 할 수 있습니다. 이러한 행동들은 제어 흐름에 따라 흘러가는데 C언어에서의 if문과 같은 조건문(분기문), for, while과 같은 반복문들이 이에 속합니다.
- 이러한 제어 흐름은 어셈블리 언어에서도 존재합니다. 이 경우 영향을 받는 레지스터는 인덱스 포인터와 관련된 레지스터들입니다. IP/EIP로, 다음 실행 명령이 있는 주소를 가지고 있습니다.
조건문
JMP
→ JUMP 명령어는 ‘JMP’로 축약되어 사용됩니다. JMP는 C언어에서의 goto문과 비슷합니다. JMP 뒤에 오는 주소를 통해, 해당 주소로 이동하여 명령어를 수행하는 역할을 하는데 이때 EIP는 이 주소로 초기화됩니다. 예시를 통해 EIP가 어떻게 적용되고 JMP가 어떻게 사용되는지 알아보겠습니다.
jmp 00401000 ; 해당 라인은 00401000 번지로의 이동을 나타내며 이 명령어가 가동되면 EIP는 00401000으로 설정됩니다.
JMP 명령어 뒤에는 위의 예시처럼 직접적인 주소를 부여하는 경우도 있는 반면에, 간접적인 주소 명시도 가능합니다. 이 JMP는 점프가 실행될 때만 알 수 있는 주소로 점프할 수 있습니다. 주소는 JMP 명령어 이전에 검색하거나 계산해야 합니다.
; 각자 다른 예시입니다. 각 case들을 보여주는 것 뿐입니다.
jmp eax
jmp dword ptr [00403000]
jmp dword ptr [eax+edx]
jmp dowrd ptr [eax]
jmp dword ptr [ebx*4+eax]
CALL과 RET
→ CALL과 RET는 함수와 관련된 명령어입니다. CALL 명령어 이후 주소를 명시해줄 경우 해당 주소로 이동하는 것은 JMP와 같지만 CALL 명령어는 이후 해당 주소를 스택에 저장합니다. 함수를 선언할 경우 해당 함수가 스택에 적재되는 것과 같은 원리죠. 이렇게 스택에 저장된 주소는 이후 RET 명령어에 의해 EIP를 다시 가리키기 위해 사용됩니다. 즉 다음과 같이 생각하면 됩니다.
CALL → 함수 호출
RET → 함수 종료 및 원위치 복귀, 이때 피연산자를 가질 수 있는데 해당 피연산자가 우리가 알고 있는 함수에서의 반환값입니다.
CALL이 특정 주소에서 발생할 때 스택의 상단에는 돌아올 주소인 CALL 명령어의 다음 주소가 설정됩니다. 이를 리턴 주소라고 합니다. 이후 RET 명령어가 발생하면 RET는 스택 상단의 리턴 주소를 가져오고 그 주소로 EIP를 설정합니다.
CMP
→ 첫 번째와 두 번째 피연산자에 대해 SUB 명령을 수행하지만, 레지스터나 값을 수정하지는 않습니다. 다만, 결과값을 통해 FLAG에만 영향을 줍니다.
TEST
→ 첫 번째와 두 번째 피연산자에 대해 AND 명령을 수행하지만, 레지스터나 값을 수정하지는 않고 마찬가지로 플래그에만 영향을 줍니다.
플래깅 인스트럭션(Flagging Instruction)
→ CMP와 TEST 같이 레지스터에 대한 값을 수정하지는 않지만 플래그에만 영향을 주는 플래그 조정 명령어를 칭하는 말입니다.
조건부 JMP
→ 조건부 JMP란, 특정 조건에서 해당되는 주소로 넘어가는 것입니다. C언어로 보자면 if문과 같다고 보시면 됩니다. if문에서도 괄호( ) 안 조건문이 참이여야 아래 코드들을 실행시키잖아요? 그런 것과 같은 원리입니다. 조건부 JMP 문 다음 주소에 해당하는 부분이 if문에서의 중괄호 { } 안의 내용이라고 생각하시면 편해요! 그럼 조건부 JMP엔 어떠한 것들이 있는지 알아보겠습니다. 참고로 조건은 연산 결과의 상태를 보는 것이기 때문에 FLAG들을 참조합니다.
JZ / JE
→ ZF = 1 일 경우 JMP 합니다. 즉 EAX가 0이거나 비교 대상이 같으면 JMP 하는 것입니다.
어셈블리에서의 비교는 주로 SUB 명령어를 활용합니다. 즉, 빼기를 활용하는 셈인데요, 같은 값을 빼면 0이 되겠죠? 그렇기 때문에 같은 값일 때 JMP를 하는 것입니다.
JNZ / JNE
→ JZ와 반대 개념이라고 생각하시면 됩니다. 즉, 제로 플래그(ZF)가 0일 경우(=EAX가 0이 아닌 경우) 또는 비교 값이 같지 않으면 JMP를 수행합니다.
JS / JNS
→ JS는 Sign Flag(SF)를 참조합니다. 부호인 경우에 점프합니다. JNS는 그 반대이겠죠? N은 NOT의 의미입니다.
JC / JB / JNAE
→ 단어로 생각하시면 편합니다. C는 캐리 플래그(CF)를 의미하고 해당 CF가 1이면 점프합니다. B는 Below로 미만일 시 점프합니다. NAE는 Not Above or Equal로 이상이 아닐 시 점프한다는 의미 입니다.
JNC / JNB/ JAE
→ 모든 것이 위와 반대입니다.
JO / JNO
→ O는 overflow를 의미하는 플래그인 OF 를 참조하는 것으로 Overflow 발생 시 점프하고 JNO는 그 반대입니다.
JA / JNBE
→ Jump Above를 의미합니다. 초과란 뜻이므로, Not Below or Equal과 같은 의미겠죠? 참조하는 플래그는 CF와 ZF를 참조하고 둘 다 ‘0’으로 설정되어 있어야합니다. 그럼 JNA / JBE는 그 반대란 것!
JG / JNLE
→ JG에서 G는 greate의 약자입니다. 그렇담 NLE는 Not Less or Equal이 되겠죠. 참조하는 플래그와 상태는 ZF = 0, SF = OF 입니다. JNG / JLE는 이와 반대라고 생각하시면 됩니다.
JL / JNGE
→ 작으면 점프하고 크거나 같지않으면 점프한다는 명령어입니다. JG / JNLE를 반대하기 때문에 참조하는 플래그도 같을 것 같지만 JL / JNGE는 ZF를 참조하지 않고, SF ≠ OF인 경우 점프합니다. JNL / JGE는 이와 반대입니다.
JP / JPE (Parity Even)
→ P는 Parity를 뜻하며 당연히 Parity Flag인 PF의 상태를 참조합니다. PF가 on일 경우 점프하는 것이니까 짝수면 점프라는 소리입니다. 그렇다면 JNP / JPO(Parity Odd)는 그와 반대라는 것을 알 수 있습니다.
JCXZ
→ Flag가 아닌 CX의 상태를 참조합니다. CX가 0이면 점프합니다.
JECXZ
→ JCXZ에서 E가 추가 되었는데 여기서 E는 ECX에서 ‘E’를 가져온 것 입니다. 당연히 CX가 아닌 ECX를 참조한다는 뜻이겠죠? ECX가 0이면 점프합니다.
LOOP
→ 이전에 레지스터 설명에서 ECX는 Count 역할은 한다고 언급했습니다. LOOP는 반복 명령어를 의미하며 ECX가 0이 아닌 상태일 경우 주소로 점프하고 ECX를 1 감소시킵니다.
LOOPE / LOOPNE
→ LOOP의 조건에서 ZF까지 참조하는 명령어입니다. LOOPE는 ZF가 1일 경우 점프하고 LOOPNE는 그 반대입니다. 전제적으로 ECX가 0보다 크다는 LOOP의 조건도 동반합니다.