​改机系列-字符串打印

admin 2024年8月5日14:23:35评论13 views字数 11491阅读38分18秒阅读模式

改机系列-字符串打印

字符串打印有时候可以辅助我们分析日志和应用开发的bug,在涉及不同的引擎中(比如游戏unity引擎中可以通过Il2CppString* il2cpp_string_new(const char* str),方法创建属于游戏自己类型的字符串,安卓中创建一个字符串new String("Hello, World!")还有传入不同的参数来创建java字符串)今天我们主要说下安卓中的字符串在java虚拟机中的处理。

学习目录

1.安卓中字符串的探索

2.监控字符串改机

安卓中字符串的探索

  • • 对于逆向安全研究人员,部分应用我们可以通过hook string 打印堆栈来快速定位,创建字符串的地方,那么创建字符串的方法都在虚拟机做了什么处理,我们先从java层创建一个字符串开始说起,进入java中的类发现都是废弃的方法和空实现。

        String a1 = new String("asd");

​改机系列-字符串打印

这是因为在Android运行时环境中,所有对这个构造函数的调用都被拦截并重定向到StringFactory。这是为了优化性能和内存使用。由于字符串在Java中是不可变的,所以复制字符串通常是不必要的。相反,Android的StringFactory可以重用已有的字符串实例,从而节省内存。我们抛弃了利用“new”关键字随意制造对象的方法,改用这个StringFactory类来构建并共享字符串元,外部需要字符串直接向StringFactory索要即可,感觉它还是用了一定的设计思想在里边,看安卓源码中一定要考虑到设计思想和代码的实现思路,这对我们学习帮助和印象的提升非常大,那么,我们从安卓源码中寻找看看虚拟机是怎么处理的。

创建字符串的代码走到汇编的解释器中,执行MterpInvokeDirect

​改机系列-字符串打印


                                                   // Hack for String init:
  //
  // Rewrite invoke-x java.lang.String.<init>(this, a, b, c, ...) into:
  //         invoke-x StringFactory(a, b, c, ...)
  // by effectively dropping the first virtual register from the invoke.
  //
  // (at this point the ArtMethod has already been replaced,
  // so we just need to fix-up the arguments)
  //
  // Note that FindMethodFromCode in entrypoint_utils-inl.h was also special-cased
  // to handle the compiler optimization of replacing `this` with null without
  // throwing NullPointerException.    
  
extern "C" size_t MterpInvokeDirect(Thread* self,
                                    ShadowFrame* shadow_frame,
                                    uint16_t* dex_pc_ptr,
                                    uint16_t inst_data)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  JValue* result_register = shadow_frame->GetResultRegister();
  const Instruction* inst = Instruction::At(dex_pc_ptr);
  return DoInvoke<kDirect, /*is_range=*/ false/*do_access_check=*/ false/*is_mterp=*/ true>(
      self, *shadow_frame, inst, inst_data, result_register) ? 1u : 0u;
}

这里进入到了DoInvoke,最终又会调用DoCallCommon

static ALWAYS_INLINE bool DoInvoke(Thread* self,
                                   ShadowFrame& shadow_frame,
                                   const Instruction* inst,
                                   uint16_t inst_data,
                                   JValue* result)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  // Make sure to check for async exceptions before anything else.
  if (is_mterp && self->UseMterp()) {
    DCHECK(!self->ObserveAsyncException());
  } else if (UNLIKELY(self->ObserveAsyncException())) {
    return false;
  }
  const uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c();
  const uint32_t vregC = (is_range) ? inst->VRegC_3rc() : inst->VRegC_35c();
  ArtMethod* sf_method = shadow_frame.GetMethod();
  ...
  return DoCallCommon<is_range, do_assignability_check>(
      called_method, self, shadow_frame,
      result, number_of_inputs, arg, vregC);
}

