一种基于ro属性的改机检测思路

问题

最近在研究一个游戏样本时,发现了一种基于 prop_info#serial 来判定是否改机设备的思路。
ro属性是不允许修改,因此prop_info#serial后四位应该为0。但是一般的改机思路都是简单绕过ro属性不可写的限制,强行写入,但是强行写入后prop_info#serial会记录改动的次数。

__int64 __fastcall checkifsetroprop_1A7378(__int64 a1)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]

  v1 = a1;
  v9 = *(_QWORD *)(_ReadStatusReg(TPIDR_EL0) + 40);
  if ( a1 )
  {
    memset(s, 0, sizeof(s));
    if ( (int)findprop_177C34(v1, s) < 1 )      // prop not exist
    {
      LODWORD(v1) = 0;
      return (unsigned int)v1;
    }
    *(_DWORD *)v7 = 52;
    v2 = 0;
    v7[4] = 17;
    strcpy(&v7[5], "M");
    do
    {
      v7[v2 + 4] ^= (_BYTE)v2 + v7[0];
      ++v2;
    }
    while ( v2 != 2 );
    v7[6] = 0;
    format_str_11C794(fmt_ret, &v7[4]);         // format("%x", serial)
    v3 = fmt_ret[1];
    if ( (fmt_ret[0] & 1) == 0 )
      v3 = (unsigned __int64)LOBYTE(fmt_ret[0]) >> 1;
    if ( v3 < 5 )
    {
      LODWORD(v1) = 0;
      if ( (fmt_ret[0] & 1) == 0 )
        return (unsigned int)v1;
    }
    else
    {
      v4 = 0;
      strcpy(v7, "7777");
      do
        v7[v4++] -= 7;
      while ( v4 != 4 );
      if ( check_if_ends_with_1A8E4C((unsigned __int8 *)fmt_ret, v7) )// checkif_ends_with(serial, "0000")
        LODWORD(v1) = 1;                        // prop clean
      else
        LODWORD(v1) = -1;
      if ( (fmt_ret[0] & 1) == 0 )              // prop updated
        return (unsigned int)v1;
    }
    sub_2D4258((void *)fmt_ret[2]);
  }
  return (unsigned int)v1;
}

如何理解 prop_info#serial

prop_info 定义如下

struct prop_info {  
  atomic_uint_least32_t serial;
  union {
    char value[PROP_VALUE_MAX];
    struct {
      char error_message[kLongLegacyErrorBufferSize];
      uint32_t offset;
    } long_property;
  };
  char name[0];
...
}

已知prop_info是用来存某个android属性的,name就是key,中间那个union就是value,还有个serial是干啥的?搜一把先


image.png
image.png

看上去用法都集中在这三四个cpp中,context_split.cpp不用看,一般是用contexts_serialized.cpp。
system_properties.cpp

#define SERIAL_DIRTY(serial) ((serial)&1)
#define SERIAL_VALUE_LEN(serial) ((serial) >> 24)

prop_info.cpp
普通属性初始化:
atomic_init(&this->serial, valuelen << 24);
更新属性时:

 uint32_t serial = atomic_load_explicit(&pi->serial, memory_order_relaxed);
 serial |= 1;
...修改属性
atomic_store_explicit(&pi->serial, (len << 24) | ((serial + 1) & 0xffffff), memory_order_release);//每次修改,serial低24位便+1

libc/system_properties/system_properties.cpp

int SystemProperties::Update(prop_info* pi, const char* value, unsigned int len) {
  if (len >= PROP_VALUE_MAX) {
    return -1;
  }

  if (!initialized_) {
    return -1;
  }

  prop_area* pa = contexts_->GetSerialPropArea();
  if (!pa) {
    return -1;
  }

  uint32_t serial = atomic_load_explicit(&pi->serial, memory_order_relaxed);
  serial |= 1;
  atomic_store_explicit(&pi->serial, serial, memory_order_relaxed);
  // The memcpy call here also races.  Again pretend it
  // used memory_order_relaxed atomics, and use the analogous
  // counterintuitive fence.
  atomic_thread_fence(memory_order_release);
  strlcpy(pi->value, value, len + 1);

 atomic_store_explicit(&pi->serial, (len << 24) | ((serial + 1) & 0xffffff), memory_order_release);
  __futex_wake(&pi->serial, INT32_MAX);

  atomic_store_explicit(pa->serial(), atomic_load_explicit(pa->serial(), memory_order_relaxed) + 1,
                        memory_order_release);
  __futex_wake(pa->serial(), INT32_MAX);

  return 0;
}

