오픈소스

바이너리만으로 오픈소스를 식별하는 기술 :: 바이너리 SCA의 원리와 정확도의 과제 by. 엘에스웨어 오석훈 이사

lsware 2026. 10. 21. 14:28

 

 

바이너리만으로 오픈소스를 식별하는 기술 — 바이너리 SCA의 원리와 정확도의 과제

 

소스 코드 없는 소프트웨어라는 사각지대

오늘날 기업이 운영하는 소프트웨어의 상당 부분은 소스 코드를 손에 쥐지 못한 상태로 돌아간다. 외부에서 구매한 상용 패키지, 협력사가 납품한 펌웨어, 이미 빌드가 끝나 배포된 실행 파일이 그렇다. 보안 담당자 앞에 놓인 것은 사람이 읽을 수 있는 코드가 아니라, 컴파일과 링크를 거친 바이너리 한 덩어리인 경우가 많다.

문제는 이 바이너리 안에 수십, 수백 개의 오픈소스 컴포넌트가 들어 있지만, 겉으로는 무엇이 어느 버전으로 포함되었는지 드러나지 않는다는 점이다. 자바 애플리케이션은 수많은 라이브러리를 아카이브(JAR)로 묶고, 네이티브 실행 파일은 압축·암호화 같은 라이브러리를 정적 링크로 코드에 합친다. 2014년 OpenSSL 하트블리드(CVE-2014-0160), 2021년 Log4j(CVE-2021-44228), 2024년 xz-utils 백도어(CVE-2024-3094)가 모두 보여 주듯, 깊숙이 포함된 컴포넌트 하나가 전사 시스템의 위험 요소가 된다. 이들 사건은 소프트웨어에 포함된 구성요소와 버전을 신속하게 파악하는 일이 얼마나 중요한지 보여 준다.

특히 소스 코드나 의존성 명세가 없는 완성품 바이너리에서는 이러한 확인이 더욱 어려워진다. 소스 코드나 빌드·배포 산출물에는 의존성 명세와 패키지 메타데이터, 빌드 기록 같은 부가 정보가 함께 따라오지만, 완성품 바이너리에 주어지는 단서는 파일 그 자체뿐이다. 참고할 맥락 없이 산출물만으로 '무엇이 들어 있는가'를 복원해야 하므로, 대응의 출발점을 세우는 일조차 소스가 있을 때와는 비교할 수 없이 어렵다. 이 사각지대를 메우는 기술이 바이너리 기반의 오픈소스 식별이다.

바이너리에서 오픈소스를 찾아내는 원리

소프트웨어 구성 분석(SCA, Software Composition Analysis)은 제품에 포함된 오픈소스 컴포넌트와 그 버전을 식별하는 기술이다. SCA 도구는 소스 코드의 의존성 명세, 패키지 메타데이터, 빌드 산출물 등 다양한 정보를 활용해 컴포넌트를 식별한다. 이 가운데 매니페스트나 락파일에 의존하는 방식은 Java의 pom.xml이나 Node.js의 package-lock.json 같은 명세를 읽어 목록을 만드는데, 이런 명세가 없는 완성품 바이너리에는 그대로 적용하기 어렵다.

바이너리 SCA는 완성된 산출물 그 자체에서 컴포넌트의 흔적을 복원한다. 식별 방식은 분석 대상에 따라 달라진다. JAR·WAR처럼 패키지 메타데이터나 매니페스트가 남아 있는 경우에는 그 정보를 직접 활용할 수 있다. 반면 네이티브 실행 파일(ELF·PE)이나 정적 링크된 라이브러리처럼 원본 정보가 사라진 경우에는, 바이너리에 남은 특징을 조합한 지문 기반 분석이 필요하다.

지문 기반 분석의 재료는 컴파일된 바이너리에 남은 원본 코드의 자취다. 함수와 심벌의 이름, 오류 메시지와 로그 문자열, 상수와 매직 넘버, 그리고 코드의 구조적 패턴이 그것이다. 이런 흔적을 토큰으로 추출한 뒤, 사전에 구축한 오픈소스 지문(fingerprint) 데이터베이스와 대조해 어떤 라이브러리가 포함되었는지 추정한다.

