나는 동일한 코드를 이용해서 x86과 x64 두 아키텍처에서 동작하는 쉘코드가 필요했다.
하나를 가정해보자. 너가 만약에 x64 또는 x86인 시스템 플랫폼을 공격한다고 가정해보자, 사전에 정확한 플랫폼을 알 수 없다.
문제점은 실제로 동작해야하는 것이지?
여기에 너가 x64에서 동작하는지 안하는지 알려주는 내가 오래동안 사용했던 작으 트릭이 있다.
이 작은 트릭(아이디어)는 매우 간단하다. 그 이유는 x64와 x86은 많은 opcode 값들을 공유하기 때문이다.
x86 시스템에서 사용하는 한바이트 inc, dec opcode 는 0x40-0x50 범위의 작은 유사성이 있습니다.
8개의 범용 레지스터와 2 opcode 가 있기 때문에, 전체 범위는 0x40-50사이입니다.
현재 AMD64's ISA(Instruction Set Architecture) 디자인은 전체 범용 레지스터를 16개 만들면서 8개의 범용 레지스터를 추가시켰다.
x86 시스템에서는 특정 레지스터에 접근하기 위해서 사용되는 ModeRM 바이트에서 오직 3bit만 필요하다.
ModeRM 바이트 : 프로세서에게 해당 오퍼랜드를 어덯게 읽는지 지시해줌
새로운 ISA와 함께, 모든 16개의 레지스터를 설명하기 위해서는 여분의 비트가 더 필요했다.
그래서, 새로운 접두사(rex preix라고 불리는)를 추가해서 여분의 비트가 필요한 문제점을 해결했다.
새로운 0x40-0x50 범위의 prefix는 이전의 한바이트 inc/dec 바이트를 제거한다. (현재 컴파일러에서는 안전성을 위해서 현재 2 바이트 모두 사용)
다시 어셈블리어 코드로 돌아와서, x86 시스템에서는 inc eax는 eax 의 값이 1증가 할 것이다.
그리고 x64 시스템에서 동작할 때는 이것은 nop 명령어의 prefix가 된다. 그래서 eax는 그대로 0의 값으로 유지될 것이다.
해보지 않는 사람들을 위해서 그냥 한마디로 요약하면 32bit에서는 값이 증가할 것이고 64bit에서는 rax값이 그대로 0일 것이다.
위의 그림은 32bit 메뉴얼에서 명령어이다. 40+r 부분이 opcode이고 행위는 1의 값증가
위의 그림은 64bit 메뉴얼이다 앞 부분은 40은 prefix를 설명하고 있고, 새로운 8 bit 레지스터에 접근할 때 사용한다고 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | XOR EAX, EAX INC EAX ; = DB 0x40 NOP JZ x64_code x86_code: bits 32 . . . RET x64_code: bits 64 . . | cs |
이제 한번 실험하면...
1 2 3 4 5 6 7 8 9 10 11 12 | #include "stdio.h" int main() { char *a = "\x31\xc0\x40\x90"; void (*shell)(void); shell = (void*)a; shell(); return 0; } | cs |
위와 같이 매우 간단한 코드로 테스트해본다. 해당 코드를 x64 버전에서 x32버전에서 빌드하고 디버깅 상태에서 본다.
위에서 보면 둘다 0x9040c031로 같은데 명령어는 다르다.
[출처] : http://www.ragestorm.net/blogs/?p=376
[참고] http://ref.x86asm.net/coder32.html
'학부_대학원 > 대학원_학과공부정리' 카테고리의 다른 글
[HW] Write up 1 (0) | 2017.09.23 |
---|---|
ROP 문제풀이 (0) | 2017.09.23 |
64bit reverse shellcode (0) | 2017.09.06 |
리눅스 커널 부팅 과정[0] (0) | 2017.08.22 |
리눅스 커널 이미지 구조 (0) | 2017.08.22 |