내 휴가를 WSC 리버스 엔지니어링으로 망친 이야기
(blog.es3n1n.eu)- 서울 여행 중 만든 defendnot은 Windows Security Center(WSC) 서비스 API를 직접 호출해 Windows Defender를 비활성화하려는 도구였고, 며칠간의 리버스 엔지니어링으로 이어짐
- 이전 도구 no-defender는 기존 백신 코드를 이용해 WSC에 다른 백신이 있다고 등록하는 방식이었으며, 약 1.5k GitHub 스타를 얻은 뒤 백신 개발사의 DMCA 요청으로 삭제됨
- 처음에는 WSC의 COM API 호출을 약 1시간 만에 재현했지만, WSC가 호출 프로세스를 검증하면서 일반 프로세스에서는 접근이 거부됨
- 분석 결과 WSC는 WinDefend SID, 관리자 권한, 서명, PE의
DllCharacteristics중ForceIntegrity플래그를 확인했고, 최종 구현에는Taskmgr.exe가 사용됨 - arm64 MacBook, 미국 원격 PC, Parsec 210ms 지연, Shadow.tech VM까지 얽힌 환경에서
ctx.bin경로 오류와 autorun 전원 조건 문제를 고치며 구현을 마무리함
defendnot을 만들게 된 배경
- defendnot은 Windows Security Center 서비스 API를 직접 사용해 Windows Defender를 비활성화하는 도구임
- 구현 과정의 핵심은 WSC 호출 자체보다, 이를 막는 검증 조건과 불편한 작업 환경을 하나씩 우회하는 데 있었음
- 더 자세한 WSC 기술 문서는 나중에 다른 사람이 공개할 예정임
1년 전 no-defender와 DMCA
- 약 1년 전 공개한 no-defender는 Windows Defender를 끄기 위해 백신용 Windows API를 사용했음
- 이 API는 시스템에 “다른 백신이 있으므로 Defender 검사를 실행할 필요가 없다”고 알리는 용도였음
- 해당 영역을 관리하는 시스템 구성요소가 Windows Security Center(WSC) 임
- no-defender는 기존 백신의 서드파티 코드를 이용해 그 백신이 WSC에 등록되도록 강제하는 방식으로 동작했음
- 공개 후 몇 주 만에 약 1.5k stars를 얻었고, 사용된 백신 개발사가 DMCA takedown 요청을 제출하자 저장소 내용을 지우고 끝냄
서울 여행 중 다시 시작된 WSC 분석
- 서울의 Airbnb에 머무르던 중 MrBruh가 no-defender를 살펴보다가, 백신 바이너리 없이 “깨끗한” 구현이 가능한지 조사하고 있다고 메시지를 보냄
- 평소 CTF나 x86 리버싱 작업에는 별도 x86 노트북을 가져가지만, 이번 여행에는 M4Pro MacBook만 가져온 상태였음
- x86 리버싱용 장비가 없는 불리한 조건에서도 친구들이 일어나기 전 시간을 이용해 WSC 분석을 시작함
Day 1: WSC COM API 재현과 첫 접근 거부
- MrBruh가 최신 WSC 바이너리를 제공했고, 이전에 사용했던 백신의 WSC 등록 구현을 참고 구현으로 삼음
- WSC에는 백신들이 사용하는 COM API가 있고, 기존 백신이 하던 호출을 약 1시간 만에 재현함
- arm64 Windows를 Parallels에서 부팅해 테스트했지만 결과는 access denied였음
- 이전 경험상 WSC가 API 호출 프로세스를 검증한다는 점을 알고 있었고, 처음에는 서명 검증일 가능성을 의심함
- 기존 백신이 WSC 작업에 사용하던 프로세스에 코드를 주입하자 새 백신 등록과 상태 업데이트 COM 호출이 정상 동작함
백신 바이너리를 제거하려는 시도
- 새 프로젝트가 기존 백신 업체의 문제 제기를 다시 받지 않도록, 백신 바이너리 대신 시스템 제공 바이너리를 쓰려 함
- 첫 대상 프로세스로
cmd.exe를 골랐지만 WSC API 호출은 거부됨 wscsvc.dll을 살펴보니 호출 프로세스의 PPL 여부를 확인하는 코드가 있었음- 단순
CreateProcessA로 만든 프로세스는 PPL 보호 프로세스가 아니었고, 그날 새벽에는 더 진행하지 못함
Day 2: 불편한 원격 디버깅 환경 구축
- arm64 MacBook에서는 x86 Windows를 제대로 다루기 어려웠고, arm64 환경에서 작업하거나 x64dbg를 못 쓰는 상황을 피하고 싶었음
- 친구 pindos가 PC 접근을 허용했고, Parsec으로 원격 접속함
- 한국에서 미국에 있는 PC에 접속했기 때문에 평균 지연은 약 210ms였음
- 당시 작업 흐름은 매우 번거로웠음
- Parallels의 Windows arm64에서 MSVC로 모듈 빌드
- 공유 폴더로 빌드 산출물을 호스트와 공유
- AnyDesk로 pindos의 PC 위 VM에 산출물 복사
- Parsec 210ms 지연 환경에서 WSC 서비스 디버깅
- 개발과 분석 속도가 크게 떨어질 정도로 환경이 불편했음
WSC 서비스 디버깅과 WinDefend SID
- WSC 서비스는
svchost가 실행하는 DLL이며, 디버거 연결을 막는 주요 요소는 PPL 보호였음 - VM에서 테스트 모드를 켜고, 커널 모드에서 몇 줄의 코드로 대상 프로세스의 PPL을 제거하는 드라이버를 사용함
cmd.exe가 WSC에 백신 등록을 요청할 때 실패하는 지점은WscServiceUtils::CreateExternalBaseFromCaller였음- 해당 함수는 RPC 클라이언트를 가장한 뒤 호출 프로세스 토큰에
WinDefendSID가 있는지 확인함 - 당시 Windows 토큰 동작을 잘 몰랐기 때문에,
WinDefend를 가장하면 검사를 통과할 수 있다고 판단함 - 수면 부족 상태에서 기존 백신 바이너리도 이
IsMember검사를 통과한다고 잘못 해석함
WinDefend 가장 시도와 실패
- Windows 토큰을 몇 시간 학습한 뒤, WinDefend SID가 포함된 토큰으로 실행하면 WSC 검사를 통과할 수 있다는 방향으로 구현함
cmd에WinDefendSID를 붙인 상태에서 코드를 실행해 봤지만, COM 호출은STATUS_SUCCESS를 반환해도 실제 새 백신 등록은 일어나지 않음- 이 접근은 결과적으로 유용하지 않았고, 다음 날 다시 검증해야 했음
Day 3: 실제 검증 알고리듬 재구성
- 다시 확인해 보니 기존 백신 바이너리는 WinDefend SID 검사를 통과하지 않았음
- 그 검사를 통과하면 Windows Defender의 WSC 객체를 다루게 되지만, WSC 호출만으로 Defender를 바로 비활성화할 수는 없어 쓸모가 없었음
- WinDefend 가장 코드를 제거하고 조건문의 다른 분기를 분석함
- 해당 분기에서는 호출 바이너리에 대해 다음을 확인함
- 프로세스가 elevated 상태인지
- 바이너리 서명이 유효한지
- PE의
DllCharacteristics에 특정 플래그가 있는지
CSecurityVerificationManager::CreateExternalBaseFromPESettings에서 확인하던DllCharacteristics플래그는 ForceIntegrity였음- WSC가 하는 바이너리 검사를 재현한 코드를 defendnot 저장소의
wsc-binary-check폴더에 만들고, System32 바이너리를 대상으로 테스트함
Taskmgr.exe 사용과 ctx.bin 버그
- 친구가 PC를 다시 써야 해서 VM에 직접 Parsec으로 접속했지만, 소프트웨어 인코딩까지 겹쳐 환경은 더 느려짐
cmd.exe대신Taskmgr.exe를 사용했지만 여전히 RPC 오류가 발생함- 지연과 입력 문제 때문에 같은 VM에서 디버깅하기 어려워졌고, 추천을 받아 shadow.tech 구독에 $30를 지불함
- Shadow.tech VM은 Windows 버전이 더 오래되어
wscsvc코드가 최신 버전처럼 한 함수에 많이 인라인되어 있지 않았고, 디컴파일 결과도 더 읽기 쉬웠음 - 실제 오류 원인은 WSC에 넘긴 AV 이름이 잘못된 것이었음
defendnot-loader에서defendnot.dll로 데이터를 넘기기 위해 직렬화된 매개변수를 담은ctx.bin을 사용함- 상태 추적용 IPC는 따로 구현했지만, 설정 전달에는 예전 no-defender 코드의 잔재인
ctx.bin방식이 남아 있었음 ctx.bin경로를 찾는 함수가nodefend.dll이 아니라Taskmgr.exe모듈의 기본 폴더를 기준으로 삼음- 그 결과 널 바이트를 AV 이름으로 넘겼고, WSC가 해당 버퍼를 거부함
- 경로 문제를 고친 뒤 테스트가 성공함
코드 정리와 autorun 문제
- 같은 날 끝내기 위해 오전 8시까지 코드 정리와 추가 기능 구현을 진행함
- 추가 기능에는 자신을 autorun에 추가하는 기능이 포함됨
- 오전 8시 시점에는 autorun만 제대로 동작하지 않았고, 여러 방법을 테스트했지만 실패한 뒤 잠듦
- 다음 날 다시 확인해 보니 작업 스케줄러 작업 생성 시 전원 관련 체크박스 두 개가 원인이었음
- 노트북이 AC 전원에 연결되어 있지 않아 작업이 실행되지 않았고, 해당 플래그를 해제하자 autorun이 동작함
- 이후 몇 시간 더 코드를 정리하고 작업을 마무리함
마무리
- 작업 자체는 재미있었지만, 며칠간 반복한 환경 문제와 원격 디버깅 흐름은 다시 겪고 싶지 않은 경험이었음
- 특히 arm64 MacBook, 원격 x86 VM, 높은 지연, 느린 인코딩, 뒤늦게 드러난 파일 경로 버그가 구현 난도를 크게 높임
- 더 기술적인 WSC 문서는 이후 별도로 공개될 예정임
댓글과 토론
Hacker News 의견들
-
Defender를 끄는 방법 중 가장 침습적이지만 효과적이었던 건 라이브 Linux USB로 부팅해서
C:\ProgramData\Microsoft\Windows Defender를 이름 변경한 뒤, 그 자리에 빈 파일을 만들어두는 방식이었음- 그룹 정책은 여전히 매우 잘 먹혀서, 홈랩에 로컬 도메인 컨트롤러를 두고 모든 사용자에 대해 Defender 정책만 자동으로 바꾸게 해뒀음
- 인기 제품 하나도 거의 같은 방식으로 동작하다가 전체 인터넷의 25% 쯤을 같이 내려버린 셈임
- Windows가 서명된 매니페스트로 그런 변경을 감지하지 않는다는 게 이상함
-
참고로 WSC는 Windows Security Center의 약자임
-
the antivirus I was using이라는 표현 때문에 꽤 헷갈렸음. 그 백신 업체가 왜 작성자에게 DMCA 삭제 요청을 보낼 이유가 있는지 이해가 안 됐음
아마 작성자가 다른 백신을 역공학해서 그 일부를 오픈소스 프로젝트에 넣었다는 뜻 같음. 그런데Impersonating WinDefend같은 제목도 보여서, 결국 작성자가 어떤 식으로든 저작권법을 어긴 건지가 궁금함- 이해하기로는 서명 요구사항을 우회하려고 다른 백신 도구의 껍데기를 쓴 것 같음. 회색지대인 건 이해되며, 변형적 이용이라고 볼 여지도 있다고 생각하지만 법률가는 아님
- 맞음. 기존 백신 프로그램 일부를 복사해서 저작권법을 어긴 것임
인용한 문단 바로 앞에 “프로젝트는 이미 존재하던 백신의 서드파티 코드를 사용했고, 그 백신이 WSC에 백신으로 등록되도록 강제했다”는 설명이 있음
-
이 코드는 저주받은 느낌임:
https://github.com/es3n1n/defendnot/blob/master/defendnot-lo...
실제로 무슨 일이 벌어지는지 궁금하면 여기임:
https://github.com/es3n1n/defendnot/blob/master/cxx-shared/s...- C++ 마법에 능한 사람이 무슨 일이 벌어지는지, 왜 저주받았다고 하는지 설명해줄 수 있나?
- 시간 제약 때문에 COM 관련 객체들에 대해 직접 RAII를 구현하고 싶지 않았음. 그래도 다음 업데이트에서 바꿀 예정임
- 뭐가 저주받았다는 건지 모르겠음. 호출부 시그니처는 개인 취향으로 조금 다르지만, 내 코드에서도 이 패턴을 여기저기 씀
예를 들어 D 언어에는 스코프 끝에서 실행되는 문(statement) 개념이 언어에 내장돼 있음 - “코드는 동료를 대하는 방식이다” - Michael Feather, https://x.com/mfeathers/status/1031176879577780224
요약하면 AI가 쓴 건 아님. 이 코드는 객체가 스코프를 벗어나는 시점까지 함수 호출을 지연함. 구현은 C 매크로로 필요한 C 람다/익명 함수 정의 일부를 생략하는 더 짧은 문법을 만들고, 지연 호출을 관리할 고유 변수명을 생성함
다만 결과 문법이 C 매크로를 나타내는 흔한 관례인 대문자 표기를 피하고, 얼핏 보면 객체 포인터의 함수 호출처럼 보임. 이 패턴에 익숙하지 않거나 매크로가 다르게 표시될 거라고 기대하면 혼란스러울 수 있음
어떤 사람들에게는 충분히 흔하고 유용해서 특정 맥락에서는 거의 관용구처럼 볼 수 있음. 기술적 설명은 https://news.ycombinator.com/item?id=43959403#43960905가 매크로 동작을 잘 풀어줌
-
휴가 중에 Windows의 가상 데스크톱을 역공학해서 휴가가 훨씬 좋아졌음. 작년 최고의 기억은 역공학이 정말 재미있다는 걸 느낀 것임
흥미로운 것도 많이 배웠는데, Windows의 RPC 아래에 문서화되지 않은 메시징 계층이 있음: https://csandker.io/2022/05/24/Offensive-Windows-IPC-3-ALPC.... -
프로필 사진이 애니 캐릭터면 글이 좋을 거라는 걸 매번 알게 됨. 나중에 별로인 Windows 환경으로 돌아가게 될 때를 대비해 저장해둠
-
궁금한 사람들을 위해: WSC는 Windows Security Center의 약자임. 나도 찾아봐야 했음
- 글 안에 “이 난장판을 관리하는 시스템 부분은 Windows Security Center, 줄여서 WSC라고 부른다”라고 나와 있음
-
왜 WSC를 끄고 싶어 하는 거지?
- 성능 때문일 수도 있고, 악성코드 개발이나 해킹 때문일 수도 있음
- 공격자라면 다른 엔드포인트 탐지·대응 제품이 설치돼 있지 않은 운 좋은 상황을 노릴 수 있음. 그런 제품이 있으면 거의 확실히 가로챌 것임
EDR 벤더라면 이건 Windows Firewall을 억제하거나 비활성화하는 데 쓸 수 있는 난독화된 API 호출임. 예를 들어 CrowdStrike는 Windows Firewall을 쓰거나 자체 구현을 쓰는 둘 다 가능하다고 봄 - 내 하드웨어니까 내가 하고 싶은 대로 할 뿐임
- 모든 백신 소프트웨어는 최소한 파워바이러스임. 누가
netcat.exe는 안 된다고 보모처럼 간섭하는 건 신경 쓰고 싶지 않음 - 일부러 자기 자신에게 루트킷을 심고 싶을 이유가 없기 때문임
-
최근에 https://nostarch.com/windows-security-internals를 읽었는데, 이 글 덕분에 훨씬 더 와닿음
Windows에서 이런 뒷단 동작이 대충 어떻게 돌아가는지는 조금 알고 있었지만, 타이밍이 좋았음. 그 책의 마지막 장도 이 글 작성자가 다룬 것처럼 토큰과 SID를 꽤 자세히 파고듦