Skip to main content

Rust: Iterators are not faster; Anyway, you should use them

· 6 min read
Jan Starke
Senior Forensic Analyst

Since some time, I'm wondering whether using iterators in Rust has some benefits over traditional for loops.

The idea

Consider the following loop:

let mut sum = 0;
for i in 1..=100 {
sum += i;
}

which expands during execution to

let mut sum = 0;
sum += 1;
sum += 2;
sum += 3;
// ...

The way we expressed the sum also defines in which order the summation has to be done. But, to be honest, we are not interested in the concrete order. In fact, the single summations could be done on multiple cores simultaneously.

An alternative way of implementing the above algorithm using iterators could be

let sum: u32 = (1..=100).sum();

Can this be faster? I don't know. So, let's use a more complicated example and check it out

The test case

To test which approach is faster, we need to prevent the compiler to optimize too simple things. For example, the above code using the for loop compiles to

    xor     eax, eax
mov ecx, 100
xor edx, edx
.LBB45_1:
mov esi, edx
lea edx, [rsi, +, 1]
cmp esi, 100
cmovae edx, ecx
add eax, esi
cmp esi, 99
ja .LBB45_3
cmp edx, 101
jb .LBB45_1
.LBB45_3:
ret

while the iterator based approach compiles to

    mov     eax, 5050
ret

Of course, this results from the fact that the compiler knows a lot about the code to be optimized. So, again, we need a more complicated code.

We'll do the following:

  1. iterate through the first 1_000_000 integers, stored as String
  2. convert the String into u64
  3. filter out numbers which are greater or equal to 100_000
  4. filter out numbers which are no prime numbers
  5. calculate the sum of those numbers

We use the following implementations:

Loop approach

#[inline(never)]
fn sum1<'s>(data: impl Iterator<Item = &'s String>) -> u64 {
let mut s = 0;
for i in data {
if let Ok(i) = i.parse::<u64>() {
if i < 100_000 {
if i.is_prime() {
s += i;
}
}
}
}
s
}

Iterator approach

#[inline(never)]
fn sum2<'s>(data: impl Iterator<Item = &'s String>) -> u64 {
data.filter_map(|s| s.parse::<u64>().ok())
.filter(|i| i < &100_000)
.filter(|i| i.is_prime())
.sum()
}

Common code

To implement the is_prime method, I use the following simple code, which is using the primes crate:

trait IsPrime {
fn is_prime(&self) -> bool;
}

impl IsPrime for u64 {
fn is_prime(&self) -> bool {
primes::is_prime(*self)
}
}

In addition, I generate a static data array NUMBERS:

lazy_static! {
static ref NUMBERS: Vec<String> = (1..1_000_000).map(|i| format!("{i}")).collect();
}

Benchmarking code

To test the speed of the above code, I'm using cargo bench with the following test code


#[cfg(test)]
mod tests {
use super::*;
use test::Bencher;

#[bench]
fn test_sum1(b: &mut Bencher) {
b.iter(|| assert_eq!(sum1(NUMBERS.iter()), 454396537))
}

#[bench]
fn test_sum2(b: &mut Bencher) {
b.iter(|| assert_eq!(sum2(NUMBERS.iter()), 454396537))
}
}

Benchmarking results

Running the benchmark yields the following result:

test tests::test_sum1 ... bench:  25,637,873.10 ns/iter (+/- 1,921,695.73)
test tests::test_sum2 ... bench: 25,639,836.30 ns/iter (+/- 1,405,975.61)

OMG, what has happened???!!? Both approaches are equally performant. There seems to be no important difference between using a loop or iterator.

That's our first result: It doesn't matter whether you use traditional loops or iterators. You can expect both to be similar fast.

Iterators on steroids

Let's change out iterator based sum function a little bit: replacing Iterator by ParallelIterator, which is a trait from the rayon crate. rayon allows you to do anything in parallel what you can do with normal iterators. So, if you have some code which is based on using iterators, you can simply switch to parallel iterators.

We add an additional sum function:


#[inline(never)]
fn sum3<'s>(data: impl ParallelIterator<Item = &'s String>) -> u64 {
data.filter_map(|s| s.parse::<u64>().ok())
.filter(|i| i < &100_000)
.filter(|i| i.is_prime())
.sum()
}

