시작하기에 앞서, Demon팀에서 진행한 프로젝트가 아니라 현재 근무중인 Horangi에서 진행중인 프로젝트 일부이다.
같이 프로젝트를 수행하고 있는 팀원에게 고객사 이름을 말하지 않는 조건으로 글을 작성하는데 허락을 맡았으며, 안티바이러스 우회를 위해 진행한 짤막한 삽질을 공유하기 위해 포스팅하게 되었다.
나는 Horangi에서 Red Team, Penetration Testing 그리고 Cloud Infra Develop을 맡고 있다.
최근 한 고객사로부터 침투 시나리오를 만들어 공격을 수행해달라고 요청받았다.
COVID-19 상황으로 인해 사내에서 사용하는 여러 노트북을 받아왔다.
내가 받은 노트북은 Mac 3EA, 팀원이 받은 노트북은 Windows 2EA이였다.
사진 중 3대의 Mac은 내것이 아니다.. ㅠㅠ
우리 팀 역시 COVID-19로 인해 재택근무를 진행중이기에 서로 원활한 소통을 하기 위해 C2서버를 제작하기로 결정하였다.
프로젝트가 시작되기 1주일 전, Terraform을 이용하여 모든 인프라를 자동화 해두고 악성코드 제작에 들어갔다.
악성코드를 실행하게 되면, 사용자가 예상하지 못한 정보 노출이 발생할 수 있다. 이를 방지하기 위해 Windows는 물론이고 많은 Antivirus Vendor에서 탐지 프로그램을 제공한다.
매일마다 끊임없이 해커와 개발자는 뚫고 막는 여정을 이어나간다.
오늘은 해커 관점에서 안티바이러스 우회에 대해 이야기를 해볼까 한다.
나는 탐지를 대비해 며칠 간 여러 버전(x86)을 만들어두었다. 버전을 간단하게 설명하면 쉘코드를 2중 암호화 해둔 버전, 인라인 어셈으로 작성한 버전, PE에서 기본적으로 제공하는 섹션이 아닌 다른 섹션을 로드해서 사용하는 버전, 패커를 사용한 버전, Process Hollowing을 이용한 버전 등이다.
모든 바이너리는 프로젝트 시작 전, Windows Defender, Avast에서 미 탐지 됨을 확인하였다.
고객사로부터 전달받은 하나의 Windows 디바이스는 내가 작성한 모든 버전이 원활하게 동작하였다.
하지만, AD접근이 가능한 디바이스의 경우 ‘Crowd Strike’가 설치되어 있어 너무 허무하게 탐지되어 버렸다. 잠시후, 고객사로부터 탐지가 되었다고 전달 받았다.
무조건 우회를 성공해야 원격으로 프로젝트 수행이 가능하기 때문에 여러가지 방식을 떠올려보았다. 현재 우리는 노트북을 물리적으로 소지하고 있지만, 물리적으로 비활성화를 시키는 것은 침투 시나리오에서 어긋나기 때문에 진행하지 않았다.
무력화시켜 무조건 나의 악성코드가 실행되게 하는 방법 역시 생각해보았지만, 고객이 요청한 사항 중 악성코드를 제외한 다른 프로그램의 설치는 자제해주길 바랬기에 디버거를 물리적으로 설치할 수도 없었다.
(유료라서 내 호스트에 다운로드를 할 수도 없었다..)
한참을 고민하는 도중 팀원 한분께서 링크 하나를 던져주셨다.
나는 이 내용을 다 읽진 않고, 아래의 부분에만 초점을 맞추었다.
그렇다. 나의 바이너리는 VirtualAlloc과 CreateThread를 기본적으로 사용하고 있었고, 그러므로 CrowdStrike가 너무 쉽게 탐지해버린 것이다.
하지만, 쉘코드를 로드해서 사용하기 위해서는 가상 함수를 할당하여 사용하는 API는 필수적으로 필요하다고 생각하고 있었기 때문에 잠시 멍해졌었다.
.rdata섹션에 대놓고 보여지기 때문에 행위기반 분석이 아니라 정적으로 분석만 하더라도 문자열로 VirtualAlloc, CreateThread가 노출되어진다.
나는 .rdata에 노출되지 않으려면 어떻게 하면 되는지를 생각해보았다.
.rdata 섹션에 자리잡는 녀석들이 어떻게 생기는지를 설명하기 위해 작고 귀여운 바이너리를 하나 생성하였다.
기본적으로 제공하는 함수의 경우, DLL에서 주소를 찾아와서 이용하게 된다. 이 방법을 생각해보면, 직접적으로 그 주소를 가지고와서 다른 이름으로 사용하면 노출이 되지 않을 수 있지 않을까? 라는 아이디어를 얻을 수 있다.
LoadLibrary와 GetProcAddress를 사용한 예제가 많이 있다. 하지만 GetProcAddress 또한 직접 찾아서 코딩하면 좀 더 안전한 바이너리가 될 것 같다는 생각을 하였다.
키워드를 정해 검색해보니 이미 누군가가 깔끔하게 작성해둔 코드가 있었고 이를 이해하고 사용하기로 결정했다.
모든 프로그램은 ntdll.dll과 kernel32.dll을 가지고 있다.
GetProcAddress는 kernel32.dll에 존재한다. 하지만 더 접근해보면 GetProcAddress는 상위 ntdll.LdrGetProcedureAddressForCaller에서 호출되는 API임을 알 수 있다.
LdrGetProcedureAddressForCaller를 검색해보니 LdrGetProcedureAddress와 같다는 것을 알게 되었다.
LdrGetProcedureAddress는 ntdll 에 있다는 것을 디버거를 통해 확인하였으니, ntdll의 베이스주소를 구하는 방법을 생각해보아야 한다. DLL의 베이스주소를 알면, 오프셋을 통해 함수에 접근할 수 있기 때문이다.
어떻게 접근할 수 있을까?
Windows에는 fs 세그먼트가 존재한다. fs[0x18] 위치에 TIB(Thread Information Block)가 존재하게 되며 TIB의 0x0C 위치에 Ldr이라는 구조체가 존재한다.
Ldr에 접근하게 되면 0x1C 부분에 InInitialzationOrderModuleList가 존재한다.
InInitialzationOrderModuleList에서 BaseDllName을 만날 수 있다.
여기까지를 코드로 표현하면 다음과 같다.
그 후, EAT(Export Address Table)를 살펴보아야 한다.
EAT는 Windows DLL 하나를 까서 그 안에 무슨 함수들이 있는지 훔쳐본다고 생각하면 이해하기 편하다.
EAT에 접근하기 위해 이전에 구한 ntdll 베이스 주소와 IMAGE_NT_HEADER를 이용하여야 한다.
Export Table에 접근 후, 반복적으로 함수명을 찾다 보면 현재의 목표인 LdrGetProcedureAddress 주소를 얻을 수 있다.
이 과정을 통해 이제 IAT에 등록하지 않고, 프로그램안에서 API함수를 사용할 수 있게 된다.
IAT 미등록 리스트는 VirtualAlloc, VirtualProtect, CreateThread이다.
API 함수는 다음과 같이 선언하였다. msdn에 아주 상세하게 설명이 되어 있다.
쉘코드 호출은 다음과 같은 방법을 이용하였다. 인라인어셈과 C문법을 병행하였다.
그 이후, MS binary를 이용하여 코드 서명을 진행하였다.
그 결과, 탐지되지 않고 연결이 되었음을 확인할 수 있었다.
PE구조에 대해 많이 까먹고 있었는데, 오늘 복습하게되어 기분이 좋았다.
해당 프로젝트에서는 Cobalt Stirke 프레임워크의 내장 된 쉘코드 일부를 수정, 희생자가 분석을 진행했을 때 공격자의 IP를 알 수 없게 Redirect 서버 2개를 사용하였다.
ref.
https://www.redcursor.com.au/blog/bypassing-crowdstrike-endpoint-detection-and-response
https://blog.kowalczyk.info/articles/pefileformat.html
http://www.rohitab.com/discuss/topic/43078-ldrgetprocedureaddress-hook-windows-10/
https://www.aldeid.com/wiki/PEB-Process-Environment-Block
http://undocumented.ntinternals.net/index.html?page=UserMode%2FStructures%2FPEB_LDR_DATA.html
https://5kyc1ad.tistory.com/329?category=753762
https://github.com/secretsquirrel/SigThief