首页
社区
课程
招聘
[原创]【从0到1】-某系统漏洞挖掘之固件分析
2021-12-9 17:32 28551

[原创]【从0到1】-某系统漏洞挖掘之固件分析

2021-12-9 17:32
28551

写在前面

本篇章已提取加密的固件的文件系统为目标,从系统的启动到内核解密到文件系统解密的加载做一个调试。
并对系统内核以及文件系统进行提取,为后面漏洞挖掘做铺垫。
以及提升自身对linux系统逆向知识面。

环境搭建

在官网可以看到有两个版本可供下载。一个是gho一个是img,这里两个都下载了。

从提供的下载包可以看出这是软路由的系统,没有硬件限制,所以下面用vmware进行了安装。

配置好后,进入后台管理界面如下

初始环境安装完后,发现系统并没有提供输入。所以需要上qemu进行调试将文件系统提取出来。

文件系统提取

使用qemu运行该系统

对img进行进行提取,在grub中的menu.list可以看出initrd对应的是root.gz。

可以看到root.gz是属于加密的。

GRUB调试

linux大概的启动过程:
注:具体细节请参考GRUB源码。
00.BIOS:

1
寻找启动设备并将设备的第0个扇区载入到0x7c00处,即载入MBR

01.MRB:

1
0x7c00处开始执行,并将第1个扇区载入到0x2000,并跳转到0x2000处继续执行,这里为加载Stage1.5

02.Stage1.5:

1
将第1个扇区之后的几个扇区装载到0x2200,并跳转到0x2200处继续执行,并加载Stage2加载到0x8000处后跳转到0x8200处开始执行。

03.Stage2:

1
2
3
4
5
读取配置文件,根据配置文件进行执行操作,其中包括了内核镜像(这里是bzImage)的加载,当内核镜像加载完毕后会有3个地址需要关注。
    1.linux_data_tmp_addr指向bzImage数据。
    2.LINUX_BZIMAGE_ADDR指向压缩后的kernel数据。
    3.linux_data_real_addr指向部分bzImage数据
从linux_data_tmp_addr开始将部分数据拷贝到linux_data_real_addr并切换到实模式跳转到linux_data_real_addr+200处执行。

================================================================================================
使用qemu+ida进行启动调试

1
./qemu-system-i386 -s -S -m 512 -drive file=Wowfk.img,format=raw,index=0

MBR

使用IDA附加,并在0x7c00处下断后断下,可以看到是磁盘的第0个扇区的数据。
MBR入口
随后将第1个扇区载入到0x70000处

将第0x70000的数据复制到0x2000即Stage1.5然后跳转到0x2000处继续执行。

Stage1.5

循环从第2个扇区开始载入到0x70000

接着将数据复制到0x2200+index处

数据载入完后跳转到0x2200处继续执行

在经过一系列的初始化后,便开始将Stage2载入到0x8000,大小是0x400

1
注:圈起来的函数是grub_read,参考源码:/stage2/stage1_5.c:cmain


剩余的Stage2数据载入到0x8400,并跳转到0x8200处执行

1
2
3
call    near ptr unk_2360在源码中对应的是/stage2/asm.S:chain_stage2
用于转移ip
0x8200的代码位于:/stage2/asm.S:_start


在asm.S:_start结尾调用了init_bios_info

init_bios_info最后跳转到了00027E9C(/stage2/stage2.c:cmain)处继续执行

最后开始解析配置文件寻找对应的处理函数进行调用,如下是kernel命令对应的。

/stage2/builtins.c中对应的kernel_func函数,最终调用load_image



load_image中在将bzImage的前0x2000大小的数据读取到0x66000处后,再从0x66000处复制到linux_data_tmp_addr(0x5CFD30)中

继续读取之后的0x1800字节,保存到linux_data_tmp_addr+0x2000处

接着将0x3800之后的所有数据读取到LINUX_BZIMAGE_ADDR(0x100000)

...
当到了boot命令后,便进入boot_func函数


kernel_type是3,最终调用big_linux_boot函数


在/stage2/asm.S:big_linux_boot中可以清晰的看到
从linux_data_tmp_addr将0x9400大小的数据复制到(linux_data_real_addr)0x90000

切换到实模式后,通过jmp far跳转到90200处执行,改变了cs=9020 ip=0

那么真实的ip=cs*16+ip=90200,显然16位的寄存器存不下该值,所以通过来段寄存器的方式去执行。
且ida并不能去识别这样的进程环境,导致出现了反汇编的窗口指向了错误的页面但能F7 F8。


解决这样情况的办法就是将cs清零,将ip修改成正确的地址即可,但是遇到ip被改变的需要修改回原来的样子再执行。所以等一手正版人员给ida提一个issues,非常感谢!!!!!

经过一顿反复的下断调试,虽然其中对应的是linux源码中的/boot/main.c,但因为ida的问题还是很难调试,最后切换到保护模式跳转进入LINUX_BZIMAGE_ADDR(0x100000)

