자체 수정 코드

자체 수정 코드 (self-modifying code)는 실행 중에 자신의 명령어를 바꾸는 코드를 말한다. 주로 명령어 경로 길이 (instruction path length)를 줄이고, 성능을 향상시키거나 비슷한 반복되는 코드를 줄임으로써 유지보수를 단순화 시켜준다. 자체 수정 코드는 주로 테스트될 필요가 있는 조건의 수를 줄이는데 사용되는 조건부 프로그램 분기와 "플래그 설정" 방식의 대체재이다.

이 방식은 주로 추가적인 입출력 사이클의 오버헤드 요구 없이 조건부로 테스트/디버깅 코드를 유발시키기 위해 사용된다.

이 수정은 다음의 경우에 사용된다.

  • 오직 초기화 기간에만 - 입력 파라미터에 기반한다. 프로그램의 시작 포인터 변경은 자체 수정 코드의 간접적인 방식과 동등하지만, 하나 이상의 대체 명령어 경로를 요구함으로 인해 프로그램의 크기를 증가시킨다.
  • 실행 기간 동안 - 실행 기간 동안 미치게 되는 특정한 프로그램 선언에 기반한다.

다른 경우로, 새로운 명령어를 이미 존재하는 명령어에 덮어 씌움으로써, 수정은 기계어 명령어에 직접적으로 수행된다. (예를 들면, 비교와 분기를 무조건 분기 또는 NOP으로 대체한다.)

저급 그리고 고급 언어에서의 적용편집

자체 수정 코드는 프로그래밍 언어와 이것의 포인터 또는 동적 컴파일러나 인터프리터 '엔진'에 대한 지원에 의존하는 다양한 방식에 의해 성취될 수 있다.

  • 존재하는 명령어들의 덮어쓰기 (또는 옵코드, 레지스터, 플래그 또는 주소 같은 명령어들의 부분들) 또는
  • 전체 명령어들의 생성 또는 메모리에서 명령어들의 순서
  • 소스코드 선언의 생성 또는 변경
  • 전체 프로그램을 동적으로 생성하고 실행하기

어셈블리어편집

자체 수정 코드는 어셈블리어를 사용할 때 구현하기 매우 간단하다. 명령어들은 메모리에서 동적으로 생성될 수 있다. 이것은 표준 컴파일러가 생성하는 목적 코드와 동등한 순서를 갖는다. 현대의 프로세서들에서는, 고려되어야 하는 CPU 캐시에 의한 의도되지 않은 부작용이 생길 수 있다. 이 방식은 아래의 예시처럼 첫 번째 조건을 테스트하는데 종종 사용된다. 이것은 명령어 덮어쓰기를 명령어 경로 길이를 줄이는데 사용한다. N이 파일 레코드의 번호일 때 (N x 1)-1 이 된다. (-1 은 덮어쓰기를 수행하기 위한 오버헤드이다.).

SUBRTN NOP OPENED      FIRST TIME HERE?
* The NOP is x'4700'<Address_of_opened>
       OI    SUBRTN+1,X'F0'  YES, CHANGE NOP TO UNCONDITIONAL BRANCH (47F0...)
       OPEN   INPUT               AND  OPEN THE INPUT FILE SINCE IT'S THE FIRST TIME THRU
OPENED GET    INPUT        NORMAL PROCESSING RESUMES HERE
      ...

각 시간에 "플래그"를 테스트하는 대체 코드가 수반된다. 무조건 분기는 전체 경로 길이를 줄일 뿐만 아니라 비교 명령어보다 약간 빠르다. 프로그램이 보호 스토리지에 상주하는 최신 운영체제에서, 이 기법은 사용될 수 없고 대신 서브루틴을 가리키는 포인터를 변경한다. 이 포인터는 동적 저장소에 상주하며 OPEN을 우회하기 위해 첫 번째 패스 후에 교체될 수 있다. (경로 길이에 N 명령어들을 더할 서브루틴에 대한 직접 분기와 링크 대신 포인터를 첫 번째로 로드해야 한다. - 그러나 더 이상 필요치 않은 무조건 분기를 위한 상응하는 N의 축소가 있을 수 있다.)

고급 언어편집

어떤 컴파일된 언어는 명시적으로 자체 수정 코드를 허용한다. 예를 들면 COBOL의 ALTER 동사는 실행 중 수정되는 분기 명령어로 구현될 수 있다.[1] 한 배치 프로그래밍 기법은 자체 수정 코드를 사용할 수 있다.[2] Clipper와 SPITBOL도 명시적인 자체 수정을 위한 기능을 제공한다.

인터프리터 언어는 기계어가 소스 코드이며 실행 중에 변경할 수 있다. , 파이썬 그리고 자바스크립트 같은 다른 언어들은 eval 함수를 통해 프로그램이 실행 중에 새로운 코드를 만들 수 있게 허용한다. 그러나 존재하는 코드를 바꾸는 것은 허용하지 않는다.

사용편집

