<- Blog

Fixing Android’s 3-minute screen recording limitation

How we used binary rewriting to remove the 3-minute screen recording limitation on Maestro Cloud

Leland Takamine August 24, 2023

Android provides a nice utility for recording the device screen from the command-line via ADB:

adb shell screenrecord

Unfortunately, it comes with one BIG caveat: You can only record up to 180 seconds in one go.

This was a serious issue for us at Mobile.dev because we rely on this tool for Android screen recordings on Maestro Cloud. In fact, the 3-minute recording limit was one of the most common complaints we were hearing from our users.

We tried several creative approaches, but ultimately only one solution gave us everything we needed:

  • Allows recordings beyond 3 minutes
  • Produces a single video file that doesn’t skip
  • Doesn’t require maintaining many branches of changes on AOSP
  • Works on any version of Android
  • Works on emulators and production physical devices
  • Easy to integrate into our infrastructure codebase

What we tried

Run the command multiple times

This solution outputs multiple mp4 files. It’s possible to stitch the resulting files together (eg. with ffmpeg) but your recording will skip at the seams between the recordings.

Modify and recompile from source

The screenrecording binary is dynamically linked so you’ll need to build a separate binary for each version of Android you want to run this on. Possible, but if you’ve ever dealt with AOSP before, you already know how painful this is.

❌ Use scrcpy instead of adb screenrecord

scrcpy doesn’t suffer from the 3-minute time limit, but it’s a user-facing product and not built to easily integrate with other tools or existing codebases.

❌ Build a statically linked screenrecord binary

This would potentially allow a single modified binary to run on multiple Android OS versions. Unfortunately, it’s not possible to compile a statically linked screenrecord binary due to it’s many dynamic library dependencies in AOSP.

What ended up working

✅ Edit a few bytes in the screenrecord binary

Ultimately, we opted for an approach that allowed us to directly modify the screenrecord maximum time limit. Using binary rewriting, we were able to increase this limit to any duration we wanted.

Just tell me how you did it already!

Alright, now for the fun stuff:

The 3-minute limitation in the screenrecord command is hardcoded as a single constant in the AOSP source code. That’s great, because it means it will be relatively easy to modify this value, even in the compiled binary.

static const uint32_t kMaxTimeLimitSec = 180;       // 3 minutes

Finding the right bytes to modify

We used a tool called Redare2 to help poke around in the screenrecording binary. Ramping up on this tool took a bit of time, but luckily there’s this cool new thing called ChatGPT that’s super helpful in these situations 😉.

So with our new friends Redare2 and ChatGPT, we were able to find a simple command that searches for the exact bytes we were after:

r2 -q -c "/x b4000000" screenrecord

The screenrecord argument is the path to the file we want to inspect. We pulled this particular binary off an Android device using:

adb pull /system/bin/screenrecord

But what’s all this other stuff? Here’s where ChatGPT is really helpful:

So we’re searching the binary for this sequence of bytes: b4000000 . Why? This is the 4-byte little-endian hexidecimal representation of 180, which is the value of the kMaxTimeLimitSec constant we were interested in.

If we can find where this constant value is defined, we can update those bytes to a different number!

As it turns out, b4000000 appears multiple times in the binary. The left column lists the address of each occurrence:

# search for occurrences of 0xb4000000

$ r2 -qc "/x b4000000" screenrecord

0x00020854 hit0_0 b4000000
0x00015c79 hit0_1 b4000000
0x000169a2 hit0_2 b4000000
0x000183b6 hit0_3 b4000000
0x000199a5 hit0_4 b4000000
0x000199b6 hit0_5 b4000000
0x000199f7 hit0_6 b4000000
0x00019c8e hit0_7 b4000000
0x0001affa hit0_8 b4000000
0x0001b158 hit0_9 b4000000
0x0001b173 hit0_10 b4000000
0x0001c0f9 hit0_11 b4000000
0x0001d487 hit0_12 b4000000
0x00004d1c hit0_13 b4000000
0x000077ad hit0_14 b4000000
0x00007c9a hit0_15 b4000000
0x00007cff hit0_16 b4000000
0x0000814c hit0_17 b4000000
0x0000eaa7 hit0_18 b4000000
0x00010648 hit0_19 b4000000

