Program registration tester
Introduction
How to hide values in the registry and use it for program registration!
Would it be possible to perform a program registration for a certain program (or other similar objects) and only keep the reg.info. hidden on the end-user machine - in a "pretty safe" way - so that the average user would/could not mess around with it? This is what my article tries to solve. Put simply: how to register a program.
I wouldn't say that my solution is anything state-of-the art, or even close to being elegant, but it works and it's safe to the extent that I feel that most users would not dare to look into how it is made and how it is avoided. Also, in writing this I hope to learn more from the feedback it may produce, especially if I unintentionally re-invent techniques or functions already known.
Background
Before I begin please allow me to first define what the article and code actually solves and exactly what I mean by saying "program registration" (it may not be obvoius). After that I'll go into more detail.
Definitions and Limits
- A registration object is the entity provided to pinpoint (1) an official UTC*-timestamp and (2) a serial no. (SN) for the program being installed.
- The code deals with the smallest possible set of information to establish such a registration entity and is by def. and for this demo. purpose (1) and (2).
- The aticle does 'not' go into discussions about how a SN is made and how secure they are etc.
- Except for time-services the code must not be dependend of any servers in the outside world.
- The registration performed must be 100% free of charge for us.
*) Universal Time Coordinate.
Trivials
- Although having a tiny Win-registry API the point is not to produce yet-another-such :-).
What is the idea?
The basic idea is relatively simple: Would it be possible to write something somewhere and then hide it for the user to see? And next, would it be possible to get the right time - and then let the 'something' be this time? If 'yes' to both it could be done. Or, in other words, could I hide a timestamp in a secret Harry-Potter-spot? Of course in the end we are forced to write our 'thingy' in a registry-branch/key already present so that the (advanced) user does not just remove an added (and visible) key - the key must exist to begin with - like "Console" or something!
That's the idea. Let's have some fun!
The Hidden Registry Value
One month ago I came across this:
Nowadays, everyone has access to the registry editor and can modify, delete or add registry keys. This is a great thing, as long as you know how and which registry values need to be modified. Creating an invisible registry key gives you the guarantee that your keys will remain what you set them, and the applications you develop won’t encounter any problems in reading wrong data. If you want to create an invisible registry folder, this is the solution for you!
The trick lies in a Windows flaw, a flaw regularly exploited by malicious applications (virus, spyware) to hide their traces. The flaw (trick) is that a registry value name can only have the length of 255, if a name longer than 255 characters is given to a registry value, windows will think the end of the key has been reached and all values in that key will be hidden from view. You can see them only with special software designed for this purpose.
Source: ReviewingIT
I tested it and to my surprice it worked. Is this generally known? I have to admit that I didn't know about it. So after reading this I came up with this tiny and simple win-registry-class:
class CRegistryAccess
{
private:
bool m_bHiddenName;
HKEY m_hKey;
CString m_cstrMainKey,
m_cstrSubKey;
protected:
static const bool
CloseKey(HKEY);
static HKEY
CreateKey(const CString&);
static BOOL WINAPI
EnableProcessPrivilege(LPCTSTR, BOOL);
static const CString
FormatKey(const CString&, const CString&);
const bool
GetDataDWORD(const CString&, DWORD&);
const bool
GetDataSZ(const CString&, CString&);
static const CString
GetLongName(const CString&);
static HKEY
OpenKey(const CString&);
const bool
SetDataDWORD(const CString&, CONST DWORD&);
const bool
SetDataSZ(const CString&, const CString&);
public:
struct SNameTypeData
{
CString m_cstrName;
CString m_cstrType;
CString m_cstrData;
};
CRegistryAccess(const CString&, const CString &cstrSubKey = _T(""),
const bool bHiddenKeyEx = false);
~CRegistryAccess();
const bool
DataExist(const CString&);
const bool
DeleteData(const CString&);
const bool
DeleteData();
const bool
DeleteKey();
const void
GetAll(CArray<SNameTypeData, SNameTypeData>&);
const void
GetAllNames(CStringArray&);
const void
GetAllTypes(CStringArray&);
const void
GetAllDatas(CStringArray&);
const bool
GetData(const CString&, DWORD&);
const bool
GetData(const CString&, CString&);
const bool
GetData(const CString&, int&);
const bool
GetData(const CString&, bool&);
const bool
SetData(const CString&, CONST DWORD&);
const bool
SetData(const CString&, const CString&);
const bool
SetData(const CString&, const int&);
const bool
SetData(const CString&, const bool&);
};
I prefer Set- and GetData(...)
over Set- and GetValue(...)
since this is what we do - setting/getting data - like the third column in regedit.exe suggests. For most purposes, at least for me right now, dealing with the registry is the same as working at one key (and ONE key only) at a time and then handling N data's under it. In that way I can concentrate on my one-and-only key opened (think of it as *this)! Hence the only method in this class dealing with a key is DeleteKey(...)
and calling this is suicide - it removes the key and jumps up the tree one step making the parent node the open one. It fails if the key holds sub-keys BUT it wipes all data - so be careful. DeleteData(...)
wipes all data under a key and DeleteData(const CString&)
a specific. The group GetAll...
is really just for enumerating the name(s)/type(s)/data(s) and the rest is overloaded get/set for the most important datatypes.
Also worth mentioning is: static BOOL WINAPI EnableProcessPrivilege(LPCTSTR, BOOL)
which enables the owner of the object to have full access to the registry (got this from C Board and MS) AND the rest more or less are a protected by a thin layer over the win32 registry API.
The 007 stuff is bool m_bHiddenName;
. If this is T the data is hidden under its key (hidden defaults to F in its constructor). 255 char's are padded in front of the name using:
const CString CRegistryAccess::GetLongName(const CString &cstrName)
{
TRY
{
static const CString s_cstr255Chars(
_T("MAGGIEBARTLISAMARGEHOMERSIMPSONSPIDERPIG")
_T("MAGGIEBARTLISAMARGEHOMERSIMPSONSPIDERPIG")
_T("MAGGIEBARTLISAMARGEHOMERSIMPSONSPIDERPIG")
_T("MAGGIEBARTLISAMARGEHOMERSIMPSONSPIDERPIG")
_T("MAGGIEBARTLISAMARGEHOMERSIMPSONSPIDERPIG")
_T("MAGGIEBARTLISAMARGEHOMERSIMPSONSPIDERPIG")
_T("ITCHYORSCRATCHY"));
CString cstrNameLong(s_cstr255Chars + cstrName);
return cstrNameLong;
}
CATCH_ALL(e)
{
WNDTRACEEXCEPTION_USERDLG("CRegistryAccess", "GetLongName", "", e);
THROW_LAST();
}
END_CATCH_ALL
}
Test stub for the class...
...after a value added...
Simple and straightforward. Next step is to get the right time.
What time is it?
Sync. to internet time (sorry for the Danish language)
Stupid question... but, chances are that the time sync. in Windows is turned off by the user making the registration worthless if we ask for his local PC-time. Therefore we need a time&date-server to ask instead, and luckily there is one such server on the internet, and has been for many years. They are called:
Region | HostName |
Worldwide | pool.ntp.org |
Europe | europe.pool.ntp.org |
Asia | asia.pool.ntp.org |
North America | north-america.pool.ntp.org |
Oceania | oceania.pool.ntp.org |
South America | south-america.pool.ntp.org |
And there are many more. This host-list serves as the basis for my function inline const bool GetInternetTime(COleDateTimeEx &tNTP, const CString &cstrNTPServer = _T("")){...}
. Calling this you'll have the current UTC! I was able to write this based on another CP article called CSNTPClient - An SNTP Implementation written by Mr. PJ Naughter.
After cutting some and adding some (mostly cut) I ended up with a set of classes and structs to retrieve the std. time as orig. defined by Mr. PJ Naughter. For this impl. we are only interested in the precision YYYYMMDD.
To make GetInternetTime(...)
work we need a bunch of classes - defining the packet look, socket behaviour and some neat conversions etc. They are:
class SNTPTimePacket
- a struct rep. seconds and fraction of s. as two DWORD's.class CNTPTime
- a class having SNTPTimePacket- and SYSTEMTIME cast operators etc.class SNTPServerResponse
- a cut down of a more advanced struct to just a CNTPTime for this purpose (so it could be removed all together).class CNTPClient
- class having const bool CNTPClient::GetServerTime(const CString &cstrHostName, SNTPServerResponse& sntpResponse, const int iPort).
class CNTPSocket
- encapsulates native Win. sock calls like Close, Create and Connect.class SNTPBasicInfo
- struct holding the mandatory part of an NTP packet.class SNTPAuthenticationInfo
- struct holding the optional part of an NTP packet.class SNTPFullPacket
- struct rep. the two above.
I'll not go into all the time-code, but in short it's basically just setting up a Win32 UPD-socket (NTP uses UDP instead of the usual TCP) - ::WSAStartup(...)
blah. blah. (see the books) and then create and connect it. GetServerTime(...)
Sends and Recieves and after its success it goes: sntpResponse.m_ReceiveTime = sntpFullPacket.m_Basic.m_ReceiveTimestamp;
after which we have a CNTPTime and following a SYSTEMTIME and in the end a COleDateTime. All hosts are found on port 123 (all that I know of at any rate).
class CNTPClient : public CObject
{
public:
CNTPClient();
const bool GetServerTime(const CString&, SNTPServerResponse&,
const int iPort = 123);
protected:
DWORD m_dwTimeout;
};
If GetInternetTime(...)
fails it continues using the next server in the table until success. In the unlikely event that no time (actually date only) can be returned an appropriate exception is thrown.
Using the Code
When we want to register our program now, the class for that deals with a hidden name under a predefined reg. key named in the class - we pick "..\HKEY_CURRENT_USER\Console" for the purpose. So at time of creation it says:
CProgramRegistration::CProgramRegistration(const CString &cstrAppName,
const int iDaysToUseFreeOfCharge, const bool bHidden) :
m_cstrAppName(cstrAppName),
m_iDays2UseFreeOfCharge(iDaysToUseFreeOfCharge),
m_pregAccess(NULL)
{
try
{
if(!::GetInternetTime(m_dtNow))
{
AfxThrowUserException();
}
m_pregAccess = new CRegistryAccess(_T("Console"), _T(""),
bHidden);
VERIFY(m_pregAccess);
m_cstrAppName.MakeUpper();
if(!GetFirstRunDate(m_dtFirstRun))
{
m_dtFirstRun = m_dtNow;
SetFirstRunDate(m_dtFirstRun);
UnRegister();
}
m_dtExpire = m_dtFirstRun + COleDateTimeSpanEx(
m_iDays2UseFreeOfCharge, 0, 0, 0);
}
catch(CUserException *pe)
{
pe->ReportError();
pe->Delete();
}
catch(CException *pe)
{
pe->ReportError();
}
}
To create a trial version of your program for 7 days:
m_pReg = new CProgramRegistration(_T("My Program"), 7, false);
On new it UTC-timestamps and call UnRegister(...)
after which the registry looks:
Trial...
After registration using a random SN the registry looks:
Register...
Other methods of interest in CProgramRegistration
are (bold text):
class CProgramRegistration
{
private:
static const CString sm_cstrSNDummy;
static const CString sm_cstrFirstRunRegKeyPostfix;
static const CString sm_cstrSNRegKeyPostfix;
int m_iDays2UseFreeOfCharge;
CString m_cstrAppName;
COleDateTimeEx
m_dtNow,
m_dtExpire,
m_dtFirstRun;
CRegistryAccess *m_pregAccess;
protected:
inline const CString GetFirstRunRegName()
{ return(m_cstrAppName + sm_cstrFirstRunRegKeyPostfix); }
inline const CString GetSNRegName()
{ return(m_cstrAppName + sm_cstrSNRegKeyPostfix); }
const bool
SetFirstRunDate(const COleDateTimeEx&);
public:
CProgramRegistration(const CString&, const int,
const bool bHiddenEx = true);
~CProgramRegistration();
const int
GetDaysLeft();
static inline const CString&
GetDummySerialNumber()
{ return sm_cstrSNDummy; }
const bool
GetFirstRunDate(COleDateTimeEx&);
const bool
GetSerialNumber(CString&);
const bool
IsLegalCopy();
const bool
IsRegistred();
const bool
Register(const CString&);
const void
RemoveRegistration();
const bool
UnRegister();
};
In real life situations use:
m_pReg = new CProgramRegistration(_T("My Program"), 7, true);
Also use:
const bool CRegistryAccess::DeleteData()
with extreme caution!
Points of Interest
One downside of this is that hidden data can be seen if enumerated! Try running the RegistryAccessTester and press 'Get all data' - long names are among the possibilities. Also remember to remove the registration if the user uninstalls the program!
End note: Some of the ...XXXEx classes are not in the project - I work with one set of classes and if not relevant I only release the XXX version. Like for COleDateTimeEx
and COleDateTimeSpanEx
where only the MFC-bases are used. Open prgreg.sln - it holds the two projects.
History
- 1.00: 09.01.28: Initial ver.