nasm-2.14-null-pointer-dereferens

nasm null pointer dereferens

版本号:nasm-2.14rc15
漏洞描述:空指针的解引用
编译命令:./nasm -f bin ./test.asm -o /dev/null
运行时错误信息:error: EQU not preceded by label

poc

1
equ foo

load core dump file

1
2
3
4
5
6
<!--back trace-->
#0 0x000055d2360d042c in islocal (l=0x0) at asm/labels.c:64
#1 0x000055d2360d067d in find_label (label=0x0, create=true, created=0x7ffd289d75c6) at asm/labels.c:222
#2 0x000055d2360d0de5 in define_label (label=0x0, segment=-1, offset=0, normal=false) at asm/labels.c:441
#3 0x000055d2360c4345 in assemble_file (fname=0x55d2374a7d60 "./test.asm", depend_ptr=0x0) at asm/nasm.c:1484
#4 0x000055d2360c25e5 in main (argc=6, argv=0x7ffd289d7948) at asm/nasm.c:566
1
2
3
4
5
6
7
8
// asm/labels.c:64
59 if (tasm_compatible_mode) {
60 if (l[0] == '@' && l[1] == '@')
61 return true;
62 }
63
64 return (l[0] == '.' && l[1] != '.');
65 }
1
2
3
4
5
6
<!--asm-->
► 0x55d2360d042c <islocal+56> movzx eax, byte ptr [rax]
0x55d2360d042f <islocal+59> cmp al, 0x2e
0x55d2360d0431 <islocal+61> jne islocal+85 <0x55d2360d0449>
<!--register-->
RAX 0x0

分析

简要分析

程序发生崩溃在islocal函数中,根据backtrace和崩溃时寄存器信息可以看出,是发生了空指针解引用导致的程序崩溃。回退find_label函数,空指针由参数label传入。继续回退define_label,空指针同样由label参数传入。在函数assemble_file

1
2
3
define_label(output_ins.label,
output_ins.oprs[0].segment,
output_ins.oprs[0].offset, false);

函数的空指针由output_ins.label传入。而output_insparse_line(pass1, line, &output_ins);所赋值的。
因此,在分析label是何时被赋值为空可以跟踪parse_line函数。

详细分析

gdb 调试程序运行,gdb ./nasm进入gdb调试界面。对assemble_file下断点,运行命令r -f bin ./test.asm -o /dev/null继续运行。
添加watchpoint,观察line变量的使用情况,

1
2
3
4
5
6
7
8
9
► 1435         while ((line = preproc->getline())) {
1436 if (++globallineno > nasm_limit[LIMIT_LINES])
1437 nasm_fatal(0,
1438 "overall line count exceeds the maximum %"PRId64"\n",
1439 nasm_limit[LIMIT_LINES]);
1440

-> $ p line
$1 = 0x5555559248d0 "equ foo"

该变量line读入的内容为输入文件中的符合要求的一行内容。
b parse_line函数,进入该函数查看函数。watch result->label查看label在什么时候被修改,在第一次被修改时:

1
► 452     result->eops        = NULL; /* must do this, whatever happens */

之后就直接退出了parse_line函数,因此,在该函数中label只是被赋值了初始值NULL。
继续执行,

1
2
3
4
5
6
7
8
9
10
11
12
  1475             /*  forw_ref */
1476 if (output_ins.opcode == I_EQU) {
► 1477 if (!output_ins.label)
1478 nasm_error(ERR_NONFATAL,
1479 "EQU not preceded by label");
1480
1481 if (output_ins.operands == 1 &&
1482 (output_ins.oprs[0].type & IMMEDIATE) &&
1483 output_ins.oprs[0].wrt == NO_SEG) {
1484 define_label(output_ins.label,
1485 output_ins.oprs[0].segment,
1486 output_ins.oprs[0].offset, false);

由于output_ins.opcode == I_EQU成立,在poc中语句为EQU foo,因此会被解析为EQU,所以会直接进入define_label函数中,但是由于label没有任何信息,在后续的处理中会引起null pointer dereference错误。

parse_line

  • 当poc为equ "foo"时,i = stdscan(NULL, &tokval);取出的值为263 == TOKEN_INSN
    1
    2
    3
    4
       equ                "foo"
    ^ ^
    | |
    TOKEN_INSN TOKEN_STR
1
2
3
4
5
if (i == TOKEN_ID || (insn_is_label && i == TOKEN_INSN)) {
/* there's a label here */
first = false;
result->label = tokval.t_charptr;
i = stdscan(NULL, &tokval);

该分支不能得到满足,因此label的值为默认初始化值NULL

  • 当poc 为label: equ "foo"时,i = stdscan(NULL, &tokval);取出的值为256 == TOKEN_ID
    result->label应赋值为label,

    1
    2
    3
    4
    label:                 equ                "foo"
    ^ ^ ^
    | | |
    label(TOKEN_ID) TOKEN_INSN TOKEN_STR
  • 当poc为a equ 0xff时,

    1
    2
    3
    4
    5
      a              equ                0xff
    ^ ^ ^
    | | |
    label TOKEN_INSN immediate
    (TOKEN_ID)

小节

crash产生的原因是因为,当解析EQU命令时,如果在EQU之前没有非指令字符时,在parser.c#470位置,会导致没有条件成立,不能进入if语句,因此在处理result->label时,所返回的值为默认初始化的值,即result->label = NULL