在这里就有关于创建字符串相关的操作

 static inline bool DoCallCommon(ArtMethod* called_method,
                                Thread* self,
                                ShadowFrame& shadow_frame,
                                JValue* result,
                                uint16_t number_of_inputs,
                                uint32_t (&arg)[Instruction::kMaxVarArgRegs],
                                uint32_t vregC) {
  bool string_init = false;
  // Replace calls to String.<init> with equivalent StringFactory call.
  if (UNLIKELY(called_method->GetDeclaringClass()->IsStringClass()
               && called_method->IsConstructor())) {
    

called_method = WellKnownClasses::StringInitToStringFactory(called_method);

string_init = true;
}
关键方法  ArtMethod* WellKnownClasses::StringInitToStringFactory

                                                                ArtMethod* WellKnownClasses::StringInitToStringFactory(ArtMethod* string_init) {

#define TO_STRING_FACTORY(init_runtime_name, init_signature, new_runtime_name,
new_java_name, new_signature, entry_point_name)
DCHECK((init_runtime_name) != nullptr);
if (string_init == (init_runtime_name)) {
DCHECK((new_runtime_name) != nullptr);
return (new_runtime_name);
}
STRING_INIT_LIST(TO_STRING_FACTORY)
#undef TO_STRING_FACTORY
LOG(FATAL) << "Could not find StringFactory method for String.<init>";
UNREACHABLE();
}
#define STRING_INIT_LIST(V)
V(java_lang_String_init, "()V", newEmptyString, "newEmptyString""()Ljava/lang/String;", NewEmptyString)
V(java_lang_String_init_B, "([B)V", newStringFromBytes_B, "newStringFromBytes""([B)Ljava/lang/String;", NewStringFromBytes_B)
V(java_lang_String_init_BI, "([BI)V", newStringFromBytes_BI, "newStringFromBytes""([BI)Ljava/lang/String;", NewStringFromBytes_BI)
V(java_lang_String_init_BII, "([BII)V", newStringFromBytes_BII, "newStringFromBytes""([BII)Ljava/lang/String;", NewStringFromBytes_BII)
V(java_lang_String_init_BIII, "([BIII)V", newStringFromBytes_BIII, "newStringFromBytes""([BIII)Ljava/lang/String;", NewStringFromBytes_BIII)
V(java_lang_String_init_BIIString, "([BIILjava/lang/String;)V", newStringFromBytes_BIIString, "newStringFromBytes""([BIILjava/lang/String;)Ljava/lang/String;", NewStringFromBytes_BIIString)
V(java_lang_String_init_BString, "([BLjava/lang/String;)V", newStringFromBytes_BString, "newStringFromBytes""([BLjava/lang/String;)Ljava/lang/String;", NewStringFromBytes_BString)
V(java_lang_String_init_BIICharset, "([BIILjava/nio/charset/Charset;)V", newStringFromBytes_BIICharset, "newStringFromBytes""([BIILjava/nio/charset/Charset;)Ljava/lang/String;", NewStringFromBytes_BIICharset)
V(java_lang_String_init_BCharset, "([BLjava/nio/charset/Charset;)V", newStringFromBytes_BCharset, "newStringFromBytes""([BLjava/nio/charset/Charset;)Ljava/lang/String;", NewStringFromBytes_BCharset)
V(java_lang_String_init_C, "([C)V", newStringFromChars_C, "newStringFromChars""([C)Ljava/lang/String;", NewStringFromChars_C)
V(java_lang_String_init_CII, "([CII)V", newStringFromChars_CII, "newStringFromChars""([CII)Ljava/lang/String;", NewStringFromChars_CII)
V(java_lang_String_init_IIC, "(II[C)V", newStringFromChars_IIC, "newStringFromChars""(II[C)Ljava/lang/String;", NewStringFromChars_IIC)
V(java_lang_String_init_String, "(Ljava/lang/String;)V", newStringFromString, "newStringFromString""(Ljava/lang/String;)Ljava/lang/String;", NewStringFromString)
V(java_lang_String_init_StringBuffer, "(Ljava/lang/StringBuffer;)V", newStringFromStringBuffer, "newStringFromStringBuffer""(Ljava/lang/StringBuffer;)Ljava/lang/String;", NewStringFromStringBuffer)
V(java_lang_String_init_III, "([III)V", newStringFromCodePoints, "newStringFromCodePoints""([III)Ljava/lang/String;", NewStringFromCodePoints)
V(java_lang_String_init_StringBuilder, "(Ljava/lang/StringBuilder;)V", newStringFromStringBuilder, "newStringFromStringBuilder""(Ljava/lang/StringBuilder;)Ljava/lang/String;", NewStringFromStringBuilder)

这个TO_STRING_FACTORY函数宏套宏,当 STRING_INIT_LIST(TO_STRING_FACTORY) 被预处理器处理时,会生成一系列的代码,每一行都是 TO_STRING_FACTORY 宏的一个实例,使用 STRING_INIT_LIST 定义中的一组参数 例如,第一行 V(java_lang_String_init, "()V", newEmptyString, "newEmptyString", "()Ljava/lang/String;", NewEmptyString) 将会生成以下的代码:

DCHECK((java_lang_String_init) != nullptr);
if (string_init == (java_lang_String_init)) {
  DCHECK((newEmptyString) != nullptr);
  return (newEmptyString);
}           

这里说下在 ART 中,EnterInterpreterFromDeoptimize 是用来处理反优化(Deoptimization)的情况的。反优化是指在某些情况下,已经被 JIT(Just-In-Time)编译器优化过的代码需要被转回到解释执行的情况。也会涉及到判断是不是字符串初始化函数的。


static inline bool IsStringInit(const DexFile* dex_file, uint32_t method_idx)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  const dex::MethodId& method_id = dex_file->GetMethodId(method_idx);
  const char* class_name = dex_file->StringByTypeIdx(method_id.class_idx_);
  const char* method_name = dex_file->GetMethodName(method_id);
  // Instead of calling ResolveMethod() which has suspend point and can trigger
  // GC, look up the method symbolically.
  // Compare method's class name and method name against string init.
  // It's ok since it's not allowed to create your own java/lang/String.
  // TODO: verify that assumption.
  if ((strcmp(class_name, "Ljava/lang/String;") == 0) &&
      (strcmp(method_name, "<init>") == 0)) {
    return true;
  }
  return false;
}