자체 수정 코드는 다양한 목적을 위해 사용될 수 있다.

  • 상태 의존 루프의 반자동적 최적화
  • 런타임 코드 생성 또는 일반적인 유틸리티 같은 런타임이나 로드 타임 시에 알고리즘의 특수화
  • 객체인라인 상태의 변경 또는 클로저의 고수준 구조 시뮬레이션
  • 서브루틴 (포인터) 주소 호출 (보통 동적 라이브러리로드/초기화 시간에 수행되는)의 패치, 자신의 실제 주소를 사용하기 위한 자신의 파라미터에 대한 서브루틴의 내부 참조 패칭 (즉, 간접 자체 수정)
  • 유전 프로그래밍 같은 진화 컴퓨팅 시스템
  • 리버스 엔지니어링을 예방하거나 바이러스/스파이웨어나 스캐닝 소프트웨어에 의한 탐지를 회피하기 위한 코드 숨김
  • 코든 프로그램이나 데이터를 지우기 위해서 반복되는 명령 코드의 규칙적인 패턴으로 메모리의 100%를 채우는 것
  • 암호화된 코드를 런타임 시에 복호화하고 실행시키기. (예를 들면 메모리나 디스크 공간이 제한적일 때)

상태 의존 루프 최적화편집

의사코드 예시

repeat N times {
   if STATE is 1
      increase A by one
   else
      decrease A by one
   do something with A
}

이 경우의 자체 변환 코드는 단순하게 아래와 같이 루프를 재작성하는 것이다.

 repeat N times {
    increase A by one
    do something with A
 }
 when STATE has to switch {
    replace the opcode "increase" above with the opcode to decrease, or vice versa
 }

옵코드의 2-상태 대체는 쉽게 '주소에서 "opcodeOf(Inc) xor opcodeOf(dec)" 값을 사용해서 xor var'로 쓰일 수 있다는 사실을 기억하자.

이 방법을 선택하는 것은 반드시 'N'의 값과 상태변화의 빈도에 의존해야 한다.

위장으로의 사용편집

자체 수정 코드는 1980년대에 디스크 기반 프로그램에서 복제 방지 명령어들을 숨기기 위해 사용되었다.

또한 바이러스셸코드 같은 프로그램들에 의해서 자신의 존재를 드러내지 않기 위한 용도로 사용된다. 이것들은 다형적인 코드와 자체 수정 코드의 조합을 주로 사용한다. 또한 버퍼 오버플로 같은 공격에서 실행 중인 코드를 수정하는 방법이 사용된다.

운영체제편집

자체 수정 코드의 보안 영향들 때문에 모든 주요 운영체제들은 알려진 이러한 취약점들을 제거하는데 조심한다. 주요한 걱정은 프로그램들이 의도적으로 스스로를 수정하는 것이 아니라 익스플로잇 되어 악의적으로 변경되는 점이다.

이러한 익스플로잇들에 의해 유발될 수 있는 문제들의 결과로서 W^X ("write xor execute") 라고 불리는 OS 특징이 있다. 이것들은 프로그램이 쓰기 가능하고 동시에 실행 가능한 메모리 페이지를 만드는 것을 금지하는 것이다. 어떤 시스템들은 비록 쓰기 권한이 제거되었더라도 쓰기 가능한 페이지가 실행 가능하게 바뀌는 것을 금지한다. 다른 시스템들은 페이지의 다중 매핑이 다른 권한들을 가질 수 있게 허용함으로써 '백도어'의 일종을 제공한다. W^X를 우회하기 위한 상대적으로 이식성 있는 방식은 파일을 모든 권한을 갖고 생성하며, 메모리에 파일을 두 번 매핑하는 것이다.

메타 레벨에서, 프로그램은 다른 곳에 저장된 데이터를 바꾸거나 다형성을 사용함으로써 아직 자신의 행동을 수정할 수 있다.

장점편집

  • 빠른 경로는 다른 반복적인 조건 분기들을 감소시키면서 프로그램의 실행 기간 동안 설정될 수 있다.
  • 자체 수정 코드는 알고리즘 효율성을 개선시킬 수 있다.

단점편집

자체 수정 코드는 소스 프로그램의 명령어들이 실행될 필요가 없는 것들이기 때문에 읽고 유지보수하기가 더 어렵다. 호출되는 함수 포인터의 서브루틴으로 이루어진 자체 수정 코드는 함수들의 이름이 나중에 식별되는 함수의 플레이스 홀더인 경우 암호같지 않을 수 있다.

자체 수정 코드는 테스트의 결과에 기반하여 순서를 대체하기 위해 플래그와 분기를 테스트하는 코드로 재작성될 수 있지만, 자체 수정 코드가 일반적으로 더 빨리 동작한다.

명령어 파이프라인을 가진 현대의 프로세서에서, 코드가 프로세서가 메모리에서 이미 파이프라인을 통해 읽은 명령어들을 수정한다면, 자신을 수정하는 것은 더 느려질 것이다. 이러한 프로세서에서 수정된 명령어들이 정확히 실행되는 것을 보장하는 유일한 방법은 파이프라인을 flush하고 많은 명령어들을 다시 읽는 것이다.

자체 수정 코드는 다음과 같은 환경에서는 아예 실행되지 않는다.

  • 엄격한 W^X 보안을 갖는 운영체제에서 실행되는 애플리케이션 소프트웨어는 쓰기가 허락된 페이지들에서 명령어들을 실행할 수 없다.

같이 보기편집

각주편집

  1. 〈The ALTER Statement〉. 《COBOL Language Reference》. Micro Focus. 
  2. Self-modifying Batch File by Lars Fosdal

외부 링크편집