Be aware that the only difference to sum2 is the trait ParallelIterator. No other code change is necessary.

Also, we need a benchmarking function:

#[bench]
fn test_sum3(b: &mut Bencher) {
b.iter(|| assert_eq!(sum3(NUMBERS.par_iter()), 454396537))
}

As you can see, there is another difference: to create a ParallelIterator, we use the function par_iter() instead of iter(). So, you have the full control whether you want to use parallel iterators or not.

Benchmarking parallel iterators

The benchmarking result (on a system with 4 virtual CPUs) is very informative:

test tests::test_sum1 ... bench:  25,621,303.60 ns/iter (+/- 1,528,567.84)
test tests::test_sum2 ... bench: 25,752,965.60 ns/iter (+/- 2,927,367.52)
test tests::test_sum3 ... bench: 11,761,348.20 ns/iter (+/- 2,472,399.91)

We can conclude:

  • using the parallel iterator in our test case is significantly faster
  • migrating the code from traditional iterators to parallel iterators is simple

Iterators as design pattern

Using iterators is an implementation of the Pipes-and-Filters design pattern:

This approach has a lot of practical benefits:

  • it is easy to
    • insert a processor into the pipeline
    • remove one processor from the pipeline
    • change the order of processors in the pipeline
  • every single processor is independant of the other, which allows to distribute them over multiple cores or even machines
  • it can be easier to read (if you're familiar with the concept)

Conclusion

It doesn't matter if you use traditional loops or iterators. There will be no significant performance impact. But, if you want to be able to do significant changes, such as distributing loop iterations over multiple CPUs, without breaking your code, use iterators.

One more thing

It is very important to implement you algorithm in the right way. But it is more important to understand the algorithm in detail, and to keep your implementation as neat to that as possible.

For example, let's change sum1 by inserting a break:


#[inline(never)]
fn sum1<'s>(data: impl Iterator<Item = &'s String>) -> u64 {
let mut s = 0;
for i in data {
if let Ok(i) = i.parse::<u64>() {
if i >= 100_000 {
break;
}
if i.is_prime() {
s += i;
}
}
}
s
}

and again benchmarking:

test tests::test_sum1 ... bench:  11,603,891.70 ns/iter (+/- 742,577.75)
test tests::test_sum2 ... bench: 25,899,039.20 ns/iter (+/- 1,916,178.50)
test tests::test_sum3 ... bench: 11,899,570.60 ns/iter (+/- 5,477,771.27)

What has happened? Clearly, it doesn't make sense to iterate through 1_000_000 numbers, while we're only interested in the first 100_000. Because when using a loop we know the order of the execution, we can abort as soon as we crossed the border to uninteresting numbers (those above 100_000). In our test case, this has a similar effect to the performance such as using parallel iterators.

So, again: First you need to completely understand your problem, choose the best matching algorithm, and implement it correctly. Then, you can optimize.

Analyzing Linux memory images with volatility

· 2 min read
Jan Starke
Senior Forensic Analyst

Why volatility3? Because version 2 is deprecated.


# download the current git repo
git clone https://github.com/volatilityfoundation/volatility3.git

# create local python venv
python3 -m venv venv
source venv/bin/activate

# install volatility
pushd volatility3
pip3 install -r requirements.txt
python3 setup.py build
python3 setup.py install
popd

At this point in history, you have volatility3 installed in venv.

Obtaining a matching profile

Volatility needs to know a lot about the memory layout you're going to work with. Because every linux kernel can have a different layout, you need to get the special layout for your kernel. volatility calls this the profile.

To generate the profile, you need the following:

  • the tool dwarf2json, which is a separate github project
  • the kernel with debug information (not the debug kernel)
  • the System.map file

Installing dwarf2json

Assuming you already have the go language installed, you can do the following:

git clone https://github.com/volatilityfoundation/dwarf2json.git
pushd dwarf2json
go build
popd

This compiles a binary file named dwarf2json in the current directory. Copy it to whereever you want.

Obtaining the kernel with debug information`

First, you need to know the exact kernel version. I managed to get it by grepping for Linux version in the memory image and obtained 3.10.0-862.3.2.el7.x86_64. For the distribution and this version, there existed a package named kernel-debuginfo-3.10.0-862.3.2.el7.x86_64.rpm, which I downloaded from the official repository. Now, you can extract the required file. I used rpm2cpio for this:

mkdir tmp_kernel
pushd tmp_kernel
rpm2cpio ../kernel-debuginfo-3.10.0-862.3.2.el7.x86_64.rpm |cpio -i --make-directories
popd

The resulting file in my case was usr/lib/debug/lib/modules/3.10.0-862.3.2.el7.x86_64/vmlinux. Let's keep this in mind...

Obtaining System.map

This file is part of the normal kernel package: kernel-3.10.0-862.3.2.el7.x86_64.rpm

mkdir tmp_system_map
pushd tmp_system_map
rpm2cpio ../kernel-3.10.0-862.3.2.el7.x86_64.rpm |cpio -i --make-directories
popd

I found the System.map as boot/System.map-3.10.0-862.3.2.el7.x86_64

Generating the profile

Now, we have all we need. Let's go

dwarf2json/dwarf2json linux \
--elf tmp_kernel/usr/lib/debug/lib/modules/3.10.0-862.3.2.el7.x86_64/vmlinux \
--system-map tmp_system_map/boot/System.map-3.10.0-862.3.2.el7.x86_64 \
>centos7-3.10.0-862.3.2.el7.x86_64.json

# cleaning up
rm -rf tmp_*

Installing the profile

volatility expects the profile as xz file, so let's compress it:

xz centos7-3.10.0-862.3.2.el7.x86_64.json

Assume, you have Python v3.9 and volatility3 v2.0.3, then you symbols directory is venv/lib/python3.9/site-packages/volatility3-2.0.3-py3.9.egg/volatility3/symbols/. We will refer to it as $SYMBOLS_DIR:

mkdir $SYMBOLS_DIR/linux
mv centos7-3.10.0-862.3.2.el7.x86_64.json.xz $SYMBOLS_DIR/linux

That's it. Now, you have a matching profile ready to be used.

Using regripper on MacOS

· One min read
Jan Starke
Senior Forensic Analyst
git clone https://github.com/keydet89/RegRipper3.0.git

cpan install Parse::Win32Registry

# find out where Parse::Win32Registry was installed

cp -r /usr/local/Cellar/perl/5.34.0/lib/perl5/site_perl/5.34.0/Parse .

mv *.pm Parse/Win32Registry/WinNT/

Fixing construction of the plugins path

On unixoid systems, rip.pl ignores the folder where it is stored and looks for the plugins folder in the current working directory (https://github.com/keydet89/RegRipper3.0/issues/42). This can be fixed using the following patch:

diff --git a/Analysis/RegRipper3.0/rip.pl b/Analysis/RegRipper3.0/rip.pl
index 8f626a7..9027209 100644
--- a/Analysis/RegRipper3.0/rip.pl
+++ b/Analysis/RegRipper3.0/rip.pl
@@ -67,7 +67,7 @@ $str =~ s/($path[scalar(@path) - 1])//;
# code updated 20190318
my $plugindir;
($^O eq "MSWin32") ? ($plugindir = $str."plugins/")
- : ($plugindir = File::Spec->catfile("plugins"));
+ : ($plugindir = File::Spec->catfile($str, "plugins"));
#my $plugindir = $str."plugins/";
#my $plugindir = File::Spec->catfile("plugins");
#print "Plugins Dir = ".$plugindir."\n";

Usage example

PERL5LIB=$(pwd) perl rip.pl -r Amcache.hve -p amcache_tln >amcache.tln

Convert TLN format to bodyfile format

awk -F '|' '{OFS="|";print 0,$5,0,0,0,0,0,-1,$1,-1,-1}' <test.tln |TZ=UTC mactime -b - -d

Forensic analysis of deleted `$MFT` entries

· 3 min read
Jan Starke
Senior Forensic Analyst

In the book FILE SYSTEM FORENSIC ANALYSIS, the author Brian Carrier states that "Every MFT entry also has a 16-bit sequence numberthat is incremented when the entry is allocated. For example, consider MFT entry 313 with a sequence number of 1. The file that allocated entry 313 is deleted, and the entry is allocated to a new file. When the entry is reallocated, it has a new sequence number of 2." (page 276).

I think this i partially wrong. Let's see what Microsoft is saying: "The sequence number. This value is incremented each time that a file record segment is freed; it is 0 if the segment is not used. The SequenceNumber field of a file reference must match the contents of this field; if they do not match, the file reference is incorrect and probably obsolete." (https://docs.microsoft.com/en-us/windows/win32/devnotes/file-record-segment-header)

This information is crucial when you found a file which has been content of some deleted folder. To retrieve the folders name, you need to find its $FILE_NAME information. So, you take the parent field from the $FILE_NAMEattribute of the deleted file. Let's assume this is 313-1 (where 313 is the parent entry number and 1 is its sequence number). Further, let's assume that this parent has been deleted, but the MFT entry was not reallocated:

  • If the sequence number was incremented upon reallocated, it would still be 1
  • Otherwise, if the sequence number was incremented when deleting the folder, it would be 2 now.

But how can we be sure that we can use the $FILE_NAME of 313-2, if the deleted file refered to 313-1?

Let's test what happens.

Test setup

I created a NTFS partition using Windows 8, and created three folders:

  • bulk delete
  • single delete
  • mixed delete

Each folder had two files: sample1.rtf and samples.rtf. Than I did the following:

  • delete bulk delete with all its contents
  • delete mixed delete/sample2.rtf
  • delete mixed delete with all of its contents (sample1.rtf)
  • delete single delete/sample1.rtf
  • delete single delete/sample2.rtf
  • delete single delete with all of its contents (none)

Results

After deletion, I found the following MFT references

NameEntry #Parent entry #
bulk delete37-25-5
bulk delete/sample1.rtf43-237-1
bulk delete/sample2.rtf44-237-1
single delete41-2
single delete/sample1.rtf47-241-1
single delete/sample2.rtf48-241-1
mixed delete42-2
mixed delete/sample1.rtf45-142-1
mixed delete/sample2.rtf46-142-1

Obviously, our test shows that sequence numbers are incremented right after deletion, but not nessecarily at reallocation. The Microsoft documentation is right.

$MFT file parser improved

· One min read
Jan Starke
Senior Forensic Analyst

I added some improvements to mft2bodyfile (https://github.com/janstarke/mft2bodyfile) and released version 0.5.0. Mainly, the following things have changed:

  • I fixed the problem with nonresident attributes: mft2bodyfile reads all (really: all) MFT entries and tries to correlate them. This is possible, because there is a bidirectional relationship between the base entry and the nonbase entries. Until now, I started with the base entry and tried to find the corresponding nonbase entries, which failed if the $ATTRIBUTE_LIST was nonresident. Now, I don't try to find the nonbase entries, but instead use the base reference to find the base entry.
  • The performance has improved a lot.

Only one thing (at the moment): Sometimes there are nonbase entries which belonged to base entries of deleted files (this can be detected to the sequence numbers of the base reference and the entry at the base position do not match). This is no problem, until we try to read $STANDARD_INFORMATIONor $FILENAME, which may not be existing anymore. These entries are ignored by mft2bodyfile.

$MFT file parser finalized

· 2 min read
Jan Starke
Senior Forensic Analyst

After some days of work, I finalized version 0.3.0 of mft2bodyfile (https://github.com/janstarke/mft2bodyfile), which is aimed to be a replacement for analyze_mft.py, which is abandoned.

Until now, me and my team used analyze_mft.py to extract data from the $MFT, when we got triage data from a customer. Unfortunately, analyze_mft.py has some disadvantages:

  • python2 is required
  • either the $STANDARD_INFORMATION or the $FILE_NAME attribute used to generate the timestamps, bot not both of them at the same time. This always required us to merge both outputs, which is a little bit messy
  • from time to time we had problems parsing the $MFT

So, at first we started to work on analyze_mft.py to fix our complaints, but we soon got stuck when we discovered one additional disadvantage:

  • If a file has its $FILE_NAME attribute not stored in its base entry, but in some nonbase entry which is refered by an $ATTRIBUTE_LIST attribute, then this file is not shown in the bodyfile.

You might think that "non-base MFT entries do not have the $FILE_NAME and $STANDARD_INFORMATION attributes in them", as Brian Carrier has stated in his great book. But we found that this does happen. Further investigation showed us that nearly all fast and simple tools have the same problem. So this was the last bit that led us write a tool for our own.

What are the advantages of this tool?

  • way more faster than analyze_mft.py
  • all files are displayed, even if they don't have a '$FILENAME' (Really??? Files can have no filename? Yes, they can. See below)

What are the limits of this tool?

Consider the following situation: You have a file, which has a lot of attributes. The list of attributes is so long, that it cannot be stored in an $MFT entry. So, the $ATTRIBUTE_LIST attribute is stored as a nonresident attribute, outside the $MFT. At the moment, mft2bodyfile is not able to find the corresponding $MFT entries, and will generate a filename.

Can we fix this? Yes, we can. If we detect such a situation, we can search the $MFT entries which refer to our base entry, and use those to find a $FILE_NAME attribute.

Update: fixed in 0.5.0

DoSing TLS endpoints

· 3 min read
Jan Starke
Senior Forensic Analyst

During these day, I had an interesting task: to test if som specific endpoint is vulnerable against a client-side TLS renegotiation DoS vulnerability.

Negotiating a shared secret requires a lot of computation and thus needs some CPU time. The idea of the attack is to initiate a TLS connection and then request a renegotiation of the shared secret over and over again. This would consume a lot of CPU time from the server, which is the resource we're going to exhaust.

The test

To exploit the vulnerability, you must make sure that the client consume much less CPU time than the server does. So, you have to tweak the renegotiation. I didn't do this, becaus I wasn't interested in really exhausting the server resources. I was only interested in the following questions:

  • Does the server really support client-initiated renegotiation?
  • How many renegotiations are allowed by the server before it terminates the connection?

What did I learn?

Most TLS implementations have removed the client-initiated renegotiation from its source code, so If you use an up-to-date implementation, chances are good that you are not vulnerable.

Expoit source code

use Socket;
use Net::SSLeay qw(die_now die_if_ssl_error) ;
use strict;
use warnings;
use Errno qw(EINTR EIO :POSIX);
use IO::Socket::INET;

Net::SSLeay::load_error_strings();
Net::SSLeay::SSLeay_add_ssl_algorithms();
Net::SSLeay::randomize();

my $hostname = "<yourhost>";
my $port = "443";
my $request = "HEAD / HTTP/1.1\r\n\r\n";

sub error_name($) {
my $id = shift;
my @errors = (
'SSL_ERROR_NONE',
'SSL_ERROR_SSL',
'SSL_ERROR_WANT_READ',
'SSL_ERROR_WANT_WRITE',
'SSL_ERROR_WANT_X509_LOOKUP',
'SSL_ERROR_SYSCALL',
'SSL_ERROR_ZERO_RETURN',
'SSL_ERROR_WANT_CONNECT',
'SSL_ERROR_WANT_ACCEPT'
);
die "invalid error number" unless $errors[$id];
my $result;
return $errors[$id];
}

sub display_result($$$) {
my $ssl = shift;
my $result = shift;
my $function = shift;

die_if_ssl_error("ssl $function ($!)");

if (0 != (my $error_id = Net::SSLeay::get_error($ssl, $result))) {
my $error = error_name($error_id);
print ("$function() = $error\n");

while (0 != (my $rv = Net::SSLeay::ERR_get_error())) {
my $error_desc = Net::SSLeay::ERR_error_string($rv);
print ("stack: $error_desc\n");
}
}
}

sub set_options($$) {
my $ctx = shift;
my $option = shift;
Net::SSLeay::CTX_set_options($ctx, $option)
and die_if_ssl_error("ssl ctx set options ($!)");
}

sub set_version($$) {
my $ctx = shift;
my $version = shift;
Net::SSLeay::CTX_set_ssl_version($ctx, $version)
and die_if_ssl_error("ssl ctx set version ($!)");
}

sub set_ciphers($$) {
my $ctx = shift;
my $ciphers = shift;
Net::SSLeay::CTX_set_cipher_list($ctx, $ciphers)
and die_if_ssl_error("ssl ctx set ciphers suites ($!)");
}

sub ssl_connect($$) {
my $ctx = shift;
my $socket = shift;
my $ssl = Net::SSLeay::new($ctx) or die_now("Failed to create SSL $!");
Net::SSLeay::set_fd($ssl, $socket->fileno);
my $res = Net::SSLeay::connect($ssl) and die_if_ssl_error("ssl connect ($!)");
my $error = error_name(Net::SSLeay::get_error($ssl, $res));
print ("connect() = $error\n");
print "Cipher `" . Net::SSLeay::get_cipher($ssl) . "'\n";
return $ssl;
}

