게시 날짜 : 14, 2023
애플리케이션 강화 마스터링: 난독화에 대한 심층 분석
난독화와 당신
애플리케이션 보안 이제는 널리 알려진 사실입니다. 모두가 그것이 필요하다는 것을 알고 있으며, 공격자들은 기본적인 보호 기능을 무력화하는 방법을 알고 있습니다. 보호되지 않은 애플리케이션을 배포하는 것은 선택 사항이 아닙니다. 모든 강력한 보호는 정적 분석 방지와 동적 공격 탐지, 이 두 가지 부분으로 구성됩니다. 정적 분석은 공격자에게 애플리케이션별 구현 세부 정보에 대한 고유한 통찰력을 제공하는 강력한 도구입니다. 애플리케이션의 가장 취약한 부분을 노출시켜 공격 속도를 높이거나 귀중한 비즈니스 IP를 훔치는 데 도움을 줍니다. 소스 코드뿐만 아니라 데이터도 난처 정적 분석 보호의 중요한 부분입니다. 악의적인 행위자의 속도를 늦추기 위해 코드와 데이터를 변형할 수 있는 몇 가지 방법을 소개합니다. 악의적인 행위자가 더 열심히 노력할수록 safe우리의 세상은 이렇게 될 수 있다.
보호를 민영화하세요
애플리케이션은 어떤 형태로든 난독화 없이 배포되어서는 안 되지만, 모든 난독화가 동일하게 생성되는 것은 아닙니다. 강력한 난독화의 두 가지 중요한 구성 요소는 개인 IP와 무작위성입니다. 현재 사용 가능한 오픈소스 난독화 도구는 애플리케이션 개발자뿐만 아니라 공격자에게도 유용할 수 있습니다. 이러한 오픈소스 난독화 도구가 제공하는 보호 수준은 엔터프라이즈급 애플리케이션을 안정적으로 보호하기에 충분히 강력하지 않습니다. 강력한 난독화는 소스 코드를 사람이 읽을 수 없고 자동화된 공격 도구로는 되돌릴 수 없는 형태로 변환해야 합니다. 공개적으로 사용 가능한 난독화 기술은 이러한 난독화를 무력화하는 데 있어 그 자체로 로제타 스톤과 같은 역할을 합니다. 공개적으로 접근이 불가능한 맞춤형 난독화 기술을 사용하면 Timmy Turner가 자신의 생성 AI 요정을 사용하여 앱의 난독화를 되돌리는 것을 막을 수 있습니다. 이러한 개인 알고리즘과 고유한 무작위성을 결합하여 사람이나 LLM이 난독화의 실타래를 풀 수 없도록 합니다.
PRNG의 반격
공격자는 동일한 애플리케이션의 여러 릴리스를 비교하여 상당한 정보를 얻을 수 있습니다. 앱 버전 간에 난독화를 변경하는 것은 절대적으로 필요합니다. 대부분의 의사 난수화 엔진은 시드를 사용하여 내재된 난수성을 유지하면서 예측 가능성을 강제하며, 난독화도 마찬가지입니다. 시드 값을 증가시키거나 감소시키는 것만으로도 입력 애플리케이션에 적용되는 난독화에 큰 영향을 미칩니다. 이는 필요에 따라 재현성을 제공하지만, 새 버전이 출시된 후 애플리케이션을 되돌릴 때 공격자는 처음부터 다시 시작해야 합니다. 이는 공격자가 진입로의 절반을 삽으로 치우고, 따뜻한 코코아를 마시며 휴식을 취한 후, 다시 돌아와 갓 내린 눈을 마주치는 것과 같습니다. 강력한 보안은 시간을 낭비하는 데 집중합니다. 리버스 엔지니어 시간이 흐르고 끊임없이 변화하는 풍경은 시간을 낭비하는 가장 좋은 방법 중 하나입니다.
난독화 난독화 난독화
개념적으로 난독화는 최적화의 반대 개념입니다. 컴파일러가 종종 여러 번의 최적화 단계를 거치는 것처럼, 우리도 여러 번의 난독화 단계를 거칩니다. 즉, 방금 추가된 난독화에 다시 난독화를 적용하는 것입니다. 그 결과, 읽기가 엄청나게 어려워집니다.

