- Name - unknown
- Category - Reverse Engineering
- Points - 3
- Description - n/a
- Binary - Download here
root@kali: ~/Desktop
root@kali:~/Desktop# exiftool unknown
ExifTool Version Number : 9.74
File Name : unknown
Directory : .
File Size : 89 kB
File Modification Date/Time : 2016:09:24 12:34:41-04:00
File Access Date/Time : 2016:10:16 07:49:40-04:00
File Inode Change Date/Time : 2016:10:16 07:49:40-04:00
File Permissions : rwxrw-rw-
File Type : Win32 EXE
MIME Type : application/octet-stream
Machine Type : Intel 386 or later, and compatibles
Time Stamp : 2016:07:31 20:00:00-04:00
PE Type : PE32
Linker Version : 12.0
Code Size : 62464
Initialized Data Size : 35840
Uninitialized Data Size : 0
Entry Point : 0x3771
OS Version : 5.1
Image Version : 0.0
Subsystem Version : 5.1
Subsystem : Windows command line
root@kali:~/Desktop#
Add the .exe extension and run with the argument "some_random_input":
Command Prompt
C:\>unknown.exe some_random_input
No rite arguhments!
C:\>
Load it in IDA and search for the string "No rite arguhments!". This leads us to function sub_4027A0, at the bottom of which we have the following:
Now that we know where we need to end up, let's start tackling this function top-down:
The first basic block ensures that we have supplied 1 argument to the binary. The second basic block calls function __LDint with values 0x72 ('r') and the path to the executable, and returns a substring of the input path starting from the last 'r' encountered. For example if the path is C:\somefolder\unknown.exe, the returned string is r\unknown.exe. If the character 'r' is not present, the execution flow jumps to the "No rite arguhments!" basic block. This suggests that the path and/or name of the binary have to be some specific values, and hence why the binary was called unknown and lacked an extension.
Running strings on the binary we get an interesting entry:
root@kali: ~/Desktop
root@kali:~/Desktop# strings unknown | grep -i pdb
C:\extraspecial.pdb
root@kali:~/Desktop#
Change the name of the binary to extraspecial.exe; the location doesn't matter since the name contains an 'r', and the result of __LDint will always return raspecial.exe.
The binary then removes the first character of this string (which is always 'r') and passes it to sub_402760 which does some computation on it and returns an integer. As this function is used quite a few times throughout the binary, from now on I will be calling it singlify and can be implemented in the following way:
def singlify (input_string): temp_ans = 0 for char in input_string: temp_ans = temp_ans * 37 + ord(char) temp_ans = temp_ans & 0xffffffff return temp_ansIn essence, the binary then does the following:
- Singlifies the subpath (described above)
- Singlifies the argument we passed
- Computes length of argument we passed, and increments it
- Allocates memory of size 0x40000 and copies itself in this region
- Searches for string "RSDS" in this region (finds it twice)
- Copies the string located next to "RSDS"; let's call this the key
- Frees previously-allocated memory region
- Byte 1 : Always 0x00
- Byte 2 : Length (argv[1]) + 1
- Bytes 3-4 : (Singlify ( __LDint(binary path) ) & 0xFFFF) - 1
- Concatenates it with "[`abcdefghijklmnopqrstuvwxyz]Flare On!"
- Chooses the next char from []; 1st round = `, 2nd round = a, 3rd = b, etc..
- Singlifies result
- Compares result with part of encrypted/decrypted buffer that was computed earlier
import string import struct import sys result = ["2F3E61EE","45EB79DE","3D2F1BAF","D7BB4787", "9CC49A73","AEF5A4C9","C1C53246","249B02A0", "595016D6","5194B7A6","BA239DE7","CE92AE8A", "181A9985","9958E0FE","94790C43","6FF3B91A", "8124C470","CF27BD05","6F6EFFC4","7C84775A", "B37792DD","FF3C8425","44A9DC5F","9628E48E", "C761E92A","DA3177A7"] nonce = "`abcdefghijklmnopqrstuvwxyz" def singlify(input_string): temp_ans = 0 for char in input_string: temp_ans = temp_ans * 37 + ord(char) temp_ans = temp_ans & 0xffffffff return temp_ans for id in range(len(result)): for x in string.printable: test_string = x + nonce[id] + "FLARE On!" singlified_string = singlify(test_string) endian_string = format(struct.unpack("<I", struct.pack(">I", singlified_string))[0], 'x') if endian_string.upper() == result[id]: sys.stdout.write(x) breakTesting the result:
Command Prompt
C:\>extraspecial.exe Ohs0pec1alpwd@flare-on.com
yOU MAKE GOOD Arguhments!
C:\>
Hi, Apologies for my n00bness but I don't understand how did you get the hash for result in your script above?
ReplyDeleteHey .. god question .. I didn't explain it fully. The 'result' string is basically the decrypted buffer desribed earlier. You can either get it
ReplyDelete1) straight after it has been properly decrypted (which happens when name of the binary = extraspecial.exe and len(argv[1]) == 26; or
2) you can get it byte by byte from the compare statement which is the 2nd to last instruction in the yellow basic block
hi.. nice post.
ReplyDeletecan you explain this part in the code?
http://i.imgur.com/QwzsgsW.png