x86 에뮬레이터를 작성하면서 배운 이상한 것들
- x86 및 amd64 에뮬레이터를 작성하면서 배운 다양한 트리비아와 이상한 점들에 대해 설명함
- Time Travel Debugging(TTD)에서 CPU 에뮬레이터를 사용하여 프로세스의 실행을 명령어 수준에서 기록함
- 첫 번째 버전의 TTD는 iDNA로 불리며, 어셈블리 코드로 작성되어 빠르지만 유지보수가 어려웠음
- 두 번째 버전은 C++로 작성되어 유지보수성이 향상되었음
쓸모없는 x86 인코딩 트리비아
- x86 인코딩 스킴은 동일한 명령어를 여러 가지 방법으로 인코딩할 수 있음
-
int 3
명령어는 CD 03
또는 CC
로 인코딩될 수 있음
-
EAX
레지스터는 "누산기 레지스터"로 불리며, 인코딩에서 실제로 차이가 있음
-
REX
프리픽스는 64비트 코드에서 더 넓은 범위의 레지스터에 접근할 수 있게 함
- 명령어는 최대 15바이트까지 길어질 수 있으며, 이를 초과하면 예외가 발생함
- 주소 오버라이드 프리픽스는 64비트 모드에서 32비트 주소를 참조할 수 있게 함
이상한 플래그 특성
-
INC
명령어는 ADD
명령어와 달리 캐리 플래그를 업데이트하지 않음
-
CMPXCHG8B
/CMPXCHG16B
명령어는 제로 플래그만 수정함
- 시프트 및 회전 명령어는 시프트 양이 1보다 클 경우 오버플로우 플래그를 정의되지 않은 상태로 둠
시프트 명령어의 더 많은 놀라움
-
shr ax, 10h
는 ax
레지스터를 16비트 시프트하여 0으로 만듦
-
shr eax, 20h
는 eax
레지스터를 32비트 시프트하지만 값은 변경되지 않음
- 시프트 양은 1FH로 마스킹됨
세그먼트 오버라이드
- 세그먼트는 32비트 및 64비트 코드에서 여전히 사용되며, 주로 스레드 로컬 스토리지에 사용됨
- Windows에서는
FS
또는 GS
레지스터를 사용하여 TEB(Thread Execution Block)를 참조함
- 32비트 프로세스에서는
FS
를 사용하고, 64비트 프로세스에서는 GS
를 사용함
- 64비트 모드에서는 세그먼트 레지스터의 값이 중요하지 않음
세그먼트 오버라이드: 더 많은 트리비아
- 32비트 모드에서는 세그먼트 레지스터의 실제 값이 세그먼트 디스크립터를 참조함
- 64비트 모드에서는 MSR에 의해 베이스가 제어됨
- WinDbg에서 64비트 프로세스의 세그먼트 값을 직접 읽을 수 없음
결론
- 이 글은 x86 트리비아의 무작위 목록을 제공함
- 에뮬레이터를 작성하는 것은 CPU가 어떻게 작동하는지 깊이 이해하는 데 도움이 됨
- Agner Fog의 웹사이트에서 훌륭한 리소스를 확인할 수 있음
GN⁺의 정리
- x86 및 amd64 에뮬레이터를 작성하면서 배운 다양한 트리비아와 이상한 점들을 설명함
- 에뮬레이터를 작성하는 것은 CPU의 작동 방식을 깊이 이해하는 데 도움이 됨
-
int 3
명령어의 다양한 인코딩 방법, REX
프리픽스, 세그먼트 오버라이드 등 다양한 트리비아를 다룸
- Agner Fog의 웹사이트에서 더 많은 리소스를 확인할 수 있음