飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 2746|回复: 1

Crumbling the Crackers

[复制链接]

该用户从未签到

发表于 2006-7-6 12:14:11 | 显示全部楼层 |阅读模式
Crackers
by
Charles A. Appel Jr.
This article was originally published in Delphi Developer (Volume 7, number 3 - March 2001) under the title "Crumbling the Crackers".
Source Code for this Article
may be downloaded here.
They call themselves 'crackers'. Their job is to break protected software. A few do it for money. Many more do it for the challenge. In this article, I examine ways to increase the level of challenge - to make the crackers job a bit harder.
What is cracking? Cracking is the process of removing protection from a computer program. Commercial programmers protect programs in order to maximize the return on the time and effort put into the product. I would like to make money. I can't do that unless I am paid for the work I have done. To ensure payment, many companies resort to some sort of protection scheme. These come in many flavors, including (but not limited to):
No protection at all: This route is often taken by authors of Shareware and it can be quite successful. The author hopes for massive distribution of the product and additionally hopes that a percentage of the users will decide to pay. The user is generally reminded (on a regular basis) that the software should be "registered" - that is, paid for.
Time (or use) limits: This scheme involves limiting the number of days or number of times that a program can be used. This allows the author to distribute a working demo version of the product, which allows potential customers to try before they buy.
Crippled demos: Here the author allows potential customers to try a limited version of the package. The program might not allow the user to save files for example. If the user buys the product, he or she receives a copy with all the features.
Per seat licensing: This is often done by suppliers of vertical market software. It allows the user to run the program only on a limited number of computers or at only one location.
Copy protection: This is done to prevent users from copying and distributing program media to others.
How do Crackers Crack?
Crackers endeavor to render the above schemes useless. They do this in a number of ways, but normally by modifying the program code or data. The cracker has two great advantages over the programmer. The first advantage lies in the fact that it is easier to take something apart than to put something together. Like a fixed fortification, protection is easy to get around. The second advantage is that the cracker generally operates at a lower level than the programmer does. What I write in Delphi, the skilled cracker breaks in machine code or assembly language. Top level crackers hold high level programmers in contempt.
In addition, the cracker has a large arsenal of tools. These include commercial products intended to make programming simpler - debuggers, programs to monitor program activity, dissassemblers to convert the code into assembly language and hex editors that allow direct viewing of the machine language itself. Others tools are written by the cracker specifically to break code. Using a debugger or monitor, the cracker can determine when and where our protection code is activated. Once the protection code is isolated, the cracker can use a disassembler or a simple hex editor to read this code, determine what it does, and alter it.
Can Cracking be Prevented?
Well-informed sources said, "Well, it depends on who you ask". Some years ago, I was asked to research this question for a small company. I divided the research into two phases. First, I went to the web sites of the various companies that make protection devices or market software solutions to the problem. I downloaded and/or requested as much information from each organization as I could get. After reviewing the information, it seemed these companies fell into two different groups. The first group claimed that their solution was unbreakable. The second group stated that no scheme was unbreakable but that theirs was the toughest to crack.
After reviewing what each company had to say, I moved on to the crackers themselves. Cracker web sites were fascinating and I recommend looking at a few of them, if you are ever called upon to implement a protection scheme. Be prepared for a small problem. Many crackers expressed a dislike for both Microsoft?and Netscape. Some went so far as to prohibit the use of browsers marketed by one or both of these firms. You may need to download a web browser from another company to view these sites.
Unlike the protection companies, the answer obtained from the crackers was always the same. No - cracking can not be prevented. Protection schemes are futile - your program will be assimilated. We (the crackers) can break any scheme you throw at us. The sites often contained many 'cracks' - their term for programs that have had protection removed. Some of these cracks were programs protected by schemes touted as "unbreakable". Many of the sites also contained tutorials on cracking software and hardware based protection schemes. Some were general guides to removing protection. Others were step by step guides to breaking a particular program.
In other words, the crackers claimed they could not be beaten, presented proof (in the form of cracks) and explained why they can't be beaten. The cracking tutorials made for interesting reading. They also made a compelling case that (total) protection is indeed futile. They did concede that there were some things we could do to make their jobs a bit harder. (A few were even so kind as to explain how.)
A Time-Limited Demo
If crackers love a challenge, well so do I. In this article I am going to present a strategy and a few tips which may make the cracker's job a little harder. To examine the problem of cracking, I am going to build a simple time limited demo. Creating a time-limited demo is relatively simple. At some point, the code must determine the current date, compare it with the cut off date, and take appropriate action. In building a time limited demo, there are several questions to consider. These are:
When will the comparison be made?
Where will the cut off date be stored?
How (in what format) will the date be stored?
What action will we take when the date is exceeded?
When will we take that action?
When to Make the Comparison
The generally accepted time to perform a protection test is as the program loads. For example, I could make the comparison immediately while the program's splash screen displays or while the first form is being created. This offers the advantage that the legitimate user is not inconvenienced. The disadvantage is that the cracker will be expecting this. One of the first places the cracker looks will be within my startup code. The alternative is to delay the test until the program is running and in use. This permits several options:
The test could be performed every time a particular routine is run. This routine might be one that is used often, if we want to maximize the chance of catching an illicit user. Or it could be one that is seldom used, if we hope to slip the protection past the cracker.
It could be made after the user clicks the mouse a certain number of times. We might catch all mouse clicks or only those on a small set of controls. We could set the number low of clicks between tests low or high. A low number maximizes hits but is easier to find and eliminate.
The test could be performed only on certain days or at a fixed time each day. If we only test on alternate Tuesdays, the cracker may miss the protection - but there isn't very much protection to miss.
The test could be performed (or not) based on a randomly generated number. This number could be generated at startup and the test performed later in the code. By controlling the range of this number in relation to the value that must be exceeded, we can determine (roughly) how often the test will be made. Remember that the more we test, the more we catch and the easier our code is to find.
It could be made only after the program has been used a certain number of times. To trick the cracker, we might not begin testing until the program has been run twenty or thirty times.
Where and How to Store the Date
We have a number of choices on where to store the date.
In the system registry.
In a disk file on the user's hard disk.
In a file on a separate disk or CD-ROM.
As a file name.
As the date and time stamp of a file?
In the program itself or appended to the executable.
In a "dongle". A dongle is a hardware key. Usually these are attached to the parallel port on a computer but it could be an internal card.
How we go about storing the date is at least as important as where it is stored. No matter our choice of locations, one thing is clear. The date must be disguised. This can be accomplished by encrypting the date or by storing it in an unexpected format. The date can also be embedded into a larger piece of data. Lets look at some examples.
Suppose I decide to store the date in the registry. I could create a key with a name like "Cut Off Date" and store in unencrypted string format. This would make it very easy to find and defeat. The only good reason for doing this would be as a decoy or red herring. It would be much better to create several keys, give them cryptic names and store the date as an integer or in encrypted form (or both). The date might be stored in more than one of the keys. By comparing the keys, we might be able to determine if someone is attempting a crack. I might also make the keys much longer than necessary with the date stored at an offset. Another method might be to store the month in one key, the day in another, and the year in a third.
Storing the date in a disk file is a common method that offers several advantages. This is particularly true if the program we are protecting reads and writes files during normal use. A word processing program might start each file with an encrypted header containing the name of the user, the date the file was created or other tracking information. We could easily store the cut off date in each file header. If the file is used only for protection, we could make it a large file (preferably containing encrypted data). We could store the date at one or more offsets which only we would know. There is no reason why we can't use multiple files - for the same reasons we might use multiple registry keys. Our code could run a checksum on the file (or registry) data to see if it has been tampered with. The file(s) might also be hidden or stored in another directory.
While the above methods may seem strong, they are subject to a simple attack. The cracker can monitor the program for disk activity. (Programs that do a lot of disk I/O may obfuscate this.) While doing this, the cracker finds the code that caused the activity. This code can then be eliminated or modified to return a result which let's the program run.
Storing the date in the program removes the problem of disk access but causes problems of its own - the cracker may be able to read the date from the executable file. In preparing this article, I built several versions of the demo program. In the first, I stored the cut off date as a string constant and as several character constants, which were later concatenated to produce a date in string format. It took only minutes to find the date in the executable file using a hex editor. I couldn't readily locate the characters amidst all the other code and data - but when I performed a simple search and replace on the date string, the program was left totally unprotected. The compiler had replaced my lengthy concatenations with the final product. I had forgotten the optimizer. Oops!
Here is something you might want to try. Create a simple project, such as the one in Listing 1. Compile the program and view it in a hex editor. All the control names on the form, their types and their method names were present in the executable - as easy to read strings. So were the string constant and the encrypted string. I could not find the character constants or the numeric constant - but a cracker might be able to. The names of constants, variables, and routine names (other than the methods, types, and controls named above) were not visible. You might want to consider giving those items that were visible innocuous sounding names - something other than "GetCutOffDate". Listing 1. Partial listing of unit HexTest
unit HexTest;
type  
  TfrmHexTest = class(TForm)
    ChuckEdit: TEdit;
    ChuckButton: TButton;
    ChuckLabel: TLabel;
    procedure ChuckButtonClick(Sender: TObject);
  end;
  TMyObject = class(TObject)
  private
    FName : string;
  protected
    procedure SetName(s: string);
    function GetName: string;
  public
    property Name: string read GetName write SetName;
  end;
