Low-level vulnerabilities
Updated:
13. Low-level vulnerabilities
Objectives – low level
- To explore how C’s printf functions can be abused (SIN 6)
- To consider vulnerabilities in the way that c++ supports dynamic binding of method calls
- To understand the risk of careless integer arithmetic (SIN 7)
- To discuss mitigations of these issues
Format string bugs
- These affect the printf family of C standard library – but also sprint - vulnerable
- Known for long time – 1999 and there’s been a lot vulnerabilities in the past mainly after 1999
- V easily spotted by compiler auditing tools – most compilers will warn us when using printf wrong
- Why is there a problem in printf?
Format string bugs II
- Correct way– printf(“%s”, user_input);
- to use %s to format input and feed the value to be formatted - user input
- Incorrect – printf(user_input);
- Bc the first argument of printf is a string and string contains a formatting directive but doesn’t have to, it can just be text w/o directive.
- What if this user input string contains formatting directives? Printf is expecting a formatting string containing any format directives, then there would be corresponding values supplied as arguments. But in this eg there isn’t.
Information disclosure threat
- In the absence of suitable arguments, prinf takes the values need by the format string from the stack
- Rather than giving compiler error, it takes whatever’s on the stack. Hence you can use this to see what’s on the stack
- So if the attacker provide that format string as an input and can see the output of printf – way of probing stack contents!! – looking at values of variables they have access to.
E.g. -
- printf has 8 formatting directives but without variables – you can see what’s on the stack!!
Code execution threat
- Weird thing with printf – you can use this to write to memory! How? Coz there’s a special formatting directive %n.
- %n writes the number of characters formatted so far, to a given memory.
- If no address is supplied as an argument, then the memory address written to will be taken from the stack
- Attacker can construct a format string by injecting an address onto stack, and generate the value to be stored in that address when printf runs
-
This gives you a way to overwrite the function return address, pointer, and pointing the shellcode
- E.g.
- Print bunch of periods, and using %n to supply the address of \&i.
- If the attacker can somehow provide both the value to be written and the memory address to which it should be written, it may be possible to them to gain control of program execution.
- This is a design flaw in printf – the risk
Palo Alto VPN bug
- Historical bug, BUT in July, it happened
- Shocked that it’s still being found in 2019- printf bug of VPN appliances, even on uber
- Other point is that Palo Alto – their research paper find that they
found it in the old version. What happened is that they fixed and
shut up about it
- Ethics of that action- concealing the bug to the client isn’t a professional nor ethical thing to do. It could be a serious bug that may cause a significant impact in the future, and client data could have already been exploited or is in a vulnerable state. Concealing it may keep the company’s credibility and reputation of the software high, but clients have the right to know about the state of software they are using.
C++ VTable attacks
- Dynamic binding – happens when we have inheritance, and in
subclasses that overwrites methods defined in the superclass.
- It is all about making decisions on runtime about which method gets called on an object based on the actual type of the object on runtime, not the type of reference to it in the code
- Decision on which method to be called is made by looking at the type of the object and referencing a lookup table – VTable – virtual methods is what triggers VTable, which decides what methods to call- one that bind inside that class or one that’s been inherited
-
In C++ you gotta turn it on explicitly. By defining using ‘virtual’ [vptr.xpp]
- E.g.
- Only difference btwn classes are ‘virtual’ – meaning that dynamic binding is possible.
- Result – the size of the VirtualThing is bigger!! 32 bytes – coz it contains a VTable and a VPtr to reference the VTable.
- So how does the attack work?
- If we supply input that overruns the buffer contained within the VirtualThing object, we can overwrite the VPtr to make it point to a fake VTable that references shellcode.
- VPtr is a pointer to the VTable, and allows you to lookup in runtime what method to call
- When methods are invoked, shellcode is executed
C++ VTable attacks
- We have an object and one of the fields is buffer which could be overwritten, and VPtr is on the end after the definition of the memory
- What happens is the attacker overruns the buffer bc user input is used but no checking on the size done, then they’ve overrun the buffer, other instance variables and VPtr.
-
After: The payload, VPtr, contains fake VTable which the function pointer all points to the shellcode. Link to the genuine VTable is gone, and VPtr points a fake table. And when the code invokes one of the methods, the shell code executes.
- Defences
- Gnu C++ has compiler options to allow VPtr protection method to be added to the executable analogous to stack protection function that guards the return address.
- But this relies on VPtr being after the buffer in memory, but in C++ its before doesn’t mean its safe – next
Defeating Visual C++
- This time you are relying on having 2 objects allocated on the heap. Instance of class A which defines a buffer which ur gonna overwrite, then there’s a gap which ur gonna overrun everything, and after the gap there’s an instance of B which has the VPtr.
- If you overrun buffer in A, it can overrun all the instance variables, gap in heap and VPtr in B and make it point back to the injected VTable and Shellcode.
- So you got compiler option to add VPtr protection option. So good idea to use them
Integer overflow
- 300 *300 = 24464 ???
- -15000 – 25000 = 25536 ????
- 32767+1 = -32768 ???
- The result is due to working in 16-bit signed integers– it shows the fixed range of values. And if its over that value it wraps around to the lowest value of that range.
- In C sharp, it does warn you – that arithmetic operation resulted in an overflow, but not in C++ and Java.
Example
double* allocData(size_t n). {
double* data = new double[n];
if (data == NULL) {
throw ApplicationException("out of memory");
}
return data;
}
- In terms of security and the consequences
- The functions job is to allocate storage – we are passing variable n of size t. size_t is the same as unsigned int, where the range of value of n could be from 0 to 232-1.
- Line highlighted – this does the arithmetic and hence problematic.
- 232-1 = 4294967295.
- E.g – Double occupies 8 bytes. If n*sizeof(double), how many bytes
will be allocated when:
- n = 536870911 ? A: 4294967288. When we multiply 8*n, its still in the range of 2^32 -1
- n= 536870913? A: 8. 8*n exceeds the range of size t. So it wraps around and we get allocated 8 bytes. Problem is bc the size we need and what we got isn’t the same, overwriting can happen.
Another example - concat data
size_t total = length1 + length2;
where length1 = 320, length2 = 4,294,967,232 ⇒ total = 256
if (total > 256)
return false;
int i = 0;
for (; i < length1; ++i)
tmp[i] = buf1[i]; tmp overrun by 64 bytes
for (; i < total; ++i)
tmp[i] = buf2[I – length1];
- A function takes 2 buffers and we concatenate and create a new buffer
- First it calculates length 1 and 2 and if its bigger than 256 then it can’t so it returns false. – it’s being careful with the if function (total > 256) of buffer overrun but not Integer overflow.
- BUT if fails to check if each length is less than 256. If length 1 =
320, then problem! That first if statement doesn’t solve the
problem, so we are overrunning 320 when its 256.
- The value wraps around and becomes 256 and it wraps around. And the bugger only has 256 where tmp got overrun by 64 bytes.
- Watch out both buffer overrun and integer overflow!!
Defensive strategies
- How can we avoid all of the low-level issues?
- Memory protection technology such as NX bit \&ASLR
- Change our development practices
- A more secure language
- Appropriate use of compiler options – warnings, add bound checking code and etc
- Proper use of standard library – provide input with format sting
- Special safe libraries – safestrcpy
- Do integer arithmetic correctly – to prevent buffer and integer overflow
Development practices
- Use libraries to do arithmetic safely
- Consider using languages with array bounds checking – java, C#, Python
- Be careful with integer arithmetic
- Use unsigned types where possible
- Examine very carefully any code that calculates array indices or buffer lengths
- Perform comparison properly – by checking explicitly for
wrap-around
- if (a+b >=a && a+b < MAX)– also checking that sum is greater than one of the values
- e.g.2 – use !jas_safe_size_mul checks to see whether the multiplied value doesn’t exceed the required value
Library usage
- When writing C++ use its standard library, not C’s!
- std:: string instead of char*
- Use C standard library cautiously
- Never ever use gets to read a string
- Avoid strcpy, strcat bound limited version is available but still can be abused
- Take care with printf – never allow a user input to be a format specifier
Summary
- Seen C’s printf functions plat far too much trust in format strings, allowing unrestricted manipulation of memory
- Buffer overruns can affect the VTables of C++ classes
- Problems with fixed-precision integer arithmetic can also trigger buffer overruns
- There are various defences against low level issues – using hardware & OS features, compiler options, safer languages or libraries
Leave a comment