问题
最近在研究一个游戏样本时,发现了一种基于 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是干啥的?搜一把先


看上去用法都集中在这三四个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();
}