const
  MyStringDate = '06/20/01';  
  MyNumericDate = 37062;
  MyEncryptedDate = 'khukhukccc';
  d1 = '1';  d2 = '2';
  m1 = '1';  m2 = '2';
  y1 = '1';  y2 = '9'; y3 = '9'; y4 = '9';
  
var
  frmHexTest: TfrmHexTest;
  MyString: string;
implementation
function Expired: boolean;
begin
  Result := (Now > StrToDate(MyStringDate));
end;
{ TMyObject }
function TMyObject.GetName: string;
begin
  Result := FName;
end;
What Action?
What action you take, when you find an illicit user, is up to you (or your company). Before deciding what action to take it would be advisable to seek the advice of legal counsel - especially if you contemplate doing something that is destructive.
Let's look at a few actions that are not likely to cause problems. The simplest would be to shut the program down. We might simply cause the package to exit, letting the user think it was a simple crash. We might even pop up a phony dialogue box to reinforce this idea. There is a potential downside to this. It might persuade a potential buyer running the cracked copy not to use your program. Here are some other options to consider:
A pop up a dialogue that informs the user that this is a pirate copy, that the program is shutting down, and to contact your company to register.
A pop up a dialogue that indicates that the program has suffered a fatal error and urging that the user contact technical support with an error code. The purpose here is to fool the user into thinking the program is buggy and into contacting you. (Believe it or not, some people will.)
Modify a setting in the registry, on disk or in the program so that a nag screen pops up every time the program is used. Settings like this might also be used to disable key functionality - effectively converting the program to a crippled demo.
When to Act
This may be a more important question than what to do. The cracker can find some of our test code easily but other parts are a little harder to find. Any test involving disk access falls into the former category. Other tests are more likely to be found only after some action is taken. So the question here is, do we act immediately or delay action to a later time. If we choose the latter, how do we control when the action takes place? We could:
Set a global variable indicating that the program is a pirate copy. One or more event handlers could then test the value of this variable and perform the chosen action.
We could start counting mouse clicks - all or just those on certain controls. Once a set number had been made, the action code could be activated.
We might start a timer and act when a certain period has elapsed. This could be anywhere from a few minutes to a few hours.
We could alter a disk file or registry setting and thus defer action for several days or even weeks. This option would be especially useful in products like word processors, painting packages, spread sheets and databases where the modification could take place as part of a normal save - with the information stored in a header.
What we are hoping to accomplish by any of these delays, is to engender a certain amount of confusion. By disassociating the comparison with the action taken, we force the cracker to work a little harder. The cracker may find the action easily but if the comparison code is somewhere else, it tougher to find. For this to be effective, each comparison should be able to trigger multiple actions.
An Anti-Cracking Strategy
It appears that delaying action may be the most effective form of protection. So, is there any place for immediate action? I think so. What I am proposing as a general strategy is a two level approach. In the first level, we should place all of the tests methods that are generally used to protect a product. The first level will contain those routines that we expect (and want) the cracker to find. In the second level, we will put all of our tricky routines - the ones we hope the cracker doesn't find. In actual practice, the cracker will find some of these but (with a little luck and good planning) the cracker may not find them all.
For the first level, we might consider some of the following:
All actions should be immediate or only slightly delayed. Delays should be for short periods. If based on a random number, they should be frequent. The first five tests in the demo fall into this category.
All tests that are normally expected, such as a test at start up. If we do the obvious and expected things, the cracker may be lulled into believing we aren't very clever. If we don't do the obvious, the cracker is likely to dig a little deeper and work a little harder.
Tests involving use of the most common elements in our program. Testing when the user saves a document would be better than testing when a special character is inserted into that document.
Tests involving disk access at times when the program isn't loading or saving a file.
We might want to give the cracker a few clues as to where to look. Placing a component on the form called "DateProtector" or a form method named "GetDate" could be helpful here. We might also want to create a few registry keys with obvious (or even cryptic) names. The same idea could be carried over into disk files.
We might also consider the purchase and use one of the commercial protection packages that are on the market. If this turns out to be "unbreakable", so much the better. If not, the cracker may not expect our "home brewed" code to be lurking in the background.
The second level will consist of our "real" protection. For that, we should consider things like:
Lengthy delays in actions. Consider putting off any actions for hours, days, or even weeks. Consider using a different scheme each time.
Tests that can trigger multiple actions. Have two or more actions for each possible test, triggered at different places in your code.
Tests that occurs one time in a hundred or one time in two hundred.
Tests that are called when a little used component is clicked. Resist any test that is called "every time". Combine the little used component with a random number generator.
Delayed tests that occur only with long periods of use. For example, perform the date test when the program has been running for two or three hours.
Delayed tests that occur only after a certain number of days have elapsed or after the user has run the program a large number of times.
Tests which only occur within small time windows. We could check every Thursday between 1:00 p.m. and 1:15 p.m.
The purpose of the above is to fool the cracker, but all have the drawback of offering limited protection. The solution is to use multiple tests. We want to do that in any case, because the cracker may find some of them.
The Demo
There are two versions of the demo. The first contains no protection. I did this to thoroughly test the program prior to installing the protection. On a large project with multiple programmers, this may not be practical. The demo converts linear measurements from one unit to another. In this case, the only units supported are inches, feet and yards. (Listing 2 contains the protected version, edited to save space.) Listing 2 - Cracker2.pas (heavily editted)
unit Cracker2;
(?
type
  TLinear = (Inches, Feet, Yards);
  TLinearMeasurement = class(TObject)
  (?
  end;
  { simple date comparison type }
  TDateCompare = class(TObject)
  private
    FCutOff,
    FCurrent : TDateTime;
  protected
    function GetCutOff: TDateTime;
    function GetCurrent: TDateTime;
    procedure SetCutOff(Value: TDateTime);
    procedure SetCurrent(Value: TDateTime);
  public
    property CutOff: TDateTime
      read GetCutOff write SetCutOff;
    property Current: TDateTime
      read GetCurrent write SetCurrent;
    function DemoExpired: boolean;
  end;
  TfrmCracker = class(TForm)
  (?
  public
    { Public declarations }
    ClickCount: Integer;
    ProgramExpired: Boolean;
    function Crypt(Value: string): string;
  end;
const
  strCutOffDate = '12/12/1999';
  m1 = '1';  d1 = '1';  y1 = '1';  m2 = '2';
  d2 = '2';  y2 = '9';  y3 = '9';  y4 = '9';
  encCutOffDate = 'khukhukccc';
  numCutOffDate: TDateTime = 36506;
var
  frmCracker: TfrmCracker;
  Measurement: TLinearMeasurement;
  DateCompare: TDateCompare;
  TestTime: TDateTime;
implementation
function BuildDate: TDateTime;
var
  tmpStr: string;
begin
  tmpStr := '  /  /    ';
  tmpStr[1] := m1;  tmpStr[2] := m2;
  tmpStr[4] := d1;  tmpStr[5] := d2;
  tmpStr[7] := y1;  tmpStr[8] := y2;
  tmpStr[9] := y3;  tmpStr[10] := y4;
  Result := StrToDate(tmpStr);
end;
{ TDateCompare methods }
(?
function TDateCompare.DemoExpired: boolean;
begin
  Result := (GetCurrent > GetCutOff);
end;
(?
procedure TfrmCracker.FormCreate(Sender: TObject);
begin
  Measurement := TLinearMeasurement.Create;
  { Test 1 - Immediate Date Test on Startup }
  DateCompare := TDateCompare.Create;
  with DateCompare do
  begin
    SetCurrent(Date);
    SetCutOff(StrToDate(strCutOffDate));
    if DemoExpired then
     (?
  end;
  (?
  randomize;
  ClickCount := 0;
  ProgramExpired := False;
  { set time to test, five minutes in future }
  DecodeTime(Now, caHour, caMin, caSec, caMSec);
  TestTime := Date +
    EncodeTime(caHour, caMin+5, caSec, caMSec);
end;
procedure TfrmCracker.btnClearClick(Sender: TObject);
begin
  (?
  { Mouse Click Test }
  inc(ClickCount);
  if ClickCount > 20 then
  begin
    with DateCompare do
    begin
      SetCurrent(Date);
      SetCutOff(numCutOffDate);
      if DemoExpired then
        (?
  end;
    ClickCount := 0;
  end;
end;
procedure TfrmCracker.btnCalculateClick(Sender: TObject);
begin
  { Test 2 - Test called when calculate button is clicked
    Same general format as test 1, but we are using the
    second version of the hardwired date }
  with DateCompare do
  begin
    SetCurrent(Date);
    SetCutOff(BuildDate);
    if DemoExpired then
      (?
  end;
(?
end;
procedure TfrmCracker.editLengthKeyPress(Sender: TObject; var Key: Char);
begin
  if not(Key in ['0'..'9','.',#8]) then
  begin
    Key := #0;
    exit;
  end;
  if (Key = '.') and (pos('.',editLength.Text) > 0) then
    Key := #0;
end;
procedure TfrmCracker.editLengthExit(Sender: TObject);
begin
  { Test 3 - Each time a user exits the editLength box, a
    random number is generated}
  if trunc(random(100)) > 67 then
    with DateCompare do
    begin
      SetCurrent(Date);
      SetCutOff(StrToDate(Crypt(encCutOffDate)));
      if DemoExpired then
        (?
  end;
end;
procedure TfrmCracker.editLengthClick(Sender: TObject);
begin
  { Mouse Click Test }
  inc(ClickCount);
  if ClickCount > 20 then
  begin
    with DateCompare do
    begin
      SetCurrent(Date);
      SetCutOff(numCutOffDate);
      if DemoExpired then
        (?
    ClickCount := 0;
  end;
end;
procedure TfrmCracker.rgrpFromClick(Sender: TObject);
begin
  { for Mouse Click Test }
  inc(ClickCount);
  { Test 5 - Delayed Response Test: Testing Phase }
  with DateCompare do
  begin
    SetCurrent(Date);
    SetCutOff(StrToDate(strCutOffDate));
    ProgramExpired := DemoExpired;
  end;
end;
procedure TfrmCracker.rgrpToClick(Sender: TObject);
begin
  { for Mouse Click Test }
  inc(ClickCount);
  { Test 5 - Delayed Response Test: Testing Phase }
  with DateCompare do
  begin
    SetCurrent(Date);
    SetCutOff(StrToDate('12' + '/12/' + '1999'));
    ProgramExpired := DemoExpired;
  end;
end;
procedure TfrmCracker.editLengthChange(Sender: TObject);
begin
  { Test 5 - Delayed Response Test: Action Phase }
  if ProgramExpired then
    (?
  ProgramExpired := False;
end;
procedure TfrmCracker.lblLengthClick(Sender: TObject);
begin
  { Test 6 - Delayed Time Test }
  if Now > TestTime then
    with DateCompare do
    begin
      SetCurrent(Date);
      SetCutOff(numCutOffDate);
      if DemoExpired then
       (?
    end; { with }
  ProgramExpired := False;
end;
function TfrmCracker.Crypt(Value: string): string;
var
  tmpStr: String;
  len, strPos: Integer;
begin
  tmpStr := '';
  len := Length(Value);
  for strPos := 1 to len do
    tmpStr := tmpStr + chr( ord('Z') xor ord(Value[strPos]));
  Result := tmpStr;
end;
end.
The protected version makes six tests.
This test is called in the FormCreate event. It is the most expected test, a comparison on startup followed by an immediate action.
This test occurs in the btnCalculateClick event. It is called every time the user clicks the button. Action is again immediate.
This test is made in the editLengthExit event. It is the first test that attempts any form of deception. A random number between 0 and 99 is generated. If this is above 67, then the test is run. This works out to about one chance in three. For a real application, the range should be larger and the cut off number should be set much higher. Action is immediate.
This test appears in the btnClearClick event. The code tests whether the global variable ClickCount exceeds 20. The low number makes the code easier to test, but in a real application, it should probably be set much higher. ClickCount is incremented whenever the user clicks on a radio group or the edit box. Again, action is immediate.
This is the first delayed response test. The test is made when either radio group is clicked. If the test fails, a global variable (ProgramExpired) is set to true. This variable is checked each time the value in the edit box changes. If true, then action is taken.
This test is set up in the FormCreate method by setting a time for testing. For demonstration purposes, this is only five minutes into the future. Each time a label is clicked, the time is checked. If it has been exceeded, a date test is made, followed by immediate action.
Some Final Thoughts
One problem with fighting the crackers is that code quality tends to suffer. Even in the simple demo program, a great deal of complexity has been added. Best practices have had to suffer. Compare the original to the protected version and you will see what I mean. Unfortunately, this added complexity is necessary. Every step we take to make our code maintainable seems to aid the cracker.
In the demo, I created a simple date comparison object. If this were a real-world program, I would have created multiple objects or at least multiple ways to make a date comparison. As it stands, the single object is a potential weakness. In a real-world application, I would also store the date in multiple encrypted formats. Some of these would have been buried in functional code. There would also be more than one encryption routine, and these would not have been modeled on the toy used here.
This article presents some of my ideas to make cracking a little bit harder. As you can guess, I've barely scratched the surface. It is possible to build a career combating crackers. I'm sure many of you have ideas of your own. If you do, I would be interested in hearing from you.
PYG19周年生日快乐!

该用户从未签到

发表于 2006-7-6 13:31:30 | 显示全部楼层
感谢惊涛兄的转载了,先收藏了.
PYG19周年生日快乐!
您需要登录后才可以回帖 登录 | 加入我们

本版积分规则

快速回复 返回顶部 返回列表