在类的加载链接中也会设计到字符串的初始化,FinishInit方法在ClassLinker的初始化过程中被调用,以完成其初始化。不过这些是artruntime初始化时候做的,应该是挖坑占座,涉及到具体的处理还得是执行过程中的行为。最终,我们发现都会走到java_lang_StringFactory.cc和java.lang.StringFactory.java这些类中,

   public static String newStringFromBytes(byte[] data) {

String result = newStringFromBytes(data, 0, data.length);
if (!result.isEmpty()) {
System.out.println("[" + TAG + "] Returned value: " + result);
logNewEmptyStringStackTrace();
}
return result;
}

public static String newStringFromBytes(byte[] data, int high) {
String result = newStringFromBytes(data, high, 0, data.length);
if (!result.isEmpty()) {
System.out.println("[" + TAG + "] Returned value: " + result);
logNewEmptyStringStackTrace();
}
return result;
}

而jni中创建字符串

  jclass stringClass = env->FindClass("java/lang/String");
    if (stringClass == NULL) {
        return NULL;
    }
    jmethodID midInit = env->GetMethodID(stringClass, "<init>", "([C)V");
    if (midInit == NULL) {
        return NULL;
    }
    jcharArray charArray = env->NewCharArray( 5);
    jchar chars[5] = {'H', 'e', 'l', 'l', 'o'};
    env->SetCharArrayRegion( charArray, 0, 5, chars);
    jstring newString = (jstring)env->NewObject(stringClass, midInit, charArray);
    std::string hello = "Hello from C++";
    const char* cStr = env->GetStringUTFChars(newString, 0);
    __android_log_print(4,"qqq","%s", cStr);
    return env->NewStringUTF(hello.c_str());

这里我们就要从NewObject说起

 static jobject NewObject(JNIEnv* env, jclass java_class, jmethodID mid, ...) {
    va_list args;
    va_start(args, mid);
    ScopedVAArgs free_args_later(&args);
    CHECK_NON_NULL_ARGUMENT(java_class);
    CHECK_NON_NULL_ARGUMENT(mid);
    jobject result = NewObjectV(env, java_class, mid, args);
    return result;
  }

static jobject NewObjectV(JNIEnv* env, jclass java_class, jmethodID mid, va_list args) {
CHECK_NON_NULL_ARGUMENT(java_class);
CHECK_NON_NULL_ARGUMENT(mid);
ScopedObjectAccess soa(env);
ObjPtr<mirror::Class> c = EnsureInitialized(soa.Self(),
soa.Decode<mirror::Class>(java_class));
if (c == nullptr) {
return nullptr;
}
if (c->IsStringClass()) {
// Replace calls to String.<init> with equivalent StringFactory call.
jmethodID sf_mid = jni::EncodeArtMethod<kEnableIndexIds>(
WellKnownClasses::StringInitToStringFactory(jni::DecodeArtMethod(mid)));
return CallStaticObjectMethodV(env, WellKnownClasses::java_lang_StringFactory, sf_mid, args);
}
ObjPtr<mirror::Object> result = c->AllocObject(soa.Self());
if (result == nullptr) {
return nullptr;
}
jobject local_result = soa.AddLocalReference<jobject>(result);
CallNonvirtualVoidMethodV(env, local_result, java_class, mid, args);
if (soa.Self()->IsExceptionPending()) {
return nullptr;
}
return local_result;
}

最终执行到CallNonvirtualVoidMethodV到InvokeWithVarArgs这里又涉及到了字符串的创建相关处理

  bool is_string_init = method->GetDeclaringClass()->IsStringClass() && method->IsConstructor();
  if (is_string_init) {
     // LOG(ERROR)<<"STRING BEFORE REPLACE "<<method->PrettyMethod();

// Replace calls to String.<init> with equivalent StringFactory call.
method = WellKnownClasses::StringInitToStringFactory(method);
// LOG(ERROR)<<"STRING AFTER REPLACE "<<method->PrettyMethod();
}
ObjPtr

也是最终用到了StringInitToStringFactory这个方法。至此java和jni反射创建字符串的流程看完了,然后还有涉及 std::string下的字符串创建,这里大家可以自行探索,理论上能想通的,都可以进行实现的。

监控字符串改机

根据以上我们可以在java_lang_StringFactory.cc和java.lang.StringFactory.java这些类中,写入一些堆栈代码,当有app函数执行这些方法的时候,我们可以将其堆栈打印出来,当然也可以使用Frida hook ,用啥都无所谓了,效果图演示,会有一些日志影响我们分析,我们可以进行过滤即可。

​改机系列-字符串打印

思路总结

字符串在安卓虚拟机的处理流程

1.java层字符串创建处理执行流程

2.jni反射字符串创建处理执行流程

3.在关键类中添加自己的堆栈代码。

 

 

原文始发于微信公众号(小瑶在工地):​改机系列-字符串打印

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年8月5日14:23:35
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   ​改机系列-字符串打印https://cn-sec.com/archives/2012027.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息