main function and its parameters#
In Chapter 17, the program was unpacked through remote debugging, and the program used in this experiment comes from the unpacked program in Chapter 17. Here, the author's provided program is used directly, without loading the program that was unpacked by myself.
This chapter analyzes the above program and writes a keygen.
Review the program, 32-bit architecture.
Open IDA and load the unpacked program, first check the strings.
After running this program, it outputs Pone un User
in the command line, which in English is Enter a user
.
Click on Pone un User
to jump to the specific location in the program.
Press the "X" key to view string references.
Click OK to enter the specific location of the reference.
In the above image, in the function with ebp as the base, the push ebp
instruction is first executed to save the ebp value of the previous function onto the stack, then the mov ebp, esp
instruction is executed, setting ebp as the base for calculating the function's local variables, parameters, and buffer.
Then, the sub esp, 94h
instruction is executed to allocate 0x94 bytes from the base of ebp for storing local variables and buffers.
Double-click any variable or parameter of the function, IDA will display the static stack view of the function. In the image below, clicking on var_4 shows the static stack view.
In the image above, this function has two parameters, which are the default parameters of the function argc and argv, passed to the stack via the push instruction before the function call, and displayed below the return address (r). (Note: The book explains that initially there are no parameters, and only after renaming sub_201070 to main function do these two parameters appear.)
Although these two parameters are displayed, they are not referenced, so they can also be considered as having no parameters.
Security cookie and other local variables#
Returning to the static stack view, S represents STO RED EBP
, which is the ebp of the previous function saved by the push ebp
instruction. Above that is the local variable space, which generally has a var_4
variable to protect the stack and prevent buffer overflow.
var_4
has two references, the first is at the start of the function, used to store a security cookie.
This is a random number, which is XORed with ebp when the function first runs, and the result is saved to var_4
.
The above image shows another reference, where the value of var_4
is passed to ecx, then XORed with ebp to restore the original value, and then another function is called to check its value.
Click on @__security_check_cookie@4
to enter this function, as shown in the image below.
In the image above, if the value is normal, it returns; if ecx does not contain the value of _security_cookie
, the process will terminate instead of returning, which only happens when a memory overflow overwrites var_4.
Press the N key, and now rename var_4
to COOKIE
.
Rename the following function to check_cookie
.
After modification, it looks like this:
Returning to the starting point of the function, there are two variables whose functions are still unclear, one is var_90
with an initial value of 0
, and the other is size
, with an initial value of 8
, as shown in the image below.
In the image below, check the reference of var_7d
. After a certain function is called, the value of var_7d
is passed to the AL register, and then the value is passed to the EDX register to check if it is 0, determining whether to output Good reverser or Bad reverser. Therefore, this is a single-byte variable, renamed SUCCESS_FLAG, which is the final calculated flag value.
Press N to rename var_7d
.
Change the code block for successful registration on the left to green, and the code block for failed registration on the right to yellow. In the image above, as long as the JZ instruction is modified, successful registration can be achieved.
Username and password processing#
In the image above, there is another variable var_90
, with an initial value of 0. The program reads each byte from BUF, passes it to edx (at the 0x231109 instruction), and adds it to var_90
. In the first loop, it adds 0, and the result is saved to var_90
. In subsequent loops, edx adds the sum of all previous bytes, so var_90
is renamed to SUMMARY
. As shown in the image below.
In the image above, var_84
is the loop counter, which increments by 1 and only loops through the first 4 bytes. When var_84
is greater than or equal to 4, it will exit the loop. Rename it to COUNTADDR
.
To better display this loop, hold down the ctrl key, click on these three loops, right-click and select group nodes.
It finally displays as follows:
If you want to ungroup, right-click and select unhide group or click the icon.
After the loop, the password is obtained.
The program uses Buf to store the obtained password, as the program has already saved the sum of the first 4 bytes of the username.
In the image above, the program then calls the strlen()
function to get the length of the password. If the length is less than 4, it exits the program; if it is not less than 4, the program enters the green code block on the right.
In the green code block on the right, the atoi
function is used to convert the password string into a hexadecimal number, which is similar to the hex()
function in Python.
Then the hexadecimal password is XORed with 0x1234
, and the result is saved to the edx variable.
In the image above, the result of XORing the password with 0x1234
and the sum of the first 4 bytes of the username is passed to the sub_231010
function for comparison. Based on the comparison result, the output result is determined.
In the parameters of the sub_231010
function, the arg_4 parameter is the first to be passed to the stack, and these two parameters can be renamed.
Right-click and select set type
, IDA will recognize the function prototype based on the parameters.
The function declaration is shown in the image below.
IDA automatically comments to match the passed parameters.
Inside sub_231010
, before comparing the two parameters, the password variable is passed to eax, and shl eax, 1
is executed, which is equivalent to multiplying by 2.
Finally, a comparison is made. If these two numbers are equal, the program jumps to the green code and passes 1 to al, exiting the loop, and then passing to SUCCESS_FLAG
, ultimately deciding whether the registration is successful.
Algorithm Summary#
The program first sums the first 4 bytes of the username. The password is converted to hexadecimal and XORed with 0x1234, and the result is then multiplied by 2.
Next, construct a formula based on the username. The keygen is also based on this point, calculating the password based on the username.
x = password (hexadecimal number)
(x ^ 0x1234)*2 = SUCCESS_FLAG
Then x ^ 0x1234 = (SUCCESS_FLAG/2)
Since XOR operation is reversible.
A ^ B = C
A = B ^ C
Then x = (SUCCESS_FLAG/2) ^ 0x1234
Writing a Keygen in Python#
If the user inputs "pepe", with a length of less than 8 bytes, the sum of the bytes can be calculated as follows:
sum = 0
user='pepe'
length=len(user)
for i in range(length):
sum+=ord(user[i])
print(hex(sum))
Then the sum of the first 4 bytes of pepe is:
sum = 0
user='pepe'
for i in range(4):
sum+=ord(user[i])
print(hex(sum))
Next, write a keygen suitable for any valid username.
sum = 0
user=input("input user name:")
length=len(user)
for i in range(4):
sum+=ord(user[i])
if(length>=4):
print(hex(sum))
Using the input() function to get command line input, but now the above code is suitable for any valid account.
According to the previously summarized formula x = (SUCCESS_FLAG/2) ^ 0x1234
, divide the calculated result of the username by 2, and XOR it with 0x1234 to find the hexadecimal password.
sum = 0
user=input("input user name:")
length=len(user)
for i in range(4):
sum+=ord(user[i])
print(user)
if(length>=4):
print("success_flag",hex(sum))
password = (sum//2)^0x1234
print("password:",password)
The keygen is now complete, and the password has been converted from hexadecimal to decimal; the default output in Python is decimal.
The above code will crash when the input username character count reaches 8 characters because there is also a null terminator at the end of the string, which cannot exceed 8 characters including the terminator. However, inputting 7 characters is fine.
Finally, there is another issue: if the sum of the 4 characters is an odd number. Because before comparison, the password is multiplied by 2, the result will always be an even number, so this case has no solution.
Add a check process.
sum = 0
user=input("input user name:")
length=len(user)
for i in range(4):
sum+=ord(user[i])
print(sum)
print(user)
if(sum%2==0):
print("Even")
if(length>=4):
print("success_flag",hex(sum))
password = (sum//2)^0x1234
print("password:",password)
else:
print("Odd")
Check the remainder of the sum of the username bytes divided by 2. If it is not equal to 0, the sum is odd, and the corresponding password has no solution.
Odd situation:
Even situation:
Thus, the keygen is complete.