
macOS 앱 번들, plist 파일 및 launchd 프로세스 동작에 대한 교육용 심층 분석으로, 페이로드를 .app 파일로 패키징하고 프로세스 트리 모니터링을 회피하는 공격적 보안 노트를 포함합니다.
Linux나 Windows에서 macOS로 전환하는 것은 낯선 새 땅을 걷는 것처럼 느껴질 수 있습니다. Linux는 오픈소스이고 Windows는 문서화가 잘 되어 있고 매우 인기가 있지만(macOS는 정확히 그 둘 다 아닙니다), macOS는 때때로 도전적으로 느껴질 수 있습니다. 이 블로그 게시물에서는 macOS에서 처음으로 눈에 띌 수 있는 몇 가지 것들 - 앱, 어디에나 있는 앱! - 에 대해 논의하려고 합니다.
Windows나 Linux 배경에서 온 경우, 앱이라는 개념이 이상하게 보일 수 있습니다. 우리 모두 스레드가 "실행 단위"이고 프로세스는 고유한 주소 공간을 가진 스레드의 컨테이너라는 것을 알고 있습니다. 그 이상이 뭐가 있을까요? 음, 프로세스는 단일 파일로 배포되는 경우가 거의 없습니다. Windows와 Linux 모두에서 코드가 작동하는 데 필요한 많은 것들이 있으며, 그중 일부는 다음과 같습니다:
.dll, .so). 예를 들어, C 런타임 라이브러리(Windows의 msvcr<version>.dll, libc-<version>.so) 및 기타 의존성.PE라는 형식으로 제공되며 디렉터리를 가지고 있습니다. 그중 하나는 리소스 디렉터리로, 리소스(이미지, 문자열 등)를 포함할 수 있습니다(여기에 어느 정도 문서화되어 있습니다). 물론 리소스는 디스크에서 동적으로 로드될 수도 있습니다.PE 파일 자체(여기 참조) 또는 카탈로그 파일(즉, 외부)에 존재할 수 있습니다.xml, ini, json)과 Windows 레지스트리로 나뉩니다.음, macOS는 앱 번들(Application Bundles)에 큰 중점을 둡니다. 그 아이디어는 프로그램 실행에 필요한 (거의) 모든 것 - 리소스, 지역화 정보 등 - 을 디렉터리 구조에 패키징하는 것입니다. 물론 모든 것을 깔끔하게 패키징할 수 있는 것은 아닙니다(예를 들어 C 런타임 라이브러리). 하지만 그럼에도 불구하고 모든 것이 함께 번들되어 있다는 것을 의미합니다. 거대한 레지스트리를 탐색하거나 난해한 구성 파일 위치를 찾기 위해 매뉴얼 페이지를 읽을 필요가 없습니다. 앱 번들은 단지 .app으로 끝나는 디렉터리입니다. UI가 .app 확장자(그리고 그것이 디렉터리라는 사실)를 숨기긴 하지만요.
공격자의 관점에서 이것은 흥미롭습니다. Application Bundle은 임의의 아이콘을 가질 수 있고 .app 확장자를 숨기기 때문에, 악성코드 전달은 의심하지 않는 사용자가 그런 앱을 클릭하도록 속임으로써 이루어질 수 있습니다. 예를 들어, PDF 아이콘이 있는 Resume.app 파일을 생각해 보세요.
Application Bundle의 디렉터리 구조는 내장된 Calculator 앱으로 쉽게 살펴볼 수 있습니다:
jbo@McJbo ~ % cd /System/Applications/Calculator.app
jbo@McJbo Calculator.app % ll
total 0
drwxr-xr-x 3 root wheel 96 Mar 17 21:34 .
drwxr-xr-x 43 root wheel 1376 Mar 17 21:34 ..
drwxr-xr-x 9 root wheel 288 Mar 17 21:34 Contents
jbo@McJbo Calculator.app % cd Contents
jbo@McJbo Contents % ll
total 16
drwxr-xr-x 9 root wheel 288 Mar 17 21:34 .
drwxr-xr-x 3 root wheel 96 Mar 17 21:34 ..
-rw-r--r-- 1 root wheel 2147 Mar 17 21:34 Info.plist
drwxr-xr-x 3 root wheel 96 Mar 17 21:34 MacOS
-rw-r--r-- 204 root wheel 8 Mar 17 21:34 PkgInfo
drwxr-xr-x 4 root wheel 128 Mar 17 21:34 PlugIns
drwxr-xr-x 54 root wheel 1728 Mar 17 21:34 Resources
drwxr-xr-x 3 root wheel 96 Mar 17 21:34 _CodeSignature
-rw-r--r-- 1 root wheel 461 Mar 17 21:34 version.plist
jbo@McJbo Contents % cd MacOS
jbo@McJbo MacOS % ll
total 344
drwxr-xr-x 3 root wheel 96 Mar 17 21:34 .
drwxr-xr-x 9 root wheel 288 Mar 17 21:34 ..
-rwxr-xr-x 1 root wheel 540912 Mar 17 21:34 Calculator
jbo@McJbo MacOS %
보시다시피, Calculator.app은 디렉터리입니다. 그 아래에는 단일 항목 - Contents라는 또 다른 디렉터리 - 이 있습니다.
Contents 아래에는 여러 항목이 있습니다:
Info.plist - 앱에 대한 메타데이터를 포함합니다. 이에 대해서는 나중에 더 자세히 다룹니다.MacOS - 앱의 기본 실행 파일을 포함합니다(세 번째 디렉터리 목록에서 볼 수 있듯이).PkgInfo - 선택 사항. 패키지 정보를 포함하는 이진 파일입니다.PlugIns - 선택 사항. 앱의 플러그인을 포함할 수 있는 디렉터리입니다. Calculator에는 두 개가 있습니다. 하나는 "Basic and Scientific"용이고 다른 하나는 "Hexadecimal"용입니다(왜 그렇게 나누었는지 정말 모르겠고, 관심도 없습니다).Resources - 선택 사항. 이름에서 알 수 있듯이 리소스를 포함합니다. 아이콘이 있는 .icns 파일과 지역화와 관련된 .lprroj 접미사가 있는 디렉터리 등 여러 항목을 찾을 수 있습니다._CodeSignature - 선택 사항. 이름에서 알 수 있듯이 코드 서명 정보를 포함합니다.version.plist - 선택 사항, 버전 정보를 포함합니다.공식적으로 필수인 항목은 거의 없다는 점에 유의하세요. 사실, 우리는 아무것도 컴파일하지 않고도 우리만의 첫 번째 앱을 만들 수 있습니다!
하지만 먼저 Info.plist 파일에 대해 논의해야 합니다.
macOS를 더 많이 살펴볼수록 이런 이상한 파일들을 더 많이 발견하게 될 것입니다. 이것들은 그저 화려하게 포장된 구성 파일에 불과합니다.
항상 .plist 확장자를 가지며, 이는 공식 이름인 Property list 파일의 짧은 표현입니다.
안타깝게도 Apple이 유지 관리하는 3가지 서로 다른 plist 형식이 있습니다:
xml 형식.json 형식.bplist 텍스트가 매직으로 표시됩니다.다행히도 모든 형식을 지원하는 plutil이라는 유틸리티가 있습니다. plist 파일을 출력하려면 plutil -p를 사용하면 됩니다. 예를 들어:
jbo@McJbo Contents % plutil -p Info.plist | head -n 20
{
"BuildMachineOSBuild" => "22A380007"
"CFBundleDevelopmentRegion" => "English"
"CFBundleExecutable" => "Calculator"
"CFBundleGetInfoString" => "10.14, Copyright © 2000-2018, Apple Inc."
"CFBundleHelpBookFolder" => "Calculator.help"
"CFBundleHelpBookName" => "com.apple.Calculator.help"
"CFBundleIconFile" => "AppIcon"
"CFBundleIconName" => "AppIcon"
"CFBundleIdentifier" => "com.apple.calculator"
"CFBundleInfoDictionaryVersion" => "6.0"
"CFBundleName" => "Calculator"
"CFBundlePackageType" => "APPL"
"CFBundleShortVersionString" => "10.16"
"CFBundleSignature" => "????"
"CFBundleSupportedPlatforms" => [
0 => "MacOSX"
]
"CFBundleVersion" => "223"
"CTIgnoreUserFonts" => 1
jbo@McJbo Contents %
plutil에는 변환 기능도 내장되어 있습니다. 지금은 그것들을 시연하지는 않겠습니다.
Apple은 앱의 Info.plist에서 몇 가지 요구 사항을 문서화하고 있지만, 실제로 필수인 필드는 거의 없습니다. 몇 가지 흥미로운 필드는 다음과 같습니다:
CFBundleExecutable - 기본 실행 파일의 이름으로, MacOS 디렉터리 아래에 있을 것으로 예상됩니다.CFBundleIconFile - 아이콘 파일의 이름. 선택 사항.CFBundleIdentifier - 앱 번들의 식별자. Apple은 역DNS 표기법(예: com.apple.calculator)을 사용할 것을 권장합니다.CFBundleName - 번들 이름.이를 염두에 두고, 우리는 코딩조차 하지 않고 우리만의 첫 번째 멋진 앱을 만들 수 있습니다! 한번 보세요:
#!/bin/zsh
# Create Bundle structure
mkdir -p ./MyApp.app/Contents/MacOS
# Create main executable file - a shell script in our case
cat <<EOF > ./MyApp.app/Contents/MacOS/MyApp
#!/bin/zsh
osascript -e 'tell app "Finder" to display dialog "Hello from MyApp!"'
EOF
chmod +x ./MyApp.app/Contents/MacOS/MyApp
# Create the Info.plist file
cat <<EOF > ./MyApp.app/Contents/Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>MyApp</string>
<key>CFBundleIdentifier</key>
<string>com.myapp</string>
<key>CFBundleName</key>
<string>MyApp</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
</dict>
</plist>
EOF
이렇게 하면 MyApp이라는 새 앱이 생성됩니다. 클릭하면 zsh 스크립트(MyApp이라고 함)가 실행될 뿐입니다.
osascript를 사용한다는 점에 유의하세요. 이는 AppleScript 인터프리터이자 완전한 골칫거리이지만, 단지 Hello from MyApp!이라는 대화상자를 표시할 것입니다. 물론 그 zsh 셸 파일에 임의의 코드를 작성할 수 있습니다. 최신 macOS 버전에서는 zsh가 osascript를 호출하도록 허용할지 묻는 프롬프트가 표시될 수 있습니다. 왜 그런 일이 발생하는지는 향후 게시물에서 논의하겠지만, 첫 번째 승인 후에는 묻지 않는다는 점에 유의하세요.
앱을 실행한다는 것은 여전히 프로세스가 생성된다는 뜻입니다. 당연히 CFBundleExecutable이 가리키는 프로세스입니다. 그 프로세스는 무엇 아래에서 실행될까요? 확인해 봅시다:
jbo@McJbo ~ % open -a Calculator
jbo@McJbo ~ % ps -A -j | grep Calculator | grep -v grep
jbo 12067 1 12067 0 1 S ?? 0:00.41 /System/Applications/Calculator.app/Contents/MacOS/Calculator
jbo@McJbo ~ %
open 명령은 Calculator 앱을 더블 클릭하는 것과 동일합니다. 꽤 흥미로우며 곧 논의하겠습니다.
이제 Calculator가 실행 중이므로 ps를 사용하여 실행 중인 프로세스를 표시합니다. 예상대로 /System/Applications/Calculator.app/Contents/MacOS/Calculator가 실행 중인 프로세스이며 PID는 12067입니다. 그러나 부모 프로세스 ID는 1입니다!
macOS에서 프로세스 ID 1은 /sbin/launchd입니다. 이는 "시스템 전체 및 사용자별 데몬/에이전트 관리자"입니다. Windows 배경에서 왔다면 services.exe로, Linux에 익숙하다면 systemd로 상상할 수 있습니다. 서비스(macOS에서는 Launch Agents 및 Launch Daemons라고 함)를 관리하는 것 외에도 모든 앱의 부모이기도 하며, 이는 macOS에서 의미 있는 프로세스 트리를 얻는 것이 어려운 이유 중 하나입니다.
흥미롭게도 /System/Applications/Calculator.app/Contents/MacOS/Calculator를 직접 호출하여 Calculator 앱을 단순한 프로세스로 실행할 수도 있지만, 그 경우에는 launchd의 자식이 아닙니다:
jbo@McJbo ~ % /System/Applications/Calculator.app/Contents/MacOS/Calculator &
[1] 12502
jbo@McJbo ~ % 2023-04-04 15:54:22.987 Calculator[12502:1297087] XType: XTFontStaticRegistry is enabled by Info.plist.
jbo@McJbo ~ % ps -A -j | grep Calculator | grep -v grep
jbo 12502 951 12502 0 1 SN s000 0:00.31 /System/Applications/Calculator.app/Contents/MacOS/Calculator
jbo@McJbo ~ % echo $$
951
jbo@McJbo ~ %
실제로 Calculator는 정상적으로 실행되지만, 이제는 우리 터미널의 자식 프로세스입니다.
우리가 본 동작에서 주목할 점은 공격자가 이를 다른 목적으로 사용할 수 있다는 것입니다. 예를 들어, 공격자는 보안 도구를 회피하기 위해 프로세스 트리에서 쉽게 벗어날 수 있으며, 로직 취약점을 악용할 수도 있습니다(시간이 있다면 제 macOS 샌드박스 탈출 취약점 분석 글을 읽어 보세요).
launchd에는 다른 흥미로운 역할도 있습니다(LaunchAgents 및 LaunchDaemons에 대해 읽어 보세요). 하지만 지금은 이에 대해 논의하지 않겠습니다.
여기서 한 가지 유형의 번들을 보여드렸지만 더 많은 유형이 있습니다(완전한 목록은 아닙니다):
.app - 이미 본 것으로, 앱의 컨테이너인 Application Bundles입니다..framework - 로드 가능한 번들인 Frameworks를 포함합니다. 네, macOS에서는 로드 가능한 파일(.dylib)에 dlopen을 호출하거나 전체 프레임워크 번들(리소스, 코드 등 포함)을 로드할 수 있습니다..kext - 커널 확장을 포함합니다. macOS 커널에 대한 로드 가능한 번들이지만, 최근 OS 버전에서 Apple은 커널 확장 수를 줄이기 위해 정말 많은 노력을 기울이고 있습니다..plugin - 이름에서 알 수 있듯이 플러그인을 위한 컨테이너입니다.이 글은 macOS 연구로 전환하는 사람들을 돕기 위한 일련의 짧은 블로그 게시물 중 첫 번째입니다.
공격 보안 관점에서 가장 먼저 눈에 띈 것은 페이로드를 멋진 앱 구조로 패키징하는 것이 얼마나 쉬운지였습니다.
하지만 상황은 그렇게 단순하지 않습니다. 다음 몇 개의 블로그 게시물에서는 macOS의 많은 보안 기능 때문에 코드 실행을 얻는 것이 그렇게 간단하지 않다는 것을 발견하게 될 것입니다.
계속 지켜봐 주세요!
Jonathan Bar Or (https://jonathanbaror.com)