Luckily, the first entry (0x00020854) is the one we are looking for. We can verify this by searching for references to that address:

# analyze the binary, then list all references to 0x00020854

$ r2 -qc "aaa; axt 0x00020854" screenrecord

(nofunc) 0x15c6e [DATA:r--] mov dword [0x00020854], eax
(nofunc) 0x16344 [DATA:r--] mov r9d, dword [0x00020854]
(nofunc) 0x16cdc [DATA:r--] mov r15d, dword [0x00020854]

The exact assembly here isn’t super important. What does matter is that yes, there are indeed instructions that reference the 4-byte value at 0x00020854. The same command does not yield any results for any of the other addresses listed.

Here’s what that tells us: 0x00020854 is the only address that both…

  1. …contains the 4-byte representation of the value 180, and
  2. …is referenced by at least one instruction in the binary

This alone gives us high confidence that we have the right address that corresponds to the kMaxTimeLimitSec constant. But as a final check, let’s take a look at the first instruction that references this address:

(nofunc) 0x15c6e [DATA:r--] mov dword [0x00020854], eax

We can use Radare2’s pd command to view the instruction right before that one:

# seek to 0x15c6e, then print 1 instruction before that address

$ r2 -qc "s 0x15c6e; pd -1" screenrecord

0x00015c69      e8126d0000     call sym.imp.atoi

It’s a call to the sym.imp.atoi function. Looking back at the source, the only reference to atoi is in this part of the code:

case 't':
    gTimeLimitSec = atoi(optarg);
    if (gTimeLimitSec == 0 || gTimeLimitSec > kMaxTimeLimitSec) {
        fprintf(stderr,
                "Time limit %ds outside acceptable range [1,%d]\n",
                gTimeLimitSec, kMaxTimeLimitSec);
        return 2;
    }
    break;

And we see that kMaxTimeLimitSec is referenced there as expected.

Rewriting the binary

So we just did a lot of work to verify that 0x00020854 is the address we need to modify in the file. It currently holds the value 180 — Let’s update it to 3600 to see what happens!

# seek to 0x00020854, then write 3600(0x100E0000) to that address

r2 -q -c "s 0x00020854; wx 0x100E0000" -w screenrecord

Now we have a local screenrecord binary that we hope has been updated to increase its time limit to 3600 seconds (1 hour). Let’s verify that:

# push the modified screenrecord binary to /data/local/tmp/
adb push ./screenrecord /data/local/tmp

# make the file executable
adb shell chmod +x /data/local/tmp/screenrecord

# print the help message
adb shell /data/local/tmp/screenrecord --help

If you’ve done this sort of work before, you never expect this to behave as expected without a lot of debugging and trial and error. But to our surprise, this is what we saw on our first attempt:

The usage message was updated with the modified value! We then validated that we could indeed record a video longer than the original 3-minute limit using the modified binary! 🥳🥳🥳

Integrating into our infrastructure

The last step for us was integrating the fix into our infrastructure. This was a matter of pushing the modified screenrecord binary to our Android devices before making them available to our device pool. In the end, a small change.

Wrapping up

This was an example of a very deep dive that resulted in a simple solution. We were especially relieved that, in the end, the fix required only a few lines of code changes.

And of course — most importantly — we’re happy to share that we have completely removed the Android screen recordings limits on Maestro Cloud 🙌

If you haven’t checked out Maestro yet, we promise it will blow your mind. Get started here and make sure to Join us on Slack!

We're entering a new era of software development. Advancements in AI and tooling have unlocked unprecedented speed, shifting the bottleneck from development velocity to quality control. This is why we built — a modern testing platform that ensures your team can move quickly while maintaining a high standard of quality.

Learn more ->