记一次修改LG启动第0屏的失败过程

记一次修改LG启动第0屏的失败过程

尝试

lg手机的各种底层静态资源放在raw_resources分区里,包括但不限于开机第0屏,解锁bl的手机启动时候显示的黄色感叹号等

我试图使用如下程序替换:

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
from dataclasses import dataclass
import struct
from PIL import Image

TARGET_NAME = b"lglogo_image"
REPLACE_WITH = "nl.png" #需要保证图片大小与原图一致!
ORIG_FILE = "raw_resources_a.bin"
OUT_FILE = "edited.bin"

HEAD_FMT = struct.Struct("<16s2L16sL")
IMINFO_FMT = struct.Struct("<40s6L")
IMG_LIST_ADDR = 0x1000

@dataclass
class RLE_IMG_INFO:
name:bytes
offset:int
size:int #bytes大小
width:int
height:int
offsetX:int
offsetY:int

@dataclass
class RLE_IMG:
info:RLE_IMG_INFO
data:bytes

LAST_OFF = 0x2000
def calc_off(size): #4k对齐
global LAST_OFF
ret = LAST_OFF
LAST_OFF = (LAST_OFF+size&0xfffffff000)+0x1000
return ret

def to_rle(source) -> RLE_IMG:
img = Image.open(source).convert("RGB")

counter = -1
prev = (-1,-1,-1)
result_bytes = b""

for p in img.getdata():
if p != prev or counter >= 0xff:
if counter != -1:
result_bytes += bytes([counter,prev[2],prev[1],prev[0]]) #注意RGB到BGR的转化
prev = p
counter = 1
else:
counter += 1
result_bytes += bytes([counter,prev[2],prev[1],prev[0]]) #注意处理余下的
return RLE_IMG(RLE_IMG_INFO(
b'',0,len(result_bytes),img.width,img.height,0,0 #offset在之后的流程重新安排
),result_bytes)


image_list:list[tuple[int,RLE_IMG]] = []

raw_res = open(ORIG_FILE,"rb")

magic, count, version, dev, dataend = HEAD_FMT.unpack(raw_res.read(HEAD_FMT.size))

if magic != b'BOOT_IMAGE_RLE\x00\x00':
print('bad magic!')
exit(1)
print("magic:{}\nimage_count:{}\nversion:{}\ntarget_dev:{}\ndata_end:{}\n".format(magic, count, version, dev, dataend))

raw_res.seek(dataend,0)
sign_orig = raw_res.read(0x100)#文件的最后乃签名,不过咱们是没办法签的.
#读取
for i in range(count):
raw_res.seek(IMG_LIST_ADDR+i*IMINFO_FMT.size,0)

r_info = RLE_IMG_INFO(*IMINFO_FMT.unpack(raw_res.read(IMINFO_FMT.size)))
raw_res.seek(r_info.offset,0)
img_data = raw_res.read(r_info.size)
if len(img_data) != r_info.size:
print("读取到的字节不够")
exit(1)
image_list.append((i,RLE_IMG(r_info,img_data)))
#替换
for i,img in image_list:
if not img.info.name.startswith(TARGET_NAME):
continue
print("found target_image:",img.info)
r_rle = to_rle(REPLACE_WITH)
r_rle.info.name = img.info.name
r_rle.info.offsetX = img.info.offsetX
r_rle.info.offsetY = img.info.offsetY
image_list[i] = (i,r_rle)
#写出
out_file = open(OUT_FILE,"wb")
for i,img in image_list:
out_file.seek(IMG_LIST_ADDR+i*IMINFO_FMT.size,0)
print("rearange image:",img.info.name.decode('ansi'))
img.info.offset = calc_off(img.info.size)
print(" size={},new_offset={}".format(hex(img.info.size),hex(img.info.offset)))
out_file.write(IMINFO_FMT.pack(*tuple(img.info.__dict__.values())))#这里其实有隐患的,不过不瞎改就没事
out_file.seek(img.info.offset,0)
out_file.write(img.data)

new_dataending = out_file.tell()
out_file.write(sign_orig) #保留原始签名,不过肯定是没用了
out_file.seek(0,0)
out_file.write(HEAD_FMT.pack(
magic, count, version, dev, new_dataending
))
print("new_dataending =",hex(new_dataending))
out_file.seek(0x7fffff)
out_file.write(b'\x00')
图像
原图lglogo_image_orig
测试用图lglogo_image_edited

经过验证代码没有功能性问题,可以正确生成rle图像并修改映像.

失败原因

正如我在代码中所说,lg给比较新的raw_resources都加了校验,无论是否解锁,刷入后手机并不会调用修改过的图片,而会直接用文字的形式写一条Image data corrupted在屏幕下方,而且由于是整个文件一个签名,所以连带着其他各种静态图像(比如recovery界面的选项、关机充电的图像)也不会再被使用,不过并不影响手机启动流程,只是没有图片了

lg对这些资源的强制校验貌似跟以前看过的一个cve有关,相关poc:CVE-2020-12753-POC

该cve的详细原理可见:🔋 📱❄️🥾🔓, an EL1/EL3 coldboot vulnerability affecting 7 years of LG Android devices

而且由于这些手机都开了安全启动,所以这原版bl是不能够修改的,所以应该是绕不过去了.哎,残念

参考资料

[Guide][MOD] Hide unlocked Bootloader warning boot screen | XDA Forums

[Tool] Changing LG Logo image at start | XDA Forums

🔋 📱❄️🥾🔓, an EL1/EL3 coldboot vulnerability affecting 7 years of LG Android devices


记一次修改LG启动第0屏的失败过程
https://www.hakurei.org.cn/2025/04/04/failed-lg-change-startuplogo/
作者
zjkimin
发布于
2025年4月4日
更新于
2025年4月4日
许可协议