所以这个 serial 可以这样理解:
高 8 位:属性值长度 len;低 24 位:serial / flags 区
其中:bit0:dirty 标志(更新过程中会临时置 1);bit16:kLongFlag;其余低位:更新计数/serial 递增值。

如何解决

法一

已知每次修改属性,便会将serial最后一位置为1,修改完后再加1,使得最后一位为0来做同步。因此serial有两个作用,同步和记录修改次数。

int SystemProperties::Update(prop_info* pi, const char* value, unsigned int len) {
  if (len >= PROP_VALUE_MAX) {
    return -1;
  }

  if (!initialized_) {
    return -1;
  }

  prop_area* pa = contexts_->GetSerialPropArea();
  if (!pa) {
    return -1;
  }

  uint32_t serial = atomic_load_explicit(&pi->serial, memory_order_relaxed);
  serial |= 1;
  atomic_store_explicit(&pi->serial, serial, memory_order_relaxed);
  // The memcpy call here also races.  Again pretend it
  // used memory_order_relaxed atomics, and use the analogous
  // counterintuitive fence.
  atomic_thread_fence(memory_order_release);
  strlcpy(pi->value, value, len + 1);

  // atomic_store_explicit(&pi->serial, (len << 24) | ((serial + 1) & 0xffffff), memory_order_release);
  atomic_store_explicit(&pi->serial, (len << 24) | ((serial + 1) & 0xff0000), memory_order_release);
  __futex_wake(&pi->serial, INT32_MAX);

  atomic_store_explicit(pa->serial(), atomic_load_explicit(pa->serial(), memory_order_relaxed) + 1,
                        memory_order_release);
  __futex_wake(pa->serial(), INT32_MAX);

  return 0;
}

法二

已知开机时会读取多个文件,按a=b这个规则读取放在一个map中,再遍历map统一调用PropertySet设置到共享内存中。
那么我们可以新增一个文件,让property_load_boot_defaults最后读取,用来覆盖先前的属性,但是只会调用一次PropertySet。但是这种方案需要重启才能生效。

/home/taoying/data/framework/aosp10-r39/system/core/init/property_service.cpp

void property_load_boot_defaults(bool load_debug_prop) {
    // TODO(b/117892318): merge prop.default and build.prop files into one
    // We read the properties and their values into a map, in order to always allow properties
    // loaded in the later property files to override the properties in loaded in the earlier
    // property files, regardless of if they are "ro." properties or not.
    std::map<std::string, std::string> properties;
    if (!load_properties_from_file("/system/etc/prop.default", nullptr, &properties)) {
        // Try recovery path
        if (!load_properties_from_file("/prop.default", nullptr, &properties)) {
            // Try legacy path
            load_properties_from_file("/default.prop", nullptr, &properties);
        }
    }
    load_properties_from_file("/system/build.prop", nullptr, &properties);
    load_properties_from_file("/vendor/default.prop", nullptr, &properties);
    load_properties_from_file("/vendor/build.prop", nullptr, &properties);
    if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_Q__) {
        load_properties_from_file("/odm/etc/build.prop", nullptr, &properties);
    } else {
        load_properties_from_file("/odm/default.prop", nullptr, &properties);
        load_properties_from_file("/odm/build.prop", nullptr, &properties);
    }
    load_properties_from_file("/product/build.prop", nullptr, &properties);
    load_properties_from_file("/product_services/build.prop", nullptr, &properties);
    load_properties_from_file("/factory/factory.prop", "ro.*", &properties);

    if (load_debug_prop) {
        LOG(INFO) << "Loading " << kDebugRamdiskProp;
        load_properties_from_file(kDebugRamdiskProp, nullptr, &properties);
    }

    for (const auto& [name, value] : properties) {
        std::string error;
        if (PropertySet(name, value, &error) != PROP_SUCCESS) {
            LOG(ERROR) << "Could not set '" << name << "' to '" << value
                       << "' while loading .prop files" << error;
        }
    }

    property_initialize_ro_product_props();
    property_derive_build_fingerprint();

    update_sys_usb_config();
}
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容