
암호화된 셸코드를 숨겨진 호출을 사용하여 호출하는 간단한 PoC
숨겨진 호출을 이용하여 암호화된 셸코드를 호출하는 간단한 PoC입니다.
이 코드는 실제로 셸코드로 점프하는 명령어를 숨기기 위한 간단한 트릭을 사용합니다. 이는 정적 분석이나 에뮬레이션을 더 어렵게 만듭니다.
언제나처럼, 이 개념이 다른 논문에서 이미 설명되었다면 메시지를 보내주시면 기꺼이 참고 문헌에 추가하겠습니다.
이 기법을 사용하기 위한 메모리 레이아웃은 표준이며 그림 1에 설명되어 있습니다.
┌───────────────────────────────┐
│ │
│ │
│ │
│ │
│ encrypted shellcode │
│ │
│ │
│ │
│ │
│ │
│ │
├───────────────────────────────┤
│ │
│ │
│ decryption loop │
│ │
│ │
└───────────────────────────────┘
그림1. 메모리 레이아웃
복호화 루프는 셸코드를 복호화하고 그곳으로 점프합니다. 모든 '마법'은 복호화 루프 내부에 있는데, 셸코드 복호화가 완료된 후 복호화 루프는 자신의 코드를 복호화하기 시작하기 때문입니다. 첫 번째 명령어의 복호화는 셸코드로의 점프로 이어지며, 다음 루프 반복에서 실행됩니다 :)
아래 관련 부분:
@decryption:
xor word ptr [eax], 06799h ; <jmp esi bytes> XOR <first two bytes of this instruction>
add eax, sizeof word
dec ecx
loop @decryption
보시다시피, 복호화 루프는 복호화된 셸코드로 점프하는 명령어를 포함하지 않습니다. 어셈블리 코드는 다음과 같은 바이너리 형식으로 조립됩니다:
66:8130 9967 | xor word ptr ds:[eax],6799
83C0 02 | add eax,2
49 | dec ecx
E2 F5 | loop 450006
이 경우, 복호화 키는 06799h여야 합니다. 8166h(복호화 루프의 첫 번째 명령어의 첫 2바이트)와 6799h(XOR 키) 간의 XOR 연산 결과는 e6ffh이며, 이는 jmp esi로 조립되기 때문입니다. 즉:
0x8166 (xor word ptr ds:[eax],...) XOR 0x6799 (decryption key) == 0x6eff (jmp esi)
ESI 레지스터를 셸코드의 시작 주소로 설정하면 실행을 달성할 수 있습니다 :)
아래는 디버깅 예제입니다. 처음에는 암호화된 셸코드가 복사되고, 그 다음에는 셸코드를 복호화하고 호출하는 코드가 위치합니다. 복호화 루프의 세 번째 실행 후, xor word ptr ds:[eax],6799 명령어가 jmp esi로 변경되는 것을 확인할 수 있습니다.

이 기법을 사용하는 단계는 다음과 같습니다:
복호화 코드를 덜 식별 가능하게 만들기 위해 셸코드를 호출하는 대체 방법을 사용할 수 있습니다. 더 많은 자유를 얻기 위해 각 반복에서 암호화되는 청크 크기를 늘리는 것을 고려할 수 있습니다. 제 PoC에서는 jmp esi가 2바이트를 필요로 하므로 2바이트를 사용했지만, 4바이트 또는 8바이트 청크 크기를 사용할 수 있으며, 이를 통해 운영자는 4바이트 또는 8바이트 청크에 맞는 더 많은 대안을 가질 수 있습니다. 셸코드 호출 방식에 따라 암호화 상수도 변경됩니다.