随着android系统的更新,在android7.0中,recovery模式中已经不在挂载data分区,而且data分区可以设置加密,这样更促进了在recovery分区中不挂载data分区,加强了用户的安全性。但是这样就会有问题,当升级包较大时cache分区是放不下的,增大cache分区只会浪费资源,最好的办法还是把它放在data分区下。但是因为加密和不挂载的原因导致在recovery模式下是无法使用升级包的。而uncrypt机制就是解决这个问题的。它的基本原理就是,在正常模式下记录升级包所在的磁盘区块号,而且如果加密了就通过读进行解密,再绕过加密写到存储磁盘上。当记录好了磁盘区块号后,启动到recovery模式后就可以不挂载data分区,直接根据区块号读取升级包中的数据进行升级。下面记录代码的分析流程。
frameworks/base/core/java/android/os/RecoverySystem.java中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | public static void processPackage(Context context, File packageFile, final ProgressListener listener, final Handler handler) throws IOException { String filename = packageFile.getCanonicalPath(); if (!filename.startsWith( "/data/" )) { return ; } RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); IRecoverySystemProgressListener progressListener = null ; if (listener != null ) { final Handler progressHandler; if (handler != null ) { progressHandler = handler; } else { progressHandler = new Handler(context.getMainLooper()); } progressListener = new IRecoverySystemProgressListener.Stub() { int lastProgress = 0 ; long lastPublishTime = System.currentTimeMillis(); @Override public void onProgress( final int progress) { final long now = System.currentTimeMillis(); progressHandler.post( new Runnable() { @Override public void run() { if (progress > lastProgress && now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) { lastProgress = progress; lastPublishTime = now; listener.onProgress(progress); } } }); } }; } if (!rs.uncrypt(filename, progressListener)) { throw new IOException( "process package failed" ); } } |
首先判断升级包是否是在data分区下,如果不是则不需要进行uncrypt进行处理,否则使用uncrypt进行处理。最后调用rs.uncrypt(filename,progressListener)。
bootable/recovery/uncrypt/uncrypt.cpp中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | static constexpr int WINDOW_SIZE = 5; static constexpr int FIBMAP_RETRY_LIMIT = 3; static const std::string CACHE_BLOCK_MAP = "/cache/recovery/block.map" ; static const std::string UNCRYPT_PATH_FILE = "/cache/recovery/uncrypt_file" ; static const std::string UNCRYPT_STATUS = "/cache/recovery/uncrypt_status" ; static const std::string UNCRYPT_SOCKET = "uncrypt" ; static bool uncrypt_wrapper( const char * input_path, const char * map_file, const int socket) { // Initialize the uncrypt error to kUncryptErrorPlaceholder. log_uncrypt_error_code(kUncryptErrorPlaceholder); std::string package; if (input_path == nullptr ) { if (!find_uncrypt_package(UNCRYPT_PATH_FILE, &package)) { write_status_to_socket(-1, socket); // Overwrite the error message. log_uncrypt_error_code(kUncryptPackageMissingError); return false ; } input_path = package.c_str(); } CHECK(map_file != nullptr ); auto start = std::chrono::system_clock::now(); int status = uncrypt(input_path, map_file, socket); std::chrono::duration< double > duration = std::chrono::system_clock::now() - start; int count = static_cast < int >(duration.count()); std::string uncrypt_message = android::base::StringPrintf( "uncrypt_time: %d\n" , count); if (status != 0) { // Log the time cost and error code if uncrypt fails. uncrypt_message += android::base::StringPrintf( "uncrypt_error: %d\n" , status); if (!android::base::WriteStringToFile(uncrypt_message, UNCRYPT_STATUS)) { PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS; } write_status_to_socket(-1, socket); return false ; } if (!android::base::WriteStringToFile(uncrypt_message, UNCRYPT_STATUS)) { PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS; } write_status_to_socket(100, socket); return true ; } |
通过调用该方法进行uncrypt的处理。
1.判断升级包路径是否为NULL,如果为NULL则从UNCRYPT_PATH_FILE中读取升级包的路径名,并且检查map文件路径
1 2 3 4 5 6 7 8 9 10 11 12 | static bool find_uncrypt_package( const std::string& uncrypt_path_file, std::string* package_name) { CHECK(package_name != nullptr ); std::string uncrypt_path; if (!android::base::ReadFileToString(uncrypt_path_file, &uncrypt_path)) { PLOG(ERROR) << "failed to open \"" << uncrypt_path_file << "\"" ; return false ; } // Remove the trailing '\n' if present. *package_name = android::base::Trim(uncrypt_path); return true ; } |
2.调用uncrypt(input_path, map_file, socket),并且记录该方法的执行时间。
3.将更新时间和升级后的状态记录到UNCRYPT_STATUS所指向的文件
接着继续分析uncrypt函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | static int uncrypt( const char * input_path, const char * map_file, const int socket) { LOG(INFO) << "update package is \"" << input_path << "\"" ; // Turn the name of the file we're supposed to convert into an // absolute path, so we can find what filesystem it's on. char path[PATH_MAX+1]; if (realpath(input_path, path) == NULL) { PLOG(ERROR) << "failed to convert \"" << input_path << "\" to absolute path" ; return 1; } bool encryptable; bool encrypted; const char * blk_dev = find_block_device(path, &encryptable, &encrypted); if (blk_dev == NULL) { LOG(ERROR) << "failed to find block device for " << path; return 1; } // If the filesystem it's on isn't encrypted, we only produce the // block map, we don't rewrite the file contents (it would be // pointless to do so). LOG(INFO) << "encryptable: " << (encryptable ? "yes" : "no" ); LOG(INFO) << " encrypted: " << (encrypted ? "yes" : "no" ); // Recovery supports installing packages from 3 paths: /cache, // /data, and /sdcard. (On a particular device, other locations // may work, but those are three we actually expect.) // // On /data we want to convert the file to a block map so that we // can read the package without mounting the partition. On /cache // and /sdcard we leave the file alone. if ( strncmp (path, "/data/" , 6) == 0) { LOG(INFO) << "writing block map " << map_file; return produce_block_map(path, map_file, blk_dev, encrypted, socket); } return 0; } |
1.将升级包的路径转换为绝对路径
2.寻找文件所在的分区(块设备)同时记录该分区是否进行了加密处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | static const char * find_block_device( const char * path, bool * encryptable, bool * encrypted) { // Look for a volume whose mount point is the prefix of path and // return its block device. Set encrypted if it's currently // encrypted. for ( int i = 0; i < fstab->num_entries; ++i) { struct fstab_rec* v = &fstab->recs[i]; if (!v->mount_point) { continue ; } int len = strlen (v->mount_point); if ( strncmp (path, v->mount_point, len) == 0 && (path[len] == '/' || path[len] == 0)) { *encrypted = false ; *encryptable = false ; if (fs_mgr_is_encryptable(v) || fs_mgr_is_file_encrypted(v)) { *encryptable = true ; if (android::base::GetProperty( "ro.crypto.state" , "" ) == "encrypted" ) { *encrypted = true ; } } return v->blk_device; } } return NULL; } |
3.判断升级包是否在data分区下,如果是在data分区下则调用produce_block_map方法,生成升级包对应的block文件
继续分析produce_block_map方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | static int produce_block_map( const char * path, const char * map_file, const char * blk_dev, bool encrypted, int socket) { std::string err; if (!android::base::RemoveFileIfExists(map_file, &err)) { LOG(ERROR) << "failed to remove the existing map file " << map_file << ": " << err; return kUncryptFileRemoveError; } std::string tmp_map_file = std::string(map_file) + ".tmp" ; android::base::unique_fd mapfd(open(tmp_map_file.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)); if (mapfd == -1) { PLOG(ERROR) << "failed to open " << tmp_map_file; return kUncryptFileOpenError; } // Make sure we can write to the socket. if (!write_status_to_socket(0, socket)) { LOG(ERROR) << "failed to write to socket " << socket; return kUncryptSocketWriteError; } struct stat sb; if (stat(path, &sb) != 0) { LOG(ERROR) << "failed to stat " << path; return kUncryptFileStatError; } LOG(INFO) << " block size: " << sb.st_blksize << " bytes" ; int blocks = ((sb.st_size-1) / sb.st_blksize) + 1; LOG(INFO) << " file size: " << sb.st_size << " bytes, " << blocks << " blocks" ; std::vector< int > ranges; std::string s = android::base::StringPrintf( "%s\n%" PRId64 " %" PRId64 "\n" , blk_dev, static_cast <int64_t>(sb.st_size), static_cast <int64_t>(sb.st_blksize)); if (!android::base::WriteStringToFd(s, mapfd)) { PLOG(ERROR) << "failed to write " << tmp_map_file; return kUncryptWriteError; } std::vector<std::vector<unsigned char >> buffers; if (encrypted) { buffers.resize(WINDOW_SIZE, std::vector<unsigned char >(sb.st_blksize)); } int head_block = 0; int head = 0, tail = 0; android::base::unique_fd fd(open(path, O_RDONLY)); if (fd == -1) { PLOG(ERROR) << "failed to open " << path << " for reading" ; return kUncryptFileOpenError; } android::base::unique_fd wfd; if (encrypted) { wfd.reset(open(blk_dev, O_WRONLY)); if (wfd == -1) { PLOG(ERROR) << "failed to open " << blk_dev << " for writing" ; return kUncryptBlockOpenError; } } off64_t pos = 0; int last_progress = 0; while (pos < sb.st_size) { // Update the status file, progress must be between [0, 99]. int progress = static_cast < int >(100 * ( double (pos) / double (sb.st_size))); if (progress > last_progress) { last_progress = progress; write_status_to_socket(progress, socket); } if ((tail+1) % WINDOW_SIZE == head) { // write out head buffer int block = head_block; if (ioctl(fd, FIBMAP, &block) != 0) { PLOG(ERROR) << "failed to find block " << head_block; return kUncryptIoctlError; } if (block == 0) { LOG(ERROR) << "failed to find block " << head_block << ", retrying" ; int error = retry_fibmap(fd, path, &block, head_block); if (error != kUncryptNoError) { return error; } } printf ( "head==%d,tail==%d,head_block==%d,pos=%ld\n" ,head,tail,head_block,pos); printf ( "uncrypt:block=%d\n" ,block); add_block_to_ranges(ranges, block); if (encrypted) { if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd, static_cast <off64_t>(sb.st_blksize) * block) != 0) { return kUncryptWriteError; } } head = (head + 1) % WINDOW_SIZE; ++head_block; } // read next block to tail if (encrypted) { size_t to_read = static_cast < size_t >( std::min( static_cast <off64_t>(sb.st_blksize), sb.st_size - pos)); if (!android::base::ReadFully(fd, buffers[tail].data(), to_read)) { PLOG(ERROR) << "failed to read " << path; return kUncryptReadError; } pos += to_read; } else { // If we're not encrypting; we don't need to actually read // anything, just skip pos forward as if we'd read a // block. pos += sb.st_blksize; } tail = (tail+1) % WINDOW_SIZE; } while (head != tail) { // write out head buffer int block = head_block; if (ioctl(fd, FIBMAP, &block) != 0) { PLOG(ERROR) << "failed to find block " << head_block; return kUncryptIoctlError; } if (block == 0) { LOG(ERROR) << "failed to find block " << head_block << ", retrying" ; int error = retry_fibmap(fd, path, &block, head_block); if (error != kUncryptNoError) { return error; } } add_block_to_ranges(ranges, block); if (encrypted) { if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd, static_cast <off64_t>(sb.st_blksize) * block) != 0) { return kUncryptWriteError; } } head = (head + 1) % WINDOW_SIZE; ++head_block; } if (!android::base::WriteStringToFd( android::base::StringPrintf( "%zu\n" , ranges.size() / 2), mapfd)) { PLOG(ERROR) << "failed to write " << tmp_map_file; return kUncryptWriteError; } for ( size_t i = 0; i < ranges.size(); i += 2) { if (!android::base::WriteStringToFd( android::base::StringPrintf( "%d %d\n" , ranges[i], ranges[i+1]), mapfd)) { PLOG(ERROR) << "failed to write " << tmp_map_file; return kUncryptWriteError; } } if (fsync(mapfd) == -1) { PLOG(ERROR) << "failed to fsync \"" << tmp_map_file << "\"" ; return kUncryptFileSyncError; } if (close(mapfd.release()) == -1) { PLOG(ERROR) << "failed to close " << tmp_map_file; return kUncryptFileCloseError; } if (encrypted) { if (fsync(wfd) == -1) { PLOG(ERROR) << "failed to fsync \"" << blk_dev << "\"" ; return kUncryptFileSyncError; } if (close(wfd.release()) == -1) { PLOG(ERROR) << "failed to close " << blk_dev; return kUncryptFileCloseError; } } if ( rename (tmp_map_file.c_str(), map_file) == -1) { PLOG(ERROR) << "failed to rename " << tmp_map_file << " to " << map_file; return kUncryptFileRenameError; } // Sync dir to make rename() result written to disk. std::string file_name = map_file; std::string dir_name = dirname(&file_name[0]); android::base::unique_fd dfd(open(dir_name.c_str(), O_RDONLY | O_DIRECTORY)); if (dfd == -1) { PLOG(ERROR) << "failed to open dir " << dir_name; return kUncryptFileOpenError; } if (fsync(dfd) == -1) { PLOG(ERROR) << "failed to fsync " << dir_name; return kUncryptFileSyncError; } if (close(dfd.release()) == -1) { PLOG(ERROR) << "failed to close " << dir_name; return kUncryptFileCloseError; } return 0; } |
该方法是生成map文件的主要方法,也是uncrypt的核心方法
1.如果map_file已经存在则进行删除
2.创建并且打卡map_file的临时文件
3.确保socket是可写的,定义ranges保存块的区间,buffers用来保存数据,进行解密。
4.获取升级文件的详细信息,包括块的大小,文件的大小,计算出块数。
5.将块设备路径,文件的大小,块的大小写入到map_file.tmp文件中
6.以只读的方式打开升级包,以写的方式打开块设备
7.在第一个while循环中,首先输出进度,然后(如果进行了加密)填充窗口大小的数据(WINDOW_SIZE)指定。填充好后,获取物理块号,保存块号,如果加密了就进行解密。据逻辑块号获取物理块号,核心方法是ioctl(fd, FIBMAP, &block) ,block传入的时候是逻辑块号,方法调用成功后将物理块号回写入到block中。例如你传入的是0获取的就是第0块的物理块号,传入的是1则获取的就是第一块的物理块号。方法调用成功返回0,如果block获取到的值为0说明没有获取到物理块号,head_block获取已经得到的块数。注意使用了数据窗口机制获取数据,并且add_block_to_ranges也很巧妙的记录了物理块号的范围。
add_block_to_ranges
1 2 3 4 5 6 7 8 9 10 11 | static void add_block_to_ranges(std::vector< int >& ranges, int new_block) { if (!ranges.empty() && new_block == ranges.back()) { // If the new block comes immediately after the current range, // all we have to do is extend the current range. ++ranges.back(); } else { // We need to start a new range. ranges.push_back(new_block); ranges.push_back(new_block + 1); } } |
这个函数利用了磁盘保存数据时往往都是在连续的块中存储的特性。
8.跳出第一个循环,执行到第二个循环,将最后范围内的物理块好进行记录,同时如果进行了加密就解密。
9.在tmp_map_file中记录range数,range的范围。重命名tmp_map_file等做一些资源关闭释放的工作
总结:uncrypt思路不复杂,关键是要知道可以绕过文件系统直接对块进行I/O操作。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具