Skip to content

Instantly share code, notes, and snippets.

@darkarnium
Last active March 17, 2019 13:43
Show Gist options
  • Save darkarnium/9463bf42a1ff0e7df55a149bf17eb92e to your computer and use it in GitHub Desktop.
Save darkarnium/9463bf42a1ff0e7df55a149bf17eb92e to your computer and use it in GitHub Desktop.
NVRAM shim to allow certain embedded binaries to be used with QEMU - intended for use with LD_PRELOAD.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/file.h>
/* From acosTypes. */
#ifndef BOOL
#define BOOL int
#endif
#ifndef IN
#define IN
#endif
#ifndef OUT
#define OUT
#endif
static const char LOG_FILE[] = "nvram.log";
static const char NVRAM_FILE[] = "nvram.ini";
static const char NVRAM_TEMP[] = "nvram.new";
/**
* Log to file.
*/
void logOperation(char *operation, char *message) {
FILE *fin = fopen(LOG_FILE, "a");
// If file can be opened, gain an exclusive lock and write.
if(fin != NULL) {
if(flock(fileno(fin), LOCK_EX) != 0) {
exit(-2);
}
fprintf(fin, "[Operation: %s] :: %s\n", operation, message);
}
flock(fileno(fin), LOCK_UN);
fclose(fin);
}
/**
* Attempt to read a given nvram 'tag' from the ini file.
*/
char *readFromIni(const char *name) {
char *key;
char *val;
FILE *fin = fopen(NVRAM_FILE, "r");
if(fin != NULL) {
char line [255];
// Read the entire file, line by line and process key / value pairs.
while(fgets(line, sizeof line, fin) != NULL) {
key = strtok(line, "=");
val = strtok(NULL, "=");
// Check if the key matches the requested tag.
if(strstr(key, name)) {
return(val);
}
}
fclose(fin);
}
return("");
}
/**
* Attempt to update a given nvram 'tag' in the ini file.
*/
void writeToIni(const char *name, const char *value) {
char *key;
char *val;
FILE *fin = fopen(NVRAM_FILE, "r");
FILE *fout = fopen(NVRAM_TEMP, "w");
if(fin != NULL) {
char line [255];
// Write to the new file.
while(fgets(line, sizeof line, fin) != NULL) {
key = strtok(line, "=");
val = strtok(NULL, "=");
if(strcmp(key, name) != 0) {
fprintf(fout, "%s=%s", key, val);
}
}
fprintf(fout, "%s=%s\n", name, value);
fclose(fin);
fclose(fout);
// TODO: Chance for a race here, use atomic counter-part (renameat2)
// if supported on target platform, or flock().
unlink(NVRAM_FILE);
rename(NVRAM_TEMP, NVRAM_FILE);
}
}
/**
* Log 'get' operation and read from 'nvram.ini' file.
*/
char *acosNvramConfig_get(const char *name) {
char logMessage [255];
sprintf(logMessage, "%s", name);
logOperation("acosNvramConfig_get", logMessage);
return(readFromIni(name));
}
/**
* Log 'set' operation and set and write to file.
*/
int acosNvramConfig_set(const char *name, const char *value) {
char logMessage [255];
sprintf(logMessage, "%s => %s", name, value);
logOperation("acosNvramConfig_set", logMessage);
writeToIni(name, value);
return(0);
}
/**
* Log 'read' operation and read from 'nvram.ini' file.
*/
int acosNvramConfig_read(IN char *pcTagName, OUT char *pcValue, IN int iMaxBufSize) {
char logMessage [255];
sprintf(logMessage, "%s into buffer of size %d", pcTagName, iMaxBufSize);
logOperation("acosNvramConfig_read", logMessage);
strncpy(pcValue, readFromIni(pcTagName), iMaxBufSize);
return(0);
}
/**
* Log 'read' operation and read from 'nvram.ini' file.
*/
int acosNvramConfig_readDefault(IN char *pcTagName, OUT char *pcValue, IN int iMaxBufSize) {
char logMessage [255];
sprintf(logMessage, "%s into buffer of size %d", pcTagName, iMaxBufSize);
logOperation("acosNvramConfig_readDefault", logMessage);
strncpy(pcValue, readFromIni(pcTagName), iMaxBufSize);
return(0);
}
/**
* Log 'read' operation and read from 'nvram.ini' file.
* TODO: This should be casting to the right type...
*/
int acosNvramConfig_readAsInt(IN char *pcTagName, OUT int *pcValue, IN int iMaxBufSize) {
char logMessage [255];
sprintf(logMessage, "%s into buffer of size %d", pcTagName, iMaxBufSize);
logOperation("acosNvramConfig_readAsInt", logMessage);
strncpy(pcValue, readFromIni(pcTagName), iMaxBufSize);
return(0);
}
/**
* Log 'write' operation and mock.
*/
int acosNvramConfig_write(IN char *pcTagName, IN char *pcValue, IN BOOL blSaveImmediately) {
char logMessage [255];
sprintf(logMessage, "%s => %s", pcTagName, pcValue);
logOperation("acosNvramConfig_write", logMessage);
return(0);
}
/**
* Log 'writeAsInt' operation and mock.
*/
int acosNvramConfig_writeAsInt(IN char *pcTagName, IN int iValue, IN BOOL blSaveImmediately) {
char logMessage [255];
sprintf(logMessage, "%s => %d", pcTagName, iValue);
logOperation("acosNvramConfig_writeAsInt", logMessage);
return(0);
}
/**
* Log 'match' operation and check.
*/
int acosNvramConfig_match(char *name, char *match) {
char logMessage [255];
sprintf(logMessage, "%s == %s", name, match);
logOperation("acosNvramConfig_match", logMessage);
if(strcmp(match, readFromIni(name))) {
return(0);
} else {
return(-1);
}
}
/**
* Log 'invmatch' operation and check.
*/
int acosNvramConfig_invmatch(char *name, char *match) {
char logMessage [255];
sprintf(logMessage, "%s != %s", name, match);
logOperation("acosNvramConfig_invmatch", logMessage);
if(strcmp(match, readFromIni(name))) {
return(0);
} else {
return(-1);
}
}
/**
* Log 'save' operation and mock.
*/
int acosNvramConfig_save(void) {
logOperation("acosNvramConfig_save", "");
return(0);
}
/**
* Log 'exist' operation and always return zero.
*/
int acosNvramConfig_exist(char *name) {
logOperation("acosNvramConfig_exist", name);
return(1);
}
@darkarnium
Copy link
Author