핵심은 단순 일치가 아니다. 다수의 라이브러리가 흔한 문자열이나 표준 함수 이름을 공유하기 때문에, 어디에나 등장하는 토큰은 식별 근거로서 가치가 낮다. 특정 오픈소스에서만 나타나는 토큰일수록 변별력이 높다. 그래서 정보 검색에서 쓰이는 TF-IDF 같은 가중치 기법을 적용해, 희소하고 특징적인 토큰에 더 큰 비중을 두어 후보를 좁힌다. 다만 희소하다고 해서 반드시 특정 오픈소스의 고유한 증거인 것은 아니다. 컴파일러가 삽입한 문자열이나 우연히 등장한 상수도 드물게 나타날 수 있으므로, 희소성만이 아니라 토큰의 출처와 안정성, 다른 증거와의 일관성을 함께 따져 변별력을 판단한다.

바이너리 식별이 어려운 이유

바이너리 식별이 소스 분석보다 어려운 이유는 컴파일과 링크가 원본의 형태를 바꾸고 단서를 지우기 때문이다.

심벌 제거(stripped)가 대표적이다. 배포용 바이너리는 용량과 보안을 이유로 디버그 정보와 내부 함수 이름을 덜어내는 경우가 많고, 이렇게 되면 이름 기반 식별의 단서가 크게 줄어든다. 다만 모든 이름이 사라지는 것은 아니다. 동적 링크에 필요한 내보내기 심벌처럼 일부 정보는 남기도 하므로, 남은 단서를 최대한 활용하는 설계가 필요하다. 컴파일러 최적화는 인라인화, 함수 병합, 명령어 재배치를 통해 코드의 형태 자체를 바꾼다.

정적 링크는 여러 라이브러리를 하나의 실행 파일로 합치기 때문에, 어디까지가 어느 컴포넌트인지 경계가 사라진다. 오픈소스끼리 코드를 공유하거나 한 라이브러리가 다른 라이브러리를 품는 경우도 흔해, 같은 토큰이 여러 후보를 동시에 가리키기도 한다. 게다가 정적 링크 과정에서 실제로 호출되는 오브젝트만 포함되거나 링크 단계의 최적화로 쓰이지 않는 코드가 제거되기도 해, 라이브러리의 일부만 바이너리에 남는 부분 포함(partial inclusion)이 생긴다. 이때 일부 증거만으로 전체가 들어 있다고 단정하기 쉽다.

그래서 바이너리 식별에서는 '컴포넌트가 존재하는가'와 '정확히 어느 버전인가'가 서로 다른 난이도의 문제가 된다. 존재는 소수의 특징 토큰으로도 추정할 수 있지만, 버전 확정에는 버전 간 차이를 구분할 수 있는 단서가 필요하지만, 이러한 단서는 컴파일러 최적화와 심벌 제거 과정에서 사라지거나 변형될 수 있다.

승부처는 식별 정확도다 — 오탐과 미탐

컴포넌트를 추출하고 지문과 대조하는 것까지는 여러 도구가 해낸다. 기술의 수준을 가르는 지점은 그 결과가 얼마나 정확한가, 즉 식별의 오탐과 미탐을 얼마나 줄이는가다. 여기서 말하는 오탐과 미탐은 취약점 판정이 아니라 컴포넌트 식별 그 자체의 문제다.

오탐(false positive)은 실제로 포함되지 않은 컴포넌트를 있다고 보는 경우다. 여러 라이브러리가 공유하는 흔한 토큰만 보고 OpenSSL이 들어 있다고 단정하는 식이다. 미탐(false negative)은 반대로, 정적 링크된 zlib이 분명히 들어 있는데도 심벌이 제거되어 식별하지 못하는 경우다. 버전 오인식은 더 미묘하다. OpenSSL 1.1.1k와 1.1.1w는 대부분의 토큰을 공유하므로, 버전을 가르는 소수의 단서를 놓치면 한 버전을 다른 버전으로 잘못 짚는다. 부분 포함은 라이브러리의 일부 함수만 들어 있는데 전체가 포함된 것으로 보아 과잉 식별로 이어진다.

이 오류들을 줄이는 길은 하나의 단서에 기대지 않는 것이다. 문자열, 심벌, 상수, 코드 구조처럼 성격이 다른 증거를 결합하고, 각 증거의 변별력에 따라 가중치를 부여해 종합 신뢰도를 산출한다. 이때 서로 다른 증거가 실은 같은 출처에서 비롯된 상관된 단서일 수 있으므로, 이를 중복으로 계산하지 않도록 설계해야 신뢰도가 부풀려지지 않는다. 그리고 단정적인 '포함/미포함' 대신 식별 근거와 신뢰도를 함께 제시해, 신뢰도가 낮은 추정과 확실한 식별을 구분한다. 존재 식별과 버전 확정을 분리해 다루는 것도 같은 맥락이다. 버전을 가를 근거가 충분하지 않으면 특정 버전을 억지로 확정하지 않고, 버전 범위나 '버전 미확정'으로 보고하는 편이 신뢰성 측면에서 안전하다.

