strings -a -t x /lib32/libc-2.24.so | grep /bin/sh
The TEST instruction performs a bitwise AND on two operands. The flags SF(Sign Flag a.k.a. Negative Flag), ZF(Zero Flag), PF(Parity Flag) are modified while the result of the AND is discarded. The OF(Overflow Flag) and CF(Carry Flag) flags are set to 0, while AF(Auxillary Carry Flag) flag is undefined. There are 9 different opcodes for the TEST instruction depending on the type and size of the operands. It can compare 8-bit, 16-bit, 32-bit or 64-bit values. It can also compare registers, immediate values and register indirect values.
The Sign Flag(Negative Flag) is set to the most significant bit of the result of the AND. In two's complement, a number with most significant bit = 1 is negative. If the result is 0, the Zero Flag is set to 1, otherwise 0. The Parity Flag is set to the bitwise XNOR of the least significant BYTE of the result i.e. if the number of 1s in the least significant byte is even, then the PF is set to 1 else 0.
Unless we consider goto
as a branch, branch coverage doesn't imply statement coverage and vice-versa.
If we consider goto
as a branch, then branch coverage implies statement coverage but not vice-versa(Even if we take simple statements).(Note that if we have a goto
statement, then one branch will never be covered so we'll never have full branch coverage in case of a goto statement.
See the following example:
if(x)
then S1;
S2;
So if x=1
, then every statement gets covered but not every branch(the false branch).
Take a look at this presentation. Note that the numbers on the left show the order of execution for a certain input.
Note: Path coverage >= Branch coverage >= Statement coverage
if(x){
S1;
S2;
S3;
}
This whole is a single compuound statement while S1, S2, S3 are simple statements.
export x='() { :;}; echo $(/bin/cat /home/shellshock/flag)'
The pattern () { :;};
is the key!
Also see this stackoverflow question and this video.
-
-
Broken input functions
- scanf("%s") - Doesn't limit the size of the input, so you can overflow basically anything. It also taken in null bytes, only stops at whitespace. The problem is with the format string "%s" and not the scanf function per se. If you use "%100s" it will take in 100 characters only.
- gets([char*]) - Here the problem is the function itself. It doesn't allow adding string length, so NEVER use this!
-
String length mismatch
- Constant length inconsistencies -
- strlen() vs counted length - Say you have a
#define Counted_Length 10
i.e. you know a string is 10 characters in length. But "null byte injection"! If you usestrlen()
somewhere and the string has a null byte then you will get a different value. - Counted length contradictions - This can be thought of with respect to the heartbleed bug. This occurs when you supply the length of a string at two different places but you don't check if the two values are equal.
-
Off by One errors
- 1-index/0-index - Remember to use the correct values in your
for
loops. First element of list is list[0]; Last element of a list is list[length-1], not list[length]. List[length] will give you the data in memory just after the list. Someone may have place some malicious data here.
- 1-index/0-index - Remember to use the correct values in your
-
Numeric errors
- negative negative 2^31 == negative 2^31 - Think of how -2^31 is represented in memory(It's all 1s. negating it means taking it's two's complement which is negative 2^31). You can bypass checks like
if(x == -x and x!=0)
! - Up-casting signed numeric types, e.g. char -> int - Consider the following code:
char input_data[20]; char transformation[256]; for(int i = 0; i < 20; i++) { input_data[i] = transformation[input_data[i]]; }
Now here you may assume that
input_data
being a char will be converted into an integer b/w 0 and 255. But this is not true. I am not sure if this is false all the time though. But to be safe you should useunsigned char
in such cases where you think you'll be using it as an index later.- failing to account for overflow - assuming
y > 0 => x + y > x
is wrong sincex+y
may overflow the buffer and you might end up getting a smaller value thanx
. - floating point: Failing to account for inf and nan
- floating point: Assuming x + 1 != x
- negative negative 2^31 == negative 2^31 - Think of how -2^31 is represented in memory(It's all 1s. negating it means taking it's two's complement which is negative 2^31). You can bypass checks like
-
Order of operations errors
- assignment expressions and ternary statements have extremely high precedence
- Operator Priority (See
mistake
challenge on pwnable.kr)
-
User-controlled format strings
- format string exploitation is a really strange and very powerful black magic. Gives you the power to read and write to memory at an address of your choice!
- printf(variable) should be a huge red flag - Always pass the format string as the first argument to printf!
-
Use after free
- failure to clean up freed pointers
- type confusion
-
Improper sanitization, injection
- Quotation breakout
- Null bytes
- Fail-check sanitization escape
- Decoding after sanitization
- Generally, using non-constant command strings is extremely dangerous
-
Failure to check error conditions
- malloc returns NULL on error - There is no crash at malloc but you may try to dereference the pointer somewhere. So whenever malloc-ing, do check if NULL is returned.
- scanf returns number of filled out parameters - This is a fun one.
- functions with buffers as outparams will leave the buffer unchanged on error, return -1
- failure to check returned length (assuming maximum) can be a memory leak
-
To Bypass /
when PATH variable is empty, use cd ..;cd ../; $(pwd)
. $(pwd) in root directory gives /
objdump -R [binary] | grep [function_name]
readelf -s [path/to/library.so] | grep [function_name]
nasm -f [output_file_format] [assembly_file.asm]
ld -o [outfile_name] [object_file.o]
(Note that null bytes might be stripped in objdump)
for i in `objdump -d print_flag | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\x$i" ; done
X86-64 system calls use syscall instruction. This instruction saves return address to rcx, and after that it loads rip from IA32_LSTAR MSR. I.e. rcx is immediately destroyed by syscall. This is the reason why rcx had to be replaced for system call ABI.
This same syscall instruction also saves rflags into r11, and then masks rflags using IA32_FMASK MSR. This is why r11 isn't saved by the kernel.
So, these changes reflect how the syscall mechanism works. This is why the kernel is forced to declare rcx and r11 as not saved and even can't use them for parameter passing.
According to System V X86-64 ABI, function calls in the applications use the following sequence of registers to pass integer arguments:
rdi, rsi, rdx, rcx, r8, r9
But system call arguments (other than syscall number) are passed in another sequence of registers:
rdi, rsi, rdx, r10, r8, r9
For x86
, the order of registers for a syscall is as follow:
ebx, ecx, edx, esi, edi
eax
stores the syscall number in both cases.
Sometimes executables maybe compressed with UPX leading to its behaviour not being what you expect it to. Use strings [executable] | grep UPX
to see if there is UPX compression! Also see the flag
challenge on pwnable.kr
-
Note: x64 provides a new rip-relative addressing mode. Instructions that refer to a single constant address are encoded as offsets from rip. For example, the
mov rax, [addr]
instruction moves 8 bytes beginning ataddr + rip
torax
. -
- It's not as easy as it sounds, since there are many different opcodes for
jmp
, each with different syntax for the address. The opcode changes based on whether you want to jump to an address relative to the current address of the shellcode or to an absolute address. - From my share of experience, I believe that the best way to jump to an absolute address is to avoid the
jmp
instruction altogether and rather use the following assembly:
push [Absolute address] ret
- For relative addresses, depending on the magnitude of difference between the current and destination address, there are different opcodes. The most commonly used is
E9
. With this you can make a jump of as large as a 32-bit value can be. A common mistake is to useE9 08 01
to jump 264 bytes from the current instructions, but this offset is actually from the next instruction; so in this case you would want to useE9 05 01
. Basicallydestination_address - current_address - sizeof("E9 xx xx xx xx")
is how much you want to jump. - Reference for syntax and opcodes
- It's not as easy as it sounds, since there are many different opcodes for
-
To write 4 bytes, 2 bytes, 1 byte using format string vulnerability use
%n
,%hn
,%hhn
respectively. -
When executing a binary, whether a given page is executable or not can be inferred from the contents of the special file
/proc/$$/maps
where$$
is the process ID. -
When using IDA with
wine
in MacOS,Command
key maps toAlt
and not theoption
key. -
leal
in AT&T sytax means the same thing aslea
in Intel syntax.
while read -r line; do echo -e $line; done | ./my_binary
Now you'll be able to give input like \x41l33t\x41
which normally interacting with the binary. This will be read by the bash shell, converted to Al33tA
and then sent to the binary! Try it out!