[Android Software/Hacking General [Developers Only]] Vince_02100:
Hi,

A little tool or frontend that I've made and share to the community.

Intro

If you are like me :

Searching a way to backup your device, try some tools like SP Flash Tool, or MTK Droid Tools (for generating a Scatter File).

I have found a lot of thread, but I've allways got a dead end or a risk to brick the device (Never take a risk to brick your device if no stock rom available or backup).

A few days ago, i've found this thread : https://xdaforums.com/v20/development/h918-recowvery-unlock-v20-root-shell-t3490594

It's not for my device, it's maybe not for your device, but help a lot to do our need. This exploit work for everyone and what to do the little tools below.

What's the change ?

Instead of that does jcadduono (a big thanks to him), via applypatch, it don't patch the recovery partition to run an Android in Permissive mode, my applypatch only open and read the boot or recovery partition and display all data to logging (binary converted to hex value).

Yes, I know, logging is not for that, it's realy hard-core, but it's the only way working. I've tried with socket, but SELinux in Enforced mode don't allow this.

You can see my recowvery-applypatch.c below :

#include <unistd.h>
#include <stdio.h>

#include <stdint.h>
#include <time.h>

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>
#include <sys/stat.h>

#define APP_NAME "recowvery"
#define HOST_NAME "applypatch"

#ifdef DEBUG
#include <android/log.h>
#define LOGV(...) { __android_log_print(ANDROID_LOG_INFO, APP_NAME, __VA_ARGS__); printf(__VA_ARGS__); printf("\n"); }
#define LOGE(...) { __android_log_print(ANDROID_LOG_ERROR, APP_NAME, __VA_ARGS__); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); }
#else
#define LOGV(...) { printf(__VA_ARGS__); printf("\n"); }
#define LOGE(...) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); }
#endif

#define SEP LOGV("------------")

#include "bootimg.h"

/* Time delay in microsecond for next loop (1000 = 1ms)
* 250 is good for every PC
* (you can try with 0 to boost the process, but you can have an <unexpected EOF>)
*/
#define DELAY_T	250

void delay(long t)
{
if (t == 0)
return;
long timens = t * 1000;
nanosleep((const struct timespec[]){{0, timens}}, NULL);
}

/*
* Search in *str the word *word.
* &rslt => Result, a sort of substr version of *str from 0 to the last char of the searched *word if found.
* &len => Length of &rslt.
*
* Return 0 if found or -1 if not found.
* (A substr like)
*/
int findStr(char *str, char *word, char** rslt, int* len)
{
int i = 0;
int j = 0;
int allmatch = 0;
char *temp;

*len = 0;

for (i = 0; i < (int)strlen(str); i++)
{
if (str[i] == word[0])
{
allmatch = 0;
for (j = 0; j < (int)strlen(word); j++)
{
if (str[i + j] != word[j])
{
allmatch = 1;
break;
}
}
if (allmatch == 0)
{
*len = i + strlen(word);
break;
}
}
}
if (*len != 0)
{
temp = malloc(*len);
for (i = 0; i < *len; i++)
temp[i] = str[i];
*rslt = temp;
return 0;
}
return -1;
}

/*
* run "mount" and find "/by-name/" from result.
* if matched, fill path var
* return 0 if success else -1
*/
int getBlockDevice(char** path)
{
FILE* cmd;
char br[512];
char* search = "/by-name/";
char* tmp;
int slength = 0;

cmd = popen("mount 2>&1", "r");
if (cmd)
{
/* Read result and try to find the first corresponding mount point */
while(fgets(br, sizeof br, cmd) != NULL)
{
/* If found, log the result */
if (findStr(br, search, &tmp, &slength) != -1)
{
/* Append "boot" (your can replace this by "recovery", "system") at the end */
sprintf(*path, "%srecovery", tmp);
break;
}
}
fclose(cmd);
}
else
{
LOGE("ERROR Getting filesystem mountpoint");
}

if (slength > 0)
return 0;
else
return -1;
}

