Crafting Shellcode: Techniques and Strategies for Bypassing Restricted Characters - 2
- 01 Crafting Shellcode: Techniques and Strategies for Bypassing Restricted Characters - 1Read →
- 02 Crafting Shellcode: Techniques and Strategies for Bypassing Restricted Characters - 2Now reading
Contents
☑️ How to bypass when there are characters that cannot be input?
When writing shellcode, if certain bytes (such as 0x00, 0x0a, etc.) cannot be input, there are various methods to bypass this limitation.
Bypassing through Opcode Modification
Most architectures, including ARM and MIPS, have many instructions that perform similar functions but have different Opcodes. Below, we'll look at examples of different Opcodes for addition operations in ARM and MIPS.
ARM
ARM has two different instructions: ADD and ADDS. ADD has an Opcode of 0x81, while ADDS has an Opcode of 0x91, and they perform similar functions.

The ADD instruction adds the values of two registers or adds an immediate value to a register and stores the result. This instruction does not modify condition flags.
ADD R0, R1, #1 ; R0 = R1 + 1
The ADDS instruction performs addition in the same way as the ADD instruction, but additionally updates the condition flags (CPSR, Current Program Status Register's condition flags). This is useful for conditional branching or conditional instruction execution.
ADDS R0, R1, #1 ; R0 = R1 + 1, N, Z, C, V flags of CPSR are updated
The main flags affected by the ADDS instruction are as follows:
N (Negative flag): Set when the result is negative.
Z (Zero flag): Set when the result is zero.
C (Carry flag): Set when there's a carry from addition.
V (Overflow flag): Set when an overflow occurs in addition.
MIPS
MIPS has more ADD operations than ARM. Each operation is divided into four types based on whether it checks for arithmetic overflow (U) and whether it performs constant operations (I).

The ADD instruction stores the result of adding two registers. This instruction checks for arithmetic overflow and raises an exception if an overflow occurs.
ADD $t0, $t1, $t2 ; $t0 = $t1 + $t2, Overflow Check
The ADDI instruction stores the result of adding a register and an immediate value. This instruction also checks for arithmetic overflow and raises an exception if an overflow occurs.
ADDI $t0, $t1, 10 ; $t0 = $t1 + 10, Overflow Check
The ADDIU instruction operates similarly to ADDI but does not check for arithmetic overflow. That is, no exception is raised even if the result value overflows.
ADDIU $t0, $t1, 10 ; $t0 = $t1 + 10, No Overflow Check
The ADDU instruction operates similarly to ADD but does not check for arithmetic overflow. That is, no exception is raised even if an overflow occurs.
ADDU $t0, $t1, $t2 ; $t0 = $t1 + $t2, No Overflow Check
As can be seen from the above, not only for addition but for most operations, there exist other Opcodes with similar functions. Therefore, when writing Shellcode, if the Hex data of an Opcode is not acceptable as input, you can find and execute another Opcode that performs a similar operation.
Bypassing Through Operations
While basic operations like addition and subtraction are commonly used for bypassing, XOR or shift operations (such as LSL) can also be used.
Arithmetic Operations
Using addition and subtraction operations to bypass non-inputtable characters is generally the most commonly used simple method.
When an attacker cannot pass the value 0x0a(\n) as Shellcode, they can bypass this using addition and subtraction.
ADD reg, 0x0a ; Add 0x0a to the register value -> 0x0a enters the Shellcode
ADD reg, 0x10 ; Add 0x10 to the register valueSUB reg, 0x06 ; Subtract 0x06 from the register value
Bitwise Operations
XOR operations are very useful for bypassing non-inputtable characters. Using XOR operations, you can convert non-inputtable characters into new values by XORing them. Also, XOR operations have the characteristic of being able to restore the original value.
For example, an XOR operation to bypass ASCII code 0x00 by converting it to 0x01 is as follows:
XOR reg, 0x01 ; Transform the register value by XOR operation with 0x01
Shift Operations
LSL (Logical Shift Left) operation shifts bits to the left. It can be used to bypass non-inputtable characters. In particular, it can move values at specific bit positions to convert them into values within the desired ASCII range.
For example, the process of creating 0xf10 can be separated into three steps and written using the LSL operator as follows:
MOV reg, 0x0f ; Put 0xf into the registerLSL reg, 8 ; Shift operation on the register to 0xf00ADD reg, 0x10 ; Register value becomes 0xf10
ARM Thumb Mode
What is Thumb mode?
ARM mode
Uses a 32-bit instruction set, with each instruction being 32 bits in size. This mode can include complex instructions and provides many features.
Thumb mode
Uses a 16-bit instruction set, with each instruction being 16 bits in size. This mode reduces code size compared to ARM mode and makes memory usage more efficient. While some functionality is limited, it has the advantage of shorter instruction lengths.
Characteristics of Thumb mode
Instruction size
In Thumb mode, instructions are fixed at 16-bit length, making them smaller than the 32-bit instructions in ARM mode. This results in higher code density, allowing more code to be stored in a smaller memory space.
Instruction set
The Thumb instruction set is a subset of the ARM instruction set, with some complex instructions omitted or simplified. However, it still includes enough instructions to perform most common operations.
Performance
While Thumb mode focuses on increasing code density, the constraints of 16-bit instructions may require more cycles for some operations. However, due to improved cache efficiency and reduced memory bandwidth usage, actual performance can be similar to or better than ARM mode.
Switching
ARM processors support switching between ARM and Thumb modes. The BX instruction can be used to switch between the two modes. For example, to switch from ARM mode to Thumb mode, you can do the following:
BX R0 ; If the lower bit of R0 is 1, switch to Thumb mode; if 0, maintain ARM mode
Advantages of Thumb mode
Memory savings
The use of 16-bit instructions reduces code size, allowing more code to be stored in the same memory space.
Reduced power consumption
Smaller code size and efficient memory usage help reduce power consumption.
Increased cache efficiency
Smaller instruction size increases the cache hit rate, improving performance.
Bypassing using Thumb mode
When using Thumb mode, the Hex data for both the Opcode and data portions are modified, making it very useful when certain characters need to be bypassed.
Thumb mode is particularly convenient for bypassing Null (0x00) characters. This is due to the advantage created by the instruction size explained above. Since instructions must be expressed in 16 bits, there is less spare memory space compared to ARM mode, making it difficult for 0x00 to be included.
Here's an example comparing the encoding of instructions performing the same task in ARM mode and Thumb mode:
ARM mode: movs r0, #1 → 0x0100B0E3 (32-bit)
Thumb mode: movs r0, #1 → 0x0120 (16-bit)
Using Thumb mode allows you to write Shellcode with shorter Hex data, enabling character bypassing. However, it is not supported in older versions of ARM (before ARMv4), so it's essential to verify the ARM version of the target system.
☑️ Where should strings used in Shellcode be stored?
Using the Data Segment
The Data Segment consists of the Data area, which stores initialized global and static variables, and the BSS (Block Started by Symbol) area, which stores uninitialized global and static variables. This segment is generally where global data is stored, apart from memory dynamically allocated during program execution.

The BSS area is automatically initialized to 0 when the program starts. Storing arbitrary data in the BSS area is one of the most commonly used methods in Shellcode, with the following advantages:
Automatic initialization
Variables in the BSS area are initialized to 0 at program start, so no separate initialization code is needed.
Free access
Variables stored in the BSS area are accessible throughout the program, making them easy to use in various parts of the Shellcode.
However, accessing the BSS area may require additional code for address calculation, as you need to know the exact address. Also, even when accessing the BSS area, you need a process to input additional data using write functions, which can be difficult in embedded devices where additional input is generally challenging.
The Data area contains initialized data. Storing strings in this area has the following advantages:
Convenience
It's convenient to use in Shellcode because it contains configuration data or fixed values.
Stability
As it's already allocated and initialized at program start, there's less risk of it being overwritten by other data during code execution.
However, logic must exist to store the desired strings in the Data area, and there are often length restrictions on configuration values, which can make it difficult to input all desired strings.
Storing behind Shellcode
Another method of storing strings in Shellcode is to store them behind the Shellcode, using the PC (Program Counter) register as a reference. This provides the following advantages:
Easy address calculation
Using the PC register as a reference allows access to strings using relative addresses, making address calculation simple.
Real-time string modification
Strings can be modified in real-time during Shellcode execution, allowing for more flexible Shellcode writing.
The disadvantage of this method is that addresses may vary depending on the environment in which the Shellcode is executed. In other words, when executing the same Shellcode in different environments, the location of strings may differ, potentially making relative address calculations inaccurate.

☑️ Conclusion
Writing Shellcode is a crucial part of system security and hacking techniques. Creating safe and efficient code under various constraints is challenging but a very important skill. Across Parts 1 and 2, we looked at the definition and usage conditions of shellcode, basic writing methods, and various techniques for bypassing prohibited characters such as Null bytes.
In particular, we examined bypass methods considering the characteristics of ARM, MIPS, and x86_64 architectures, learning strategies that can be applied in practice. We expect these techniques to be of great help to security experts in identifying and defending against security vulnerabilities.
If you're interested in this content, we recommend studying more complex shellcode writing methods and protection technique bypasses for future learning. For example, methods to bypass security mechanisms of the latest operating systems, or remote code execution techniques using various network protocols.
We hope this article helps security experts better understand shellcode writing and prompts embedded developers to reconsider protection techniques during compilation.