티스토리 뷰

안녕하세요!

 

오늘은 어셈블리어 inc, cmp, test, dec, jcc, call, jmp 명령어에 대해서 알아보겠습니다.

 

● x86 명령어 레퍼런스에 대한 자세한 내용은 이곳을(http://kernfunny.org/x86) 참고하세요. 

 

INC : 피연산자의 값을 1 증가시킴.

# OF, SF, ZF, AF, PF에 영향

 

DEC : 피연산자의 값을 1 감소시킴.

# OF, SF, ZF, AF, PF에 영향

 

INC나 DEC는 보통 반복문에서 많이 쓰입니다.

 

CMP : 두 값을 비교

# cmp 레지스터, 상수값

# cmp r/m32, 상수값

# cmp r/m32, 레지스터

# cmp 레지스터, r/m32

# 참고 레퍼런스에 따르면 Source1에서 Source2를 뺀 결과에 의해 Flags가 영향을 받는다고 설명되어 있습니다.

# CMP의 결과에 따라 CF, OF, SF, ZF, AF, PF flags가 설정됩니다.

 

TEST : 논리 비교

# test eax, eax (값이 0인 경우 ZF = 1)

# 보통 jcc랑 같이 쓰임.

 

JMP : JUMP

# 피연산자 주소로 EIP(다음에 실행할 명령어의 주소를 가지고 있음)를 변경.

# Short Jump(OF 85) : 현재 EIP 값에서 -128 ~ 127 범위로 이동.

# Far Jump(75) : 다른 세그먼트에 위치한 명령어로 이동.

 

JCC : Jump if Condition is Met

# 특정 조건을 만족할 경우 지정한 주소로 제어 흐름을 이동.

# JNE : Jump if Not Equal (ZF=0)

# JE : Jump if Equal (ZF=1)

# JLE : Jump if Less Equal

# 많은 jcc가 있지만 설명은 생략 ( 레퍼런스 참고 )

 

CALL : 함수 호출

# call printf / 0x80480000 / [eax+4]

# 우선 다음에 실행할 명령어 주소를 스택에 삽입한 뒤에 EIP에 해당 주소를 옮긴 뒤 이동하는 방식.

# 외부 함수를 사용하는 경우에는 반드시 해당 함수가 포함된 라이브러리를 linker에 함께 전달해야 함.

 

명령어들을 사용하여 프로그램을 직접 짜보면서 분석을 해보겠습니다.

 

 

다음과 같은 프로그램을 한번 봅시다.

 

cmp 명령어와 test 명령어를 수행하면서 플래그 레지스터들의 값들이 어떻게 변하는지 gdb를 통해 확인해볼게요.

 

 

main에서 5만큼 offset에 떨어진 곳에 break point를 걸겠습니다.

 

 

mov 명령어를 실행하고나서 eax에 2가 저장되어있고, eflags에는 IF(Interrupt enable flag)가 있습니다.

 

eflags 레지스터는 현재 1로 설정된 flags 정보를 가지고 있는 레지스터라고 생각하시면 됩니다.

 

IF(Interrupt enable flag) : The Interrupt flag (IF) is a system flag bit in the x86 architecture's FLAGS register, which determines whether or not the central processing unit (CPU) will handle maskable hardware interrupts.

The bit, which is bit 9 of the FLAGS register, may be set or cleared by programs with sufficient privileges, as usually determined by the operating system. If the flag is set to 1, maskable hardware interrupts will be handled. If cleared (set to 0), such interrupts will be ignored.

 

 

eax와 0x2를 cmp 명령어를 사용하여 비교를 한 뒤에 설정된 flags를 보면 PF, ZF, IF가 있습니다.

 

기본적으로 cmp의 연산은 eax - 0x2로 이루어지는데, 현재 eax에 0x2가 들어가 있기 때문에 연산의 결과는 0 (ZF=1)이 됩니다.

 

2진수로 바꿔서 연산을 해보면 10 - 10 이므로 00 (PF=1)이 됩니다.

 

따라서 ZF(zero flag)와 PF(parity flag)가 추가적으로 1로 설정되었습니다.

 

 

다음은 eax와 0x1를 cmp 명령어를 사용하여 비교를 한 뒤에 설정된 flags를 보면 IF만 있다는 것을 확인할 수 있습니다.

 

eax에는 0x2가 저장되어있고 eax에서 0x1를 뺀 결과가 0x1이므로 이전에 설정되었던 ZF는 0으로 설정되었습니다.

 

또한 2진수로 바꿔서 연산을 해보면 10 - 1 = 1 이므로 PF도 0으로 설정됩니다.

 

 

eax의 값과 0x3을 cmp 명령어를 사용하여비교를 한 뒤에 설정된 flags를 보면 이전보다는 많은 flag가 1로 설정되었음을 확인할 수 있습니다.

 

eax에는 0x2가 저장되어있고 0x2보다 큰 0x3을 뺀 결과는 음수가 되게 됩니다.

 

따라서 연산결과에 의해서 CF(Carry flag), PF(Parity flag), AF(Adjust flag), SF(Sign flag)가 추가적으로 1로 설정되었습니다.

 

 

test 명령어를 실행하면 eax에는 2가 저장되어 있으므로 ZF는 0으로 설정될 것입니다.

 

 

eax의 값이 0일 때 test명령어의 수행 결과는 0이므로 ZF는 1로 설정되고, PF도 1로 설정이 됩니다.

 

IF => https://en.wikipedia.org/wiki/Interrupt_flag

PF => https://en.wikipedia.org/wiki/Parity_flag

ZF => https://en.wikipedia.org/wiki/Zero_flag

CF => https://en.wikipedia.org/wiki/Carry_flag

AF => https://en.wikipedia.org/wiki/Adjust_flag

SF => https://en.wikipedia.org/wiki/Negative_flag

 

다음은 inc, cmp, jcc, call 명령어를 사용하여 간단한 반복문 프로그램을 구현해보겠습니다.

 

 

외부 함수인 printf를 사용하기 위해 extern printf를 입력합니다.

 

이 프로그램을 보면 처음에 ecx에 0을 넣고 eax에 ecx의 값을 복사를 합니다.

 

ecx의 값을 1 증가시킨 다음에 3과 비교를 합니다.

 

이때 cmp 명령어의 결과가 0이 아니라면 어디론가(0x080481c7)로 점프를 합니다.

 

만약 cmp 명령어의 결과가 0이라면 ecx와 3이 동일하다는 이야기이고, 점프를 하지않고 스택에 message를 push하고 printf 함수를 call 합니다.

 

그럼 gdb를 통해 분석을 해보겠습니다.

 

 

일전에 설명했듯이 외부 함수를 사용하는 경우에는 반드시 해당 함수가 포함된 라이브러리를 linker에 함께 전달해야 합니다. printf 함수는 밑줄친 라이브러리에 포함되어 있기 때문에 링크를 하는 과정에서 함께 전달을 해주어야 합니다.

 

 

아까 우리가 구현한 프로그램에서 보면 어디론가 jump하는 부분이 있었습니다.

 

어디로 점프를 하는지 아시겠습니까??

 

네, inc 명령어 라인으로 점프를 하게 됩니다.

 

이 위치는 제가 미리 gdb 디버거로 프로그램을 열어봐서 주소를 확인한 다음에 프로그램을 구현하였습니다.

 

그럼 이 프로그램이 어떻게 동작하는지 감이 오시죠??

 

네, 이 프로그램은 ecx를 1씩 증가시키다가 3이되면 loop를 탈출하여 printf를 call 하는 프로그램입니다.

 

자 이제 cmp 명령어 라인에 break point를 걸고 ecx의 값이 어떻게 변하는지, loop를 탈출하였을 때 printf 함수를 정상적으로 호출하는지 확인해보겠습니다.

 

 

자 처음에 ecx를 1 증가시키고 3과 비교하는 부분에서 break point에 걸렸습니다.

 

1-3은 0이 아니므로 조건을 만족하여 0x80481c7로 점프를 할 것입니다.

 

(gdb 명령어 중 c는 다음 break point까지 프로그램을 실행시킵니다.)

 

네 예상대로 0x80481c7로 점프를 하였고 ecx의 값을 1 증가시켜 현재 ecx의 값은 2가 되었습니다.

 

하지만 이번에도 2-3은 0이 아니므로 조건을 만족하여 0x80481c7로 점프를 할 것입니다.

 

 

네 예상대로 0x80481c7로 점프를 하였고 ecx의 값을 1 증가시켜 현재 ecx의 값은 3이 되었습니다.

 

이제 cmp 명령어를 실행하면 3-3 = 0 이므로 jcc 조건을 만족하지 못하여 점프를 하지 않고 다음 명령어를 수행하게 됩니다.

 

스택에 0x804a010을 push 하는데요, 그 다음에 이어지는 명령어가 printf를 call하는 명령어이므로 0x804a010는 printf 함수의 인자라고 예상해볼 수 있습니다.

 

 

자 0x804a010의 위치에 어떤 값이 들어있는지 확인해보니 우리가 프로그램을 짤 때 메시지에 넣었던 문자열이 들어있네요!

 

 

break point를 없애고 프로그램을 실행시켜보면 앞에서 분석한 ecx를 3까지 증가시킨다음에 정상적으로 loop를 빠져나와서 문자열을 출력하는 것을 확인할 수 있습니다.

 

지금까지 inc, dec, cmp, test, jcc, call, jmp 명령어에 대해서 알아보았습니다.

 

다음시 간에는 지금까지 배운 명령어들을 사용하여 난독화 프로그램을 만들어보도록 하겠습니다!

 

 

 

 

 

 

 

 

 

 

 

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함