int main(int argc, char **argv)
{
int ret = 0;
int i = 0;

LOGV("Welcome to %s! (%s)", APP_NAME, HOST_NAME);

char *blockDev = malloc(256);
if (getBlockDevice(&blockDev) == -1)
{
LOGE("ERROR : Could not find FileSystem mount point.");
ret = errno;
goto oops;
}
else
{
LOGV("BLOCK_DEVICE : %s", blockDev);
SEP;
}

/*
* Sometimes <applypatch> run before <dirtycow> finish its process that cause our device not ready to start <adb logcat -s recowvery>
* and we have to wait more than 3min...
* A little sleep of 30 sec ensure that our device is ready.
*/
LOGV("The process start in 30s");
sleep(30);

byte rb[32];
char *content = malloc(256);
FILE *fp;
size_t nread;

fp = fopen(blockDev, "r");
if (fp) {
LOGV("*** DUMP START ***");
while ((nread = fread(rb, 1, sizeof rb, fp)) > 0)
{
sprintf(content, "HEXDUMP = [");
for (i = 0; i < (int)nread; i++)
{
if (i == 0)
sprintf(content, "%s%.2x", content, rb[i]);
else
sprintf(content, "%s,%.2x", content, rb[i]);
}
sprintf(content, "%s];", content);
LOGV("%s", content);

/* sleep to prevent any unexpected EOF with with pipe stream */
delay(DELAY_T);
}

if (ferror(fp)) {
ret = errno;
LOGE("*** DUMP ERROR ***");
LOGE("Error while reading the file...");
}

LOGV("*** DUMP END ***");
fclose(fp);
}
else
{
LOGV("Can't read the file...");
ret = errno;
goto oops;
}

return 0;
oops:
LOGE("*** DUMP ERROR ***");
LOGE("Error %d: %s", ret, strerror(ret));
LOGE("Exiting...");
return ret;
}

Don't laugh please, I am very new in C :(

Ok, but about the tool ?

The tool is a frontend and easy to use, it copy exploit files for you, run exploit, read logging from adb and do the revert of applypatch (Convert hex to binary and write them to the image file) and finaly reboot your device when it's finish.

An example here :

~/Documents/dirtydump/bin/Debug$ ./dirtydump boot
***************
**** Init *****
***************

adb push ./bin/dirtycow /data/local/tmp
159 KB/s (9984 bytes in 0.061s)

adb push ./bin/recowvery-applypatch_boot /data/local/tmp
234 KB/s (10200 bytes in 0.042s)

adb push ./bin/recowvery-applypatch_recovery /data/local/tmp
238 KB/s (10200 bytes in 0.041s)

adb push ./bin/recowvery-app_process64 /data/local/tmp
240 KB/s (10200 bytes in 0.041s)

adb push ./bin/recowvery-app_process32 /data/local/tmp
411 KB/s (17992 bytes in 0.042s)

adb shell chmod 0777 /data/local/tmp/dirtycow

adb shell chmod 0777 /data/local/tmp/recowvery-applypatch_boot

adb shell chmod 0777 /data/local/tmp/recowvery-applypatch_recovery

adb shell chmod 0777 /data/local/tmp/recowvery-app_process64

adb shell chmod 0777 /data/local/tmp/recowvery-app_process32

* Android x64 version detected.

**********************
**** Run Exploit *****
**********************

adb shell /data/local/tmp/dirtycow /system/bin/applypatch /data/local/tmp/recowvery-applypatch_boot
warning: new file size (10200) and file old size (74712) differ
size 74712
[*] mmap 0x7faa6a7000
[*] exploit (patch)
[*] currently 0x7faa6a7000=10102464c457f
[*] madvise = 0x7faa6a7000 74712
[*] madvise = 0 1048576
[*] /proc/self/mem 1031798784 1048576
[*] exploited 0x7faa6a7000=10102464c457f

adb shell /data/local/tmp/dirtycow /system/bin/app_process64 /data/local/tmp/recowvery-app_process64
warning: new file size (10200) and file old size (22456) differ
size 22456
[*] mmap 0x7f8f303000
[*] exploit (patch)
[*] currently 0x7f8f303000=10102464c457f
[*] madvise = 0x7f8f303000 22456
[*] madvise = 0 1048576
[*] /proc/self/mem 2071986176 1048576
[*] exploited 0x7f8f303000=10102464c457f

