자체 수정 코드
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
자체 수정 코드는 프로그램이 실행 중에 자체의 기계어 코드를 수정하는 기술을 의미한다. 초기 컴퓨터 과학에서 메모리 사용량 절약, 성능 향상 등을 위해 사용되었으며, 어셈블리 언어에서 구현이 용이하다. 고급 언어에서는 `eval` 함수나 Lisp 매크로 등을 통해 런타임 코드 생성을 지원하며, 상태 의존 루프 최적화, 코드 보호, 난독화, 그리고 운영체제의 자체 최적화 등에 활용된다. 그러나, 이해와 유지보수가 어렵고, 현대 프로세서의 명령어 파이프라인 및 보안 기능과 충돌할 수 있다는 단점이 있다. 또한, 악성 코드 변조에 취약하여 W^X와 같은 보안 기능을 통해 보호하려는 노력이 이루어지고 있다.
IBM SSEC는 1948년 1월에 시연되었는데, 명령어 수정과 데이터 처리를 동일하게 취급하는 기능을 갖추고 있었다. 그러나 이 기능은 실제로 거의 사용되지 않았다. 컴퓨터 초창기에는 제한된 메모리 사용을 줄이거나 성능을 향상시키기 위해, 또는 두 가지 모두를 위해 자체 수정 코드가 자주 사용되었다. 또한 명령어 집합이 단순한 분기 또는 건너뛰기 명령어만 제공하여 제어 흐름을 변경할 때 서브루틴 호출 및 반환을 구현하는 데에도 사용되었다. 이러한 사용법은 적어도 이론적으로는 특정 초 RISC 아키텍처에서 여전히 유효하다. 예를 들어 단일 명령어 집합 컴퓨터를 참조하라. 도널드 커누스의 MIX 아키텍처 역시 서브루틴 호출을 구현하기 위해 자체 수정 코드를 사용했다.
자체 수정 코드는 프로그래밍 언어와 이것의 포인터 또는 동적 컴파일러나 인터프리터 '엔진'에 대한 지원에 의존하는 다양한 방식으로 구현될 수 있다.
자체 수정 코드는 다양한 목적으로 사용될 수 있다.
2. 역사
3. 언어별 적용
3. 1. 어셈블리 언어
어셈블리 언어에서 자체 수정 코드는 구현하기가 비교적 간단하다. 명령어 덮어쓰기나 메모리 내 동적 명령어 생성을 통해 가능하다.
다음은 IBM/360 어셈블러에서 명령어 덮어쓰기를 사용한 예시이다. 이 코드는 처음 실행될 때 입력 파일을 열고, 이후 실행에서는 이 과정을 건너뛴다.
```assembly
SUBRTN NOP OPENED 처음인가?
OI SUBRTN+1,X'F0' 예, NOP를 무조건 분기로 변경(47F0...)
OPEN INPUT 그리고 처음 실행이므로 INPUT 파일을 연다
OPENED GET INPUT 여기에 정상적인 처리가 재개된다.
```
위 코드에서 `NOP` 명령어는 무조건 분기 명령어(`X'F0'`)로 덮어씌워진다. 매번 플래그를 검사하는 대신 무조건 분기를 사용하면 명령어 경로 길이를 줄이고, 비교 명령어보다 약간 더 빠르게 실행할 수 있다.
최신 운영 체제에서는 프로그램이 보호된 저장소에 상주하는 경우가 많아 명령어 덮어쓰기가 불가능할 수 있다. 이 경우, 서브루틴을 가리키는 포인터를 변경하는 방식을 사용한다. 이 포인터는 동적 저장소에 위치하며, 처음 실행 후 포인터를 변경하여 `OPEN`을 건너뛸 수 있다.
다음은 Zilog Z80 어셈블리 언어에서 자체 수정 코드를 사용한 예시이다. 이 코드는 레지스터 `B`의 값을 0부터 5까지 증가시킨다. 각 반복마다 `CP` 비교 명령어가 수정된다.
```assembly
;==========
ORG 0H
CALL FUNC00
HALT
;==========
FUNC00:
LD A,6
LD HL,label01+1
LD B,(HL)
label00:
INC B
LD (HL),B
label01:
CP $0
JP NZ,label00
RET
;==========
```
자체 수정 코드는 인텔 8080과 같이 명령어 집합에 제약이 있는 경우에도 유용하게 사용될 수 있다. 예를 들어, 8080 명령어 집합에서는 레지스터에 지정된 입력 포트에서 바이트를 입력하는 명령어가 없다. 자체 수정 코드를 사용하면 레지스터의 내용을 명령어의 두 번째 바이트에 저장한 다음 수정된 명령어를 실행하여 원하는 작업을 수행할 수 있다.
IBM SSEC는 1948년 1월에 시연되었으며, 명령어를 데이터처럼 수정할 수 있는 기능을 가지고 있었다. 하지만 이 기능은 실제로 거의 사용되지 않았다.
3. 2. 고급 언어
COBOL, 클리퍼와 같은 일부 컴파일 언어는 명시적으로 자체 수정 코드를 허용한다.[3] 펄, 파이썬, 자바스크립트와 같은 인터프리터 언어는 `eval` 함수를 통해 프로그램이 실행 중에 새로운 코드를 만들 수 있게 허용하지만, 기존 코드를 바꾸는 것은 허용하지 않는 경우가 많다. Lisp 매크로는 런타임 코드 생성을 허용한다.
4. 활용
컴퓨터 초창기에는 메모리 절약, 성능 향상, 또는 서브루틴 호출/반환 구현을 위해 자체 수정 코드가 자주 사용되었다. 도널드 커누스의 MIX 아키텍처도 서브루틴 호출에 자체 수정 코드를 사용했다.
리눅스 커널은 부팅 시 환경에 맞춘 자체 수정, 디버깅용 코드 삽입을 통한 최적화, 성능 분석 등에 자체 수정 코드를 활용한다.
4. 1. 성능 최적화
상태 의존 루프의 반자동 최적화는 조건 분기를 줄여 실행 속도를 향상시킬 수 있다. 예를 들어, 다음 의사 코드를 보자.
```
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
}
```
여기서 ''increase''는 A를 1 증가시키는 연산 코드이고, STATE가 변경되면 ''decrease'' (A를 1 감소시키는 연산 코드)로 바뀐다. 연산 코드의 두 상태 교체는 '주소의 xor 변수를 "opcodeOf(Inc) xor opcodeOf(dec)" 값으로' 쉽게 작성할 수 있다. 이 방법을 선택하는 것은 N의 값과 상태 변화 빈도에 의존해야 한다.
런타임 코드 생성 또는 특수화는 특정 상황에 맞게 알고리즘을 최적화할 수 있다. 예를 들어, 실시간 그래픽스에서는 렌더링할 객체의 특성에 따라 코드를 동적으로 생성하여 성능을 향상시킬 수 있다.
4. 2. 코드 보호 및 난독화
자체 수정 코드는 리버스 엔지니어링과 소프트웨어 크래킹으로부터 보호하기 위해 사용될 수 있는데, 그 이유는 분석하기가 더 복잡하기 때문이다.[1] 1980년대 IBM PC 호환 기종 및 애플 II와 같은 시스템용 디스크 기반 프로그램에서 복사 방지 지침을 숨기는 데 사용되었다.[1] 예를 들어, IBM PC에서 플로피 디스크 드라이브 접근 명령어 `int 0x13`은 실행 파일 이미지에 나타나지 않지만, 프로그램 실행 시작 후 실행 파일의 메모리 이미지에 기록되었다.[1]
컴퓨터 바이러스 및 일부 셸코드와 같이 자신의 존재를 숨기려는 프로그램에서도 자체 수정 코드가 사용되기도 한다.[1] 이러한 바이러스 및 셸코드는 대부분 다형성 코드와 함께 자체 수정 코드를 사용한다.[1] 버퍼 오버플로와 같은 특정 공격에도 실행 중인 코드 조각을 수정하는 방법이 사용된다.[1]
4. 3. 기타
객체의 인라인 상태 변경, 클로저 시뮬레이션, 서브루틴 주소 호출 패치, 유전 프로그래밍과 같은 진화 컴퓨팅 시스템, 운영체제의 자체 최적화 (예: 리눅스 커널), JIT 컴파일러 등에 사용된다.[1][2]
5. 장점 및 단점
자기 수정 코드는 장점과 단점을 모두 가지고 있다.
장점
단점
- 이해하기 어렵고 유지보수가 어렵다.
- 최신 프로세서에서는 명령어 파이프라인 때문에 오히려 속도가 느려질 수 있다.[3]
- 엄격한 W^X 보안, 하버드 아키텍처 마이크로컨트롤러, 다중 스레드 환경 등 일부 환경에서는 사용할 수 없다.[4]
- 명령어 캐시 문제로 속도 저하가 발생할 수 있다.
- 프로세서가 수정 전 코드를 실행할 수 있다.
초기 컴퓨터에서는 메모리 절약을 위해 사용되었고, 도널드 커누스의 MIX 아키텍처처럼 서브루틴 호출을 위해 사용되기도 했다. 일부에서는 미래 인공지능의 핵심 기능이 될 것이라는 주장도 있다. 하지만, 다른 유효한 선택지가 있다면 자기 수정 코드는 권장되지 않는다.
5. 1. 장점
빠른 경로 설정은 프로그램 실행 중에 반복적인 조건 분기를 줄여 실행 속도를 향상시킬 수 있다. 자체 수정 코드는 알고리즘 효율성을 개선하는 데 사용될 수 있다. 예를 들어, 상태에 따라 다른 연산을 수행하는 반복문에서 자체 수정 코드를 활용하면 조건 분기 없이 필요한 연산만 수행하도록 코드를 최적화할 수 있다.가상 코드 예시를 통해 자체 수정 코드가 어떻게 알고리즘 효율성을 향상시키는지 살펴보자.
'''예시 1:'''
```text
repeat N 회 {
if STATE == 1
A = A + 1
else
A = A - 1
A에 관해 처리를 한다
}
```
위 코드는 STATE 값에 따라 A 값을 증가시키거나 감소시키는 연산을 N회 반복한다. 이 경우, 자체 수정 코드를 적용하면 다음과 같이 코드를 수정할 수 있다.
```text
repeat N 회 {
A = A '''+''' 1
A에 관해 처리를 한다
}
when STATE가 변화했을 때 {
상기의 '''+''' 명령을 '''-'''명령으로 수정한다.
}
```
이렇게 하면 STATE 값이 변할 때마다 '+' 명령을 '-' 명령으로 수정하여 조건 분기 없이 반복문을 실행할 수 있다. 단, 이 기법의 효과는 N(루프 반복 횟수)이 크고 상태 변화가 빈번하지 않은 경우에 더 크다.
5. 2. 단점
자체 수정 코드는 소스 프로그램의 명령어들이 실행될 필요가 없는 것들이기 때문에 읽고 유지보수하기가 더 어렵다. 호출되는 함수 포인터의 서브루틴으로 이루어진 자체 수정 코드는 함수들의 이름이 나중에 식별되는 함수의 자리 표시자인 경우 이해하기 어렵지 않을 수 있다.자체 수정 코드는 플래그를 테스트하고 테스트 결과에 따라 대체 시퀀스로 분기하는 코드로 다시 작성할 수 있지만, 자체 수정 코드가 일반적으로 더 빠르게 실행된다.[2]
명령어 파이프라인이 있는 최신 프로세서에서, 프로세서가 이미 메모리에서 파이프라인으로 읽어 들인 명령어를 자체적으로 자주 수정하는 코드는 더 느리게 실행될 수 있다. 이러한 프로세서에서 수정된 명령어가 올바르게 실행되도록 보장하는 유일한 방법은 파이프라인을 플러시하고 많은 명령어를 다시 읽는 것이다.[3]
자체 수정 코드는 다음과 같은 일부 환경에서는 전혀 사용할 수 없다.[4]
- 엄격한 W^X 보안이 있는 운영 체제에서 실행되는 응용 프로그램 소프트웨어는 쓰기가 허용된 페이지에서 명령어를 실행할 수 없으며, 운영 체제만 메모리에 명령어를 쓰고 나중에 해당 명령어를 실행할 수 있다.
- 많은 하버드 아키텍처 마이크로컨트롤러는 읽기/쓰기 메모리에서 명령어를 실행할 수 없고, 쓰기가 불가능한 메모리, ROM 또는 자체 프로그래밍이 불가능한 플래시 메모리에서만 명령어를 실행할 수 있다.
- 다중 스레드 응용 프로그램은 자체 수정 코드의 동일한 섹션을 실행하는 여러 스레드를 가질 수 있으며, 이로 인해 계산 오류 및 응용 프로그램 오류가 발생할 수 있다.
실행 코드를 수정하면, 명령어 캐시에 보관되어 있던 명령을 사용할 수 없게 되므로, 메모리에서 캐시로 다시 로드해야 하여 속도가 느려진다.
최근 프로세서는 명령을 실행 전에 내부로 가져오기 때문에, 프로그램 카운터에 가까운 부분을 수정하면 프로세서가 이를 알아차리지 못할 가능성이 있으며, 수정 전의 코드를 실행해버릴 수 있다. 이에 대해서는 명령어 프리페치 큐를 참조하라.
6. 보안 문제
자체 수정 코드는 바이러스나 셸코드와 같이 악의적인 프로그램이 자신의 존재를 숨기기 위해 사용될 수 있으며, 버퍼 오버플로와 같은 공격에도 이용될 수 있다.[1] 이러한 보안 문제로 인해 주요 운영체제들은 자체 수정 코드의 취약점을 제거하기 위해 노력하고 있다.[1]
자체 수정 코드 악용을 방지하기 위해 주요 운영체제에서 사용되는 메커니즘은 W^X ("쓰기 XOR 실행") 기능이다. W^X는 프로그램이 쓰기와 실행이 동시에 가능한 메모리 페이지를 생성하는 것을 금지한다.[1] 일부 시스템에서는 쓰기 권한이 제거된 후에도 쓰기 가능했던 페이지가 실행 가능하게 바뀌는 것을 방지하기도 한다.[1]
OpenBSD와 같은 운영체제는 W^X 기능을 통해 메모리 페이지에 대해 쓰기 또는 실행 권한 중 하나만 허용하며, 동시에 두 권한을 모두 허용하지 않는다. 따라서 OpenBSD에서는 자체 수정 코드가 정상적으로 동작하지 않는다.[1]
참조
[1]
웹사이트
Self Modifying Code in Linux Kernel - What, Where and How
https://talk.telemat[...]
2020-01-30
[2]
웹사이트
Linux Kernel Alternatives
https://grsecurity.n[...]
2022-11-27
[3]
서적
COBOL Language Reference
http://documentation[...]
Micro Focus
[4]
웹사이트
Self-modifying Batch File
https://web.archive.[...]
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com