forked from nafiez/Vulnerability-Research
-
Notifications
You must be signed in to change notification settings - Fork 0
/
CVE-2018-16517 - Netwide Assembler (NASM) - NULL Pointer Dereference
149 lines (131 loc) · 6.68 KB
/
CVE-2018-16517 - Netwide Assembler (NASM) - NULL Pointer Dereference
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
Special thanks to Fakhri (@d0lph1n98) for helping out with the analysis :)
Description
===========
asm/labels.c in Netwide Assembler (NASM) is prone to NULL Pointer Dereference, which allows the attacker to cause a denial of service
via a crafted file.
Proof-of-Concept
================
1. Craft a ASM code
- echo "equ push rax" > poc
2. Compile the ASM file with following parameter
- nasm -f elf poc
typedef struct insn { /* an instruction itself */
char *label; /* the label defined, or NULL */
int prefixes[MAXPREFIX]; /* instruction prefixes, if any */
enum opcode opcode; /* the opcode - not just the string */
enum ccode condition; /* the condition code, if Jcc/SETcc */
int operands; /* how many operands? 0-3 (more if db et al) */
int addr_size; /* address size */
operand oprs[MAX_OPERANDS]; /* the operands, defined as above */
extop *eops; /* extended operands */
int eops_float; /* true if DD and floating */
int32_t times; /* repeat count (TIMES prefix) */
bool forw_ref; /* is there a forward reference? */
bool rex_done; /* REX prefix emitted? */
int rex; /* Special REX Prefix */
int vexreg; /* Register encoded in VEX prefix */
int vex_cm; /* Class and M field for VEX prefix */
int vex_wlp; /* W, P and L information for VEX prefix */
uint8_t evex_p[3]; /* EVEX.P0: [RXB,R',00,mm], P1: [W,vvvv,1,pp] */
/* EVEX.P2: [z,L'L,b,V',aaa] */
enum ttypes evex_tuple; /* Tuple type for compressed Disp8*N */
int evex_rm; /* static rounding mode for AVX512 (EVEX) */
int8_t evex_brerop; /* BR/ER/SAE operand position */
} insn;
static void assemble_file(const char *fname, StrList **depend_ptr)
{
char *line;
insn output_ins;
int i;
uint64_t prev_offset_changed;
int64_t stall_count = 0; /* Make sure we make forward progress... */
[...]
while ((line = preproc->getline())) {
if (++globallineno > nasm_limit[LIMIT_LINES])
nasm_fatal(0,
"overall line count exceeds the maximum %"PRId64"\n",
nasm_limit[LIMIT_LINES]);
/*
* Here we parse our directives; this is not handled by the
* main parser.
*/
if (process_directives(line))
goto end_of_line; /* Just do final cleanup */
/* Not a directive, or even something that starts with [ */
parse_line(pass1, line, &output_ins); <-- [1]
if (optimizing > 0) {
if (forwref != NULL && globallineno == forwref->lineno) {
output_ins.forw_ref = true;
[...]
In assemble_file function, there is an object (output_ins) to a structure (struct insn) which contains the informations regarding the opcode being parsed. Inside the assemble_file, a function parse_line is called in order initialize the object.
insn *parse_line(int pass, char *buffer, insn *result)
{
bool insn_is_label = false;
struct eval_hints hints;
int opnum;
int critical;
bool first;
bool recover;
int i;
nasm_static_assert(P_none == 0);
restart_parse:
first = true;
result->forw_ref = false;
stdscan_reset();
stdscan_set(buffer);
i = stdscan(NULL, &tokval);
memset(result->prefixes, P_none, sizeof(result->prefixes));
result->times = 1; /* No TIMES either yet */
result->label = NULL; /* Assume no label */ <-- [2]
result->eops = NULL; /* must do this, whatever happens */
result->operands = 0; /* must initialize this */
result->evex_rm = 0; /* Ensure EVEX rounding mode is reset */
result->evex_brerop = -1; /* Reset EVEX broadcasting/ER op position */
Here the structure’s member label which should be containing the definition of the label is clearly being assigned to NULL.
[...]
if (i == TOKEN_ID || (insn_is_label && i == TOKEN_INSN)) { <-- not taken
/* there's a label here */
first = false;
result->label = tokval.t_charptr;
i = stdscan(NULL, &tokval);
if (i == ':') { /* skip over the optional colon */
i = stdscan(NULL, &tokval);
} else if (i == 0) {
nasm_error(ERR_WARNING | ERR_WARN_OL | ERR_PASS1,
"label alone on a line without a colon might be in error");
}
if (i != TOKEN_INSN || tokval.t_integer != I_EQU) {
/*
* FIXME: location.segment could be NO_SEG, in which case
* it is possible we should be passing 'absolute.segment'. Look into this.
* Work out whether that is *really* what we should be doing.
* Generally fix things. I think this is right as it is, but
* am still not certain.
*/
define_label(result->label,
in_absolute ? absolute.segment : location.segment,
location.offset, true);
[...]
However, down to a few lines of code, there is a check before the it gets initialized with a valid value which is then skipped because the boolean insn_is_label is always FALSE. Therefore, the result->label remains NULL.
[...]
/* forw_ref */
if (output_ins.opcode == I_EQU) {
if (!output_ins.label)
nasm_error(ERR_NONFATAL,
"EQU not preceded by label");
if (output_ins.operands == 1 &&
(output_ins.oprs[0].type & IMMEDIATE) &&
output_ins.oprs[0].wrt == NO_SEG) {
define_label(output_ins.label,
output_ins.oprs[0].segment,
output_ins.oprs[0].offset, false); <-- [3]
[...]
So back to the assemble_line, there is a check if the opcode is EQU and surprisingly the nasm_error did not handle the error safely (marked as ERR_NONFATAL). The output_ins.label is then being passed into 3 functions before it gets dereferenced (define_label -> find_label -> islocal).
static bool islocal(const char *l)
{
if (tasm_compatible_mode) {
if (l[0] == '@' && l[1] == '@')
return true;
}
return (l[0] == '.' && l[1] != '.'); <-- boom
}