Changes
This commit is contained in:
parent
97a17b572e
commit
44e15ac978
BIN
work/01paper.pdf
BIN
work/01paper.pdf
Binary file not shown.
239
work/01paper.tex
239
work/01paper.tex
@ -65,7 +65,14 @@ MatrNr. 3220018}
|
||||
This paper tries to explain the details behind buffer overflows, explore the
|
||||
problems stemming from those kinds of software vulnerabilities and discus
|
||||
possible countermeasures with focus on their effectiveness, performance impact
|
||||
and ease of use.
|
||||
and ease of use. It discusses compiler based (such as ASLR, NX, stack
|
||||
canaries) as well as type system based (e.g.\ dependent types) solutions to
|
||||
this prevalent type of software bugs based on their performance impact and the
|
||||
effort needed to introduce the mitigations into existing software projects. An
|
||||
analysis of the current state of the art informs the reader about what to
|
||||
expect when writing software today. The analysis shows that most techniques
|
||||
actually tackle the problem of exploiting buffer overflows for code execution
|
||||
but do nothing to prevent introducing them in the first place.
|
||||
|
||||
\end{abstract}
|
||||
|
||||
@ -77,27 +84,26 @@ Buffer Overflow, Software Security
|
||||
|
||||
\section{Motivation}\label{ref:motivation}
|
||||
|
||||
When the first programming languages were designed, memory had to be managed
|
||||
manually to make the best use of slow hardware. This opened the door for many
|
||||
kinds of programming errors. Memory can be deallocated more than once
|
||||
(double-free), invalid pointers can be dereferenced (\mintinline{C}{NULL}
|
||||
pointer dereference; this is still a problem in many modern languages) or the
|
||||
program could read or write out of bounds of a buffer (information leaks,
|
||||
\acp{bof}). Languages that are affected by this are e.g.\ C, C++ and Fortran.
|
||||
While most if not all of these problems are solved in modern programming
|
||||
languages, these languages are still used in critical parts of the worlds
|
||||
infrastructure, either because they allow to implement really performant
|
||||
programs, offer deterministic runtime behaviour (e.g.\ no pauses due to garbage
|
||||
collection), because they power legacy systems or for portability reasons.
|
||||
Scientists and software engineers have proposed lots of solutions to this
|
||||
problem over the years and this paper aims to compare and give an overview about
|
||||
those.
|
||||
In the early days of programming, memory as managed manually to make the best
|
||||
use of slow hardware and low memory. This opened the door for many kinds of
|
||||
programming errors. Memory can be deallocated more than once (double-free),
|
||||
invalid pointers can be dereferenced (\mintinline{C}{NULL} pointer dereference;
|
||||
this is still a problem in many modern languages) or the program could read or
|
||||
write out of bounds of a buffer (information leaks, \acp{bof}). Languages that
|
||||
are affected by this are e.g.\ C, C++ and Fortran. While modern programming
|
||||
languages solve most if not all of these problems, critical parts of the worlds
|
||||
infrastructure are still implemented in these old languages, either because they
|
||||
allow the implementation of really performant programs, offer deterministic
|
||||
runtime behaviour (e.g.\ no pauses due to garbage collection), because they
|
||||
power legacy systems or for portability reasons. Scientists and software
|
||||
engineers have proposed lots of solutions to this problem over the years and
|
||||
this paper aims to compare and give an overview about those.
|
||||
|
||||
Reading out of bounds can result in an information leak and is less critical
|
||||
than \acp{bof} in most cases, but there are exceptions, e.g.\ the Heartbleed
|
||||
bug~\cite{Heardbleed2014} in OpenSSL which allowed dumping secret keys from
|
||||
memory. Out of bounds writes are almost always critical and result in code
|
||||
execution vulnerabilities or at least application crashes.
|
||||
Reading out of bounds can result in an information leak and is one of the less
|
||||
critical results of \ac{bof} in most cases, but there are exceptions, e.g.\ the
|
||||
Heartbleed bug~\cite{Heardbleed2014} in OpenSSL which allowed dumping secret
|
||||
keys from memory. Out of bounds writes are almost always critical and result in
|
||||
code execution vulnerabilities or at least application crashes.
|
||||
|
||||
In 2018, 14\% (2368 out of 16556)~\cite{Cve2018} of all software vulnerabilities
|
||||
that have a CVE assigned, were overflow related. This shows that, even if this
|
||||
@ -115,8 +121,7 @@ the code pointed to by this address is executed~\cite{Detection2018}. Other ways
|
||||
include overwriting addresses in the \ac{plt} (the \ac{plt} contains addresses
|
||||
of dynamically linked library functions) of a binary so that, if a linked
|
||||
function is called, an attacker controlled function is called instead, or (in
|
||||
C++) overwriting the vtable where the pointers to an object's methods are
|
||||
stored.
|
||||
C++) overwriting the \ac{vmt}, which stores the pointers to an object's methods.
|
||||
|
||||
A simple vulnerable C program might look like this:
|
||||
|
||||
@ -139,18 +144,18 @@ int main(int argc, char **argv) {
|
||||
|
||||
A successful stack \ac{bof} exploit would place the payload in the memory by
|
||||
supplying it as an argument to the program (or by placing it in an environment
|
||||
variable, writing it to a file that the program reads, via network packet, ...)
|
||||
and eventually overwrite the return address by providing an input with $> 50$
|
||||
bytes and therefore writing out of bounds. When executing the
|
||||
\mintinline{C}{return} instruction, and the jumps into the payload, the
|
||||
attacker's code is executed. This works due to the way, how function calls on
|
||||
CPUs work: The stack frame of the current function lies between the \ac{bp} and
|
||||
\ac{sp} as shown in~\cref{fig:before}. When a function is called, the value
|
||||
of the \ac{bp} and \ac{ip} is pushed to the stack (\cref{fig:call}) and the
|
||||
\ac{ip} is set to the address of the called function. When the function returns,
|
||||
the old \ac{ip} is restored from the stack and the execution continues from
|
||||
where the function was called. If an overflow overwrites the old \ac{ip}
|
||||
(\cref{fig:exploit}), the attacker controls where execution continues.
|
||||
variable, writing it to a file that the program reads, via network packet,
|
||||
\dots) and eventually overwrite the return address by providing an input with
|
||||
more than 50 bytes and therefore writing out of bounds. When executing the
|
||||
\mintinline{C}{return} instruction, and the \ac{ip} jumps into the payload, the
|
||||
attacker's code is executed. This works due to the way, how CPUs perform
|
||||
function calls: The stack frame of the current function lies between the \ac{bp}
|
||||
and \ac{sp} as shown in~\cref{fig:before}. When calling a function, the value of
|
||||
the \ac{bp} and \ac{ip} is pushed to the stack (\cref{fig:call}) and the CPU
|
||||
writes the address of the called function into the \ac{ip}. When the function
|
||||
returns, after restoring the old \ac{ip} from the stack, the execution continues
|
||||
from where the function call occurred earlier. If an overflow overwrites the old
|
||||
\ac{ip} (\cref{fig:exploit}), the attacker controls where execution continues.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\begin{subfigure}[b]{.3\textwidth}
|
||||
@ -177,23 +182,23 @@ This is only one of several types and exploitation techniques. Others include
|
||||
\item Heap-based \ac{bof}: In this case there is no way of overwriting the
|
||||
return address but objects on the heap might contain function pointers
|
||||
(e.g.\ for dynamic dispatch) which can be overwritten to execute the
|
||||
attackers code, when executed~\cite{Detection2018}.
|
||||
attackers code, when called~\cite{Detection2018}.
|
||||
|
||||
\item Integer overflow: Some calculation on fixed sized integers is used to
|
||||
allocate memory. The calculation leads to an integer overflow and only a
|
||||
small buffer is allocated~\cite{Detection2018}. Later a big integer into the
|
||||
buffer is used and reads or writes outside the buffer. This kind of
|
||||
vulnerability can also lead to other problems because at least in C, signed
|
||||
integer overflow is undefined behaviour.
|
||||
small buffer is allocated~\cite{Detection2018}. Later the buffer is indexed
|
||||
with a big integer and performs a read or write outside the buffer. This
|
||||
kind of vulnerability can also lead to other problems because at least in C,
|
||||
signed integer overflow is undefined behaviour.
|
||||
|
||||
\end{itemize}
|
||||
|
||||
This paper won't explore other kinds of \ac{bof} in detail because the concept
|
||||
is always the same: Unchecked indexing into memory allows the attacker to
|
||||
overwrite some kind of return or call address, which allows hijacking of the
|
||||
This paper does not explore other kinds of \ac{bof} in detail because the
|
||||
concept is always the same: Unchecked indexing into memory allows the attacker
|
||||
to overwrite some kind of return or call address, which allows hijacking of the
|
||||
execution flow.
|
||||
|
||||
The most trivial kinds of payloads is known as a \mintinline{ASM}{NOP} sled.
|
||||
The most trivial kind of payloads is known as a \mintinline{ASM}{NOP} sled.
|
||||
Here the attacker appends as many \mintinline{ASM}{NOP} instructions before any
|
||||
shell-code (e.g.\ to invoke \mintinline{shell}{/bin/sh}) and points the
|
||||
overwritten \ac{ip} or function pointer somewhere inside the
|
||||
@ -218,7 +223,7 @@ problems introduced by \acp{bof} and tries to answer the following questions:
|
||||
\acp{bof}?
|
||||
|
||||
\item How realistic is it for developers to use the technique in real-world
|
||||
code? Can it be introduced incrementally?
|
||||
code? Is an incremental introduction possible?
|
||||
|
||||
\end{itemize}
|
||||
|
||||
@ -228,12 +233,12 @@ techniques are language agnostic but this is not a focus of this paper. In the
|
||||
end, there is a discussion about the current state.
|
||||
|
||||
For the literature research, the paper~\citetitle{Detection2018} served as a
|
||||
base. From there a snowball system search with combinations of the keywords
|
||||
\enquote{buffer}, \enquote{overflow}, \enquote{detection}, \enquote{prevention}
|
||||
and \enquote{dependent typing} was performed using
|
||||
base. From there on, the author performed a snowball system search with
|
||||
combinations of the keywords \enquote{buffer}, \enquote{overflow},
|
||||
\enquote{detection}, \enquote{prevention} and \enquote{dependent typing} using
|
||||
\url{https://scholar.google.com/}.
|
||||
|
||||
Results are evaluated and prioritized using the following criteria:
|
||||
Evaluation and prioritization of results is done using the following criteria:
|
||||
|
||||
\begin{itemize}
|
||||
|
||||
@ -264,49 +269,50 @@ The easiest and maybe single most effective method to prevent \acp{bof} is to
|
||||
check, if a write or read operation is out of bounds. This requires storing the
|
||||
size of a buffer together with the pointer to the buffer (so called fat
|
||||
pointers) and check for each read or write in the buffer, if it is in bounds at
|
||||
runtime. Still almost any language that comes with a runtime, uses runtime
|
||||
checking. For this technique to be effective effective in general, writes to a
|
||||
raw pointer must be disallowed. Otherwise the security checks can be
|
||||
circumvented. \Ac{rbc} introduces a runtime overhead for every indexed read or
|
||||
write operation. This is a problem if a program runs on limited hardware or
|
||||
might impact real-time properties.
|
||||
runtime. Almost any language that comes with a managed runtime, uses \ac{rbc}.
|
||||
For this technique to be effective effective in general, writes to raw pointers
|
||||
must be disallowed. Otherwise the security checks can be circumvented. \Ac{rbc}
|
||||
introduces a runtime overhead for every indexed read or write operation. This is
|
||||
a problem if a program runs on limited hardware or might impact real-time
|
||||
properties.
|
||||
|
||||
Introducing \ac{rbc} into an existing codebase is not easy. Using fat pointers
|
||||
in a few functions does not prevent other parts of the code to use raw pointers
|
||||
into the same buffer. So for this to be effective, the whole codebase needs to
|
||||
be changed to disallow raw pointers, which, depending on the size, might not be
|
||||
feasible. Still, if done correctly and consequently, it is simply impossible to
|
||||
exploit \acp{bof} for code execution. \Ac{dos} is still possible because the
|
||||
program terminates gracefully when a out of bounds index is used.
|
||||
feasible. Still, if done correctly and consequently, there will be no \ac{bof}
|
||||
vulnerabilities. \Ac{dos} might is still possible depending on how invalid
|
||||
indexing is handled, because the program might terminate gracefully when a out
|
||||
of bounds index is used.
|
||||
|
||||
\subsection{Prevent/Detect Overwriting Return Address}
|
||||
|
||||
Since most traditional \ac{bof} exploits work by overwriting the return address
|
||||
in the current stack frame, preventing or at least detecting this, can be quite
|
||||
Since stack based \ac{bof} exploits work by overwriting the return address in
|
||||
the current stack frame, preventing or at least detecting this, can be quite
|
||||
effective without much overhead at runtime. \citeauthor{Rad2001} describe a
|
||||
technique that stores a redundant copy of the return address in a secure memory
|
||||
area that is guarded by read-only memory, so it cannot be overwritten by
|
||||
overflows. When returning, the copy of the return address is compared to the one
|
||||
in the current stack frame and only, if it matches, the \mintinline{ASM}{RET}
|
||||
in the current stack frame and only if it matches, the \mintinline{ASM}{RET}
|
||||
instruction is actually executed~\cite{Rad2001}. While this is effective against
|
||||
stack based \acp{bof}, in the described form, it does not protect against vtable
|
||||
overwrites. An extension could be made to also protect the \ac{plt} and vtables
|
||||
but custom constructs using function pointers would still be vulnerable. Since
|
||||
this technique is a compiler extension, no modification of the codebase is
|
||||
required to enable it, and while it does not prevent all kinds of \ac{bof},
|
||||
mitigates all stack based \acp{bof} with only minimal overhead when calling and
|
||||
returning from a function.
|
||||
stack based \acp{bof}, in the described form, it does not protect against
|
||||
\ac{vmt} or \ac{plt} overwrites. An extension could be made to also protect the
|
||||
\ac{plt} and \ac{vmt} but custom constructs using function pointers would remain
|
||||
vulnerable. Since this technique is a compiler extension, no modification of the
|
||||
codebase is required to enable it, and while it does not prevent all kinds of
|
||||
\ac{bof}, it mitigates all stack based \acp{bof} with only minimal overhead when
|
||||
calling and returning from a function.
|
||||
|
||||
An older technique from 1998 proposes to put a canary word (named after the
|
||||
canaries that were used in mines to detect low oxygen levels) between the data
|
||||
of a stack frame and the return address~\cite{Stackguard1998}\cite{AtkDef2016}.
|
||||
When returning, the canary is checked, if it is still intact and if not, a
|
||||
\ac{bof} occurred. This technique is implemented by major
|
||||
When returning, a check is performed, to confirm, the canary is intact, if it is
|
||||
not, a \ac{bof} occurred. This technique is implemented by major
|
||||
compilers~\cite{Gcc2003} but can be defeated, if there is an information leak
|
||||
that leaks the canary to the attacker. The attacker is then able to construct a
|
||||
payload, that keeps the canary intact. This mitigation has a minimal
|
||||
performance impact~\cite{Gcc2003} and offers a good level of protection. It is a
|
||||
compiler extension so no modification of the code base is needed.
|
||||
payload, that keeps the canary intact. This mitigation has a minimal performance
|
||||
impact~\cite{Gcc2003} and offers a good level of protection. It is a compiler
|
||||
extension so there is no need for modification of the code base.
|
||||
|
||||
\subsection{Type System Solutions}
|
||||
|
||||
@ -314,15 +320,16 @@ compiler extension so no modification of the code base is needed.
|
||||
with dependent types. These types have an associated value, e.g.\ a pointer type
|
||||
can have the buffer size associated to it~\cite{Dep2007}. This prevents indexing
|
||||
into a buffer with out-of-bounds values. This extension is a superset of C so
|
||||
any valid C code can be compiled using the extension and the codebase is
|
||||
improved incrementally. If the type extension is advanced enough, the
|
||||
additional information might form the base for a formal verification. In some
|
||||
cases, the type extensions can even be inferred~\cite{Dep2007}.
|
||||
compilation of any valid C code is possible using the extension and incremental
|
||||
improvement of the codebase is possible. If the type extension is advanced
|
||||
enough, the additional information might form the base for a formal
|
||||
verification. In some cases, inference of the type extensions is
|
||||
possible~\cite{Dep2007}.
|
||||
|
||||
This technique prevents all kinds of overflows, if used, but requires changes to
|
||||
the codebase and is only effective where these changes are applied. Since it is
|
||||
a compile-time solution, it does affect the compile-time but has no negative
|
||||
effect on the runtime.
|
||||
a compile-time solution, it affects the compile-time but has no negative effect
|
||||
on the runtime.
|
||||
|
||||
\subsection{Address Space Layout Randomization}
|
||||
|
||||
@ -348,15 +355,15 @@ existing executable code. The ret-to-libc exploiting technique uses existing
|
||||
calls to the libc with attacker controlled parameters, e.g.\ if the program uses
|
||||
the \mintinline{shell}{system} command, the attacker can plant
|
||||
\mintinline{shell}{/bin/sh} as parameter on the stack, followed by the address
|
||||
of \mintinline{shell}{system} and get a shell on the system. \ac{rop} (a
|
||||
of \mintinline{shell}{system} and get a shell on the system. \Ac{rop} (a
|
||||
superset of ret-to-libc exploits) uses so called \ac{rop} gadgets, combinations
|
||||
of memory modifying instructions followed by the \mintinline{ASM}{RET}
|
||||
instruction to build instruction chains, that execute the desired shell-code.
|
||||
This is done by placing the desired return addresses in the right order on the
|
||||
stack and reuses the existing code to circumvent the w\^{}x protection. These
|
||||
combinations of memory modification followed by \mintinline{ASM}{RET}
|
||||
instructions are called \ac{rop} chains and are Turing complete~\cite{Rop2007},
|
||||
so in theory it is possible to implement any imaginable payload, as long as the
|
||||
This is achieved by placing the desired return addresses in the right order on
|
||||
the stack and reuses the existing code to circumvent the w\^{}x protection.
|
||||
These combinations of memory modification followed by \mintinline{ASM}{RET}
|
||||
instructions, known as \ac{rop} chains, are Turing complete~\cite{Rop2007}, so
|
||||
in theory it is possible to construct any imaginable payload, as long as the
|
||||
exploited program contains enough gadgets and the overflowing buffer has enough
|
||||
space.
|
||||
|
||||
@ -367,22 +374,23 @@ space.
|
||||
|
||||
\subsubsection{\ac{aslr}}
|
||||
|
||||
\Ac{aslr} has been proven effective and is wildly used in production. It is
|
||||
included in most major operating systems~\cite{FBSDaslr}. Some even use kernel
|
||||
\Ac{aslr} has proven effective and sees wide use in production. Most major
|
||||
operating systems implement this technique~\cite{FBSDaslr}. Some even use kernel
|
||||
\ac{aslr}~\cite{Linuxaslr}. Since this mechanism is active at runtime, it does
|
||||
not require any changes in the code itself, the program only has to be compiled
|
||||
as a \ac{pie}. On 32-bit CPUs, only 16-bit of the address are randomized. These
|
||||
16-bit can be brute forced in a few minutes or seconds~\cite{AslrEffective2004}.
|
||||
|
||||
There is no runtime overhead since the only change is the position of the
|
||||
program in memory. Since there is no additional work except maybe recompilation,
|
||||
this technique can and should be used on modern systems.
|
||||
program in memory. Since there is no additional work required except maybe
|
||||
recompilation, this technique can and should be used on modern systems.
|
||||
|
||||
\subsubsection{w\^{}x}
|
||||
|
||||
With the rise of \ac{rop} techniques, w\^{}x protection has been shown to be
|
||||
ineffective. It makes vulnerabilities harder to exploit by preventing the most
|
||||
naive types of payloads but it doesn't actually prevent exploits from happening.
|
||||
The rise of code reuse exploits like \ac{rop} and ret-to-libc, shows the
|
||||
ineffectiveness of w\^{}x protection. It makes vulnerabilities harder to exploit
|
||||
by preventing the most naive types of payloads but it doesn't actually prevent
|
||||
exploits from happening.
|
||||
|
||||
\Ac{nx} does not prevent any exploits but makes it harder for an attacker that
|
||||
does not know the system, the program is running on (e.g.\ a network service).
|
||||
@ -398,7 +406,7 @@ might introduce other problems.
|
||||
|
||||
\subsection{State of the Art}
|
||||
|
||||
Operating systems started to compile C code to \ac{pie} by
|
||||
Operating systems started to compile C code to \acp{pie} by
|
||||
default~\cite{ArchPie2017} and \ac{aslr} is enabled, too. Same goes for \ac{nx}
|
||||
and stack canaries~\cite{ArchPie2017}. The combination of these mitigations
|
||||
makes it hard to write general exploits for modern operating systems.
|
||||
@ -406,7 +414,7 @@ makes it hard to write general exploits for modern operating systems.
|
||||
To check the current state, the author investigates, which mitigations are
|
||||
enabled by default in the latest release (9.2) of the \ac{gcc} and the latest
|
||||
commit of the LLVM-project (\mintinline[breaklines]{shell}{181ab91efc9}) by
|
||||
compiling both compilers using the default configuration. The experiments are
|
||||
building both compilers using the default configuration. The experiments are
|
||||
performed on a 64-bit Debian 9.11 system running on version 4.19.0 of the Linux
|
||||
kernel. The following commands compile the source codes:
|
||||
|
||||
@ -441,12 +449,13 @@ kernel. The following commands compile the source codes:
|
||||
The \mintinline{shell}{build}, \mintinline{shell}{host} and
|
||||
\mintinline{shell}{target} parameters in~\cref{lst:gcc} describe the target
|
||||
platform for the compiler and \mintinline{shell}{disable-multilib} disables
|
||||
32-bit support. The \mintinline{sh}{-j8} flag only tells make to use all 8
|
||||
available cores for compilation. \mintinline{shell}{CMAKE_BUILD_TYPE=Release}
|
||||
creates a release build of the clang compiler (see~\cref{lst:clang}).
|
||||
32-bit support, which is not needed for this experiment. The
|
||||
\mintinline{sh}{-j8} flag only tells make to use all 8 available cores for
|
||||
compilation. \mintinline{shell}{CMAKE_BUILD_TYPE=Release} creates a release
|
||||
build of the clang compiler (see~\cref{lst:clang}).
|
||||
|
||||
The fresh builds of \ac{gcc} and clang compile the code from~\cref{lst:vuln} to
|
||||
check which mitigations are enabled by default. Using
|
||||
check which mitigations are enabled by default. After using
|
||||
\mintinline[breaklines]{shell}{gcc -o vuln.gcc vuln.c} and
|
||||
\mintinline[breaklines]{shell}{clang -o vuln.clang vuln.c} to compile the source
|
||||
code, the \mintinline{shell}{checksec.sh} tool~\cite{Checksec2019} shows which
|
||||
@ -506,40 +515,6 @@ properties are required, Rust could be the way to go, without any language
|
||||
runtime and with deterministic memory management. For any other problem, almost
|
||||
any other memory safe language is better than using unsafe C.
|
||||
|
||||
% \section{Sources (Dummy Section for Deadline)}
|
||||
|
||||
% \begin{itemize}
|
||||
|
||||
% \item RAD:\ A Compile-Time Solution to Buffer Overflow Attacks~\cite{Rad2001}
|
||||
% (might not protect against e.g.\ vtable overwrites, \ac{plt} address
|
||||
% changes, \dots)
|
||||
|
||||
% \item Dependent types for low-level programming~\cite{Dep2007}
|
||||
|
||||
% \item StackGuard: Automatic Adaptive Detection and Prevention of
|
||||
% Buffer-Overflow Attachs~\cite{Stackguard1998} (ineffective in combination
|
||||
% with information leaks)
|
||||
|
||||
% \item Type-Assisted Dynamic Buffer Overflow Detection~\cite{TypeAssisted2002}
|
||||
|
||||
% \item On the Effectiveness of NX, SSP, RenewSSP, and \ac{aslr} against Stack
|
||||
% Buffer Overflows~\cite{Effectiveness2014}
|
||||
|
||||
% \item What Do We Know About Buffer Overflow Detection?: A Survey on Techniques
|
||||
% to Detect A Persistent Vulnerability~\cite{Detection2018}
|
||||
|
||||
% \item Survey of Attacks and Defenses on Stack-based Buffer Overflow
|
||||
% Vulnerability~\cite{AtkDef2016}
|
||||
|
||||
% \item Beyond stack smashing: recent advances in exploiting buffer
|
||||
% overruns~\cite{Smashing2004}
|
||||
|
||||
% \item Runtime countermeasures for code injection attacks against C and C++
|
||||
% programs~\cite{Counter2012}
|
||||
|
||||
% \end{itemize}
|
||||
|
||||
|
||||
\printbibliography{}
|
||||
% \bibliographystyle{IEEEtran}
|
||||
% \bibliography{bibliography}
|
||||
|
@ -62,3 +62,8 @@
|
||||
short = DEP,
|
||||
long = data execution prevention
|
||||
}
|
||||
|
||||
\DeclareAcronym{vmt}{
|
||||
short = VMT,
|
||||
long = virtual method table
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user