hackme: Deconstructing an ELF File

This write-up complements the analysis of

hackme: Deconstructing an ELF File

There are a couple of reasons behind this,

  • This is by far the most difficult ELF32 binary I’ve analysed, primarily because the symbols are stripped, making commands like diassemble main in gdb impossible.
  • The share library funcitons in libc.so referenced in this program are shwon as ds:0xnnnnnnn, which involves some work to guess who is who.
  • The original article does not show the correct password (however, works with some alternatives) in the first place, which will be worked out in this write-up.

I’ve learned a lot when trying to analyse this binary, therefore, I wanted to note down the parts I think is important for future reference and help other people.

If you would like to give the binary a try, feel free to download it from the original webiste above.

In this write-up, I will give more words on analysing the logic of the binary pertaining to revealing the password.

Let’s get started!

(BIG THANKS TO THE AUTHOR OF THE ORIGINAL ARTICLE! BTW)

The Start

Since the program has anit-debugger built-in, we need to preload a fake ptrace() function from our custom library to get around it, the steps are well-documented there, which won’t repeat there.

One thing I’d like to add though, is if we need to load it up in gdb for dynamic analysis, we can use the following command to set the LD_PRELOAD environment variable.

gdb-peda$ set environment LD_PRELOAD=./fake.so

gdb-peda$ run

Above assumes gdb-peda is used, and the custom fake.so share library is in the same directory as the hackme binary.

Reversing the logic

As explained in the original write-up, the code that is pertaining to the password authentication can be narrowed down to the following snippet, which instead, will be annotated thoroughly in this post,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
 8048591:	55                   	push   ebp
8048592: 89 e5 mov ebp,esp
8048594: 57 push edi
8048595: 56 push esi
8048596: 53 push ebx
8048597: 81 ec 98 00 00 00 sub esp,0x98

;Password, please?
804859d: 68 99 87 04 08 push 0x8048799

;printf()
80485a2: ff 15 94 99 04 08 call DWORD PTR ds:0x8049994

;user input stored at [ebp-0x7c]
80485a8: 8d 45 84 lea eax,[ebp-0x7c]

;0x8048799 stored in ebx
80485ab: 5b pop ebx
80485ac: 5e pop esi
80485ad: 50 push eax

;%s
80485ae: 68 ac 87 04 08 push 0x80487ac

;scanf()
80485b3: ff 15 90 99 04 08 call DWORD PTR ds:0x8049990
80485b9: 83 c4 10 add esp,0x10

;set eax to 0 to prepare for 80485c1
80485bc: 31 c0 xor eax,eax

;dlopen@plt is used as a base addr for jmp, dlopen@plt+0x1f1 = 80485c1
80485be: eb 01 jmp 80485c1 <dlopen@plt+0x1f1>

80485c0: 40 inc eax

;user input stored at [ebp+eax*1-0x7c], increment eax to loop thru the string until the end ('\0')
80485c1: 80 7c 05 84 00 cmp BYTE PTR [ebp+eax*1-0x7c],0x0

80485c6: 75 f8 jne 80485c0 <dlopen@plt+0x1f0>

;after the end of user input is reached, proceed to the next
80485c8: 31 db xor ebx,ebx

;password should be 19 chars long
80485ca: 83 f8 13 cmp eax,0x13
80485cd: 0f 94 c3 sete bl;sete set if equal

;esi=10
80485d0: be 0a 00 00 00 mov esi,0xa