sub configure_context($$) {
my $ctx = shift;
my $ciphers = shift;
set_options($ctx, &Net::SSLeay::OP_ALL);
set_options($ctx, &Net::SSLeay::OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION);
#set_options($ctx, &Net::SSLeay::OP_SSL_OP_LEGACY_SERVER_CONNECT);
set_version($ctx, &Net::SSLeay::TLSv1_2_method);
set_ciphers($ctx, $ciphers);
}
my $socket = IO::Socket::INET->new(
Type => IO::Socket::SOCK_STREAM,
Proto => 'tcp',
PeerAddr => $hostname,
PeerPort => $port,
Blocking => 1
) or die ("unable to connect: $!\n");


my $ctx = Net::SSLeay::CTX_new() or die_now("Failed to create SSL_CTX $!");
configure_context($ctx, "DHE:EDH:ECDHE:EECDH:RSA:DH:ECDH");
my $ssl = ssl_connect($ctx, $socket);

my $ret;
for (my $i=0; $i<2; $i++) {
Net::SSLeay::renegotiate($ssl);
die_if_ssl_error("ssl renegotiate ($!)");

if (! $socket->connected()) {
die("network connection has died");
}

$ret = Net::SSLeay::do_handshake($ssl);
display_result($ssl, $ret, "do_handshake");
}