*********************************
**** adb logcat -s recowvery ****
*********************************

--------- beginning of main
--------- beginning of system
--------- beginning of crash
01-24 15:40:37.206 5266 5266 I recowvery: Welcome to recowvery! (app_process64)
01-24 15:40:37.206 5266 5266 I recowvery: ------------
01-24 15:40:37.206 5266 5266 I recowvery: Current selinux context: u:r:zygote:s0
01-24 15:40:37.206 5266 5266 I recowvery: Set context to 'u:r:system_server:s0'
01-24 15:40:37.206 5266 5266 I recowvery: Current security context: u:r:system_server:s0
01-24 15:40:37.206 5266 5266 I recowvery: Setting property 'ctl.start' to 'flash_recovery'
01-24 15:40:37.211 5266 5266 I recowvery: ------------
01-24 15:40:37.211 5266 5266 I recowvery: Recovery flash script should have started!
01-24 15:40:37.211 5266 5266 I recowvery: Run on your PC or device to see progress: adb logcat -s recowvery
01-24 15:40:37.211 5266 5266 I recowvery: Waiting 3 minutes to try again (in case it didn't start or you forgot to dirtycow applypatch first)...
01-24 15:40:37.242 5269 5269 I recowvery: Welcome to recowvery! (applypatch)
01-24 15:40:37.272 5269 5269 I recowvery: BLOCK_DEVICE : /dev/block/platform/mtk-msdc.0/11230000.msdc0/by-name/boot
01-24 15:40:37.272 5269 5269 I recowvery: ------------
01-24 15:40:37.272 5269 5269 I recowvery: The process start in 30s
Start writing to file...
Block read : 524288 (Size : 16777216)
Finish

Image file saved here :
./boot.img

Rebooting your device...
************************
**** Reboot Device *****
************************

How to use ?

Extract all files from archive attached below in a directory of our choice.

./dirtydump boot : dump boot partition and store it to ./boot.img

./dirtydump recovery : dump recovery partition and store it to ./recovery.img

When all done, you have all to make your Custom Recovery for your device.

Requirements

<dirtycow> capable device.

Working adb (adb devices to check)

Linux distribution.

Source code

#include <iostream>
#include <stdio.h>
#include <regex>

using namespace std;

#define BOOT 0
#define RECOVERY 1
#define ANDROID_64 "64"
#define ANDROID_32 "32"

#ifdef __linux__
#define DIRECTORY_SEPARATOR "/"
#elif __APPLE__
#define DIRECTORY_SEPARATOR "/"
#else
#define DIRECTORY_SEPARATOR "\\"
#endif

typedef unsigned char byte;

static string appDirectory;
static string arch;
static FILE *fsout;
static bool startwrite = false;
static int ncrash = 0;
static int nBlock = 0;
static long currentSize = 0;
// Shorter regex is possible, but I prefer like that.
static regex rs("^.+I recowvery: (\\*\\*\\* DUMP START \\*\\*\\*)\\s+"); // Used to start writting binary file
static regex rl("^.+I recowvery: HEXDUMP = \\[([^\\]]+)\\];\\s+"); // Used to match all data block, and populate < datalist >
static regex rf("^.+I recowvery: (\\*\\*\\* DUMP END \\*\\*\\*)\\s+"); // Used to end writting, and exit infinit loop
static regex re("^.+I recowvery: (\\*\\*\\* DUMP ERROR \\*\\*\\*)\\s+"); // Used to intercept error from < recowvery-applypatch >
static regex radbe("^error:(.+)\\s+"); // ADB cmd error
static regex rarch("^.+(aarch64).*\\s+"); // Get arch from <uname -a>

/**
* Run command
* return : 0 if success else -1 if error
**/
int runcmd(string cmd)
{
char rslt[256];
int cmdv = 0;
FILE *fc = popen(cmd.c_str(), "r");

/* Redirect stderr to stdout */
cmd.append(" 2>&1");

// To remove the \n or \r\n at the end.
regex rcmdline("^(.+)\\s+");
if (fc)
{
while (fgets(rslt, sizeof rslt, fc) != NULL)
{

if (regex_match(string(rslt), rcmdline))
cout << regex_replace(string(rslt), rcmdline, "$1") << endl;
// If error matched, return -1
if (regex_match(rslt, radbe))
{
cmdv = -1;
break;
}
}
cout << endl;
fclose(fc);
}
else
{
cerr << "Error running '" << string(cmd) << "'" << endl;
return -1;
}
return cmdv;
}

/**
* Used to split string
* s : string to split (in)
* delim : used char for split (in)
* elems : string array result (out)
**/
void split(const string &s, char delim, vector<string> &elems) {
stringstream ss;
ss.str(s);
string item;
while (getline(ss, item, delim)) {
elems.push_back(item);
}
}

/**
* Used to split string
* s : string to split (in)
* delim : char delimeter (in)
* return : vector string
**/
vector<string> split(const string &s, char delim) {
vector<string> elems;
split(s, delim, elems);
return elems;
}

/** Convert hex string to byte array **/
void string_to_bytearray(std::string str, unsigned char* &array, int& size)
{
int length = str.length();
// make sure the input string has an even digit numbers
if(length%2 == 1)
{
str = "0" + str;
length++;
}

// allocate memory for the output array
array = new unsigned char[length/2];
size = length/2;

std::stringstream sstr(str);
for(int i=0; i < size; i++)
{
char ch1, ch2;
sstr >> ch1 >> ch2;
int dig1, dig2;
if(isdigit(ch1)) dig1 = ch1 - '0';
else if(ch1>='A' && ch1<='F') dig1 = ch1 - 'A' + 10;
else if(ch1>='a' && ch1<='f') dig1 = ch1 - 'a' + 10;
if(isdigit(ch2)) dig2 = ch2 - '0';
else if(ch2>='A' && ch2<='F') dig2 = ch2 - 'A' + 10;
else if(ch2>='a' && ch2<='f') dig2 = ch2 - 'a' + 10;
array[i] = dig1*16 + dig2;
}
}

/**
* Get architecture type
* Run <adb shell uname -a> and find the word : aarch64
* If found return <ANDROID_64> else <ANDROID_32>
**/
string getArchType()
{
char rslt[256];
string val;
FILE *fc = popen("adb shell uname -a", "r");
// To remove the \n or \r\n at the end.
if (fc)
{
while (fgets(rslt, sizeof rslt, fc) != NULL)
{

if (regex_match(string(rslt), rarch))
{
cout << "* Android x64 version detected." << endl;
val = string(ANDROID_64);
}
else
{
cout << "* Android x32 version detected." << endl;
val = string(ANDROID_32);
}
}
cout << endl;
fclose(fc);
}
else
{
cerr << "Error running 'adb shell uname -a'" << endl;
}
return val;
}

/**
* Display help
**/
void help()
{
cout << "dirtydump boot | recovery" << endl;
cout << "Usage :" << endl;
cout << "\tdirtydump boot : Dump device boot partition and save it to boot.img." << endl;
cout << "\tdirtydump recovery : Dump device recovery partition and save it to recovery.img." << endl << endl;
cout << "Information :" << endl;
cout << "\tThis app use the same exploit explained here : " << endl;
cout << "\thttps://github.com/jcadduono/android_external_dirtycow" << endl;
cout << "\tThe only difference is by the <applypatch>, instead of patching," << endl;
cout << "\tit read your boot / recovery partition." << endl;
cout << "\tConvert all data to hex value, and display it." << endl;
cout << "\tDuring the process, the app read all data through" <<endl;
cout << "\t<adb logcat -s recowvery> and do the reverse," << endl;
cout << "\tconvert all hex value to binary, and write it to a file." << endl;
cout << "\tBecause your device is like crashing, this app reboot" << endl;
cout << "\tautomaticaly when the process is finished." << endl;
cout << endl;
}

/**
* Initialize process.
* Push required files to your device and apply a chmod to them and exit.
**/
int init()
{
cout << "***************" << endl;
cout << "**** Init *****" << endl;
cout << "***************" << endl << endl;

string files[] = {"dirtycow",
"recowvery-applypatch_boot",
"recowvery-applypatch_recovery",
"recowvery-app_process64",
"recowvery-app_process32"};
string cmdlist[] = {"adb shell chmod 0777 /data/local/tmp/dirtycow",
"adb shell chmod 0777 /data/local/tmp/recowvery-applypatch_boot",
"adb shell chmod 0777 /data/local/tmp/recowvery-applypatch_recovery",
"adb shell chmod 0777 /data/local/tmp/recowvery-app_process64",
"adb shell chmod 0777 /data/local/tmp/recowvery-app_process32"};
char cmd[128];

/* Push files to the device */
for(auto s : files)
{
sprintf(cmd, "adb push %s%sbin%s%s /data/local/tmp", appDirectory.c_str(), DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, s.c_str());
cout << string(cmd) << endl;

if (runcmd(cmd) != 0)
return -1;
}

/* Apply chmod to the pushed files */
for(auto s : cmdlist)
{
cout << string(s) << endl;

if (runcmd(s) != 0)
return -1;
}

arch = getArchType();
if (arch.empty())
return -1;

return 0;
}

/**
* Apply exploit to applypatch (for boot or process) and app_process64
**/
int runExploit(int v)
{
cout << "**********************" << endl;
cout << "**** Run Exploit *****" << endl;
cout << "**********************" << endl << endl;

string cmdlist[] = {
"", // For applypatch
"" // For app_process
};
if (v == BOOT)
cmdlist[0].append("adb shell /data/local/tmp/dirtycow /system/bin/applypatch /data/local/tmp/recowvery-applypatch_boot");
else if (v == RECOVERY)
cmdlist[0].append("adb shell /data/local/tmp/dirtycow /system/bin/applypatch /data/local/tmp/recowvery-applypatch_recovery");
else
return -1;

if (arch == ANDROID_64)
cmdlist[1] = "adb shell /data/local/tmp/dirtycow /system/bin/app_process64 /data/local/tmp/recowvery-app_process64";
else
cmdlist[1] = "adb shell /data/local/tmp/dirtycow /system/bin/app_process32 /data/local/tmp/recowvery-app_process32";

for(auto s : cmdlist)
{
cout << s << endl;

if (runcmd(s) != 0)
return -1;
}

return 0;
}

/**
* reboot device from adb
**/
int rebootDevice()
{
cout << "************************" << endl;
cout << "**** Reboot Device *****" << endl;
cout << "************************" << endl << endl;
return runcmd(string("adb reboot"));
}

/**
* Function that do the stuff
* If a line contain *** DUMP START *** it start to get all hex value in HEXDUMP = [a1,e2,b4,ect.] and convert to binary before writing to output file.
* All other line are :
* <*** DUMP ERROR ***> : Error during the process, or your device is disconnected, no more battery...
* <*** DUMP END ***> : Dumping is end / end of process.
* <Other lines> : Displayed
**/
int displayLogAndConvertData(string line)
{
/**
* If an unexpected EOF from recowvery-applypatch or if no <pipe>...
* We can't receive a null string, so break the loop, close fsout, and exit the program.
**/
if (line.empty())
{
cout << string("* < null > received !") << endl;
cout << string("Try again...") << endl;
return -1;
}

/**
* *** DUMP START ***
* set startwrite = true to write parsed data to fsout
**/
if (regex_match(line, rs))
{
startwrite = true;
cout << "Start writing to file..." << endl;
}

/**
* Parse all string received if match
* Note :
* It's possible to have matched string before intercept DUMP START,
* If we convert now, it's a good idea to have a broken output file.
**/
if (startwrite && regex_match(line, rl))
{
string s = regex_replace(line, rl, "$1");
vector<string> data = split(s, ',');
for (int c = 0; c < (int)data.size(); c++)
{
try
{
byte *b = NULL;
int sb;
string_to_bytearray(data[c], b, sb);
fwrite(b, 1, sb, fsout);
}
catch (const exception &ex)
{
cout << endl;
cout << string("** Exception **") << endl;
cout << string(" - When convert : ") << data[c] << endl;
cout << string(" - Message : ") << ex.what() << endl;
}
}
nBlock++;
currentSize = nBlock * 32;

cout << "\r";
cout << "Block read : " << nBlock << " (Size : " << currentSize << ")";
}
/**
* Display the other lines (for debuging, logging...)
**/
else if (!regex_match(line, rl) && (!regex_match(line, rf) && !startwrite) && line.length() > 1)
{
cout << line;
}

/**
* *** DUMP END ***
* Flush and close fsout, inform the user, and break the loop.
**/
if (startwrite && regex_match(line, rf))
{
cout << endl << "Finish" << endl;
startwrite = false;
return 1;
}

/**
* *** DUMP ERROR ***
* An error intercepted from ADB, close fsout, set start to false.
* < applypatch > will restart every 3 min.
* We break the loop after 3 errors.
**/
if (regex_match(line, re))
{
cout << std::string("* Error received from ADB *") << std::endl;

startwrite = false;
if (ncrash == 3)
{
cout << std::string("* Too many tries, please check your < recowvery-applypatch.c > and try again.") << std::endl;
return -1;
}
cout << std::string("* Be patient, recowvery-applypatch will restart in a few minutes.") << std::endl;
ncrash++;
}
return 0;
}

/**
* run <adb logcat -s recowvery> and send line by line to <displayLogAndConvertData> function
**/
int readFromLogcat()
{
cout << "*********************************" << endl;
cout << "**** adb logcat -s recowvery ****" << endl;
cout << "*********************************" << endl << endl;

char buff[1024];
int prc = 0;
FILE *fc = popen("adb logcat -s recowvery", "r");
if (fc)
{
while(fgets(buff, sizeof buff, fc) != NULL)
{
prc = displayLogAndConvertData(string(buff));
// Error occuring
if (prc == -1)
{
cerr << "Error during the process !" << endl;
break;
}
// Process finished
if (prc == 1)
break;

}
/*
* When finish or an error received from adb, <startwrite> is set to false.
* If set to true, a NULL string has been received before receiving a DUMP_END or DUMP_ERROR.
* So, so we display an error.
*/
if (startwrite)
{
cerr << "Error during the process !" << endl;
prc = errno;
}
fclose(fc);
}
else
{
cerr << "Error running <adb logcat -s recowvery" << endl;
}
return prc;
}

/** main **/
int main(int argc, char** argv)
{
int ret = 0;
string filename;

if (argc == 1)
{
help();
return ret;
}

/* Fix for windows
* If run in same directory as the exe, return only the exe name without folder where it run.
* So, if DIRECTORY_SEPARATOR not found in argv_str, appDirectory = "." for linux, mac and windows
*/
string argv_str(argv[0]);
if (argv_str.find_last_of(DIRECTORY_SEPARATOR) != string::npos)
appDirectory = argv_str.substr(0, argv_str.find_last_of(DIRECTORY_SEPARATOR));
else
appDirectory = string(".");

ret = init();

if (ret != 0)
return ret;

if (string(argv[1]) == "boot")
{
ret = runExploit(BOOT);
filename = "boot.img";
}
else
{
ret = runExploit(RECOVERY);
filename = "recovery.img";
}

if (ret != 0)
return ret;
else
{
fsout = fopen(filename.c_str(), "wb");
if (!fsout)
{
cerr << "Can't open or create file : <" << string(filename) << ">" << endl;
rebootDevice();
return errno;
}
else
{
ret = readFromLogcat();
fclose(fsout);
}
cout << endl;
cout << "Image file saved here :" << endl;
cout << " " << appDirectory << string(DIRECTORY_SEPARATOR) << string(filename) << endl;
cout << endl;
}

cout << "Rebooting your device..." << endl;
ret = rebootDevice();

return ret;
}

Note :

There is only linux binary, the windows version come soon.

(I don't know why Windows don't work as expected :x)

If you are interested by the source code, I can attach it.

Tested and build from Ubuntu 16.04 (x64) / Code::Blocks & gedit.

If any bug, I will do the best to solve this.

So sorry for my english, or any misspelling :x