欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

冰河暗涌防不胜防 BIOS下实现的Telnet后门

程序员文章站 2022-04-26 15:48:22
该项目仅为实验性项目,主要目的是想隐藏一个Telnet后门在主板的BIOS内,并让其随着计算机系统及操作系统成功的运行起来。运行后能反向Telnet连接到指定的计算机接受控制。 项目涉及... 09-04-20...
该项目仅为实验性项目,主要目的是想隐藏一个telnet后门在主板的bios内,并让其随着计算机系统及操作系统成功的运行起来。运行后能反向telnet连接到指定的计算机接受控制。

项目涉及的相关知识及技术目录

1、 实验环境,使用bochs调试工具。

2、 刷新bios技术问题。

3、 代码植入bios问题。

4、 源代码相关技术问题:
 

a、如何编写bios模块(如:pci、 isa)。

b、实模式关于hook磁盘中断的问题。

c、磁盘中断中选择再次hook的问题。

d、nt保护模式下设置物理地址映射。

e、nt保护模式下线性地址寻址问题。

bios模块调试实验环境采用bochs

bochs虚拟机可以调试bios及操作系统,bochs使用主要是配置它的配置文件,我们以实例配置文件简单讲解,bochs实验调试等网上有很多相关文章,这里简单讲解。

我的配置实例:文件名xp.bxrc,修改后的及需要设置的内容如下:

######使用的系统bios模块######  romimage: file=$bxshare/bios-bochs-latest  ######使用的cpu 相关参数######  cpu: count=1, ips=10000000, reset_on_triple_fault=1 ######设置内存大小       ######  megs: 128  ######添加我们的bios模块######  optromimage1: file=test.bin, address=0xd0000 ######使用的vgarom模块######  vgaromimage: file=$bxshare/vgabios-lgpl-latest  ######设置虚拟机硬盘与光盘######  ata0-master: type=disk, path="c.img", mode=flat, cylinders=4161, heads=16, spt=63 #ata0-slave: type=cdrom, path="xp.iso", status=inserted ######设置引导设备        ######  boot: c  #boot: cdrom, disk 

 

bochs调试加载配置文件方法:可以设置一个bat文件,如下内容:
set bxshare=d:\bochs
%bxshare%\bochsdbg.exe -q -f xp.bxrc

如何刷新各种bios问题

各种bios刷新相关工具早已在网上流传,工具的使用这里不作介绍,iclord的作者已经给出很多编程方法实现。这里简单说下。

uniflash开源项目我也详细分析过,如果有必要我会给出uniflash源代码的详解,该项目指出可以刷写所有bios芯片,但是该项目刷新bios存在很多问题,绝大多数情况是无法刷新的我实验过很多次,也尝试修改他的代码过很多次,没找到原因。

aword bios已经有通用的刷写api调用,不管在nt下还是在实模式下,iclord也作了讲解。如果有时间我会给出实模式及nt下的刷写源代码及分析。

代码植入bios问题

关于网上提及的,iclord讲到的我就不再做重复的分析。这里主要讲下我们的模块可以植入哪些地方以方便隐藏。以前的教程讲过的方法存的问题分析。
一、 isa模块形式植入:这种方式只适合于较早的计算机,因为目前的计算机系统bios是不会加载isa模块的。故只能做实验调试用的方法。
二、 pci模块形式植入:该方法虽然系统bios都要加载pci rom,但是系统bios只加载实际存在的pci卡的rom模块。而且通常bios设置中可以
关闭相应的rom启动。
三、 hook bootblock或者说要启动的模块:该方法当然我认为是最有效的,但是又存在很多技术上的难题。检验和问题,不同bios的结构问题,过早的hook还存在再次获取cpu运行机会问题等等。

本人实验过以上提及的所有方法,我认为hook pci、vga及相关启动模块是比较可寻的办法。为什么?一般这类的rom模块是必须启动的,而且调试发现一般它的rom本身代码用不完自身设置的大小,我们可以借助剩余大小隐藏我们的代码。例如:集成显卡会把显卡rom集成到系统bios模块中,我们可以对该模块进行hook,修改rom头部的跳转指令,跳到我们的代码开始处执行,我们的代码执行完后跳转到它的代码开始处执行。

