악성코드 분석[4]
x86 아키텍처에서 EIP는
instruction pointer나 program counter로 알려져있습니다.
즉, 프로그램에서 수행할 다음 명령어의 메모리 주소를
담고있는 레지스터입니다.
만약 이 EIP가 손상된다면 어떻게 될까요?
EIP가 손상되었다는 말은 정상프로그램 코드가 아닌 곳의 메모리 주소를 가리킨다는 말이겠죠?
▶ CPU는 실행할 정상 코드를 가지고 오지 못하므로 실행 중인 프로그램은 종료될 것입니다.
▶ EIP를 제어할 때 CPU의 실행을 제어할 수 있습니다.
▶ 바로 공격자가 EIP타겟팅 하는 이유도 CPU의 제어권을 갖기 위해서이지요.
▶ 그래서 메모리상에서 자신이 실행하고자 하는 코드를 EIP가 가리키도록 합니다.!!
mov와 lea명령어의 차이를 아시나요?
"mov는 값을 옮기고, lea는 주소를 옮긴다"로
흔히 알려져 있지요.
조금 풀어 적으면
mov는 메모리를 읽고 쓰는 명령어이고,
lea는 메모리 주소를 읽고 쓰는 명령어지요.
※ lea = Load Effective Address
맞는 말이지만
[1] mov eax, [ebp+8]
▶ [ebp+8]주소의 값을 eax에 저장합니다.
[2] lea eax, [ebp+8]
▶ [ebp+8]의 주소를 eax에 저장합니다.
그렇다면
[3] mov eax, ebp+8
▶ 이렇게 적으면 ebp+8의 주소가 eax에 들어가지 않을까요?
▶ 아쉽게도 이 경우에 mov명령어는 유효하지 않습니다.
그 외에도 lea는 명령어를 줄이는데에도 사용을 합니다.
xor...
디스어셈블된 소스를 보면
꽤 자주 보는 명령어죠?
아래 두 명령어의 차이는 무엇일까요?
[1] mov eax, 0
[2] xor eax, eax
▶ 실제로 동작 결과로 보면 차이는 느껴지지 않지요.
▶ 그러나 [1]의 방식은 5바이트를 사용합니다.
▶ 반면에 [2]의 방식은 2바이트를 사용합니다.
▶ 그래서 값을 최적화할 때는 주로 xor를 사용한답니다.
rol과 ror명령어를 아시나요?
여기서 r은 Rotation(순환)을 말합니다.
비트연산 혹은 곱셈이나 나눗셈 시 사용하는
시프트 명령어(shl, shr)와 비슷합니다.
1) eax가 0xA(1010)라고 할 때
[1] shr al, 2
al은 2가 되겠지요?
[2] ror al, 2
al은 130이 됩니다.
원래 al = 00 00 10 10
연산 후 al = 10 00 00 10
▶ 원래 al에서 뒤에 2비트가
시프트연산시에는 사라지지만
로테이션연산시에는 앞쪽으로 옮겨집니다.
악성코드 분석 시 xor, or, and, shl, shr, rol, ror 명령어를
반복적으로 사용하는 부분이 있다면
암호화나 압축과 관련될 가능성이 높습니다.
즉, 정말로 필요한 경우가 아니면
명령어 하나하나 분석하는 것보다
암호나 압축루틴이라고 표시만 하고 넘어가는 것이
정신건강에 좋습니다.
nop nop ... payload... nop nop nop..
단순한 nop명령어는 아무 것도 하지 않습니다.
nop는 원래 xchg eax, eax 의 가명입니다.
즉, 같은 값끼리 바꾸면 아무 일도 일어나지 않죠
no operation!!
이 명령어의 opcode는 0x90입니다.
NOP sled 공격을 할 때 공격자가 완전하게
Exploit을 만들기 힘들경우 버퍼오버플로우 공격에 사용하지요.
노란 박스글 상단에 적은 것과 같이
Execution Padding(실행패딩)을 합니다.
스택에서 push나 pop을 사용하지 않고
명령어를 읽을 수 있을까요?
예를 들어
[1] pop eax
▶ 현재 스택포인터에 있는 값을 eax에 저장하고 esp+4를 합니다.
[2] mov eax, ss:[esp]
▶ 스택세그먼트의 esp에 있는 값을 eax에 저장만 합니다.
▶ 즉, esp에 영향을 미치지 않지요.
모든 레지스터를
스택에 저장하고 꺼내는
pusha, pushad와
popa, popad가 있습니다.
PUSHA는 16비트 레지스터에 대해
AX, CX, DX, BX, SP, BP, SI, DI 순으로 PUSH합니다.
PUSHAD는 32비트 레지스터에 대해
EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI 순으로 PUSH합니다.
POPA와 POPAD는 반대로 진행이 되겠죠?
이러한 명령어는
모든 레지스터의 현재 상태를 저장한다고 볼 수 있지요.
이 명령어는
일반프로그램에서는 보기 힘들고
shellcode에서 주로 볼 수 있습니다.
왜냐하면 컴파일러는 이 명령어를 거의 사용하지 않기 때문이지요.
만약에 사용한다면?
누군가 하드코딩한 어셈블리나 shellcode...
shellcode에서는 왜 사용할까요?
아마 나중에 복구하기 위해 현재상태를 저장하는 것이 아닐까요?
test명령어와 cmp의 차이는 무엇일까요?
흔히
test명령어는 and,
cmp명령어는 sub
명령어와 동일하지만 결과는 저장하지 않고
상태(Flag)만 저장하는 것으로 알려져 있지요.
[1] test eax, eax
▶ eax와 eax의 and연산을 한 플래그를 저장합니다.
▶ eax가 0일 경우 zf가 1이 되겠지요.
[2] cmp eax, 0
▶ [1] 명령어와 동일한 zf를 나타냅니다.
▶ 단지 적은 test가 적은 바이트를 사용하며, 적은 CPU사이클을 사용합니다.
흔하게 알려진 표도 정리해볼까요?
cmp dst, src | ZF | CF |
dst > src | 0 | 0 |
dst = src | 1 | 0 |
dst < src | 0 | 1 |
rep... 넌 누구니?
rep명령어는 데이터 버퍼를 조작하는 명령어 집합입니다.
버퍼를 조작하는 명령어는
movsx, cmpsx, stosx, scasx가 있으며
x에는 b, w, d가 들어갑니다.
바이트, 워드, 더블워드를 의미하는 것으로 보이네요.
이 명령어는
ESI(source index), EDI(destination index), ECX(count)
레지스터를 사용하지요.
movsb를 예를 들면
1바이트만 이동하고 ecx를 사용하지 않습니다.
효율적으로 데이터 버퍼를 옮기기위해
rep는!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
ESI와 EDI의 오프셋을 증가시키고
ECX를 감소시킵니다.
명령어 |
설명 |
rep |
ecx = 0일 때까지 반복 |
repe, repz |
ecx = 0 또는 ZF = 0일 때까지 반복 |
repne, repnz |
ecx = 0 또는 ZF = 1일 때까지 반복 |
그래서 문자열을 복사할 때는
[1] 복사할 문자열의 개수를 ecx에 넣고
[2] 원본문자열이 있는 주소를 ESI에 넣고
[3] 복사할 문자열의 주소를 EDI에 넣는
초기화 과정을 거칩니다.
[1] rep movsb
▶ C언어의 memcpy()와 동일합니다.
▶ ESI의 주소에서 바이트를 꺼내 EDI의 주소에 저장한 다음,
방향플래그(DF[Direction Flag])설정에 따라 하나씩
ESI와 EDI를 증가시키거나 감소시키고
ECX를 감소시키면서 ECX가 0이될 때까지 반복합니다.
▶ 만약 ECX가 0이라면 명령어는 수행되지 않지요.
???
※ 위에서는 ESI와 EDI의 오프셋을 증가시킨다고 했는데?
여기서는 증가시키거나 감소시킨다..? 그것도 DF에 따라?
▶ 컴파일된 C코드에서는 거의 볼 수 없지만
공격자는 shellcode를 사용해서 데이터를 역방향으로
저장할 수 있도록 방향플래그를 설정합니다.
DF가 0이면 증가하고
DF가 1이면 감소합니다.
cmpsb는?
▶ 두 바이트가 같은 데이터를 저장하고 있는지 비교할 때 사용합니다.
▶ ESI주소의 값과 EDI주소의 값을 비교합니다.
▶ repe와 함께 사용하면 ecx가 0이거나 zf가 0이면 중단하지요.
▶ 이것은 C언어의 memcmp()와 동일하지요.
scasb는?
▶ ESI주소의 값과 al의 값과 비교를 합니다.
▶ 즉, cmpsb는 ESI, EDI가 가리키는 값을 비교했다면
scasb는 al에 있는 값이 ESI에 있는지 확인하지요.
▶ 발견되었다면 ESI에 해당 값의 위치를 저장합니다.
stosb는?
▶ scasb와 동일하지만 탐색하는 대신 EDI가 지정한 곳에 값을 저장합니다.
rep와 scasb를 함께 사용한다면?
[1] rep scasb
▶ rep는 메모리버퍼를 초기화할 목적으로 scasb와 함께 사용해서
모든 바이트가 동일한 값을 저장하도록 합니다.
▶ 즉, C언어의 memset()과 동일하지요.
[2] repne scasb
▶ 데이터 버퍼에서 한 바이트를 탐색하는데 사용합니다.
▶ EDI는 버퍼의 주소를 저장하고 al은 찾을 바이트를 저장하고
ecx는 버퍼의 길이를 저장합니다.
▶ ecx가 0이거나 특정 바이트를 찾을 때까지 비교합니다.
[3] rep stosb
▶ 버퍼의 모든 바이트를 특정 값으로 초기화하는데 사용합니다.
▶ EDI는 버퍼 위치를 저장하고 al은 초기값을 저장하고 있어야 합니다.
▶ xor eax, eax와 함께 자주 사용되지요.
'Try Attack > Malware Analysis[basic]' 카테고리의 다른 글
Anti-VM 기법 (0) | 2019.09.13 |
---|---|
[논문요약] 악성코드 정적탐지를 위한 PE의 Rich헤더 활용방법 (0) | 2019.09.04 |
악성코드 분석[3] (0) | 2019.08.30 |
악성코드 분석[2] (0) | 2019.08.28 |
악성코드 분석[1] (0) | 2019.08.28 |
댓글