至此,就进入了内核,GRUB的过程结束。

内核调试

1
注:该处执行的代码均在/arch/x86/boot/compressed/head_32.S中

从LINUX_BZIMAGE_ADDR处往后执行,会将加密的内核数据从LINUX_BZIMAGE_ADD复制到0x17bb000,接着并跳转到0x17bb000+0x4C7EDC执行

随后将内核解密,并将该快内存dump

使用vmlinux-to-elf将内核程序提取出来

等待将内核解压后开始调用parse_elf函数

完成后会将内核程序的+0x1000处填充到0x1000000,并跳转到该处执行。

在0x1000000往下执行,重新将内核复制到0x81000000处,并跳转到0x818B6CEB

.....
经过了漫长的调试,配合linux源码看,最终定位到了0x81BDC9CE处,调用的函数具体是干嘛的我也不知道,只知道eax是指向root.gz解密后的数据,edx为长度

最后写脚本将所有数据进行dump

成功dump出文件系统

总结

1.ida对此类的调试环境,反汇编支持并不友好。望一个好心人提一个issues!!!
2.在保护模式和实模式的切换间16位和32位的调试,对系统底层有了一个模糊的了解。
3.心一定要稳,不要浮躁,F8一定不能快!!!

最后

第一次调试这方面的东西,知识面不是很足,导致分析起来很累。
如有错请各位在评论区指正,谢谢各位~


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞18
打赏
分享
打赏 + 5.00雪花
打赏次数 1 雪花 + 5.00
 
赞赏  L0x1c   +5.00 2021/12/09
最新回复 (12)
雪    币: 735
活跃值: (4842)
能力值: ( LV12,RANK:287 )
在线值:
发帖
回帖
粉丝
L0x1c 3 2021-12-9 17:36
2
0
醒哥,YYDS!!!!!!!
雪    币: 2170
活跃值: (4233)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
Dyingchen 2021-12-9 17:48
3
0
ddddhm
雪    币: 2155
活跃值: (2541)
能力值: ( LV12,RANK:667 )
在线值:
发帖
回帖
粉丝
梦游枪手 2021-12-9 18:17
4
0
醒哥,YYDS!!!!!!!
雪    币: 12435
活跃值: (14134)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
wmsuper 7 2021-12-9 20:14
5
1
太强了
雪    币: 1846
活跃值: (1841)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
zuoshang 1 2021-12-9 22:24
6
0
学习学习
雪    币: 2956
活跃值: (4826)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
舒默哦 1 2021-12-9 23:13
7
0
wmsuper 太强了

晴天师傅

最后于 2021-12-9 23:13 被舒默哦编辑 ,原因:
雪    币: 2956
活跃值: (4826)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
舒默哦 1 2021-12-9 23:14
8
0
学习了,感谢
雪    币: 269
活跃值: (358)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
*笑容* 2021-12-10 10:47
9
0
强大,学习了
雪    币: 4122
活跃值: (5644)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
Max_hhg 2021-12-10 17:40
10
0
大佬太强了
雪    币: 2
活跃值: (42)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hoopyoung 2022-1-3 12:13
11
0
感谢分享,看到这个我也动手去调试了下,大佬没有补充后面的细节。我来简单补以下:
雪    币: 2
活跃值: (42)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hoopyoung 2022-1-3 12:16
12
1
initrd加载的大致流程(linux-kernel 2.6.39)

int/main.c
Start_kernel()
     rest_init()
       kernel_init()
              do_basic_setup()    (*1 见说明1)
              prepare_namespace() (*2 见说明2)
                            // prepare_namespace最后会执行mount_root,即initrd.image 解压后mount /dev/ram0

 //说明1                       
do_basic_setup()
         do_initcalls()
                  rootfs_initcall(populate_rootfs)   // 源码 init/initramfs.c
        
在popuate_rootfs执行时,因为该固件的内核编译时设置了CONFIG_BLK_DEV_RAM,没有使用initramfs,使用initramdisk, 最后写文件initrd.image到内存中。
 
//说明2
prepare_namespace()  //源码init/do_mounts.c
         initrd_load()   //  源码init/do_mounts_initrd.c
                  rd_load_image()  //  源码init/do_mounts_rd.c
                          identify_ramdisk_image()
                                   decompress_method()
                                            gunzip ()   //  源码lib/decompress_inflate.c

楼主的dump点就在最后的gunzip中,是gz解压缩调了flush()输出initrd,ext2格式的,也就是initramdisk。

最后于 2022-1-3 12:20 被hoopyoung编辑 ,原因:
雪    币: 1015
活跃值: (4568)
能力值: ( LV12,RANK:238 )
在线值:
发帖
回帖
粉丝
零加一 2 2022-1-4 09:12
13
0
hoopyoung initrd加载的大致流程(linux-kernel 2.6.39) int/main.c Start_kernel()   & ...
知识点+1,感谢补充
游客
登录 | 注册 方可回帖
返回