Net::SSLeay::free ($ssl); # Tear down connection
Net::SSLeay::CTX_free ($ctx);
close $socket;

Pairing based cryptography in Rust

· One min read
Jan Starke
Senior Forensic Analyst

Because I was was fed up with catching memory issues in the legacy PBC code, I decided to reimplement the PBC library using Rust. You can watch my progress at https://github.com/teeshop/pbc4rust.

By the way, I like the concept of traits. This enables me to rely on the rules of universal algebra. So, my first unit tests look like this:

#[test]
fn test_z_add_zero() {
let a: Z = Z::from(12345 as i32);
assert_eq!(&a + &Z::zero(), a);
assert_eq!(&Z::zero() + &a, a);
}

#[test]
fn test_z_add_commutativity() {
let a: Z = Z::from(12345 as i32);
let c: Z = Z::from(6789 as i32);
assert_eq!(&a + &c, &c + &a);
}

#[test]
fn test_z_add_associativity() {
let a: Z = Z::from(1234 as i32);
let b: Z = Z::from(3456 as i32);
let c: Z = Z::from(91 as i32);
let d: Z = Z::from(1234 + 3456 + 91 as i32);
assert_eq!(&a + &(&b + &c), d);
assert_eq!(&(&a + &b) + &c, d);
}

