//nc1020内核保护原理
注意:这里需要你的汇编语言能力已经较强了,如果看不懂,说明你
功底不够,先练练再来吧!
我们有时候RESET后,系统显示 升级的选项,如图所示:
一般来说,只要内核的数据被改动后,RESET后,系统就会出现这种情况.之所以我没有在前面讲解内核区闪存的修改
就是因为这个原因,即使你能修改内核,但是如果不懂内核保护原理,RESET后,就出现上面的情况,所以,今天我要讲解
内核保护原理.
首先我们需要找到检验内核是否被修改的程序,其实是很简单的过程,我们就从上面显示的汉字作为突破点.
进入 NCTOOLS,输入 S C 4000 BFFF 80 A0,然后输入要查找的汉字 "*升级系统程序*"
不过没有找到,没有关系,继续 输入 S C 4000 BFFF 00 03,输入要查找的汉字"*升级系统程序*"
不过很不幸,还是没有找到,这时大家别泄气,想想还有什么地方可找呢?对了当地址0A的内容为00时,C000-DFFF也是闪存啊
立刻 S C C000 DFFF 00,输入 "*升级系统程序*",找到了,在地址 CA73 开始,按 中英数 切换到汉字模式
CA73:*升级系统程序* 1.经串口升级 2.经红外升级
现在好了,因为必然有一段程序将这些汉字显示在屏幕上,所以根据经验,应该可能存在有这样的代码
LDA $CA73,X 机器码: BD 73 CA
查找, S H C000 DFFF 00,输入 BD 73 CA,找到了,在地址CACC有这样的代码
CACC: BD 73 CA 99 D4 02 BD 81
CAD4: CA 99 E8 02 BD 8F CA 99
CADC: FC 02 88 CA 10 EA 8A 48
CAE4: A9 00 A2 09 9D F8 03 CA
CAEC: D0 FA AD F8 03 29 3F 8D
CAF4: F8 03 68 AA A9 FF 8D 2D
CAFC: 04 A9 FF 8D 2E 04 00 15
CB04: 8A A9 00 85 C7 A9 00 8D
CB0C: 6E 04 8D AE 04 A5 C7 10
CB14: F4 29 7F 85 C7 C9 6E F0
CB1C: 29 C9 62 D0 E8 20 5C CE
CB24: F0 03 4C 9D CA A9 10 8D
CB2C: 00 17 A9 11 8D 02 17 A9
CB34: F0 8D 01 17 A9 12 8D 03
CB3C: 17 A9 00 8D 04 17 00 10
CB44: 8B 60 20 5C CE F0 03 4C
CB4C: 9D CA A9 B0 8D 00 17 A9
CB54: 00 8D 03 17 A9 11 8D 02
CB5C: 17 A9 F0 8D 01 17 A9 00
CB64: 8D 04 17 00 10 8B 60 A9
现在我们将其反汇编,来让大家明白是什么意思
CACC: BD 73 CA LDA $CA73,X //读取*升级系统程序*中的汉字,发送到屏幕RAM
CACF: 99 D4 02 STA $02D4,Y
CAD2: BD CA 81 LDA $CA81,X //读取 "1.经串口升级",发送到屏幕RAM
CAD5: 99 E8 02 STA $02E8,Y
CAD8: BD 8F CA LDA $CA8D,X //读取 "2.经红外升级",发送到屏幕RAM
CADB: 99 FC 02 STA $02FC,Y
CADE: 88 DEY
CADF: CA DEX
CAE0: 10 EA BPL $CACC
CAE2: 8A TXA
CAE3: 48 PHA
CAE4: A9 00 LDA #$00
CAE6: A2 09 LDX #$09
CAE8: 9D F8 03 STA $03F8,X
CAEB: CA DEX
CAEC: D0 FA BNE $CAE8
CAEE: AD F8 03 LDA $03F8
CAF1: 29 3F AND #$3F
CAF3: 8D F8 03 STA $03F8
CAF6: 68 PLA
CAF7: AA TAX
CAF8: A9 FF LDA #$FF
CAFA: 8D 2D 04 STA $042D
CAFD: A9 FF LDA #$FF
CAFF: 8D 2E 04 STA $042E
CB02: 00 15 8A INT $8A15 //看到INT $8A15,就应该知道上面的代码就是显示汉字到屏幕上用的
CB05: A9 00 LDA #$00 //地址C7是用户按键的扫描码,这里没有调用 INT $C008,或INT $C007
CB07: 85 C7 STA $C7 //数据00送地址C7
CB09: A9 00 LDA #$00 //立即数00送地址046E,因为这里是无限循环,若不把数据00送046E,过5秒就死机
CB0B: 8D 6E 04 STA $046E 所以如果是无限循环,每次循环都应该加这代码
CB0E: 8D AE 04 STA $04AE
CB11: A5 C7 LDA $C7 //读取地址 C7的值,若用户按了键盘,就转到地址CB15
CB13: 10 F4 BPL $CB09
CB15: 29 7F AND #$7F //把按键的值和7F异或送回地址C7,那么得到的就是键盘扫描码
CB17: 85 17 STA $C7
CB19: C9 6E CMP #$6E //用户是否 按 2 键?
CB1B: F0 29 BEQ $CB46 //若是 则转到地址CB46,执行 经红外升级 代码
CB1D: C9 62 CMP #$62 //若 否,那么用户是不是按 1 键?
CB1F: D0 E8 BNE $CB09 //若不是,转地址CB09,继续查询用户的按键
CB21: 20 5C CE JSR $CE5C //调用子程序CE5C,是检查电量 的子程序
CB24: F0 03 BEQ $CB29 //若电量充足,则Z=1,否则Z=0,这里若电量足,程序转到地址CB29
CB26: 4C 9D CA JMP $CA9D //若电量不足,则程序转地址CA9D,在屏幕上显示 "电池电力已不足,不能做升级操作"
CB29: A9 10 LDA #$10 //这是 从串口升级 的程序
CB2B: 8D 00 17 STA $1700
CB2E: A9 11 LDA #$11
CB30: 8D 02 17 STA $1702
CB33: A9 F0 LDA #$F0
CB35: 8D 01 17 STA $1701
CB38: A9 12 LDA #$12
CB3A: 8D 03 17 STA $1703
CB3D: A9 00 LDA #$00
CB3F: 8D 04 17 STA $1704
CB42: 00 10 8B INT $8B10
CB45: 60 RTS
CB46: 20 5C CE JSR $CE5C //这是 从红外升级 的开始地址
CB49: F0 03 BEQ $CB4E
CB4B: 4C 9D CA JMP $CA9D
CB4E: A9 B0 LDA #$B0
CB50: 8D 00 17 STA $1700
CB53: A9 00 LDA #$00
CB55: 8D 03 17 STA $1703
CB58: A9 11 LDA #$11
CB5A: 8D 02 17 STA $1702
CB5D: A9 F0 LDA $F0
CB5F: 8D 01 17 STA $1701
CB62: A9 00 LDA #$00
CB64: 8D 04 17 STA $1704
CB67: 00 10 8B INT $8B10
CB6A: 60 RTS
这里经过反汇编,我们有理由相信,当检查到内核被修改时,一定会调用上面的程序
但是我们还应该找到这段程序的最开始代码,上面的代码是不完全的,这很简单
只要再往前些字节就应该是了,我们按 双下箭头 ,看到这样的代码:
CA9C: 20 00 2E 8A A2 28 BD 94
CAA4: CE 9D D3 02 CA D0 F7 A9
CAAC: FF 8D 2D 04 A9 FF 8D 2E
CAB4: 04 00 15 8A A9 00 8D 6E
CABC: 04 A5 C7 10 F7 29 7F 85
CAC4: C7 00 2E 8A A2 0D A0 10
我们可以继续反汇编,可能大家觉得非常疲劳了吧,不过没有办法啊,就是这样找的啊,可没有人告诉你哦!
我们目测,应该可以知道应该从地址CA9D开始反汇编,因为 00 2E 8A,我们是熟悉的,就是清屏的中断调用
所以我们就可以猜想,地址 CA9D就是入口地址了.还是先看看反汇编结果吧
CA9D: 00 2E 8A INT $8A2E //这是清屏的中断调用
CAA0: A2 28 LDX #$28
CAA2: BD 94 CE LDA $CE94,X //通过看地址CE94的内容,发现是"电池电力已不足 不能作升级操作"
CAA5: 9D D3 02 STA $02D3,X //把汉字送屏幕RAM
CAA8: CA DEX
CAA9: D0 F7 BNE $CAA2
CAAB: A9 FF LDA #$FF
CAAD: 8D 2D 04 STA $042D
CAB0: A9 FF LDA #$FF
CAB2: 8D 2E 04 STA $042E
CAB5: 00 15 8A INT $8A15 //显示 "电池电力已不足 不能作升级操作"到屏幕
CAB8: A9 00 LDA #$00 //这是按键程序,若用户没有按键,则一直等待
CABA: 8D 6E 04 STA $046E
CABD: A5 C7 LDA $C7
CABF: 10 F7 BPL $CAB8 //没有按键,则转地址CAB8,继续等待
CAC1: 29 7F AND #$7F //按了键,继续执行下面的
CAC3: 85 C7 STA $C7
CAC5: 00 2E 8A INT $8A2E
CAC8: A2 0D LDX #$0D
CACA: A0 10 LDY #$10
通过反汇编,我们可以发现,主程序并不是从地址CA9D开始,我们 G CA9D ,发现屏幕上显示"电池电力已不足 不能作升级操作"
那说明用户按了1,2中的某个键,如果检测到电量不足,才去执行CA9D,若用户按了任意键,则执行从地址 CAC5开始的程序,所以
地址CAC5才是主程序
好了,那么如果系统检测到内核被修改了,一定会调用地址CAC5开始的程序,我们于是可以判断存在这样的代码:
那就是 JSR $CAC5 或者 JMP $CAC5,机器码 分别是 20 C5 CA,4C C5 CA
我们先查找 20 C5 CA,因为这个可能性大些,结果找到了,在地址C9CC
C9CC: 20 C5 CA 4C 94 C9 A9 FF
这里反汇编就是
C9CC: 20 C5 CA JSR $CAC5
C9CF: 4C 94 C9 JMP $C994
我们可以想象,一定是有一段条件判断代码,当满足某个条件,程序才转到地址 CAC5,但这里我们没有发现
我们继续 按 双下箭头 往前看,看见这样的数据:
C994: A9 03 85 00 AD F8 BF C9
C99C: 7F F0 33 20 6B CB A9 00
C9A4: 8D 6E 04 8D 77 04 8D AE
C9AC: 04 A9 2D 8D 76 04 20 B1
C9B4: DD AD 62 04 10 12 AD 62
C9BC: 04 09 04 8D 62 04 AD BF
C9C4: 05 09 10 85 18 8D BF 05
C9CC: 20 C5 CA 4C 94 C9
我们继续反汇编,得到下面代码:
C994: A9 03 LDA #$03 //03送寄存器A
C996: 85 00 STA $00 //切换到 03 页码
C998: AD F8 BF LDA $BFF8 //读取地址BFF8的内容送寄存器A
C99B: C9 7F CMP #$7F //若地址BFF8的内容是不是等于 7F
C99D: F0 33 BEQ $C9D2 //等于7F ,程序转到地址C9D2
C99F: 20 6B CB JSR $CB6B //不等于,则执行下面的程序
C9A2: A9 00 LDA #$00
C9A4: 8D 6E 04 STA $046E
C9A7: 8D 77 04 STA $0477
C9AA: 8D AE 04 STA $04AE
C9AD: A9 2D LDA #$2D
C9AF: 8D 76 04 STA $0476
C9B2: 20 B1 DD JSR $DDB1
C9B5: AD 62 04 LDA $0462
C9B8: 10 12 BPL $C9CC
C9BA: AD 62 04 LDA $0462
C9BD: 09 04 ORA #$04
C9BF: 8D 62 04 STA $0462
C9C2: AD BF 05 LDA $05BF
C9C5: 09 10 ORA #$10
C9C7: 85 18 STA $18
C9C9: 8D BF 05 STA $05BF
C9CC: 20 C5 CA JSR $CAC5
C9CF: 4C 94 C9 JMP $C994
这里我们发现,若03 页码 的地址BFF8 的内容不等于 7F,那么就会出现要求升级的画面,如果等于 7F
程序就转到地址C9D2 开始执行那里的程序.我们这里应该清楚,内核被修改了的检测程序应该就从地址C9D2开始
为什么呢?因为检测03页码地址BFF0的内容并不能检查出内核是否被修改,所以我们转到地址C9D2开始分析那里代码
C9D2: A9 FF 8D 0A 17 8D 0B 17
C9DA: A9 01 8D 04 17 AD 04 17
C9E2: 85 00 A9 00 85 D2 A9 40
C9EA: 85 D3 A9 00 85 80 A9 80
C9F2: 85 81 20 23 CC EE 04 17
C9FA: AD 04 17 C9 03 90 DE A9
CA02: 03 85 00 A9 00 85 D2 A9
CA0A: 40 85 D3 A9 F0 85 80 A9
CA12: 7F 85 18 20 23 CC A9 0F
CA1A: 85 00 A9 00 85 D2 A9 80
CA22: 85 D3 A9 00 85 80 A9 40
CA2A: 85 81 20 23 CC A9 03 85
CA32: 00 AD F0 BF 49 FF CD 0A
CA3A: 17 D0 0B AD F1 BF 49 FF
CA42: CD 0B 17 D0 01 60 AD BF
CA4A: 05 29 FB 85 18 A9 1F 85
CA52: 57 A9 00 85 58 A9 F8 85
CA5A: 59 A9 0F 85 5A A9 00 85
CA62: 54 00 0F C0 AD BF 05 09
CA6A: 04 85 18 8D BF 05 4C 94
CA72: C9 2A C9 FD BC B6 CF B5
哎呀,我都感觉很累了,不过马上就完了,大家撑着啊,马上就完了.
C9D2: A9 FF LDA #$FF
C9D4: 8D 0A 17 STA $170A
C9D7: 8D 0B 17 STA $170B
C9DA: A9 01 LDA #$01 //这段是检查 01-02页码的地址4000-BFFF的数据
C9DC: 8D 04 17 STA $1704
C9DF: AD 04 17 LDA $1704
C9E2: 85 00 STA $00
C9E4: A9 00 LDA #$00
C9E6: 85 D2 STA $D2
C9E8: A9 40 LDA #$40
C9EA: 85 D3 STA $D3
C9EC: A9 00 LDA #$00
C9EE: 85 80 STA $80
C9F0: A9 80 LDA #$80
C9F2: 85 81 STA $81
C9F4: 20 23 CC JSR $CC23
C9F7: EE 04 17 INC $1704
C9FA: AD 04 17 LDA $1704
C9FD: C9 03 CMP #$03
C9FF: 90 DE BCC $C9DF
CA01: A9 03 LDA #$03 //这段是检查 03页码的地址4000-BFEF的数据
CA03: 85 00 STA $00
CA05: A9 00 LDA #$00
CA07: 85 D2 STA $D2
CA09: A9 40 LDA #$40
CA0B: 85 D3 STA $D3
CA0D: A9 F0 LDA #$F0
CA0F: 85 80 STA $80
CA11: A9 7F LDA #$7F
CA13: 85 81 STA $81
CA15: 20 23 CC JSR $CC23
CA18: A9 0F LDA #$0F //这段是检查 0F页码的地址8000-BFEF的数据
CA1A: 85 00 STA $00
CA1C: A9 00 LDA #$00
CA1E: 85 D2 STA $D2
CA20: A9 80 LDA #$80
CA22: 85 D3 STA $D3
CA24: A9 00 LDA #$00
CA26: 85 80 STA $80
CA28: A9 40 LDA #$40
CA2A: 85 81 STA $81
CA2C: 20 23 CC JSR $CC23
CA2F: A9 03 LDA #$03
CA31: 85 00 STA $00
CA33: AD F0 BF LDA $BFF0
CA36: 49 FF EOR #$FF
CA38: CD 0A 17 CMP $170A
CA3B: D0 0B BNE $CA48
CA3D: AD F1 BF LDA $BFF1
CA40: 49 FF EOR #$FF
CA42: CD 0B 17 CMP $170B
CA45: D0 01 BNE $CA48
CA47: 60 RTS
CA48: ...... 因为下面的代码和主题那样什么关系
在上面的程序里,频繁的调用了子程序 CC23,这就是最关键的算法
我们也将地址CC23反汇编如下:
CC23:SEI //设置中断标志
CC24:CLC //进位标志C = 0
CC25:LDA $80 //地址80的内容 与 地址 D2 的内容相加,结果放 地址 80
CC27:ADC $D2
CC29:STA $80
CC2B:LDA $81 //地址81的内容 与 地址D3的内容相加, 结果放地址 81
CC2D:ADC $D3
CC2F:STA $81
CC31:LDY #$00 //寄存器Y的内容为 0
CC33:LDA ($D2),Y //以地址 D2的内容为 低8位,地址 D3的内容为高8位,组成一个16位地址,读取该地址内容送 A
CC35:EOR $170A //把寄存器 A 的内容 和地址 170A 的内容 进行 逻辑异或 运算
CC38:TAX //把运算的结果送 寄存器X
CC39:LDA $170B //把地址170B的内容送寄存器 A
CC3C:EOR $CD5C,X //寄存器A的内容和 地址 [CD5C+X]的内容 进行逻辑异或 运算
CC3F:STA $170A //然后把异或 的结果送地址 170A
CC42:LDA $CC5C,X //读取地址 [CC5C+X]的内容 到寄存器A
CC45:STA $170B //寄存器A的内容送地址 170B
CC48:INC $D2 //地址D2的内容加1
CC4A:BNE $CC4E
CC4C:INC D3
CC4E:LDA $D3 //地址D3 的内容和 地址 81的内容比较,不相同就转 CC58,继续
CC50:CMP $81
CC52:BNE $CC58
CC54:LDA $D2 //地址 D2的内容和地址80的内容比较,小于就转 CC33
CC56:CMP $80
CC58:BCC $CC33
CC5A:CLI //这里表示已经全部结束
CC5B:RTS
子程序 CC23是整个校验程序的算法,我来分析一下其算法:
1. 地址80 的内容和地址 D2的内容相加,结果送地址 80,地址81的内容和地址 D3的内容相加,结果送地址81
这样,地址 D2,D3就是要检查的地址的 开始地址
地址 80,81就是要检查的地址的 结束地址 - 1
比如 开始时 (D2) = 00, (D3) = 40, (80) = 00, (81) = 80,那么经过这样运算,结果是这样的:
(D2) = 00, (D3) = 40, (80) = 00, (81) = C0,
那么可见检查的地址范围是 4000-BFFF,即检查整个闪存,见下面程序
CC25:LDA $80 //地址80的内容 与 地址 D2 的内容相加,结果放 地址 80
CC27:ADC $D2
CC29:STA $80
CC2B:LDA $81 //地址81的内容 与 地址D3的内容相加, 结果放地址 81
CC2D:ADC $D3
CC2F:STA $81
2.逐个读取 地址4000-BFFF的内容,和地址 170A的内容进行逻辑异或运算,结果 送 寄存器 X
3.把地址 170B 的内容和 地址[CD5C+X)的内容进行逻辑异或运算,结果送地址 170A
4.读取 地址[CC5C+X]的内容送地址 170B
5.一直循环这样的过程,直到检查完毕
检查数据是否被修改的算法就是这样,那么最后是如何检查的呢?请看下面的程序:
CA2F: A9 03 LDA #$03 //03送寄存器A
CA31: 85 00 STA $00 //切换到03页码
CA33: AD F0 BF LDA $BFF0 //03页码的地址 BFF0的内容和 FF 进行异或
CA36: 49 FF EOR #$FF
CA38: CD 0A 17 CMP $170A //异或的结果和地址 170A的内容比较
CA3B: D0 0B BNE $CA48 //不同转CA48,我们 G CA48,出现 要求你升级的画面
CA3D: AD F1 BF LDA $BFF1 //地址BFF1的内容和 FF 进行异或运算
CA40: 49 FF EOR #$FF
CA42: CD 0B 17 CMP $170B //结果和地址 170B 比较
CA45: D0 01 BNE $CA48 //不同转CA48
CA47: 60 RTS //相同就结束
CA48: ...... 因为下面的代码和主题那样什么关系
我们于是可以知道,最后面 是切换到 03 页码,读取地址BFF0的内容,和 FF 异或,然后和地址 170A比较
如果不同,说明内核被修改了,如果相同,还要读取地址 BFF1 的内容,和 FF 异或,再和地址 170B 的内容比较
如果相同,说明内核没有被修改,那么RESET 成功.
这里要注意的是,该段检查内核被修改的地址范围如下:
01-02页码 4000-BFFF
03页码 4000-BFF0 注意这里不是BFFF
0F页码 8000-BFFF
现在我们豁然开朗了,我们只要在修改了内核后,同样进行这样的检查运算,最后,把地址 170A的内容和 FF 进行 异或,保存在
03 页码的地址 BFF0,把地址 170B 的内容和 FF 进行 异或,结果保存在03 页码的地址BFF1 就可以了.
这段检查内核被修改的算法,叫CRC32算法,它的全称叫 "Cyclic Redundancy Check",中文名字: 循环冗余校验码
该算法主要就是保证数据的完整性,比如 现在的压缩软件,在压缩数据的时候,应该也会对 数据进行CRC校验,然后把校验值放在某个地址
当解压缩后,就再次对数据进行 CRC校验,把校验值和以前的比较,如果不同,说明数据已经被破坏.
本节内容比较复杂, 如果大家有什么不懂的,可以 EMAIL 我, syj22@163.net