; random() returns a random number (random#), which makes the remainder of random#/0x13 random.
; it then picks the (remainder+1)th char in user input, and the 1-byte hex code positioned the same in the predefined sequence.
; remainder decides the # of times eax will be applied with the loop (add + multiply itself)
; user input will be xor'ed with the least 1-byte of the product of above step, to see if the result equals to predefined hex code in the same position.
; above process is repeated 10 times, since the position picked in the user input is random, we need to make sure every char in user input satisfy above xor'ed check.
80485d5: e8 b6 fd ff ff call 8048390 <random@plt>
80485da: b9 13 00 00 00 mov ecx,0x13

;extend sign bit of eax to edx
80485df: 99 cdq

;(edx:eax) / ecx, eax:quotient, edx:remainder
80485e: f7 f9 idiv ecx
80485e2: 31 c0 xor eax,eax

;take the remainder as an index, store the [remainder+1]th char from user input in edi,
;and from the same poistion from predefined hex sequence at base addr 0x804869c, store it in cl
80485e4: 8a 8a 9c 86 04 08 mov cl,BYTE PTR [edx+0x804869c]
80485ea: 0f b6 7c 15 84 movzx edi,BYTE PTR [ebp+edx*1-0x7c]
80485ef: 42 inc edx

;put edx+1 at ebp-0x8c
80485f0: 89 95 74 ff ff ff mov DWORD PTR [ebp-0x8c],edx

;set edx = 0
80485f6: 31 d2 xor edx,edx
80485f8: eb 0c jmp 8048606 <dlopen@plt+0x236>

;eax starts from 0 here, eax *= 0x6d01788d, overflow might occur
80485fa: 69 c0 8d 78 01 6d imul eax,eax,0x6d01788d
8048600: 42 inc edx

;eax += 12345
8048601: 05 39 30 00 00 add eax,0x3039
8048606: 3b 95 74 ff ff ff cmp edx,DWORD PTR [ebp-0x8c]
804860c: 7c ec jl 80485fa <dlopen@plt+0x22a>

;edi = one char in user input, eax is the final number
804860e: 31 f8 xor eax,edi

;cl is the predefined hex sequence \xnn
8048610: 38 c1 cmp cl,al
8048612: b8 00 00 00 00 mov eax,0x0

;move eax to ebx if cl != al
8048617: 0f 45 d8 cmovne ebx,eax
804861a: 4e dec esi

;jump back to random() to generate another random number if esi != 0
;dec affects ZF flag
804861b: 75 b8 jne 80485d5 <dlopen@plt+0x205>

804861d: 85 db test ebx,ebx
804861f: a1 94 99 04 08 mov eax,ds:0x8049994

;jump to Oops if ebx is 0
8048624: 74 0a je 8048630 <dlopen@plt+0x260>
8048626: 83 ec 0c sub esp,0xc

;Congratulations!
8048629: 68 af 87 04 08 push 0x80487af
804862e: eb 08 jmp 8048638 <dlopen@plt+0x268>
8048630: 83 ec 0c sub esp,0xc

;Oops!
8048633: 68 c1 87 04 08 push 0x80487c1

;address of printf() is moved into eax at 804861f
8048638: ff d0 call eax
804863a: 83 c4 10 add esp,0x10
804863d: 8d 65 f4 lea esp,[ebp-0xc]
8048640: 5b pop ebx
8048641: 5e pop esi
8048642: 5f pop edi
8048643: 5d pop ebp
8048644: c3 ret

Reversing the password

The logic is clear now, let’s write a python script to do the reverse to reveal the password.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#/usr/bin/env python3

def reversing_password():

# hex sequence stored between 0x804869c - 0x80486ae (19 bytes)
predefval = ['0x6a', '0xfb', '0x4c', '0x8d', '0x58', '0x0f', '0xd4', '0xe8', '0x94',
'0x98', '0xee', '0x6b', '0x18','0x30', '0xe0', '0x55', '0xc5', '0x28', '0x0e']

m = 0
password = []

for i in range(0, 19):
m *= int('0x6d01788d', 16)
m += int('0x3039', 16)

password.append(chr(int(hex(m)[-2:], 16) ^ int(predefval[i][-2:], 16)))

print(''.join(password))

reversing_password()

Executing this script produces the following result,

1
2
3
4
5
6
$ hackme.py
SesameOpenYourself!

$ ./hackme
Password, please? SesameOpenYourself!
Congratulations!