그림 1: 여러 번의 난독화 단계를 거치면 Ghidra와 같은 도구가 프로그램 제어 흐름을 해결하는 효과가 크게 감소합니다.
간단한 `add` 또는 `mov` 명령어 같은 것들은 순식간에 50개 또는 수백 개의 명령어로 늘어날 수 있습니다. `adrp add` 쌍을 제대로 난독화하면 자동화 도구(예: ghidra 또는 IDA)의 프로그램 제어 흐름 분석 능력을 저하시킬 수 있습니다. 겉보기에 사소해 보이는 난독화조차도 공격자가 `acceptUserCredentials` 함수 호출의 시작, 끝 또는 중간 지점을 찾는 것을 거의 불가능하게 만들 수 있습니다. 수백 가지의 복합적이고 다양한 난독화로 인해 잠재적 공격자는 코드가 어떻게 성공적으로 실행되는지 알아내려고 애쓰게 됩니다.
두드리고, 돌리고, 구성하세요
애플리케이션의 모든 메서드 또는 함수 호출이 민감한 것은 아니며, 일부 메서드 또는 함수 호출은 성능에 매우 민감할 수 있습니다. 전체 애플리케이션에 균일하게 난독화를 적용하는 것이 아무것도 하지 않는 것보다는 훨씬 낫지만, 이러한 전략은 런타임 성능에 상당한 영향을 미칠 수 있습니다. 보안 엔지니어에게 민감한 호출은 강력하게 난독화하고 성능 집약적인 호출은 약하게 난독화할 수 있는 기능을 제공하는 것은 세계적인 수준의 보호 도구를 제공하는 데 중요한 요소입니다. 애플리케이션에 삽입된 모든 동적 보호(가드)는 기본 소스 코드에 필요한 만큼의 난독화가 필요합니다. 이러한 활성 가드에 동일한 고보안 난독화 기술을 적용하면 가드를 보호하고 실행하는 데 도움이 되며, 공격자가 가드를 식별하고 제거하려는 노력을 늦출 수 있습니다. 구성 가능성은 모든 보안 중심 조직에 필수적이며, 보호 도구는 회사의 방향에 따라 가능한 한 빠르게 변경되어야 합니다. 앞서 언급했듯이 일부 난독화는 다르게 구축됩니다. 다음은 모든 고급 난독화 엔진이 제공하는 몇 가지 개념과 이를 통해 승인되지 않은 항공 교통을 차단하는 방법입니다.
계산된 제어 흐름
공격자는 애플리케이션 전체에서 함수 호출의 흐름만 살펴보아도 애플리케이션에 대한 상당한 양의 정보를 얻을 수 있습니다. 이를 방지하기 위해 난독화에는 계산된 제어 흐름이라는 기법이 사용됩니다. 계산된 제어 흐름은 디컴파일러가 함수 호출과 레이블 참조를 애플리케이션의 주 진입점에 연결하는 것을 차단합니다. 이는 세계적인 수준의 보호에 필수적인 요소입니다. 디컴파일러가 바이너리를 분석할 때, 한 함수가 다른 함수를 어떻게 호출하는지 확인하는 것은 종종 사소한 일입니다. 예를 들어 `b.eq 0x10055bc42` 및 `bl 0x100abcdef`와 같은 명령어를 살펴보겠습니다. 디컴파일러는 이러한 가상 메모리 위치에 무엇이 있는지 정확히 알고 있습니다. 이전 두 명령어의 경우, 함수 내의 레이블이거나 함수 호출 자체입니다. 가치 있는 디컴파일러라면 얽힌 함수 호출의 아름다운 거미줄을 렌더링하여 애플리케이션 전체의 실행 경로를 명확하게 보여줍니다. 어셈블리에서 "로그인 성공" 메시지가 사용된 위치를 확인하는 것과 같은 다른 기본적인 기법과 결합하면 공격자가 악용 가능한 코드를 쉽게 식별할 수 있습니다. 계산된 제어 흐름은 명확한 제어 흐름 호출 전에 삽입되는 일련의 수학 연산입니다. 이러한 수학 연산은 디컴파일러가 점프하는 정확한 상대 가상 주소를 알아내지 못하게 하여 제어 흐름 그래프를 생성하는 기능을 파괴합니다.

그림 2: 계산된 제어 흐름(오른쪽)은 디컴파일러가 점프하는 정확한 상대 가상 주소를 알 수 없게 하여 작동하는 제어 흐름 그래프(왼쪽)를 생성하는 기능을 파괴합니다.
제어 흐름 평탄화
계산된 제어 흐름(CFF)은 이미 디컴파일러에게 공포의 대상입니다. 더 악화시킨다면 정말 안타까운 일이겠죠. 논리적 제어 흐름을 방해하는 또 다른 전략은 제어 흐름 평탄화(CFF)입니다. 다양한 형태의 CFF가 있지만, 최근 추가된 넥서스(The Nexus)는 이 중요한 난독화 기법에 새로운 활력을 불어넣습니다. 우리 모두는 `main`에서 체스 게임을 작성해 본 적이 있겠지만, 단일 자기 참조 switch 문으로 전체 프로덕션 수준 애플리케이션을 작성하는 것은 어떨까요? 넥서스는 런타임 성능을 우선시하면서도 바로 그 역할을 수행합니다. CFF, 그 중요성, 그리고 작동 방식에 대한 정보는 넘쳐납니다. 이 난독화 기법이 공격자를 얼마나 어렵게 만드는지는 아무리 강조해도 지나치지 않으며, 어떤 애플리케이션도 이 기법 없이는 스테이징을 종료해서는 안 됩니다.
찹업
스테인리스 스틸 나이프 세트는 스테인리스 스틸 가위 없이는 완벽할 수 없으며, Chopup은 제어 흐름 난독화를 거의 같은 방식으로 보완합니다. 바이너리 파일 형식은 일반적으로 디스크 공간을 절약하기 위해 빽빽하게 압축되어 있습니다. 즉, 대부분의 관련 함수 호출이 서로 매우 가까이에 있습니다. 결과적으로, 디스어셈블된 바이너리는 관련 함수들을 함께 그룹화하고 거의 항상 바이너리 내에서 함수를 하나의 연속된 블록으로 포함하는 경향이 있습니다. 이는 읽기 너무 쉽고, 우리의 기어를 정말 짜증나게 합니다. Chopup은 이러한 상황을 바꿔줍니다. 바이너리가 메모리에서 함수 호출이 연속적이어야 한다는 기술적 제한은 없으며, 좋은 가위(유연한 접착제 포함)는 바이너리를 예상대로 작동하게 만들지만, 공격자는 논리적 제어 흐름을 찾기 위해 바이너리의 모든 구석으로 이동해야 합니다.

그림 3: Chopup은 공격자가 논리적 제어 흐름을 찾기 위해 바이너리의 모든 모서리로 이동하도록 강요합니다.
mgDaea 및 수리
코드 흐름 자체를 난독화하는 것이 중요한 보호 계층이지만, 기본 애플리케이션 데이터를 난독화하는 것도 마찬가지로 필요합니다. 손상과 복구는 정적 분석 보호의 혼돈 악과 합법적 선이며, 거의 항상 함께 진행됩니다. 데이터 조각이나 경우에 따라서는 코드를 손상시킨 다음 읽거나 실행하기 직전에 복구하면 정적 분석이 사실상 불가능해집니다. 손상은 지정된 데이터를 가비지 메모리처럼 보이는 것으로 변경합니다. 동일한 데이터가 복구 호출에 노출되면 사용 직전에 원래 형태로 되돌아갑니다. 해당 데이터는 범위를 벗어난 후 다시 손상될 수 있으며 다음에 함수가 호출될 때까지 사용되지 않습니다. 해당 API 키는 처음에는 바이너리에 "1#&at0d$*nd@@z"로 시작될 수 있지만, 사용되기 전에 수정될 것입니다.
심볼 이름 변경
소스 코드 난독화와 데이터 난독화를 적용하더라도 컴파일된 바이너리에는 여전히 소스 코드 심볼 이름이 남아 있습니다. 예를 들어, "acceptContinuousPayment"와 같은 함수 이름의 심볼 아티팩트는 해당 함수 구현에 대한 직접 링크를 쉽게 제공할 수 있습니다. 일단 발견되면 어셈블리를 변경하고 비용을 지불하지 않고도 프리미엄 기능에 액세스하는 것은 어렵지 않습니다. 포괄적인 심볼 이름 변경은 남아 있는 아티팩트를 숨기고 공격자가 조작할 수 있는 부분을 남겨두지 않습니다. 보호된 바이너리에서 민감한 기능에 대한 힌트나 단서를 제거하면 공격자가 정적 분석을 완전히 포기하게 될 수 있습니다.

그림 4: 포괄적인 심볼 이름 변경은 남아 있는 아티팩트를 숨기고 공격자가 조작할 수 있는 아무것도 남기지 않습니다.
끝
이는 강력한 난독화 기법과 방법론의 예시일 뿐이며, 결코 완전한 목록은 아닙니다. 애플리케이션을 보호하지 않은 상태로 방치하면 누구나 고유 IP에 대한 완전한 가시성을 확보하고 공격자에게 비즈니스 로직을 투명하게 파악할 수 있는 창을 제공합니다. 생성적 AI와 소스 코드 부조종 기술이 주류로 급부상하면서 사람들이 코드를 작성하고, 어떤 사람들은 코드를 역추적하는 것이 훨씬 쉬워졌습니다. 이처럼 빠르게 변화하는 환경에서는 강력한 보안 솔루션 구현에 대한 조직 전체의 헌신만이 유일하게 실현 가능한 길입니다. 개발자의 노고가 헛되이 흘러가지 않도록 하세요.
IDC가 중요성에 대해 어떻게 생각하는지 읽어보세요. 난독화 및 변조 방지 당신의 일부로 DevSecOps 연습.