//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