식별 정확도를 결정하는 지문 데이터베이스

식별의 정확도는 대조 알고리즘만으로 결정되지 않는다. 더 근본적인 토대는 비교 대상이 되는 지문 데이터베이스의 품질이다.

지문은 오픈소스별로, 그리고 가능하면 버전별로 확보되어야 한다. 버전 지문이 없으면 존재는 알아도 버전은 좁히지 못한다. 데이터베이스가 다루는 오픈소스의 범위, 즉 커버리지가 좁으면 그 바깥의 컴포넌트는 처음부터 식별 대상에 들지 못해 미탐으로 남는다.

지문을 어디에서 추출하느냐도 중요하다. 소스 코드에서 뽑은 지문과 컴파일된 바이너리에서 뽑은 지문은 형태가 다르고, 같은 소스라도 컴파일러와 최적화 수준, 대상 아키텍처(x86-64, ARM 등)에 따라 만들어지는 바이너리가 달라진다. 바이너리 식별용 지문은 이런 변형을 함께 고려해 구축해야 현장의 다양한 산출물에 대응할 수 있다.

지문의 변별력도 관리 대상이다. 여러 라이브러리에 공통으로 나타나는 토큰과 특정 오픈소스에만 나타나는 토큰을 구분해, 공통 코드에서 비롯된 착시를 걸러내야 한다. 이를 위해서는 대규모 오픈소스 코퍼스에서 토큰의 출현 빈도를 측정하고, 흔한 토큰의 비중을 체계적으로 낮추는 작업이 필요하다.

결국 바이너리 식별은 알고리즘과 데이터가 함께 움직여야 하는 문제다. 특징적인 지문을 넓고 정밀하게 축적하고, 공통 코드의 잡음을 지속적으로 제거하며, 새로 등장하는 오픈소스와 버전을 꾸준히 반영할 때 식별 정확도가 유지된다.

맺으며 — 보이지 않는 오픈소스를 찾아내는 기술

소프트웨어 공급망이 복잡해질수록, 직접 만들지 않은 코드에 대한 책임은 오히려 커진다. 소스가 없는 바이너리에서 어떤 오픈소스가 어느 버전으로 포함되었는지 신뢰할 수 있게 식별하는 일은 그 책임을 감당하기 위한 첫 단계다.

바이너리에서 컴포넌트를 정확히 식별하는 것은 이후 모든 분석의 출발점이기도 하다. 무엇이 들어 있는지 식별하지 못하면, 그 뒤에 이어지는 취약점 판정이나 SBOM(소프트웨어 자재 명세서) 기반의 공급망 관리 역시 신뢰하기 어렵다. 정확한 식별이 뒷받침될 때 비로소 그 위의 분석이 의미를 가진다.

엘에스웨어는 이 과제를 바이너리 SCA 솔루션의 설계 원칙으로 삼고 있다. 컴파일과 링크를 거쳐 형태가 달라진 코드에서 오픈소스 컴포넌트를 식별하고, 복수의 증거로 식별 신뢰도를 관리하며, 특징적인 지문을 넓고 정밀하게 축적하는 것 — 보이지 않는 오픈소스를 찾아내는 이 기술을 심화하는 것이 지향점이다.

 

더보기

참고 자료

- NVD (National Vulnerability Database), https://nvd.nist.gov

- OSV (Open Source Vulnerabilities), https://osv.dev

- CISA, SBOM(Software Bill of Materials) 가이드, https://www.cisa.gov/sbom

- SPDX / CycloneDX SBOM 표준, https://spdx.dev / https://cyclonedx.org

- OWASP Software Component Verification Standard (SCVS), https://scvs.owasp.org

- Y. Duan et al., Identifying Open-Source License Violation and 1-day Security Risk at Large Scale (OSSPolice), ACM CCS 2017, https://taesoo.kim/pubs/2017/duan:osspolice.pdf

- Z. Yuan et al., B2SFinder: Detecting Open-Source Software Reuse in COTS Software, IEEE/ACM ASE 2019, https://www.semanticscholar.org/paper/f861d1cdf31a6250c4491cda4b1bbb00df8a0e86

- zynamics BinDiff (Google), 그래프 기반 바이너리 유사도·함수 비교 도구, https://github.com/google/bindiff

- CVE-2014-0160 (OpenSSL Heartbleed), https://www.cve.org

- CVE-2021-44228 (Apache Log4j), https://www.cve.org

- CVE-2024-3094 (XZ Utils / liblzma), https://www.cve.org