darkarnium commented Aug 6, 2017

To Compile.

For tool-chains, newer NetGear devices may ask for arm-uclibc-3.4.6 which can be found through various means.

  1. Ensure a valid ARM tool-chain is installed, which will need to be the right 'flavor' for the target device.
  2. Compile with arm-linux-gcc -shared NVRAM_Hammer.c -o NVRAM_Hammer.so
  3. Done.

To Use.

  1. Compile per above.
  2. Create an nvram.ini file in this directory and insert all required NVRAM tags in a tag=value format to use.
  3. LD_PRELOAD into QEMU via rm -f ./tmp/shm_id; sudo chroot . ./qemu-arm-static -E LD_PRELOAD="/NVRAM_Hammer.so" usr/sbin/httpd.

CAVEATS!

There are likely a number of NVRAM tags that are required to have been pre-loaded into the system at some point. As this shim assumes that the nvram.ini file contains values already, these need to be resolved and populated. The following methods can assist in doing so, as the omission of certain tags may cause the target binaries to crash.

If targeting newer NetGear devices, the the following may work - assuming that this is run from inside of the downloaded and unpacked the GPL directory for the given device:

egrep -ir '^\s*\#DEFINE\s+ACOSNVRAM_TAG_' ap/acos/include/acosNvramConfig.h | cut -d '\"' -f 2 | sed 's/$/\=/g' > nvram.ini

However, in the case of devices such as those build on the acos platform (?) there are other tags which aren't present in this header file which are required for binaries to start. In the case of the device that this shim was developed for, these additional tags are present inside of an export from an object file called defaults.o in the GPL sources, and can be used to further populate the nvram.ini file with a bit of work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment