1 #include "rar.hpp"
2 
3 #if defined(_WIN_ALL)
4 typedef BOOL (WINAPI *CRYPTPROTECTMEMORY)(LPVOID pData,DWORD cbData,DWORD dwFlags);
5 typedef BOOL (WINAPI *CRYPTUNPROTECTMEMORY)(LPVOID pData,DWORD cbData,DWORD dwFlags);
6 
7 #ifndef CRYPTPROTECTMEMORY_BLOCK_SIZE
8 #define CRYPTPROTECTMEMORY_BLOCK_SIZE           16
9 #define CRYPTPROTECTMEMORY_SAME_PROCESS         0x00
10 #define CRYPTPROTECTMEMORY_CROSS_PROCESS        0x01
11 #endif
12 
13 class CryptLoader
14 {
15   private:
16     HMODULE hCrypt;
17     bool LoadCalled;
18   public:
CryptLoader()19     CryptLoader()
20     {
21       hCrypt=NULL;
22       pCryptProtectMemory=NULL;
23       pCryptUnprotectMemory=NULL;
24       LoadCalled=false;
25     }
~CryptLoader()26     ~CryptLoader()
27     {
28       if (hCrypt!=NULL)
29         FreeLibrary(hCrypt);
30       hCrypt=NULL;
31       pCryptProtectMemory=NULL;
32       pCryptUnprotectMemory=NULL;
33     };
Load()34     void Load()
35     {
36       if (!LoadCalled)
37       {
38         hCrypt = LoadSysLibrary(L"Crypt32.dll");
39         if (hCrypt != NULL)
40         {
41           // Available since Vista.
42           pCryptProtectMemory = (CRYPTPROTECTMEMORY)GetProcAddress(hCrypt, "CryptProtectMemory");
43           pCryptUnprotectMemory = (CRYPTUNPROTECTMEMORY)GetProcAddress(hCrypt, "CryptUnprotectMemory");
44         }
45         LoadCalled=true;
46       }
47     }
48 
49     CRYPTPROTECTMEMORY pCryptProtectMemory;
50     CRYPTUNPROTECTMEMORY pCryptUnprotectMemory;
51 };
52 
53 // We need to call FreeLibrary when RAR is exiting.
54 static CryptLoader GlobalCryptLoader;
55 #endif
56 
SecPassword()57 SecPassword::SecPassword()
58 {
59   CrossProcess=false;
60   Set(L"");
61 }
62 
63 
~SecPassword()64 SecPassword::~SecPassword()
65 {
66   Clean();
67 }
68 
69 
Clean()70 void SecPassword::Clean()
71 {
72   PasswordSet=false;
73   cleandata(Password,sizeof(Password));
74 }
75 
76 
77 // When we call memset in end of function to clean local variables
78 // for security reason, compiler optimizer can remove such call.
79 // So we use our own function for this purpose.
cleandata(void * data,size_t size)80 void cleandata(void *data,size_t size)
81 {
82   if (data==NULL || size==0)
83     return;
84 #if defined(_WIN_ALL) && defined(_MSC_VER)
85   SecureZeroMemory(data,size);
86 #else
87   // 'volatile' is required. Otherwise optimizers can remove this function
88   // if cleaning local variables, which are not used after that.
89   volatile byte *d = (volatile byte *)data;
90   for (size_t i=0;i<size;i++)
91     d[i]=0;
92 #endif
93 }
94 
95 
96 // We got a complain from user that it is possible to create WinRAR dump
97 // with "Create dump file" command in Windows Task Manager and then easily
98 // locate Unicode password string in the dump. It is unsecure if several
99 // people share the same computer and somebody left WinRAR copy with entered
100 // password. So we decided to obfuscate the password to make it more difficult
101 // to find it in dump.
Process(const wchar * Src,size_t SrcSize,wchar * Dst,size_t DstSize,bool Encode)102 void SecPassword::Process(const wchar *Src,size_t SrcSize,wchar *Dst,size_t DstSize,bool Encode)
103 {
104   // Source string can be shorter than destination as in case when we process
105   // -p<pwd> parameter, so we need to take into account both sizes.
106   memcpy(Dst,Src,Min(SrcSize,DstSize)*sizeof(*Dst));
107   SecHideData(Dst,DstSize*sizeof(*Dst),Encode,CrossProcess);
108 }
109 
110 
Get(wchar * Psw,size_t MaxSize)111 void SecPassword::Get(wchar *Psw,size_t MaxSize)
112 {
113   if (PasswordSet)
114   {
115     Process(Password,ASIZE(Password),Psw,MaxSize,false);
116     Psw[MaxSize-1]=0;
117   }
118   else
119     *Psw=0;
120 }
121 
122 
123 
124 
Set(const wchar * Psw)125 void SecPassword::Set(const wchar *Psw)
126 {
127   if (*Psw==0)
128   {
129     PasswordSet=false;
130     memset(Password,0,sizeof(Password));
131   }
132   else
133   {
134     PasswordSet=true;
135     Process(Psw,wcslen(Psw)+1,Password,ASIZE(Password),true);
136   }
137 }
138 
139 
Length()140 size_t SecPassword::Length()
141 {
142   wchar Plain[MAXPASSWORD];
143   Get(Plain,ASIZE(Plain));
144   size_t Length=wcslen(Plain);
145   cleandata(Plain,ASIZE(Plain));
146   return Length;
147 }
148 
149 
operator ==(SecPassword & psw)150 bool SecPassword::operator == (SecPassword &psw)
151 {
152   // We cannot compare encoded data directly, because there is no guarantee
153   // than encryption function will always produce the same result for same
154   // data (salt?) and because we do not clean the rest of password buffer
155   // after trailing zero before encoding password. So we decode first.
156   wchar Plain1[MAXPASSWORD],Plain2[MAXPASSWORD];
157   Get(Plain1,ASIZE(Plain1));
158   psw.Get(Plain2,ASIZE(Plain2));
159   bool Result=wcscmp(Plain1,Plain2)==0;
160   cleandata(Plain1,ASIZE(Plain1));
161   cleandata(Plain2,ASIZE(Plain2));
162   return Result;
163 }
164 
165 
SecHideData(void * Data,size_t DataSize,bool Encode,bool CrossProcess)166 void SecHideData(void *Data,size_t DataSize,bool Encode,bool CrossProcess)
167 {
168   // CryptProtectMemory is not available in UWP and CryptProtectData
169   // increases data size not allowing in place conversion.
170 #if defined(_WIN_ALL)
171   // Try to utilize the secure Crypt[Un]ProtectMemory if possible.
172   if (GlobalCryptLoader.pCryptProtectMemory==NULL)
173     GlobalCryptLoader.Load();
174   size_t Aligned=DataSize-DataSize%CRYPTPROTECTMEMORY_BLOCK_SIZE;
175   DWORD Flags=CrossProcess ? CRYPTPROTECTMEMORY_CROSS_PROCESS : CRYPTPROTECTMEMORY_SAME_PROCESS;
176   if (Encode)
177   {
178     if (GlobalCryptLoader.pCryptProtectMemory!=NULL)
179     {
180       if (!GlobalCryptLoader.pCryptProtectMemory(Data,DWORD(Aligned),Flags))
181       {
182         ErrHandler.GeneralErrMsg(L"CryptProtectMemory failed");
183         ErrHandler.SysErrMsg();
184         ErrHandler.Exit(RARX_FATAL);
185       }
186       return;
187     }
188   }
189   else
190   {
191     if (GlobalCryptLoader.pCryptUnprotectMemory!=NULL)
192     {
193       if (!GlobalCryptLoader.pCryptUnprotectMemory(Data,DWORD(Aligned),Flags))
194       {
195         ErrHandler.GeneralErrMsg(L"CryptUnprotectMemory failed");
196         ErrHandler.SysErrMsg();
197         ErrHandler.Exit(RARX_FATAL);
198       }
199       return;
200     }
201   }
202 #endif
203 
204   // CryptProtectMemory is not available, so only slightly obfuscate data.
205   uint Key;
206 #ifdef _WIN_ALL
207   Key=GetCurrentProcessId();
208 #elif defined(_UNIX)
209   Key=getpid();
210 #else
211   Key=0; // Just an arbitrary value.
212 #endif
213 
214   for (size_t I=0;I<DataSize;I++)
215     *((byte *)Data+I)^=Key+I+75;
216 }
217