如何编写bios模块

bios是分模块组合在一起的。这里对pci及isa模块作下简单分析,vga模块跟pci模块几乎一样。模块主要是头部有个规范,该规范适合所有bios系统。具体可以参看《pci系统结构》及其他书籍。

源代码实例可以参看国外romos开源项目,该开源项目的思想很值得学习。该项目讲解了如何在bios中嵌入一个小型dos,如:freedos。采用了把整个dos系统盘镜像植入bios中,跟早期的pxe引导dos机制类似,然后hook磁盘中断,模拟dos系统盘镜像出一个盘,源代码编译后只有900多字节。这种思想在早期还是很值得学习的。
实模式关于hook磁盘中断问题

很早前就有业界内人士发贴问,为什么在我的rom模块中hook磁盘中断会失败呢?关于这个问题现在目前网上已经有人作出过回答,国外的开源项目在2003年我都看到过。

由于我们的rom模块过早的运行,可能运行在磁盘服务前面了,这时如果hook int 13h会因为bios加载磁盘服务时重写int 13h ivt值,故我们设法hook其他服务,这个服务要求较早被bios安装且不会再次修改且加载操作系统前调用,最佳的这个服务选择就是int 18h、int19h服务。可以参看
bios源代码,也可以参看pxe sdk说明文档略有讲过。

我们的磁盘服务代码建议放在实模式高端内存,通过bios数据区域可修改,内存40:13,即物理地址413h处的值。降低常规内存值,高端的内存就留给我们用。我们的保护模式下运行的代码建议也放在这段内存,且要求放在以页基址开始的内存中,以便后面代码的页映射我们的保护模式代码物理页。页基址:内存物理页地地址开始的低12位为零,参看《80386保护模式教程》。

若我们的代码直接在内存的rom映射区内,可能导致在nt下访问不到我们的代码,因为nt内核加载程序ntldr可能不会映射该段内存,甚至可能bios在使用后都会关闭rom区域这段内存,而且rom区域这段内存在初始化后被系统bios设置成只读不能写。当然我们可以采取用int 15h服务对rom区域这段内存映射。

当然也可以在nt启动过程中,在我们的磁盘服务中对想映射的内存都映射。由于代码大小的限制,故有些没必要的代码。尽量不使用了。

磁盘中断服务中再次hook问题

为了使我们的程序再次获得cpu运行机会,我们不得不得再次设法。调试发现ntldr进入保护模式后在加载nt内核文件时,会切换cpu到实模式调用int 13h服务进行磁盘读。

我们挂接磁盘服务就是为了截取ntldr的读操作,这里我们可以hook 或者修改ntldr另一部分osloader的代码,跳转到我们的代码执行。当然也可以直接hook ntosknrl导出的服务,参看我在2008.4.1发布的“程序从dos/bios驻留内存到winnt下监视内存数据”。

注意,hook osloader的代码时选择hook指令问题,由于ntldr切换到实模式读取数据,读完后会在保护模式下搬移数据到规划位置,进行内核的安装。故hook时选择hook指令就选择ffh/15h:使用call near [ofs32]指令进行,该指令寻址采用绝对地址,类似指令也可以。

当然我们的代码再次运行就会运行在osloader代码被我们hook处,调用我我们的代码执行,这时我们的代码运行环境:ds = es = 10h保护模式段,内存模式: flat。在这里我们可以通过扫描_blloaderdata数据结构,获取ntoskrnl镜像基址。

可以通过pe搜索ntoskrnl导出的api,可以参看网上相关教程。现在再次hook ntoskrnl导出函数keaddsystemservicetable,hook该函数
可以截获win32k.sys添加它自己的服务,以便我们再再次hook win32.sys导出函数ntuserregisterclassexwow。hook该函数可以截取所有应用层程序注册窗口类,以便我们再再再次hook窗口类过程。这时我们的代码就运行在nt的应用层模式下。
nt保护模式下设置物理地址映射

先看一个windbg实例关于在我们的磁盘服务中获取cr3值修改页映射的分析,以前我的分析内容:

nt内核被加载高端的2gb内存(80000000h~0ffffffffh)。参看nt内存安排..   a、win2k adv ser:   windbg 看到 nt kernel base = 0x80400000 也就是ntoskrnl.exe加载位置            kd> r @cr3      ;断点位置在ntoskrnl.exe里现在还没有应用程序故低端内存还未使用                 cr3=00030000 ;->页目录表所在物理页(物理地址30000h)             kd> d 80030000 80030800  ;看页目录发现现在低端2gb(0~80000000h)还未分配                 80030000  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................                 80030010  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................                 80030020  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................             kd> d 80030800           ;看高端开始分配情况页表(80000000h开始的分配情况)                 80030800  63 21 03 00 63 41 03 00-63 51 03 00 63 31 03 00  c!..ca..cq..c1..                 80030810  63 11 7c 00 63 21 7c 00-63 31 7c 00 63 41 7c 00  c.|.c!|.c1|.ca|.                 80030820  63 51 7c 00 63 61 7c 00-63 71 7c 00 63 81 7c 00  cq|.ca|.cq|.c.|.             ;实例1:看80400000h(nt kernel base),这个线性地址到物理地址映射情况.                 ;线性地址最高10位页目录项(每项占4byte):80400000h最高10位=201h.                 ;在页目表位置:201h*4=804h 在内存地址=[cr3]+804h..具体看保护模式教程                 kd> d 80030000+804 ;看在页目录表位置的值                   80030804  63 41 03 00 63 51 03 00-63 31 03 00 63 11 7c 00  ca..cq..c1..c.|.                 ;二级页表所在物理页地址:63 41 03 00转换下34163h,物理页地址:34000h,163h是页属性.                 kd> d 80034000     ;看在页表的值                   80034000  63 01 40 00 63 11 40 00-63 21 40 00 63 31 40 00  c.@.c.@.c!@.c1@.                 ;物理地址基址:63 01 40 00转换下400163h,#物理地址基址#:400000h,163h是页属性                 ;最后发现物理地址基址(页地址)在400000h..观察物理地址400000h是ntoskrnl.exe映像.                 kd> d 80400000 ;观察物理地址400000h                   80400000  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  mz..............                   80400010  b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00  ........@.......            ;实例2:看我们代码映射情况我们代码在物理地址:9e000h从线性地址8009e000h分析映射情况                 ;8009e000h在页目录位置最高10位=200h*4,在内存地址=[cr3]+200h*4...                 kd> d 80030000+200*4                   80030800  63 21 03 00 63 41 03 00-63 51 03 00 63 31 03 00  c!..ca..cq..c1..                  ;二级页表对应物理地址:63 21 03 00转换下物理页基址=32000h ,163是页属性                  ;8009e000h所在二级页表位置:32000h+9eh*4(8009e000h线性地址12~22的位)...                   kd> d 80032000 + 9e *4                     80032278  23 e1 09 00 03 f1 09 00-03 01 0a 00 03 11 0a 00  #...............                  ;物理地址基址(页基址):23 e1 09 00转换下09e000h,123是页属性..             ;#实例3:windows运行起后用的页目录表线性地址:0c0000000h映射到物理地址情况.                  kd> d 80030000 + c00 ;计算略.页目录表位置,观察发现指向自己的...                    80030c00  67 00 03 00 63 00 f0 17-00 00 00 00 63 31 a9 02  g...c.......c1..   b、winxp:  windbg 看到 kernel base = 0x804d8000 psloadedmodulelist = 0x8055b420            kd> r @cr3   ;断点位置在ntoskrnl.exe里现在还没有应用程序故低端内存还未使用                  cr3=00039000 ;->页目录表所在物理页(页目录物理地址30000h)                  kd> d 80039000 80039800  ;看页目录发现现在低端2gb(0~80000000h)还未分配                    80039000  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................                  kd> d 80039800           ;看高端开始分配情况页表(80000000h开始的分配情况)                    80039800  e9 b9 00 f6 06 a1 08 10-75 1b f6 06 a3 08 10 75  ........u......u             ;实例1:看我们代码映射情况我们代码在物理地址:9e000h从线性地址8009e000h分析映射情况                    ;8009e000h在页目录位置最高10位=200h*4,在内存地址=[cr3]+200h*4...                    kd> d 80039000+200*4                      80039800  63 b1 03 00 e3 01 40 00-63 e1 03 00 e3 01 00 01  c.....@.c.......                    ;二级页表对应物理地址:63 b1 03 00转换下物理页基址=3b000h ,163是页属性                    ;8009e000h所在二级页表位置:3b000h+9eh*4(8009e000h线性地址12~22的位)...                    kd> d 8003b000 + 9e *4                      8003b278  03 e1 09 00 03 f1 09 00-03 01 0a 00 03 11 0a 00  ................                   ;物理地址基址(页基址):03 e1 09 00转换下09e000h,103是页属性..   --------->  ;*#实例2:手工修改分页让物理地址映射到线性地址,winxp.80000000h bios/dos向量区没映射.#*                    kd> d 80000000  ;可看到现在没映射的情况,无法通过线性地址访问.                      80000000  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????                      80000010  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????                    ;80000000h的最高10位确定在页目录表位置=200h*4=800h.在页目录物理地址:[cr3]+800h                      kd> d 80039000+200*4;可发现二级页表已映射(63b10300)页属性163.物理地址(页)3b000h                        80039800  63 b1 03 00 e3 01 40 00-63 e1 03 00 e3 01 00 01  c.....@.c.......                        ;80000000h的12~22位次高10位确定在二级页表位置:物理地址3b000h+0*4                      kd> d 8003b000;观察发现确实没映射.全0.注意:一个页项占4字节...                        8003b000  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................                      ;修改成让它对应物理地址:000就可以,只修改允许访问就可以.也就是byte ptr [8003b000]=63                        kd> e 8003b000 63                        kd> d 8003b000  ;修改成功                          8003b000  63 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  c...............                      ;观察修改后的情况.;注意:要想设置生效.让windbg执行下g.让0c0000000等cr3被改等..                        kd> g        ;让系统自动刷新tlb等寄存器....                        kd> d 80000000                          80000000  53 ff 00 f0 53 ff 00 f0-c3 e2 00 f0 53 ff 00 f0  s...s.......s...             ;#实例3:windows运行起后用的页目录表线性地址:0c0000000h映射到物理地址情况.                   kd> d 80039000 + c00 ;计算略.页目录表位置,观察发现指向自己的...                     80039c00  67 90 03 00 63 00 f0 0f-00 00 00 00 63 31 e2 01  g...c.......c1..   c、分析以上总结:发现最开始获取的cr3值一直在用。   故我们修改页项的效果会在windows运行后也生效通过以上的实例分析可以看出nt系统的页映射情况。   进入保护模式之后这个cr3的值也就是页目录表位置被映射到了虚拟地址0c0000000h。   上面调试的实例页目录表地址就可以直接使用这个虚地址。 

 

例如:在我们的代码中把我们的代码拷贝到shareduserdata空间未使用处去,就使用了页映射代码,直接修改第一个页指向的物理页,就是我们代码所在的物理页。

nt保护模式下线性地址寻址问题

关于我们的代码进入保护模式以后,所有指令的寻址问题,这里作一下分析。当我们代码第一个在保护模式下执行的指令,是前面提到的磁盘服务中对osloader的代码进入hook,也提到了采用什么样的hook指令以便寻址。

接哪之后,我们的代码就开始运行在保护模式未分页情况下,我们采取的方法是把我们的代码拷贝到shareduserdata空间未使用处,涉及到拷贝的指令也必须运行在保护模式分页下,因为shareduserdata空间是一个虚地址。

在这里我们采用了把我们的拷贝代码指令搬移到ntoskrnl镜像的mz与pe之间的区域,设置hook ntoskrnl导出函数keaddsystemservicetable首先跳转到拷贝指令执行,hook ntoskrnl导出函数keaddsystemservicetable采用相对跳转指令它们在同一个空间。

当我们的拷贝指令把我们的代码拷贝到shareduserdata空间未使用处之后,我们的代码就有一个固定的虚地址,所有后续指令都可以采用这个虚地址进行寻址。