作者简介 /Profile/
朱文哲,平安科技银河实验室高级安全研究员,Blackhat Asia 2019 演讲者,专注于工业物联网设备及软件的漏洞挖掘。曾发现多款知名品牌工业物联网设备及软件的安全漏洞。
-
本系列文章主要以下三个部分进行讲解:
-
· 编写调试功能的前置条件
-
· 串口调试功能设计
-
· 设计调试shellcode
0x01 前言
在上一章文章(TP-Link-WDR-7660 安全研究之固件分析)中, 我们已经通过分析固件得到了完整功能的cmd命令行。但VxWorks系统和传统的Linux系统还是有很大的差异的,在没有编入调试功能的情况下是无法对设备进行调试的。
在19年的时候我有针对另一款MIPS架构的VxWorks路由器编写过串口调试功能(详见附录),但是考虑到几年过去了TP-Link的命令行功能已经有所变化,此外当时的项目是基于python2进行编写且尚未支持ARM架构,因此就有了这篇文章。
0x02 编写调试功能的前置条件
编写调试功能首先有几个前置条件:
1. 能够直接对VxWorks的内核态内存进行直接读写(我们的目标基于VxWorks 5操作系统,而VxWorks 5系列只有内核态)。
2. 能够获取到task的寄存器值,这个步骤主要用于判断哪个task触发了断点,进入了debug loop(VxWorks的task有点像其他系统中的线程,在VxWorks 5中他们之间共享内存)。
当满足上述两个条件后,我们就可以基于这两项基础的功能进行扩展从而实现调试的设备。第一个必要条件没什么好多说的,完整功能的cmd命令行自带内存修改功能且简单好用。
第二个必要条件略微复杂一些,TP-Link的task命令输出与之前有一些变化。下图是以前的task命令输出,可以很直观的看到各个task的PC寄存器等信息。
下图是现在的task显示命令,目前已无法直接显示所有task的PC寄存器。
现在则需要使用task -c命令来获取单个task的寄存器等关键信息。
这样看来虽然比之前要复杂一些,但是第二个必要条件也是可以通过分析单个task的输出来进行获取,这部分的具体实现大多是一些文本处理等工作就不进行展开了。
0x03 串口调试功能设计
串口调试功能设计与之前相同,主要就是以下几部分:
下面是一些调试功能的示意图:
0x04 设计调试shellcode
dbg_statck:
dbg_stack_address = SP - 0x200
dbg_stack_address + 0xa4 ~ 0x200 = reserve
dbg_stack_address + 0x20 ~ 0xa0 = regs store address
dbg_stack_address + 0x1C ~ 0x1C = reserve
dbg_stack_address + 0x18 = Debug loop count
dbg_stack_address + 0x14 = Cache updated count, use to sync update status.
dbg_stack_address + 0x10 = Cache update size(Default is bp_overwrite_size)
dbg_stack_address + 0x0c = Address Need Update Cache(Default is Break Point
Address)
dbg_stack_address + 0x08 = Break Point Address + bp_overwrite_size
dbg_stack_address + 0x04 = Original $RA Value
dbg_stack_address + 0x00 = Debug Flags(0: Keep loop, 1: Recover, 2: Need
update cache)
def create_bp_asm(self, bp_address):
"""Create breakpoint asm code
:param bp_address: break point address
:return: Breakpoint shellcode
"""
# increase stack size
asm_code = '''
mov r12, sp
sub sp, sp, #0x{:x}
// save original lr value
str lr, [sp, #0x04]
// jump to dbg loop
bl 0x{:08x}
'''.format(self.dbg_stack_size, self.debugger_base_address)
self.logger.debug("asm_code: %s" % asm_code)
asm_data = self.assemble(str(asm_code), bp_address)
if not asm_data:
return None
return asm_data
##########################
# Init DBG Stack #
##########################
init_dbg_stack_asm_code = '''
init_dbg_stack:
// first save r0 to sp + 0x20, we need use r0 in debug loop
str r0, [sp, #0x{:x}]
// set flag to zero
mov r0, 0
str r0, [sp]
// clean up debug loop count
str r0, [sp, #0x18]
// save current return address to stack
str lr, [sp, #0x08]
// set lr to break point address and do not touch lr later.
sub lr, lr, #0x{:x}
// save regs to reg_store_offset + 0x04, skip r0
add r0, sp, {}
stmea r0!, {{R1-R11, r12, lr, pc}}
// init cache update stack value to default bp address
// Save cache update address
str lr, [sp, #0x0c]
mov r0, 0x{:x}
// Save cache update size
str r0, [sp, #0x10]
// Init Cache updated count
mov r0, 0
str r0, [sp, # 0x14]
'''.format(reg_store_offset, self.bp_overwrite_size, reg_store_offset + 0x04,
self.bp_overwrite_size)
##########################
# DBG Loop #
##########################
dbg_loop_asm_code = '''
start_dbg_loop:
// Update debug loop count
ldr r0, [sp, #0x18]
add r0, 1
str r0, [sp, #0x18]
// Add delay 60
mov r0, 60
// call task_delay
bl 0x{:x}
// call cacheTextUpdate if flag == 0x02
ldr r0, [sp]
cmp r0, 2
bne part_4
debug_loop:
// update cacheTextUpdate execute count
ldr r0, [sp, #0x14]
add r0, 1
str r0, [sp, #0x14]
update_cache:
// update cache
ldr r0, [sp, #0x0c]
// Force v7_flush_kern_cache_all
mov r1, -1
bl 0x{:x}
// set flag to 0x00
mov r0, 0
str r0, [sp]
// if flag != 0x01 keep loop
part_4:
// update dbg stack cache in each loop
// mov r0, sp
// mov r1, 0x{:x}
// bl 0x{:x}
ldr r0, [sp]
cmp r0, 1
bne start_dbg_loop
'''.format(task_delay_addr, self.cache_update_address, self.dbg_stack_size,
self.cache_update_address)
##########################
# Recover #
##########################
# load_reg_offset = reg_store_offset + (4 * 12) # For test
load_reg_offset = reg_store_offset + (4 * 14)
recover_asm = '''
update dbg stack cache before recover
mov r0, sp
mov r1, 0x{:x}
bl 0x{:x}
Recover Original lr
ldr lr, [sp, #0x04]
recover other regs
mov r0, 0
add r0, sp, {}
ldmea r0, {{R0-R11, sp, pc}}
self.cache_update_address, load_reg_offset)
asm_code += recover_asm
0x05 效果展示
bh-asia-Zhu-Dive-into-VxWorks-Based-IoT-Device-Debug-the-Undebugable-Device:
银河实验室
往期回顾
技术
技术
技术
技术
长按识别二维码关注我们
微信号:PSRC_Team
球分享
球点赞
球在看
原文始发于微信公众号(平安集团安全应急响应中心):TP-Link-WDR-7660 安全研究之构造基于串口CMD的调试器