Using Pairing-based cryptography in Java

· 4 min read
Jan Starke
Senior Forensic Analyst

Since some years I'm working on this topic, and guess what: It is working now :-)

Imagine the following: You are in a hospital and get some test results (e.g. from a Covid-19 test). You want to protect the result by encrypting it, but you are not sure who needs to be able to read that result later. But you don't want to decrypt and reencrypt the result later. This is, where PBC comes in play. You can do as 2nd-level encryption, which protects the data from being read by any person. To decrypt 2nd-level encrypted data, you must first convert that to 1st-level-encrypted data, which requires the public key of the person you want to grant access to you data, and your own private key. After this step, the person for who you created the 1st-level encrypted data can decrypt the data using its own private key.

This enables the following use cases:

  • Storage of confidential data in a cloud which you don't trust, while giving partners access to your data. This works as long as the cloud provider doesn't know any private keys. It even allows the cloud provider to do the transformation from 2nd-level to 1st-level, if you calculate the reencryption-key on-premise.
  • Revocation of data access by revoking the reencryption key.

What happened until now?

My starting point is the PBC Library https://crypto.stanford.edu/pbc/ which was written by Ben Lynn as part of his PhD thesis. To use this in a prototyping project, we had to transmit encrypted content and key material from one computer to another. So we needed a machine-independant representation of all of the data, which was not subject of PBC Library. So, we wrapped all the data structures used in PBC Library by C++-based structures. After this, it was possible to easily export and import cryptographic data using

  • any data format, such as JSON, ASN.1, XML, ...
  • any other PBC library (lower case L).

