Solution-rezk2ll-BeatME
Description
Here is my write-up for the rezk2ll BeatME crackme, available here.
MD5 | 7f3007983ee8717dc0bbc377fe5a741d BeatMe |
---|---|
SHA1 | 9408039d46e0ffdaa0d4c124823ad078fdc22277 BeatMe |
SHA256 | 783c1be3b242f0cd6fa7f80fd47d370ba06387fa588f73327229a4dfa6f59cbe |
File | ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped |
What does it look like?
When we start the program, it displays a banner and asks for a username and a password. If we don't provide the expected password, it displays a failure message:
$ ./BeatMe ____ _ __ __ | __ ) ___ __ _| |_| \/ | ___ | _ \ / _ \/ _` | __| |\/| |/ _ \ | |_) | __/ (_| | |_| | | | __/ |____/ \___|\__,_|\__|_| |_|\___| | ReZK2LL USERNAME : aldeid PASSWORD : aldeid NOPE , YOU LOSE
Analysis
Obfuscated strings
Strings are obfuscated as depicted below:
.data:080492F0 44 50 53 53 46 44 55 21 2D 21+aDpssfduZpvXjo db 'DPSSFDU!-!ZPV!XJO!',0Bh,0
.data:080492F0 5A 50 56 21 58 4A 4F 21 0B 00 ; DATA XREF: f_check+9A�o
.data:080492F0 ; f_check+AE�o
.data:080492F0 ; CORRECT , YOU WIN
.data:08049304 4F 50 51 46 21 2D 21 5A 50 56+aOpqfZpvMptf db 'OPQF!-!ZPV!MPTF!',0Bh,0
.data:08049304 21 4D 50 54 46 21 0B 00 ; DATA XREF: f_check:loc_80482C5�o
.data:08049304 ; f_check+F5�o
.data:08049304 ; NOPE , YOU LOSE
.data:08049304 ;
.data:08049316 56 54 46 53 4F 42 4E 46 21 3B+aVtfsobnf db 'VTFSOBNF!;!',0 ; DATA XREF: start+5�o
.data:08049316 21 00 ; start+19�o
.data:08049316 ; USERNAME
.data:08049322 51 42 54 54 58 50 53 45 21 3B+aQbttxpse db 'QBTTXPSE!;!',0 ; DATA XREF: start+59�o
.data:08049322 21 00 ; start+6D�o
.data:08049322 ; PASSWORD :
.data:0804932E 21 60 60 60 60 21 21 21 21 21+asc_804932E db '!````!!!!!!!!!!!!!`!!!``!!``',0Bh,0
.data:0804932E 21 21 21 21 21 21 21 21 60 21+ ; DATA XREF: sub_8048132�o
.data:0804932E 21 21 60 60 21 21 60 60 0B 00 ; sub_8048132+14�o
.data:0804932E ; ____ _ __ __
.data:0804932E ;
.data:0804934C 7D 21 60 60 21 2A 21 21 60 60+a0 db '}!``!*!!```!!``!`}!}`}!!]0!!}!```',0Bh,0
.data:0804934C 60 21 21 60 60 21 60 7D 21 7D+ ; DATA XREF: sub_8048132+20�o
.data:0804934C 60 7D 21 21 5D 30 21 21 7D 21+ ; sub_8048132+34�o
.data:0804936F 7D 21 21 60 21 5D 21 30 21 60+a00A00 db '}!!`!]!0!`!]0!`a!}!``}!}]0}!}0!`!]',0Bh,0
.data:0804936F 21 5D 30 21 60 61 21 7D 21 60+ ; DATA XREF: sub_8048132+40�o
.data:0804936F 60 7D 21 7D 5D 30 7D 21 7D 30+ ; sub_8048132+54�o
.data:08049393 7D 21 7D 60 2A 21 7D 21 21 60+a00 db '}!}`*!}!!``0!)`}!}!}`}!}!!}!}!!``0',0Bh,0
.data:08049393 60 30 21 29 60 7D 21 7D 21 7D+ ; DATA XREF: sub_8048132+60�o
.data:08049393 60 7D 21 7D 21 21 7D 21 7D 21+ ; sub_8048132+74�o
.data:080493B7 7D 60 60 60 60 30 21 5D 60 60+a0SfL3mm db '}````0!]```}]``-`}]``}`}!!}`}]```}!}!Sf[L3MM',0Bh,0Bh,0
.data:080493B7 60 7D 5D 60 60 2D 60 7D 5D 60+ ; DATA XREF: sub_8048132+80�o
.data:080493B7 60 7D 60 7D 21 21 7D 60 7D 5D+ ; sub_8048132+94�o
The function at offset 0x080481D3 decrypts these strings by substracting 1 to each character of the string as shown below:
The following python script helps decrypting the strings. Just place your cursor on the string to decrypt and call the script:
start = ScreenEA()
go = True
i = 0
tmp = []
while go:
b = Byte(start+i)
if b == 0:
go = False
else:
tmp.append(b - 1)
i += 1
MakeRptCmt(start, ''.join([chr(i) for i in tmp]))
Username input
The programs starts by displaying a banner (BeatMe in ASCII art) and prompting for a USERNAME using sysread (with EAX set to 0x3 for read). The username is saved to memory location 0x80493EC:
.text:08048080 public start
.text:08048080 start proc near
.text:08048080 call f_display_banner
.text:08048085 mov esi, offset aVtfsobnf ; USERNAME
.text:0804808A call f_decrypt_message
.text:0804808F mov eax, 4 ; EAX = 4 (sys_write)
.text:08048094 mov ebx, 1 ; fd
.text:08048099 mov ecx, offset aVtfsobnf ; USERNAME
.text:0804809E mov edx, 0Ch ; len
.text:080480A3 int 80h ; LINUX - sys_write
.text:080480A5 mov eax, 3 ; EAX = 3 (sys_read)
.text:080480AA xor ebx, ebx ; fd
.text:080480AC mov ecx, offset my_username
The length of the username should be between 3 and 9 characters (don't forget that strings end with a trailing 0x0 to indicate the end of the string):
.text:080480B8 83 F8 03 cmp eax, 3 ; \ if len(my_username) <= 3
.text:080480BB 0F 8E DF 01 00 00 jle goto_LOOSE ; / goto LOOSE
.text:080480C1 83 F8 0A cmp eax, 10 ; len(username) < 10
.text:080480C4 7C 0C jl short loc_80480D2
Password input
If the length of the user input was correct, the password is then prompted, using the same syscall:
Expected password
First character
The sub_80481E4 function is then called. This is where the expected password will be built and compared to the password provided by the user.
The first character of the expected password corresponds to the length of the user input:
.text:080481E4 f_check proc near
.text:080481E4 mov esi, offset my_password
.text:080481E9 mov bl, [esi] ; BL = my_password[0]
.text:080481EB sub bl, 48 ; BL = ord(my_password[0]) - 48
.text:080481EB ; -48 used to transform ascii char to num
.text:080481EE mov al, ds:len_username
.text:080481F3 cmp al, bl ; my_password[0] should be equal to
.text:080481F3 ; len(user_name) + 48
.text:080481F5 jnz goto
-48 is actually substracted from the 1st character to do the conversion between the string value and the numeric one. The below example depicts it in python, for a length of 6:
>>> len_str = "6" >>> ord(len_str) 54 >>> ord(len_str) - 48 6
Second character
The second character of the expected password corresponds to the 3rd character of the username:
.text:0804822E loc_804822E:
.text:0804822E popa
.text:0804822F inc esi
.text:08048230 mov bl, [esi] ; BL = my_password[1]
.text:08048232 mov al, ds:byte_80493EE ; AL = my_username[2]
.text:08048237 cmp bl, al ; my_password[1] should be equal
.text:08048237 ; to my_username[2]
.text:08048239 jnz short goto_LOOSE
Remaining characters
Each of the remaining characters of the expected password correspond to the characters of the username + the length of the username divided by 2 (shr 1) + 1:
Keygen
Below is my keygen as well as a demo of it in action:
#!/usr/bin/env python
import sys
def keygen(my_username):
my_password = []
# 1st char of password is username length
my_password.append(str(len(my_username)))
# 2nd char of password is 3rd char of username
my_password.append(my_username[2])
# Remaining chars of password are username[i] + len(username)>>1 + 1
for i in my_username:
my_password.append(chr( ord(i) + (len(my_username)>>1) + 1 ))
return ''.join(my_password)
if __name__ == '__main__':
if len(sys.argv) < 2:
print "Usage: %s <username>" % sys.argv[0]
sys.exit(1)
my_username = sys.argv[1]
if len(my_username) <= 2 or len(my_username) >= 9:
print "Username should have between 3 and 9 characters"
sys.exit(2)
print "USERNAME: %s" % my_username
print "PASSWORD: %s" % keygen(my_username)
Here is the keygen in action:
$ ./keygen.py Usage: ./keygen.py <username> $ ./keygen.py aldeid USERNAME: aldeid PASSWORD: 6dephimh
Comments
Keywords: assembly elf reverse-engineering crackme rezk2ll beatme