Our next challenge was to make the C++ library accessible for higher level languages, such as Java (required for Android devices as well as Java Backends) or Swift (required for iOS devices). As of today, we succeeded. The following is running:

package com.company;
import com.tsystems_mms.je2ee.*;

public class Main {

static {
System.loadLibrary("je2ee");
}

private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars);
}

public static void main(String[] args) {
PbcContext context = je2ee.createContext();
GlobalParameters global = je2ee.getGlobal(context);

KeyPair kp = je2ee.createKeyPair(global);
Element dek = je2ee.generateDataEncryptionKey(global);

byte[] aesKey1 = je2ee.kdf256(dek);
Tuple ciphertext = je2ee.encryptFirstLevel(je2ee.publicKey(kp), dek);
Element decryptedDek = je2ee
.decryptFirstLevel(je2ee.secretKey(kp), ciphertext);

byte[] aesKey2 = je2ee.kdf256(decryptedDek);

String aes1 = bytesToHex(aesKey1);
String aes2 = bytesToHex(aesKey2);

assert aes1.equals(aes2);
System.out.println(aes2);
}
}

Where can I get the code?

You need to recursively clone the following repo: https://github.com/T-Systems-MMS/libe2ee

Can I use this for my project?

Yes, you can, but YOU SHOULDN'T! This project is a prototype and is only made to show what's possible. Neither PBC Library nor this very project has been checked thoroughly by cryptography experts. I do not give any guarantee that this code is secure against any attacks. Maybe if someone is willing to pay for it, it may be possible to raise this code to production level. Until then, this is only a freetime project of myself.

Some insights...

Example of a public key in machine independent format

{
"root": {
"id": "a990af45-2269-537b-95c0-2d1815c6bddb",
"type": "element"
},
"a990af45-2269-537b-95c0-2d1815c6bddb": {
"type": "element",
"id": "a990af45-2269-537b-95c0-2d1815c6bddb",
"field": "3709e538-b580-5e21-a2ae-333585123e35",
"y": "d5TgGd3TwTiVhhdZpQ6L0BS5YqdzIxQVK4ZTPANMqNVvCo1qdeKSz2HZnPKqM9mazGKGZcwG9toinepFSmMvcf",
"x": "AiblhfGZ24J3IA1MNyfKaV3HGMHyCIngbR2sU9D5QyZc5IJV8yxXUGQpbiGBQ3I8N8DXgHQ1dSOKYilloO5BOq"
},
"3709e538-b580-5e21-a2ae-333585123e35": {
"type": "field",
"subtype": "curve",
"id": "3709e538-b580-5e21-a2ae-333585123e35",
"order": "aWgEPTl1tmebfs3ZETd8TJLcWlT",
"pairing": "86ec11e5-802e-5ea0-9cc6-5a429f653359",
"a": "a4cb7d3e-d31e-5472-9928-0b17725c9ae4",
"b": "a47e3ca0-d570-5fe4-bd87-245b0100dbc2",
"cofac": "1iRiNwETOuqBD7ugR7bRatbQL7JDKXfb6QfKFlQdbxb46gQKAjLCS397ZbnU"
},
"86ec11e5-802e-5ea0-9cc6-5a429f653359": {
"type": "pairing",
"id": "86ec11e5-802e-5ea0-9cc6-5a429f653359",
"G1": "3709e538-b580-5e21-a2ae-333585123e35",
"G2": "3709e538-b580-5e21-a2ae-333585123e35",
"GT": "6e9b64ba-e391-5c46-a9b9-0527b3c22212",
"Zr": "a6d00cef-ce1b-5410-bd45-41a0058b4540",
"Eq": "3709e538-b580-5e21-a2ae-333585123e35",
"Fq": "49f4d9d3-3110-5d68-a069-d035ef71b54b",
"Fq2": "df25d091-9992-5b89-9ab7-487678fbd9d3",
"r": "aWgEPTl1tmebfs3ZETd8TJLcWlT",
"phikonr": "1iRiNwETOuqBD7ugR7bRatbQL7JDKXfb6QfKFlQdbxb46gQKAjLCS397ZbnU"
},
"6e9b64ba-e391-5c46-a9b9-0527b3c22212": {
"type": "field",
"subtype": "mulg",
"id": "6e9b64ba-e391-5c46-a9b9-0527b3c22212",
"order": "aWgEPTl1tmebfs3ZETd8TJLcWlT",
"pairing": "86ec11e5-802e-5ea0-9cc6-5a429f653359",
"base": "df25d091-9992-5b89-9ab7-487678fbd9d3"
},
"df25d091-9992-5b89-9ab7-487678fbd9d3": {
"type": "field",
"subtype": "fi",
"id": "df25d091-9992-5b89-9ab7-487678fbd9d3",
"order": "11QugYb4N8ouqquotyF8IDXtacHwMTbO0nzw4DSKv860wA8OmQwaM5NwQDuGzdsaq7qNH2vjKAZYuuiiAkgzugeZZJGzPCyBalRcL5Un43QnC77oOh4PwFfDudE7kPVfi9kMYY9ETj6JjicbhyWKqsrYaU4WMTQfwjMsA1IwD08m1",
"pairing": "86ec11e5-802e-5ea0-9cc6-5a429f653359",
"base": "49f4d9d3-3110-5d68-a069-d035ef71b54b"
},
"49f4d9d3-3110-5d68-a069-d035ef71b54b": {
"type": "field",
"subtype": "montfp",
"id": "49f4d9d3-3110-5d68-a069-d035ef71b54b",
"order": "10iCaKEy0HbDHT6PP7xruLvQ6exXF4OXOWMsLG1cWzUGqHPKb3iHXFPHp5i6InsKwpSoCRA0udlrariW7VXEft1",
"modulus": "10iCaKEy0HbDHT6PP7xruLvQ6exXF4OXOWMsLG1cWzUGqHPKb3iHXFPHp5i6InsKwpSoCRA0udlrariW7VXEft1",
"negpinv": "KdbJ0wZgOLZ",
"R": "Y3IcJ7iNkOkQg5TzeRWCJ5hLYyIjT6hZsVtkHTRaFzT7sr2lxvr37CT7VuW77EbDxq0kazOYLhOdzC31vZhUzz",
"R3": "ebReJS6QeiIxqNFKFf4H01oKlyOviNcOAYf0Ksc4DVIJ8Uk1AxsCiwdUCbzMFFEwkBwtsN5OHZ7ThgIxHNwAq8"
},
"a6d00cef-ce1b-5410-bd45-41a0058b4540": {
"type": "field",
"subtype": "montfp",
"id": "a6d00cef-ce1b-5410-bd45-41a0058b4540",
"order": "aWgEPTl1tmebfs3ZETd8TJLcWlT",
"modulus": "aWgEPTl1tmebfs3ZETd8TJLcWlT",
"negpinv": "1",
"R": "01lnGH9GscRlVEuYE2YC",
"R3": "2HXdeLEYzbwUJfGOVzp6EZRig0e"
},
"a4cb7d3e-d31e-5472-9928-0b17725c9ae4": {
"type": "element",
"id": "a4cb7d3e-d31e-5472-9928-0b17725c9ae4",
"field": "49f4d9d3-3110-5d68-a069-d035ef71b54b",
"x": "1"
},
"a47e3ca0-d570-5fe4-bd87-245b0100dbc2": {
"type": "element",
"id": "a47e3ca0-d570-5fe4-bd87-245b0100dbc2",
"field": "49f4d9d3-3110-5d68-a069-d035ef71b54b",
"x": "0"
}
}

Some emotet variants use bad crypto (in the first stage)

· One min read
Jan Starke
Senior Forensic Analyst

What can I say? I recently analyzed an emotet variant which I found in a customer network. Eventually, I found that a new PE binary was being encrypted and loaded into memory. As encryption, simple RC4 was being used, with a static key @nw<jss6fG3Na4jvh^c&4Cgp$*rZ<g?TD@%FgTnwg3, hashed with MD5, which results in 21755ce816bd55c92f57eb4fd2060197 as RC4 key.

Fortunately, it was not necessary to do the decryption on my own. I could simply set a breakpoint after 0x100013B5 and could read the resulting plaintext PE image.

I instantly uploaded it to https://www.virustotal.com/gui/file/f6d224a8af3d56e8d17ccea43a8ac6791e658b656e481630a770716b6116fc0c/detection