diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a1b9e9..ab0835a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -311,3 +311,17 @@ if(BN_INTERNAL_BUILD) COMMAND ${CMAKE_COMMAND} -E copy $ ${BN_CORE_PLUGIN_DIR}/scc) endif() endif() + +enable_testing() +if(WIN32) + set(SCC_TEST_PYTHON py -3) +else() + set(SCC_TEST_PYTHON python3) +endif() + +add_test(NAME scc_cli + COMMAND ${SCC_TEST_PYTHON} ${PROJECT_SOURCE_DIR}/tests/test_cli.py $) +add_test(NAME scc_x64_regressions + COMMAND ${SCC_TEST_PYTHON} ${PROJECT_SOURCE_DIR}/tests/test_x64_regressions.py $) +add_test(NAME scc_ppc_socketcall + COMMAND ${SCC_TEST_PYTHON} ${PROJECT_SOURCE_DIR}/tests/test_ppc_socketcall.py $) diff --git a/Expr.cpp b/Expr.cpp index 38815ba..5297ce1 100644 --- a/Expr.cpp +++ b/Expr.cpp @@ -2105,6 +2105,65 @@ void Expr::GenerateConditionalIL(ParserState* state, Function* func, ILBlock* bl } +bool Expr::IsDirectVariableMemberAccess() const +{ + if (m_class == EXPR_VARIABLE) + return true; + if (m_class == EXPR_DOT) + return m_children[0]->IsDirectVariableMemberAccess(); + return false; +} + + +ILParameter Expr::GenerateAddressIL(ParserState* state, Function* func, ILBlock*& block) +{ + // Preserve aggregate lvalues as addresses so nested member access does not require + // copying a structure or union into an IL temporary. + ILParameter result, a, b; + + switch (m_class) + { + case EXPR_VARIABLE: + result = func->CreateTempVariable(Type::PointerType(m_type, 1)); + block->AddInstruction(ILOP_ADDRESS_OF, result, ILParameter(m_variable)); + break; + case EXPR_DOT: + result = func->CreateTempVariable(Type::PointerType(m_type, 1)); + a = m_children[0]->GenerateAddressIL(state, func, block); + block->AddInstruction(ILOP_ADDRESS_OF_MEMBER, result, a, + ILParameter(m_children[0]->GetType()->GetStruct(), m_stringValue)); + break; + case EXPR_ARROW: + result = func->CreateTempVariable(Type::PointerType(m_type, 1)); + a = m_children[0]->GenerateIL(state, func, block); + block->AddInstruction(ILOP_ADDRESS_OF_MEMBER, result, a, + ILParameter(m_children[0]->GetType()->GetChildType()->GetStruct(), m_stringValue)); + break; + case EXPR_DEREF: + result = m_children[0]->GenerateIL(state, func, block); + break; + case EXPR_ARRAY_INDEX: + result = func->CreateTempVariable(Type::PointerType(m_type, 1)); + a = m_children[0]->GenerateIL(state, func, block); + b = m_children[1]->GenerateIL(state, func, block); + block->AddInstruction(ILOP_PTR_ADD, result, a, b, + ILParameter(Type::IntType(GetTargetPointerSize(), false), + (int64_t)m_children[0]->GetType()->GetChildType()->GetWidth())); + break; + case EXPR_STRING: + case EXPR_FUNCTION: + result = GenerateIL(state, func, block); + break; + default: + state->Error(); + fprintf(stderr, "%s:%d: error: expected lvalue\n", m_location.fileName.c_str(), m_location.lineNumber); + break; + } + + return result; +} + + ILParameter Expr::GenerateArrayAccessIL(ParserState* state, Function* func, ILBlock*& block) { ILParameter result; @@ -2182,16 +2241,32 @@ ILParameter Expr::GenerateIL(ParserState* state, Function* func, ILBlock*& block result = ILParameter(m_function); break; case EXPR_DOT: - a = ILParameter(m_children[0]->GenerateIL(state, func, block), m_children[0]->GetType(), m_stringValue); - a.type = ILParameter::ReduceType(m_type); - if (m_type->GetClass() == TYPE_ARRAY) + if (!IsDirectVariableMemberAccess()) { - result = func->CreateTempVariable(Type::PointerType(m_type->GetChildType(), 1)); - block->AddInstruction(ILOP_ADDRESS_OF, result, a); + a = GenerateAddressIL(state, func, block); + if (m_type->GetClass() == TYPE_ARRAY) + { + result = a; + } + else + { + result = func->CreateTempVariable(m_type); + block->AddInstruction(ILOP_DEREF, result, a); + } } else { - result = a; + a = ILParameter(m_children[0]->GenerateIL(state, func, block), m_children[0]->GetType(), m_stringValue); + a.type = ILParameter::ReduceType(m_type); + if (m_type->GetClass() == TYPE_ARRAY) + { + result = func->CreateTempVariable(Type::PointerType(m_type->GetChildType(), 1)); + block->AddInstruction(ILOP_ADDRESS_OF, result, a); + } + else + { + result = a; + } } break; case EXPR_ARROW: @@ -2209,31 +2284,30 @@ ILParameter Expr::GenerateIL(ParserState* state, Function* func, ILBlock*& block } break; case EXPR_ADDRESS_OF: - result = func->CreateTempVariable(m_type); - if (m_children[0]->GetClass() == EXPR_ARRAY_INDEX) - { - a = m_children[0]->m_children[0]->GenerateIL(state, func, block); - b = m_children[0]->m_children[1]->GenerateIL(state, func, block); - block->AddInstruction(ILOP_PTR_ADD, result, a, b, ILParameter(Type::IntType(GetTargetPointerSize(), false), - (int64_t)m_children[0]->m_children[0]->GetType()->GetChildType()->GetWidth())); - } - else if (m_children[0]->GetClass() == EXPR_ARROW) + result = m_children[0]->GenerateAddressIL(state, func, block); + break; + case EXPR_DEREF: + if (m_type->GetClass() == TYPE_ARRAY) { - block->AddInstruction(ILOP_ADDRESS_OF_MEMBER, result, m_children[0]->m_children[0]->GenerateIL(state, func, block), - ILParameter(m_children[0]->m_children[0]->GetType()->GetChildType()->GetStruct(), - m_children[0]->m_stringValue)); + result = m_children[0]->GenerateIL(state, func, block); } else { - block->AddInstruction(ILOP_ADDRESS_OF, result, m_children[0]->GenerateIL(state, func, block)); + result = func->CreateTempVariable(m_type); + block->AddInstruction(ILOP_DEREF, result, m_children[0]->GenerateIL(state, func, block)); } break; - case EXPR_DEREF: - result = func->CreateTempVariable(m_type); - block->AddInstruction(ILOP_DEREF, result, m_children[0]->GenerateIL(state, func, block)); - break; case EXPR_PRE_INCREMENT: - result = m_children[0]->GenerateIL(state, func, block); + if (m_children[0]->IsDirectVariableMemberAccess()) + { + result = m_children[0]->GenerateIL(state, func, block); + } + else + { + a = m_children[0]->GenerateAddressIL(state, func, block); + result = func->CreateTempVariable(m_type); + block->AddInstruction(ILOP_DEREF, result, a); + } if (m_children[0]->GetType()->GetClass() == TYPE_POINTER) { block->AddInstruction(ILOP_PTR_ADD, result, result, @@ -2245,16 +2319,20 @@ ILParameter Expr::GenerateIL(ParserState* state, Function* func, ILBlock*& block { block->AddInstruction(ILOP_ADD, result, result, ILParameter(result.type, (int64_t)1)); } - if (m_children[0]->GetClass() == EXPR_ARROW) - { - a = m_children[0]->m_children[0]->GenerateIL(state, func, block); - block->AddInstruction(ILOP_DEREF_MEMBER_ASSIGN, a, ILParameter( - m_children[0]->m_children[0]->GetType()->GetChildType()->GetStruct(), - m_children[0]->m_stringValue), result); - } + if (!m_children[0]->IsDirectVariableMemberAccess()) + block->AddInstruction(ILOP_DEREF_ASSIGN, a, result); break; case EXPR_PRE_DECREMENT: - result = m_children[0]->GenerateIL(state, func, block); + if (m_children[0]->IsDirectVariableMemberAccess()) + { + result = m_children[0]->GenerateIL(state, func, block); + } + else + { + a = m_children[0]->GenerateAddressIL(state, func, block); + result = func->CreateTempVariable(m_type); + block->AddInstruction(ILOP_DEREF, result, a); + } if (m_children[0]->GetType()->GetClass() == TYPE_POINTER) { block->AddInstruction(ILOP_PTR_SUB, result, result, @@ -2266,17 +2344,21 @@ ILParameter Expr::GenerateIL(ParserState* state, Function* func, ILBlock*& block { block->AddInstruction(ILOP_SUB, result, result, ILParameter(result.type, (int64_t)1)); } - if (m_children[0]->GetClass() == EXPR_ARROW) - { - a = m_children[0]->m_children[0]->GenerateIL(state, func, block); - block->AddInstruction(ILOP_DEREF_MEMBER_ASSIGN, a, ILParameter( - m_children[0]->m_children[0]->GetType()->GetChildType()->GetStruct(), - m_children[0]->m_stringValue), result); - } + if (!m_children[0]->IsDirectVariableMemberAccess()) + block->AddInstruction(ILOP_DEREF_ASSIGN, a, result); break; case EXPR_POST_INCREMENT: result = func->CreateTempVariable(m_type); - a = m_children[0]->GenerateIL(state, func, block); + if (m_children[0]->IsDirectVariableMemberAccess()) + { + a = m_children[0]->GenerateIL(state, func, block); + } + else + { + b = m_children[0]->GenerateAddressIL(state, func, block); + a = func->CreateTempVariable(m_type); + block->AddInstruction(ILOP_DEREF, a, b); + } block->AddInstruction(ILOP_ASSIGN, result, a); if (m_children[0]->GetType()->GetClass() == TYPE_POINTER) { @@ -2289,17 +2371,21 @@ ILParameter Expr::GenerateIL(ParserState* state, Function* func, ILBlock*& block { block->AddInstruction(ILOP_ADD, a, a, ILParameter(result.type, (int64_t)1)); } - if (m_children[0]->GetClass() == EXPR_ARROW) - { - b = m_children[0]->m_children[0]->GenerateIL(state, func, block); - block->AddInstruction(ILOP_DEREF_MEMBER_ASSIGN, b, ILParameter( - m_children[0]->m_children[0]->GetType()->GetChildType()->GetStruct(), - m_children[0]->m_stringValue), a); - } + if (!m_children[0]->IsDirectVariableMemberAccess()) + block->AddInstruction(ILOP_DEREF_ASSIGN, b, a); break; case EXPR_POST_DECREMENT: result = func->CreateTempVariable(m_type); - a = m_children[0]->GenerateIL(state, func, block); + if (m_children[0]->IsDirectVariableMemberAccess()) + { + a = m_children[0]->GenerateIL(state, func, block); + } + else + { + b = m_children[0]->GenerateAddressIL(state, func, block); + a = func->CreateTempVariable(m_type); + block->AddInstruction(ILOP_DEREF, a, b); + } block->AddInstruction(ILOP_ASSIGN, result, a); if (m_children[0]->GetType()->GetClass() == TYPE_POINTER) { @@ -2312,18 +2398,22 @@ ILParameter Expr::GenerateIL(ParserState* state, Function* func, ILBlock*& block { block->AddInstruction(ILOP_SUB, a, a, ILParameter(result.type, (int64_t)1)); } - if (m_children[0]->GetClass() == EXPR_ARROW) - { - b = m_children[0]->m_children[0]->GenerateIL(state, func, block); - block->AddInstruction(ILOP_DEREF_MEMBER_ASSIGN, b, ILParameter( - m_children[0]->m_children[0]->GetType()->GetChildType()->GetStruct(), - m_children[0]->m_stringValue), a); - } + if (!m_children[0]->IsDirectVariableMemberAccess()) + block->AddInstruction(ILOP_DEREF_ASSIGN, b, a); break; case EXPR_ARRAY_INDEX: + if (m_type->GetClass() == TYPE_ARRAY) + { + result = GenerateAddressIL(state, func, block); + break; + } result = func->CreateTempVariable(m_type); if ((m_children[0]->GetType()->GetClass() == TYPE_POINTER) || - (m_children[0]->GetClass() == EXPR_ARROW)) + (m_children[0]->GetClass() == EXPR_ARROW) || + (m_children[0]->GetClass() == EXPR_DEREF) || + (m_children[0]->GetClass() == EXPR_ARRAY_INDEX) || + ((m_children[0]->GetClass() == EXPR_DOT) && + (!m_children[0]->IsDirectVariableMemberAccess()))) { a = m_children[0]->GenerateIL(state, func, block); b = m_children[1]->GenerateIL(state, func, block); @@ -2754,10 +2844,22 @@ ILParameter Expr::GenerateIL(ParserState* state, Function* func, ILBlock*& block block = endBlock; break; case EXPR_ASSIGN: - if (m_children[0]->GetClass() == EXPR_ARRAY_INDEX) + if ((m_children[0]->GetClass() == EXPR_DOT) && + (!m_children[0]->IsDirectVariableMemberAccess())) + { + a = m_children[0]->GenerateAddressIL(state, func, block); + b = m_children[1]->GenerateIL(state, func, block); + block->AddInstruction(ILOP_DEREF_ASSIGN, a, b); + result = b; + } + else if (m_children[0]->GetClass() == EXPR_ARRAY_INDEX) { if ((m_children[0]->m_children[0]->GetType()->GetClass() == TYPE_POINTER) || - (m_children[0]->m_children[0]->GetClass() == EXPR_ARROW)) + (m_children[0]->m_children[0]->GetClass() == EXPR_ARROW) || + (m_children[0]->m_children[0]->GetClass() == EXPR_DEREF) || + (m_children[0]->m_children[0]->GetClass() == EXPR_ARRAY_INDEX) || + ((m_children[0]->m_children[0]->GetClass() == EXPR_DOT) && + (!m_children[0]->m_children[0]->IsDirectVariableMemberAccess()))) { if (m_children[0]->m_children[0]->GetType()->GetClass() == TYPE_ARRAY) result = func->CreateTempVariable(Type::PointerType(m_children[0]->m_children[0]->GetType()->GetChildType(), 1)); @@ -4071,4 +4173,3 @@ void Expr::Print(size_t indent) } } #endif - diff --git a/Expr.h b/Expr.h index 930b475..1b708b5 100644 --- a/Expr.h +++ b/Expr.h @@ -123,6 +123,8 @@ class Expr: public RefCountObject Ref m_type; bool DeserializeInternal(InputBlock* input); + bool IsDirectVariableMemberAccess() const; + ILParameter GenerateAddressIL(ParserState* state, Function* func, ILBlock*& block); public: Expr(ExprClass cls); diff --git a/Linker.cpp b/Linker.cpp index d427fb1..ea0a6c9 100644 --- a/Linker.cpp +++ b/Linker.cpp @@ -1041,14 +1041,18 @@ bool Linker::FinalizeLink() return false; } - // Find exit function - map< string, Ref >::iterator exitFuncRef = m_functionsByName.find("exit"); - if (exitFuncRef == m_functionsByName.end()) + // Find the exit function when the generated entry point will use it. + Ref exitFunc; + if ((!m_settings.allowReturn) && (!m_settings.concat)) { - fprintf(stderr, "error: function 'exit' is undefined\n"); - return false; + map< string, Ref >::iterator exitFuncRef = m_functionsByName.find("exit"); + if (exitFuncRef == m_functionsByName.end()) + { + fprintf(stderr, "error: function 'exit' is undefined\n"); + return false; + } + exitFunc = exitFuncRef->second; } - Ref exitFunc = exitFuncRef->second; // Create a function to resolve imports. This will be filled in later. FunctionInfo importFuncInfo; @@ -1077,7 +1081,7 @@ bool Linker::FinalizeLink() startInfo.callingConvention = mainFunc->GetCallingConvention(); startInfo.name = "_start"; startInfo.subarch = SUBARCH_DEFAULT; - startInfo.noReturn = !m_settings.allowReturn; + startInfo.noReturn = m_settings.concat || !m_settings.allowReturn; startInfo.imported = false; startInfo.location = mainFunc->GetLocation(); @@ -1128,7 +1132,6 @@ bool Linker::FinalizeLink() startBody->AddChild(m_initExpression); Ref mainExpr = Expr::FunctionExpr(mainFunc->GetLocation(), mainFunc); - Ref exitExpr = Expr::FunctionExpr(mainFunc->GetLocation(), exitFunc); // Generate call to main vector< Ref > params; @@ -1137,7 +1140,24 @@ bool Linker::FinalizeLink() Ref callExpr = Expr::CallExpr(mainFunc->GetLocation(), mainExpr, params); // Handle result of main - if (m_settings.allowReturn) + if (m_settings.concat) + { + // Ignore main's result so this path is independent of its return type, then + // transfer control to the byte immediately beyond the linked output. + startBody->AddChild(callExpr); + + map< string, Ref >::iterator endVar = m_variablesByName.find("__end"); + if (endVar == m_variablesByName.end()) + { + fprintf(stderr, "internal error: '__end' is undefined\n"); + return false; + } + + startBody->AddChild(Expr::UnaryExpr(mainFunc->GetLocation(), EXPR_COMPUTED_GOTO, + Expr::UnaryExpr(mainFunc->GetLocation(), EXPR_ADDRESS_OF, + Expr::VariableExpr(mainFunc->GetLocation(), endVar->second)))); + } + else if (m_settings.allowReturn) { if (mainFunc->GetReturnValue()->GetClass() == TYPE_VOID) startBody->AddChild(callExpr); @@ -1150,13 +1170,15 @@ bool Linker::FinalizeLink() vector< Ref > exitParams; exitParams.push_back(new Expr(mainFunc->GetLocation(), EXPR_UNDEFINED)); - startBody->AddChild(Expr::CallExpr(mainFunc->GetLocation(), exitExpr, exitParams)); + startBody->AddChild(Expr::CallExpr(mainFunc->GetLocation(), + Expr::FunctionExpr(mainFunc->GetLocation(), exitFunc), exitParams)); } else { vector< Ref > exitParams; exitParams.push_back(callExpr); - startBody->AddChild(Expr::CallExpr(mainFunc->GetLocation(), exitExpr, exitParams)); + startBody->AddChild(Expr::CallExpr(mainFunc->GetLocation(), + Expr::FunctionExpr(mainFunc->GetLocation(), exitFunc), exitParams)); } // Generate code for _start @@ -1641,6 +1663,42 @@ bool Linker::OutputCode(OutputBlock* finalBinary) } } + if (m_settings.concat && m_settings.pad && (m_settings.maxLength > addr) && (m_settings.alignment > 1)) + { + // Code ends on the requested architecture alignment, while the final + // padded output need not. Put the unaligned tail after all user data so + // __end can still describe any exact maximum length. + size_t tailSize = (size_t)((m_settings.maxLength - addr) % m_settings.alignment); + if (tailSize != 0) + { + vector available; + for (size_t i = 0; i < 256; i++) + { + bool ok = true; + for (vector::iterator j = m_settings.blacklist.begin(); + j != m_settings.blacklist.end(); j++) + { + if (i == *j) + { + ok = false; + break; + } + } + + if (ok) + available.push_back((uint8_t)i); + } + + for (size_t i = 0; i < tailSize; i++) + { + uint8_t choice = available[rand() % available.size()]; + *(uint8_t*)dataSection.PrepareWrite(1) = choice; + dataSection.FinishWrite(1); + } + addr += tailSize; + } + } + if (m_variablesByName.find("__end") != m_variablesByName.end()) m_variablesByName["__end"]->SetDataSectionOffset(addr); @@ -1734,32 +1792,77 @@ bool Linker::OutputCode(OutputBlock* finalBinary) // Padding is enabled, insert random code in between blocks to get the code closer to the target size while (true) { - size_t totalSize = dataSection.len; - for (vector::iterator i = codeBlocks.begin(); i != codeBlocks.end(); i++) - totalSize += (*i)->GetOutputBlock()->len; + size_t totalSize = (size_t)(m_settings.dataSectionBase - m_settings.base) + dataSection.len; ssize_t remaining = m_settings.maxLength - totalSize; if (remaining < 0) { - // Oops, added too many bytes in a previous loop, need to remove some of the random bytes + // Oops, layout expanded after padding was added. Remove only the + // excess random bytes, then lay everything out again so relocation + // sizes and __end converge on the requested output size. + bool removedPadding = false; for (vector::iterator i = codeBlocks.begin(); i != codeBlocks.end(); i++) { - if ((*i)->GetOutputBlock()->randomLen > 0) - { - remaining += (*i)->GetOutputBlock()->randomLen; - (*i)->GetOutputBlock()->len -= (*i)->GetOutputBlock()->randomLen; - (*i)->GetOutputBlock()->randomLen = 0; - } - - if (remaining >= 0) + OutputBlock* block = (*i)->GetOutputBlock(); + size_t excess = (size_t)-remaining; + size_t removeSize = (block->randomLen < excess) ? block->randomLen : excess; + if (removeSize == 0) + continue; + + block->len -= removeSize; + block->randomLen -= removeSize; + remaining += removeSize; + removedPadding = true; + if (remaining == 0) break; } - break; + + if (!removedPadding) + break; + if (!LayoutCode(codeBlocks)) + return false; + continue; } - // Don't try to add more bytes if there isn't much room left + // Usually leave the small remainder for the final output padding pass. A + // concatenation jump must include that remainder in code layout, however, + // so that __end resolves to the actual padded output boundary. if (remaining < 32) - break; + { + if ((!m_settings.concat) || (remaining == 0)) + break; + + vector available; + for (size_t i = 0; i < 256; i++) + { + bool ok = true; + for (vector::iterator j = m_settings.blacklist.begin(); + j != m_settings.blacklist.end(); j++) + { + if (i == *j) + { + ok = false; + break; + } + } + + if (ok) + available.push_back((uint8_t)i); + } + + OutputBlock* block = codeBlocks[rand() % codeBlocks.size()]->GetOutputBlock(); + for (ssize_t i = 0; i < remaining; i++) + { + uint8_t choice = available[rand() % available.size()]; + *(uint8_t*)block->PrepareWrite(1) = choice; + block->FinishWrite(1); + } + block->randomLen += remaining; + + if (!LayoutCode(codeBlocks)) + return false; + continue; + } remaining /= 2; while (remaining > 0) diff --git a/__init__.py b/__init__.py index 24a29e5..0b5fa41 100644 --- a/__init__.py +++ b/__init__.py @@ -71,7 +71,7 @@ def compile_source(source, platform="linux", arch="x86", blacklist=None, allow_r if anti_disasm_freq: cmd += ["--anti-disasm-freq", str(anti_disasm_freq)] if markov_chain: - cmd += ["--markov-chain", markov_chain] + cmd += ["--markov-chain-file", markov_chain] if additional_options: cmd += additional_options if defines: diff --git a/docs/examples.html b/docs/examples.html index 0f054e5..760b1f1 100644 --- a/docs/examples.html +++ b/docs/examples.html @@ -48,14 +48,12 @@

Shellcode Compiler Examples

Resolve and call Windows functions

-

SCC supports the ability to dynamically resolve and call windows functions for -you with the right syntax. The following simple example is a popup displaying -hello world using MessageBoxA.

+

SCC supports the ability to dynamically resolve and call Windows functions for +you. Common APIs such as MessageBoxA are declared by the Windows runtime, so +the following example can call them without repeating the import declaration.

-
int __stdcall MessageBoxA(HANDLE hwnd, const char* msg, const char* title, uint32_t flags) __import("user32");
-
-int main()
+
int main()
 {
     MessageBoxA(NULL, "Hello", "Hello World.", 0);
     return 0;
diff --git a/docs/examples.txt b/docs/examples.txt
index 69f6dc3..1818d94 100644
--- a/docs/examples.txt
+++ b/docs/examples.txt
@@ -7,13 +7,11 @@ common shellcoding tasks.
 
 Resolve and call Windows functions
 ----------------------------------
-SCC supports the ability to dynamically resolve and call windows functions for
-you with the right syntax. The following simple example is a popup displaying
-hello world using MessageBoxA.
+SCC supports the ability to dynamically resolve and call Windows functions for
+you. Common APIs such as `MessageBoxA` are declared by the Windows runtime, so
+the following example can call them without repeating the import declaration.
 
 ---------------------------------------
-int __stdcall MessageBoxA(HANDLE hwnd, const char* msg, const char* title, uint32_t flags) __import("user32");
-
 int main()
 {
     MessageBoxA(NULL, "Hello", "Hello World.", 0);
diff --git a/docs/issues.html b/docs/issues.html
index b76eb86..c560e53 100644
--- a/docs/issues.html
+++ b/docs/issues.html
@@ -93,12 +93,6 @@ 

Missing features

  • -Blacklisted code generation is not implemented. Use an external encoder if the shellcode must not -have byte values that are present in the output of the compiler. -

    -
  • -
  • -

    Polymorphic code generation is very limited. Register allocation is randomized and basic block ordering is present, but instruction sequences are constant between runs.

    @@ -111,7 +105,7 @@

    Missing features

  • -The --return-reg and --return-reg-high options are not implemented. +The --return-reg and --return-high-reg options are not implemented.

  • diff --git a/docs/issues.txt b/docs/issues.txt index 8fba245..9cff71f 100644 --- a/docs/issues.txt +++ b/docs/issues.txt @@ -17,13 +17,11 @@ Compiler bugs Missing features ---------------- * Structure packing is not supported. -* Blacklisted code generation is not implemented. Use an external encoder if the shellcode must not -have byte values that are present in the output of the compiler. * Polymorphic code generation is very limited. Register allocation is randomized and basic block ordering is present, but instruction sequences are constant between runs. * Built-in encoders and decoders (using the `--encoder` and `--decoder` command line options) are not implemented. -* The `--return-reg` and `--return-reg-high` options are not implemented. +* The `--return-reg` and `--return-high-reg` options are not implemented. * The `__initial_` variables are not implemented. Passing arguments to shellcode on the stack using parameters to `main` is supported. * The `--base` option is not implemented. By default, shellcode is automatically output using diff --git a/docs/python.html b/docs/python.html index b5f2a0b..7e57768 100644 --- a/docs/python.html +++ b/docs/python.html @@ -43,7 +43,7 @@

    Python bindings

    def compile_source(source, platform="linux", arch="x86", blacklist=None, allow_return=False, unsafe_stack=False,
    -        base=None, concat=False, encode_pointers=False, frame_reg=None, max_length=None,
    +        base=None, base_reg=None, concat=False, encode_pointers=False, frame_reg=None, max_length=None,
             optimization=NormalOptimization, pad=False, polymorph=False, preserve_regs=None, return_reg=None,
             return_high_reg=None, seed=None, stack_grows_up=False, stack_reg=None, include_dirs=None, align=None,
             anti_disasm=False, anti_disasm_freq=None, markov_chain=None, defines=None, additional_options=None)
    @@ -72,15 +72,16 @@

    Python bindings

    The source parameter contains the source code to compile. This code does not need to be present in a file on the file system, and can be dynamically constructed.

    The platform parameter contains the OS to compile for. This can be one of linux, freebsd, mac, windows, or none.

    -

    The arch parameter contains the architecture to compile for. This can be one of x86 or x64.

    +

    The arch parameter contains the architecture to compile for. This can be one of x86, x64, arm, armeb, +aarch64, mips, mipsel, ppc, ppcel, or quark.

    The blacklist parameter specifies the byte values that must not occur in the output. This should be a list of integers.

    If the allow_return parameter is True, the outputted code will issue a return instruction on completion instead of exiting the process.

    If the unsafe_stack parameter is True, the compiler will not assume that the stack is safe for use (for example, use of the stack may corrupt the code that is executing). When this is enabled, the compiler will adjust the stack pointer to ensure that it is in a safe location.

    -

    The base parameter specifies the base address of the start of the output. This can be a computed expression in terms -of register contents at the start of the program.

    +

    The base, base_reg, preserve_regs, return_reg, and return_high_reg parameters are retained for compatibility +with older callers, but are not implemented. Do not specify them.

    If the concat parameter is True, the compiler will jump to the end of the output on completion instead of exiting the process. This allows multiple pieces of code to be appended together to form a larger program.

    If the encode_pointers parameter is True, the compiler will encode all pointers to code using a key that is unique @@ -94,12 +95,6 @@

    Python bindings

    The padding is randomly chosen and will never include bytes in the blacklist list.

    If the polymorph parameter is True, the output will be randomly shuffled to produce different code on each run. The seed parameter can be specified to force a specific result from the randomization.

    -

    The preserve_regs parameter contains registers that should be preserved across execution of the code. This should be -a list of register names.

    -

    If the return_reg parameter is specified, the register that is used to hold the return value is forced to the specified -register.

    -

    If the return_high_reg parameter is specified, the register that is used to hold the high half of a large return value -is forced to the specified register.

    If the seed parameter is specified, the compiler will use the provided random seed for generating padding and polymorphic code.

    If the stack_grows_up parameter is True, the compiler will cause the stack to grow toward larger addresses.

    diff --git a/docs/python.txt b/docs/python.txt index 543a066..20f4576 100644 --- a/docs/python.txt +++ b/docs/python.txt @@ -8,7 +8,7 @@ is the definition of this function: --------------------------------------- def compile_source(source, platform="linux", arch="x86", blacklist=None, allow_return=False, unsafe_stack=False, - base=None, concat=False, encode_pointers=False, frame_reg=None, max_length=None, + base=None, base_reg=None, concat=False, encode_pointers=False, frame_reg=None, max_length=None, optimization=NormalOptimization, pad=False, polymorph=False, preserve_regs=None, return_reg=None, return_high_reg=None, seed=None, stack_grows_up=False, stack_reg=None, include_dirs=None, align=None, anti_disasm=False, anti_disasm_freq=None, markov_chain=None, defines=None, additional_options=None) @@ -29,7 +29,8 @@ the file system, and can be dynamically constructed. The `platform` parameter contains the OS to compile for. This can be one of `linux`, `freebsd`, `mac`, `windows`, or `none`. -The `arch` parameter contains the architecture to compile for. This can be one of `x86` or `x64`. +The `arch` parameter contains the architecture to compile for. This can be one of `x86`, `x64`, `arm`, `armeb`, +`aarch64`, `mips`, `mipsel`, `ppc`, `ppcel`, or `quark`. The `blacklist` parameter specifies the byte values that must not occur in the output. This should be a list of integers. @@ -40,8 +41,8 @@ If the `unsafe_stack` parameter is `True`, the compiler will not assume that the of the stack may corrupt the code that is executing). When this is enabled, the compiler will adjust the stack pointer to ensure that it is in a safe location. -The `base` parameter specifies the base address of the start of the output. This can be a computed expression in terms -of register contents at the start of the program. +The `base`, `base_reg`, `preserve_regs`, `return_reg`, and `return_high_reg` parameters are retained for compatibility +with older callers, but are not implemented. Do not specify them. If the `concat` parameter is `True`, the compiler will jump to the end of the output on completion instead of exiting the process. This allows multiple pieces of code to be appended together to form a larger program. @@ -63,15 +64,6 @@ The padding is randomly chosen and will never include bytes in the `blacklist` l If the `polymorph` parameter is `True`, the output will be randomly shuffled to produce different code on each run. The `seed` parameter can be specified to force a specific result from the randomization. -The `preserve_regs` parameter contains registers that should be preserved across execution of the code. This should be -a list of register names. - -If the `return_reg` parameter is specified, the register that is used to hold the return value is forced to the specified -register. - -If the `return_high_reg` parameter is specified, the register that is used to hold the high half of a large return value -is forced to the specified register. - If the `seed` parameter is specified, the compiler will use the provided random seed for generating padding and polymorphic code. @@ -95,4 +87,3 @@ The `defines` parameter, if specified, gives a map of preprocessor macro names t if the macro should not have a value. The `additional_options` parameter can hold a list of additional link:scc.html[command line arguments]. - diff --git a/docs/scc.html b/docs/scc.html index 79e47f0..8c26ae8 100644 --- a/docs/scc.html +++ b/docs/scc.html @@ -48,31 +48,33 @@

    Command line invocation

    Options: --arch <value> Specify processor architecture - Can be: x86 (default), x64 + Can be: x86, x64, arm, armeb, aarch64, + mips, mipsel, ppc, ppcel, quark + Default: x86 (--exec auto-selects on x86/x64 hosts) --align <boundary> Ensure output is aligned on the given boundary --allow-return Allow return from shellcode (default is to exit) --anti-disasm Generate anti-disassembly blocks --anti-disasm-freq <n> Emit anti-disassembly blocks every <n> instructions - --base <expr> Set base address of output (can be a runtime computed - expression, such as "[eax+8]-12") --blacklist <byte> Blacklist the given byte value --concat Jump to end of output on return for concatenating code -D <define>[=<value>] Define a preprocessor macro - --decoder <source> Use decoder to decode shellcode before executing --encode-pointers All code pointers are encoded with a random canary - --encoder <source> Use encoder to encode shellcode --exec Execute shellcode after generation (does not write output to a file) --exec-stack When outputting an executable, make stack executable --format <value>, -f <value> Specify output format Can be: bin (default), lib, elf, pe, macho --frame-reg <reg> Use alternate register as the frame pointer + --func <name> <address> Assume function is at a specific address + --funcptr <name> <address> Assume function is pointed to by a specific address + --gui For PE output, use GUI subsystem --header <file> Include a precompiled header -I <path> Add additional directory for include files -L <lib> Include pre-built library -m32, -m64 Specify target address size --map <file> Generate map file - --markov-chain <file> Use file for generating random instruction sequences + --markov-chain Generate random instruction sequences for padding + --markov-chain-file <file> Use file for generating random instruction sequences --max-length <value> Do not let output size exceed given number of bytes --mixed-mode Randomly choose subarchitecture for each function -o <filename> Set output filename (default is hex dump to stdout) @@ -83,11 +85,8 @@

    Command line invocation

    --platform <value> Specify operating system Can be: linux (default), freebsd, mac, windows, none --polymorph Generate different code on each run - --preserve <reg> Preserve the value of the given register + --unloaded-modules Uses modules that have not been loaded yet --unsafe-stack Stack pointer may be near the code - --return-reg <reg> Use alternate register as the return value - --return-high-reg <reg> Use alternate register as the upper 32 bits of return - value (32-bit output only) --seed <value> Specify random seed (to reproduce --polymorph runs) --shared Generate shared library instead of executable --stack-grows-up Stack grows toward larger addresses @@ -100,9 +99,7 @@

    Command line invocation

    Example: void exit(int value) __noreturn; __syscall(num, ...) Executes a system call on the target platform __undefined Gives undefined results, usually omitting code - Example: exit(__undefined); - __initial_<reg> Value of register at start of program - Example: int socketDescriptor = __initial_ebx;
  • + Example: exit(__undefined);
    diff --git a/docs/scc.txt b/docs/scc.txt index a809527..05fa71f 100644 --- a/docs/scc.txt +++ b/docs/scc.txt @@ -13,31 +13,33 @@ are automatically available without the need for include files. Options: --arch Specify processor architecture - Can be: x86 (default), x64 + Can be: x86, x64, arm, armeb, aarch64, + mips, mipsel, ppc, ppcel, quark + Default: x86 (--exec auto-selects on x86/x64 hosts) --align Ensure output is aligned on the given boundary --allow-return Allow return from shellcode (default is to exit) --anti-disasm Generate anti-disassembly blocks --anti-disasm-freq Emit anti-disassembly blocks every instructions - --base Set base address of output (can be a runtime computed - expression, such as "[eax+8]-12") --blacklist Blacklist the given byte value --concat Jump to end of output on return for concatenating code -D [=] Define a preprocessor macro - --decoder Use decoder to decode shellcode before executing --encode-pointers All code pointers are encoded with a random canary - --encoder Use encoder to encode shellcode --exec Execute shellcode after generation (does not write output to a file) --exec-stack When outputting an executable, make stack executable --format , -f Specify output format Can be: bin (default), lib, elf, pe, macho --frame-reg Use alternate register as the frame pointer + --func
    Assume function is at a specific address + --funcptr
    Assume function is pointed to by a specific address + --gui For PE output, use GUI subsystem --header Include a precompiled header -I Add additional directory for include files -L Include pre-built library -m32, -m64 Specify target address size --map Generate map file - --markov-chain Use file for generating random instruction sequences + --markov-chain Generate random instruction sequences for padding + --markov-chain-file Use file for generating random instruction sequences --max-length Do not let output size exceed given number of bytes --mixed-mode Randomly choose subarchitecture for each function -o Set output filename (default is hex dump to stdout) @@ -48,11 +50,8 @@ Options: --platform Specify operating system Can be: linux (default), freebsd, mac, windows, none --polymorph Generate different code on each run - --preserve Preserve the value of the given register + --unloaded-modules Uses modules that have not been loaded yet --unsafe-stack Stack pointer may be near the code - --return-reg Use alternate register as the return value - --return-high-reg Use alternate register as the upper 32 bits of return - value (32-bit output only) --seed Specify random seed (to reproduce --polymorph runs) --shared Generate shared library instead of executable --stack-grows-up Stack grows toward larger addresses @@ -66,7 +65,5 @@ Useful extensions: __syscall(num, ...) Executes a system call on the target platform __undefined Gives undefined results, usually omitting code Example: exit(__undefined); - __initial_ Value of register at start of program - Example: int socketDescriptor = __initial_ebx; --------------------------------------- diff --git a/runtime/linux/ppc/net.c b/runtime/linux/ppc/net.c index 0db4bab..7edf980 100644 --- a/runtime/linux/ppc/net.c +++ b/runtime/linux/ppc/net.c @@ -20,81 +20,156 @@ int socket(int domain, int type, int protocol) { - return __syscall(SYS_socket, domain, type, protocol); + size_t args[3]; + args[0] = domain; + args[1] = type; + args[2] = protocol; + return __syscall(SYS_socketcall, SYS_SOCKET, args); } int socketpair(int domain, int type, int protocol, int* fds) { - return __syscall(SYS_socketpair, domain, type, protocol, fds); + size_t args[4]; + args[0] = domain; + args[1] = type; + args[2] = protocol; + args[3] = (size_t)fds; + return __syscall(SYS_socketcall, SYS_SOCKETPAIR, args); } int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen) { - return __syscall(SYS_bind, sockfd, addr, addrlen); + size_t args[3]; + args[0] = sockfd; + args[1] = (size_t)addr; + args[2] = addrlen; + return __syscall(SYS_socketcall, SYS_BIND, args); } int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen) { - return __syscall(SYS_accept, sockfd, addr, addrlen); + size_t args[3]; + args[0] = sockfd; + args[1] = (size_t)addr; + args[2] = (size_t)addrlen; + return __syscall(SYS_socketcall, SYS_ACCEPT, args); } int accept4(int sockfd, struct sockaddr* addr, socklen_t* addrlen, int flags) { - return __syscall(SYS_accept4, sockfd, addr, addrlen, flags); + size_t args[4]; + args[0] = sockfd; + args[1] = (size_t)addr; + args[2] = (size_t)addrlen; + args[3] = flags; + return __syscall(SYS_socketcall, SYS_ACCEPT4, args); } int listen(int sockfd, int backlog) { - return __syscall(SYS_listen, sockfd, backlog); + size_t args[2]; + args[0] = sockfd; + args[1] = backlog; + return __syscall(SYS_socketcall, SYS_LISTEN, args); } int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen) { - return __syscall(SYS_connect, sockfd, addr, addrlen); + size_t args[3]; + args[0] = sockfd; + args[1] = (size_t)addr; + args[2] = addrlen; + return __syscall(SYS_socketcall, SYS_CONNECT, args); } int getsockname(int sockfd, struct sockaddr* addr, socklen_t* addrlen) { - return __syscall(SYS_getsockname, sockfd, addr, addrlen); + size_t args[3]; + args[0] = sockfd; + args[1] = (size_t)addr; + args[2] = (size_t)addrlen; + return __syscall(SYS_socketcall, SYS_GETSOCKNAME, args); } int getpeername(int sockfd, struct sockaddr* addr, socklen_t* addrlen) { - return __syscall(SYS_getpeername, sockfd, addr, addrlen); + size_t args[3]; + args[0] = sockfd; + args[1] = (size_t)addr; + args[2] = (size_t)addrlen; + return __syscall(SYS_socketcall, SYS_GETPEERNAME, args); } int shutdown(int sockfd, int how) { - return __syscall(SYS_shutdown, sockfd, how); + size_t args[2]; + args[0] = sockfd; + args[1] = how; + return __syscall(SYS_socketcall, SYS_SHUTDOWN, args); } ssize_t send(int fd, const void* buf, size_t n, int flags) { - return __syscall(SYS_sendto, fd, buf, n, flags, NULL, 0); + size_t args[4]; + args[0] = fd; + args[1] = (size_t)buf; + args[2] = n; + args[3] = flags; + return __syscall(SYS_socketcall, SYS_SEND, args); } ssize_t recv(int fd, void* buf, size_t n, int flags) { - return __syscall(SYS_recvfrom, fd, buf, n, flags, NULL, NULL); + size_t args[4]; + args[0] = fd; + args[1] = (size_t)buf; + args[2] = n; + args[3] = flags; + return __syscall(SYS_socketcall, SYS_RECV, args); } ssize_t sendto(int fd, const void* buf, size_t n, int flags, const struct sockaddr* addr, socklen_t addrlen) { - return __syscall(SYS_sendto, fd, buf, n, flags, addr, addrlen); + size_t args[6]; + args[0] = fd; + args[1] = (size_t)buf; + args[2] = n; + args[3] = flags; + args[4] = (size_t)addr; + args[5] = addrlen; + return __syscall(SYS_socketcall, SYS_SENDTO, args); } ssize_t recvfrom(int fd, void* buf, size_t n, int flags, struct sockaddr* addr, socklen_t* addrlen) { - return __syscall(SYS_recvfrom, fd, buf, n, flags, addr, addrlen); + size_t args[6]; + args[0] = fd; + args[1] = (size_t)buf; + args[2] = n; + args[3] = flags; + args[4] = (size_t)addr; + args[5] = (size_t)addrlen; + return __syscall(SYS_socketcall, SYS_RECVFROM, args); } int getsockopt(int fd, int level, int optname, void* optval, socklen_t* optlen) { - return __syscall(SYS_getsockopt, fd, level, optname, optval, optlen); + size_t args[5]; + args[0] = fd; + args[1] = level; + args[2] = optname; + args[3] = (size_t)optval; + args[4] = (size_t)optlen; + return __syscall(SYS_socketcall, SYS_GETSOCKOPT, args); } int setsockopt(int fd, int level, int optname, const void* optval, socklen_t optlen) { - return __syscall(SYS_setsockopt, fd, level, optname, optval, optlen); + size_t args[5]; + args[0] = fd; + args[1] = level; + args[2] = optname; + args[3] = (size_t)optval; + args[4] = optlen; + return __syscall(SYS_socketcall, SYS_SETSOCKOPT, args); } - diff --git a/runtime/linux/ppc/syscall.h b/runtime/linux/ppc/syscall.h index 7df2954..3faac0a 100644 --- a/runtime/linux/ppc/syscall.h +++ b/runtime/linux/ppc/syscall.h @@ -376,5 +376,23 @@ #define SYS_finit_module 353 #define SYS_kcmp 354 -#endif +// Operation numbers for the legacy socketcall multiplexer. These are distinct +// from the direct socket syscall numbers above. +#define SYS_SOCKET 1 +#define SYS_BIND 2 +#define SYS_CONNECT 3 +#define SYS_LISTEN 4 +#define SYS_ACCEPT 5 +#define SYS_GETSOCKNAME 6 +#define SYS_GETPEERNAME 7 +#define SYS_SOCKETPAIR 8 +#define SYS_SEND 9 +#define SYS_RECV 10 +#define SYS_SENDTO 11 +#define SYS_RECVFROM 12 +#define SYS_SHUTDOWN 13 +#define SYS_SETSOCKOPT 14 +#define SYS_GETSOCKOPT 15 +#define SYS_ACCEPT4 18 +#endif diff --git a/scc.cpp b/scc.cpp index 6d13ecf..d70d634 100644 --- a/scc.cpp +++ b/scc.cpp @@ -24,6 +24,25 @@ const char* g_versionString = "git"; #endif +static bool SelectNativeArchitecture(Settings& settings) +{ +#if defined(__x86_64__) || defined(__x86_64) || defined(__amd64__) || defined(_M_X64) || defined(_M_AMD64) + settings.architecture = ARCH_X86; + settings.preferredBits = 64; + settings.bigEndian = false; + return true; +#elif defined(__i386__) || defined(_M_IX86) + settings.architecture = ARCH_X86; + settings.preferredBits = 32; + settings.bigEndian = false; + return true; +#else + (void)settings; + return false; +#endif +} + + void Usage() { fprintf(stderr, "scc [options] [...]\n\n"); @@ -35,21 +54,17 @@ void Usage() fprintf(stderr, "are automatically available without the need for include files.\n\n"); fprintf(stderr, "Options:\n"); fprintf(stderr, " --arch Specify processor architecture\n"); - fprintf(stderr, " Can be: x86 (default), x64, arm, armeb, aarch64,\n"); - fprintf(stderr, " mips, mipsel, ppc, ppcel\n"); + fprintf(stderr, " Can be: x86, x64, arm, armeb, aarch64,\n"); + fprintf(stderr, " mips, mipsel, ppc, ppcel, quark\n"); + fprintf(stderr, " Default: x86 (--exec auto-selects on x86/x64 hosts)\n"); fprintf(stderr, " --align Ensure output is aligned on the given boundary\n"); fprintf(stderr, " --allow-return Allow return from shellcode (default is to exit)\n"); fprintf(stderr, " --anti-disasm Generate anti-disassembly blocks\n"); fprintf(stderr, " --anti-disasm-freq Emit anti-disassembly blocks every instructions\n"); - fprintf(stderr, " --base Set base address of output (can be a runtime computed\n"); - fprintf(stderr, " expression, such as \"[eax+8]-12\")\n"); - fprintf(stderr, " --base-reg Global register that will hold base of code\n"); fprintf(stderr, " --blacklist Blacklist the given byte value\n"); fprintf(stderr, " --concat Jump to end of output on return for concatenating code\n"); fprintf(stderr, " -D [=] Define a preprocessor macro\n"); - fprintf(stderr, " --decoder Use decoder to decode shellcode before executing\n"); fprintf(stderr, " --encode-pointers All code pointers are encoded with a random canary\n"); - fprintf(stderr, " --encoder Use encoder to encode shellcode\n"); fprintf(stderr, " --exec Execute shellcode after generation (does not write\n"); fprintf(stderr, " output to a file)\n"); fprintf(stderr, " --exec-stack When outputting an executable, make stack executable\n"); @@ -76,12 +91,8 @@ void Usage() fprintf(stderr, " --platform Specify operating system\n"); fprintf(stderr, " Can be: linux (default), freebsd, mac, windows, none\n"); fprintf(stderr, " --polymorph Generate different code on each run\n"); - fprintf(stderr, " --preserve Preserve the value of the given register\n"); fprintf(stderr, " --unloaded-modules Uses modules that have not been loaded yet\n"); fprintf(stderr, " --unsafe-stack Stack pointer may be near the code\n"); - fprintf(stderr, " --return-reg Use alternate register as the return value\n"); - fprintf(stderr, " --return-high-reg Use alternate register as the upper 32 bits of return\n"); - fprintf(stderr, " value (32-bit output only)\n"); fprintf(stderr, " --seed Specify random seed (to reproduce --polymorph runs)\n"); fprintf(stderr, " --shared Generate shared library instead of executable\n"); fprintf(stderr, " --stack-grows-up Stack grows toward larger addresses\n"); @@ -93,9 +104,7 @@ void Usage() fprintf(stderr, " Example: void exit(int value) __noreturn;\n"); fprintf(stderr, " __syscall(num, ...) Executes a system call on the target platform\n"); fprintf(stderr, " __undefined Gives undefined results, usually omitting code\n"); - fprintf(stderr, " Example: exit(__undefined);\n"); - fprintf(stderr, " __initial_ Value of register at start of program\n"); - fprintf(stderr, " Example: int socketDescriptor = __initial_ebx;\n\n"); + fprintf(stderr, " Example: exit(__undefined);\n\n"); } @@ -108,9 +117,7 @@ int main(int argc, char* argv[]) string outputFile = ""; string mapFile = ""; bool hexOutput = true; -#ifdef __x86_64 bool architectureIsExplicit = false; -#endif bool osIsExplicit = false; string decoder, encoder; bool execute = false; @@ -222,27 +229,22 @@ int main(int argc, char* argv[]) else { fprintf(stderr, "error: unsupported architecture '%s'\n", argv[i]); + return 1; } -#ifdef __x86_64 architectureIsExplicit = true; -#endif continue; } else if (!strcmp(argv[i], "-m32")) { settings.preferredBits = 32; -#ifdef __x86_64 architectureIsExplicit = true; -#endif continue; } else if (!strcmp(argv[i], "-m64")) { settings.preferredBits = 64; -#ifdef __x86_64 architectureIsExplicit = true; -#endif continue; } else if (!strcmp(argv[i], "--align")) @@ -350,11 +352,6 @@ int main(int argc, char* argv[]) } else if (!strcmp(argv[i], "--exec")) { -#ifdef __x86_64 - if (!architectureIsExplicit) - settings.preferredBits = 64; -#endif - if (!osIsExplicit) { // Use current OS @@ -734,6 +731,23 @@ int main(int argc, char* argv[]) return 1; } + if ((!architectureIsExplicit) && execute) + { + if (!SelectNativeArchitecture(settings)) + { + fprintf(stderr, "error: unable to select a native x86/x64 target for --exec; " + "use --arch to specify a target\n"); + return 1; + } + fprintf(stderr, "warning: no target architecture specified; using native %s for --exec\n", + (settings.preferredBits == 64) ? "x64" : "x86"); + } + else if (!architectureIsExplicit) + { + fprintf(stderr, "warning: no target architecture specified; defaulting to 32-bit x86 " + "(use --arch, -m32, or -m64 to specify a target)\n"); + } + // Initialize random seed if one is needed if (settings.polymorph || settings.mixedMode || settings.antiDisasm || settings.pad) { diff --git a/tests/issue_563.c b/tests/issue_563.c new file mode 100644 index 0000000..161abc5 --- /dev/null +++ b/tests/issue_563.c @@ -0,0 +1,317 @@ +// Copyright 2026 Vector 35 Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +struct a +{ + union + { + int b; + int c; + } d; +}; + +int read_nested_union_member(struct a* e) +{ + return e->d.b; +} + +void write_nested_union_member(struct a* e, int value) +{ + e->d.b = value; +} + +int* address_of_nested_union_member(struct a* e) +{ + return &e->d.b; +} + +struct inner +{ + int padding; + int value; +}; + +struct outer +{ + int padding; + struct inner inner; +}; + +struct root +{ + int padding; + struct outer outer; +}; + +struct array_inner +{ + int padding; + int values[3]; +}; + +struct array_outer +{ + int padding; + struct array_inner inner; +}; + +struct pointer_inner +{ + int* value; +}; + +struct pointer_outer +{ + int padding; + struct pointer_inner inner; +}; + +typedef int matrix_row[3]; + +int read_deeply_nested_member(struct root* value) +{ + return value->outer.inner.value; +} + +int read_nested_member_through_dereference(struct root* value) +{ + return (*value).outer.inner.value; +} + +int read_nested_member_from_array(struct root* values) +{ + return values[1].outer.inner.value; +} + +int read_nested_array_member(struct array_outer* value, int index) +{ + return value->inner.values[index]; +} + +void write_nested_array_member(struct array_outer* value, int index, int item) +{ + value->inner.values[index] = item; +} + +int* address_of_nested_array_member(struct array_outer* value, int index) +{ + return &value->inner.values[index]; +} + +int pre_increment_nested_member(struct root* value) +{ + return ++value->outer.inner.value; +} + +int post_increment_nested_member(struct root* value) +{ + return value->outer.inner.value++; +} + +int pre_decrement_nested_member(struct root* value) +{ + return --value->outer.inner.value; +} + +int post_decrement_nested_member(struct root* value) +{ + return value->outer.inner.value--; +} + +struct root* count_root_access(struct root* value, int* count) +{ + (*count)++; + return value; +} + +int post_increment_nested_member_once(struct root* value, int* count) +{ + return count_root_access(value, count)->outer.inner.value++; +} + +int* pre_increment_nested_pointer(struct pointer_outer* value) +{ + return ++value->inner.value; +} + +int* post_decrement_nested_pointer(struct pointer_outer* value) +{ + return value->inner.value--; +} + +int read_matrix_member(matrix_row* matrix, int row, int column) +{ + return matrix[row][column]; +} + +void write_matrix_member(matrix_row* matrix, int row, int column, int value) +{ + matrix[row][column] = value; +} + +int* address_of_matrix_member(matrix_row* matrix, int row, int column) +{ + return &matrix[row][column]; +} + +int read_first_matrix_row(matrix_row* matrix, int column) +{ + return (*matrix)[column]; +} + +void write_first_matrix_row(matrix_row* matrix, int column, int value) +{ + (*matrix)[column] = value; +} + +int main() +{ + struct a e; + struct array_outer array_value; + struct pointer_outer pointer_value; + struct root direct_root; + struct root roots[2]; + int* address; + int count; + int index; + matrix_row matrix[2]; + int pointer_values[3]; + + e.d.b = 0x12345678; + if (read_nested_union_member(&e) != 0x12345678) + return 1; + + write_nested_union_member(&e, 0x76543210); + if (e.d.b != 0x76543210) + return 2; + + address = address_of_nested_union_member(&e); + *address = 0x13572468; + if (e.d.b != 0x13572468) + return 3; + + roots[0].outer.inner.value = 0x24681357; + if (read_deeply_nested_member(&roots[0]) != 0x24681357) + return 4; + if (read_nested_member_through_dereference(&roots[0]) != 0x24681357) + return 5; + + roots[1].outer.inner.value = 0x10293847; + if (read_nested_member_from_array(roots) != 0x10293847) + return 6; + + array_value.inner.values[1] = 0x11223344; + if (read_nested_array_member(&array_value, 1) != 0x11223344) + return 7; + + write_nested_array_member(&array_value, 2, 0x55667788); + if (array_value.inner.values[2] != 0x55667788) + return 8; + + address = address_of_nested_array_member(&array_value, 0); + *address = 0x12344321; + if (array_value.inner.values[0] != 0x12344321) + return 9; + + roots[0].outer.inner.value = 10; + if (pre_increment_nested_member(&roots[0]) != 11) + return 10; + if (post_increment_nested_member(&roots[0]) != 11) + return 11; + if (roots[0].outer.inner.value != 12) + return 12; + if (pre_decrement_nested_member(&roots[0]) != 11) + return 13; + if (post_decrement_nested_member(&roots[0]) != 11) + return 14; + if (roots[0].outer.inner.value != 10) + return 15; + + direct_root.outer.inner.value = 20; + if (++direct_root.outer.inner.value != 21) + return 16; + if (direct_root.outer.inner.value-- != 21) + return 17; + if (direct_root.outer.inner.value != 20) + return 18; + + array_value.inner.values[0] = 30; + if (++array_value.inner.values[0] != 31) + return 19; + if (array_value.inner.values[0]-- != 31) + return 20; + if (array_value.inner.values[0] != 30) + return 21; + + address = &array_value.inner.values[0]; + if (++*address != 31) + return 22; + if ((*address)-- != 31) + return 23; + if (*address != 30) + return 24; + if (&*address != address) + return 25; + + count = 0; + roots[0].outer.inner.value = 40; + if (post_increment_nested_member_once(&roots[0], &count) != 40) + return 26; + if (roots[0].outer.inner.value != 41) + return 27; + if (count != 1) + return 28; + + index = 0; + if (array_value.inner.values[index++]++ != 30) + return 29; + if (index != 1) + return 30; + if (array_value.inner.values[0] != 31) + return 31; + + pointer_value.inner.value = &pointer_values[0]; + if (pre_increment_nested_pointer(&pointer_value) != &pointer_values[1]) + return 32; + if (post_decrement_nested_pointer(&pointer_value) != &pointer_values[1]) + return 33; + if (pointer_value.inner.value != &pointer_values[0]) + return 34; + + write_matrix_member(matrix, 1, 2, 17); + if (read_matrix_member(matrix, 1, 2) != 17) + return 35; + if (matrix[1][2] != 17) + return 36; + address = address_of_matrix_member(matrix, 1, 0); + *address = 19; + if (matrix[1][0] != 19) + return 37; + matrix[0][1] = 23; + if (read_first_matrix_row(matrix, 1) != 23) + return 38; + if (&(*matrix)[1] != &matrix[0][1]) + return 39; + write_first_matrix_row(matrix, 2, 29); + if (matrix[0][2] != 29) + return 40; + + return 0; +} diff --git a/tests/test.py b/tests/test.py index 67d090f..b338ce6 100644 --- a/tests/test.py +++ b/tests/test.py @@ -31,6 +31,7 @@ div64_testcase = {"source": "tests/div64.c", "inputfile": None, "outputfile": "tests/div64_output"} mul64_testcase = {"source": "tests/mul64.c", "inputfile": None, "outputfile": "tests/mul64_output"} fortress_testcase = {"source": "tests/fortress.c", "inputfile": "tests/fortress_input", "outputfile": "tests/fortress_output"} +issue_563_testcase = {"source": "tests/issue_563.c", "inputfile": None, "outputfile": None} shellcode_mmap_testcase = {"source": "tests/shellcode.c", "inputfile": None, "outputfile": "tests/shellcode_output", "target": "tests/sploit_mmap.c"} shellcode_stack_testcase = {"source": "tests/shellcode.c", "inputfile": None, "outputfile": "tests/shellcode_output", "target": "tests/sploit_stack.c", "targetoptions": ["-O0", "--exec-stack"]} @@ -47,6 +48,7 @@ ["crc32.c, polymorphic", crc32_testcase, ["--polymorph", "--seed", ""]], ["div64.c, normal", div64_testcase, []], ["mul64.c, normal", mul64_testcase, []], + ["issue #563, recursive aggregate lvalues", issue_563_testcase, []], ["shellcode, mmap buffer", shellcode_mmap_testcase, []], ["shellcode, stack buffer", shellcode_stack_testcase, ["--unsafe-stack"]], ["fortress.c, normal", fortress_testcase, []], @@ -255,4 +257,3 @@ def test_all(arch_name, arch_options, arch_type, native): sys.stdout.write("\033[01;32mAll tests passed\033[00m\n") sys.exit(0) - diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..416cff6 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 + +# Copyright 2026 Vector 35 Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +import importlib.util +import pathlib +import subprocess +import sys +import tempfile +import unittest +from unittest import mock + + +SCC = None +SOURCE_ROOT = pathlib.Path(__file__).resolve().parents[1] +DEFAULT_ARCH_WARNING = ( + "warning: no target architecture specified; defaulting to 32-bit x86 " + "(use --arch, -m32, or -m64 to specify a target)" +) + + +class CliTests(unittest.TestCase): + def setUp(self): + self.temp_directory = tempfile.TemporaryDirectory(prefix="scc-cli-") + self.temp_path = pathlib.Path(self.temp_directory.name) + self.output_index = 0 + + def tearDown(self): + self.temp_directory.cleanup() + + def run_scc(self, *options, source=None): + command = [str(SCC)] + list(options) + return subprocess.run(command, input=source, text=True, capture_output=True) + + def compile(self, source="void main(void) {}\n", *options, with_map=False): + self.output_index += 1 + stem = "output-%d" % self.output_index + output = self.temp_path / (stem + ".bin") + command = ["--stdin", "--format", "bin", "-o", str(output)] + list(options) + map_file = None + if with_map: + map_file = self.temp_path / (stem + ".map") + command += ["--map", str(map_file)] + + result = self.run_scc(*command, source=source) + self.assertEqual( + result.returncode, + 0, + "SCC command failed:\n%s\n%s" % (" ".join([str(SCC)] + command), result.stderr + result.stdout), + ) + self.assertTrue(output.is_file(), "SCC did not create the requested output") + + addresses = {} + if map_file is not None: + for line in map_file.read_text().splitlines(): + address, name = line.split(None, 1) + addresses[name] = int(address, 16) + + return output.read_bytes(), addresses, result + + def test_help_omits_unavailable_features(self): + result = self.run_scc("--help") + self.assertEqual(result.returncode, 0) + help_text = result.stderr + for unavailable in ( + "__initial_", "--base ", "--base-reg", "--decoder", "--encoder", + "--preserve", "--return-reg", "--return-high-reg", + ): + with self.subTest(unavailable=unavailable): + self.assertNotIn(unavailable, help_text) + + for available in ( + "--blacklist", "--concat", "quark", "Default: x86 (--exec auto-selects on x86/x64 hosts)", + ): + with self.subTest(available=available): + self.assertIn(available, help_text) + + def test_documentation_matches_available_features(self): + for filename in ("scc.txt", "scc.html"): + document = (SOURCE_ROOT / "docs" / filename).read_text() + for unavailable in ( + "__initial_", "--base ", "--base-reg", "--decoder", "--encoder", + "--preserve", "--return-reg", "--return-high-reg", + ): + with self.subTest(filename=filename, unavailable=unavailable): + self.assertNotIn(unavailable, document) + + for filename in ("issues.txt", "issues.html"): + document = (SOURCE_ROOT / "docs" / filename).read_text() + with self.subTest(filename=filename): + self.assertNotIn("Blacklisted code generation is not implemented", document) + + for filename in ("python.txt", "python.html"): + document = (SOURCE_ROOT / "docs" / filename).read_text() + with self.subTest(filename=filename): + self.assertIn("retained for compatibility", document) + self.assertIn("but are not implemented", document) + + def test_blacklist_is_enforced(self): + code, _, _ = self.compile( + "void main(void) {}\n", "--arch", "x86", "--platform", "linux", "--blacklist", "0", + ) + self.assertNotIn(0, code) + + def test_python_wrapper_uses_markov_chain_file_option(self): + spec = importlib.util.spec_from_file_location("scc_wrapper_under_test", SOURCE_ROOT / "__init__.py") + wrapper = importlib.util.module_from_spec(spec) + spec.loader.exec_module(wrapper) + + process = mock.Mock() + process.returncode = 0 + process.communicate.return_value = (b"compiled", b"") + with mock.patch.object(wrapper.subprocess, "Popen", return_value=process) as popen: + result, error = wrapper.compile_source(b"void main(void) {}\n", markov_chain="instructions.bin") + + self.assertEqual(result, b"compiled") + self.assertEqual(error, b"") + command = popen.call_args[0][0] + self.assertNotIn("--markov-chain", command) + option = command.index("--markov-chain-file") + self.assertEqual(command[option + 1], "instructions.bin") + + def test_implicit_architecture_warns_and_uses_x86(self): + implicit, _, implicit_result = self.compile( + "void main(void) {}\n", "--platform", "none", "--allow-return", + ) + explicit, _, explicit_result = self.compile( + "void main(void) {}\n", "--arch", "x86", "--platform", "none", "--allow-return", + ) + + self.assertIn(DEFAULT_ARCH_WARNING, implicit_result.stderr) + self.assertNotIn(DEFAULT_ARCH_WARNING, explicit_result.stderr) + self.assertEqual(implicit, explicit) + + def test_invalid_architecture_stops_before_compilation(self): + output = self.temp_path / "invalid-architecture.bin" + result = self.run_scc( + "--stdin", "--arch", "not-a-real-architecture", "--format", "bin", "-o", str(output), + source="void main(void) {}\n", + ) + self.assertNotEqual(result.returncode, 0) + self.assertIn("error: unsupported architecture 'not-a-real-architecture'", result.stderr) + self.assertFalse(output.exists()) + + def test_architecture_shortcuts_are_explicit(self): + for option in ("-m32", "-m64"): + with self.subTest(option=option): + _, _, result = self.compile( + "void main(void) {}\n", option, "--platform", "none", "--allow-return", + ) + self.assertNotIn(DEFAULT_ARCH_WARNING, result.stderr) + + def test_exec_uses_native_architecture_without_executing(self): + common = ("--stdin", "--platform", "none", "--allow-return", "--max-length", "1") + implicit = self.run_scc(*common, "--exec", source="void main(void) {}\n") + self.assertNotEqual(implicit.returncode, 0) + + for bits in ("-m32", "-m64"): + with self.subTest(bits=bits): + exec_first = self.run_scc( + *common, "--exec", bits, source="void main(void) {}\n", + ) + bits_first = self.run_scc( + *common, bits, "--exec", source="void main(void) {}\n", + ) + self.assertEqual(exec_first.returncode, bits_first.returncode) + self.assertEqual(exec_first.stderr, bits_first.stderr) + + if "unable to select a native x86/x64 target for --exec" in implicit.stderr: + self.assertNotIn("Output is ", implicit.stderr) + return + + self.assertTrue( + ("using native x86 for --exec" in implicit.stderr) or + ("using native x64 for --exec" in implicit.stderr), + "implicit --exec target selection was silent", + ) + self.assertIn("error: unable to satisfy size constraint", implicit.stderr) + implicit_size = next( + int(line.split()[2]) for line in implicit.stderr.splitlines() if line.startswith("Output is ") + ) + explicit_sizes = [] + for architecture in ("x86", "x64"): + explicit = self.run_scc( + *common, "--arch", architecture, "--exec", source="void main(void) {}\n", + ) + self.assertIn("error: unable to satisfy size constraint", explicit.stderr) + explicit_sizes.append(next( + int(line.split()[2]) for line in explicit.stderr.splitlines() if line.startswith("Output is ") + )) + self.assertIn(implicit_size, explicit_sizes, "implicit --exec size matched neither x86 nor x64") + + def test_concat_jumps_to_output_end_instead_of_exiting(self): + for platform in ("linux", "none"): + with self.subTest(platform=platform): + code, addresses, _ = self.compile( + "int main(void) { return 0; }\n", "--arch", "x86", "--platform", platform, "--concat", + with_map=True, + ) + + self.assertEqual(addresses["__end"], len(code)) + self.assertNotIn(b"\xcd\x80", code, "concatenation path still contains Linux syscall(exit)") + self.assertGreaterEqual(len(code), 2) + self.assertEqual(code[-2], 0xff) + self.assertEqual(code[-1] & 0xf8, 0xe0, "output does not end in an indirect x86 jump") + + def test_concat_follows_branching_global_initializers(self): + source = ( + "int condition;\n" + "int initialized = condition ? 1 : 2;\n" + "int main(void) { return initialized; }\n" + ) + code, addresses, _ = self.compile( + source, "--arch", "x86", "--platform", "none", "--concat", "-O0", with_map=True, + ) + self.assertEqual(addresses["__end"], len(code)) + + def test_concat_padding_keeps_end_at_true_output_boundary(self): + for architecture, max_length, seed in (("x86", 100, 1), ("x86", 129, 7), ("arm", 129, 7)): + with self.subTest(architecture=architecture, max_length=max_length, seed=seed): + code, addresses, _ = self.compile( + "int main(void) { return 0; }\n", "--arch", architecture, "--platform", "none", "--concat", + "--pad", "--max-length", str(max_length), "--seed", str(seed), with_map=True, + ) + self.assertEqual(len(code), max_length) + self.assertEqual(addresses["__end"], len(code)) + + +def main(): + global SCC + if len(sys.argv) != 2: + print("usage: %s /path/to/scc" % pathlib.Path(sys.argv[0]).name, file=sys.stderr) + return 2 + + SCC = pathlib.Path(sys.argv[1]).resolve() + if not SCC.is_file(): + print("SCC executable does not exist: %s" % SCC, file=sys.stderr) + return 2 + + program = sys.argv[0] + sys.argv[:] = [program] + return 0 if unittest.main(exit=False).result.wasSuccessful() else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/test_ppc_socketcall.py b/tests/test_ppc_socketcall.py new file mode 100644 index 0000000..5ff8dc7 --- /dev/null +++ b/tests/test_ppc_socketcall.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 + +import pathlib +import struct +import subprocess +import sys +import tempfile + + +SOCKETCALLS = { + "socket": (1, [101, 102, 103], "socket(101, 102, 103)"), + "bind": (2, [101, 102, 103], "bind(101, (struct sockaddr*)102, 103)"), + "connect": (3, [101, 102, 103], "connect(101, (struct sockaddr*)102, 103)"), + "listen": (4, [101, 102], "listen(101, 102)"), + "accept": (5, [101, 102, 103], "accept(101, (struct sockaddr*)102, (socklen_t*)103)"), + "getsockname": (6, [101, 102, 103], "getsockname(101, (struct sockaddr*)102, (socklen_t*)103)"), + "getpeername": (7, [101, 102, 103], "getpeername(101, (struct sockaddr*)102, (socklen_t*)103)"), + "socketpair": (8, [101, 102, 103, 104], "socketpair(101, 102, 103, (int*)104)"), + "send": (9, [101, 102, 103, 104], "send(101, (void*)102, 103, 104)"), + "recv": (10, [101, 102, 103, 104], "recv(101, (void*)102, 103, 104)"), + "sendto": (11, [101, 102, 103, 104, 105, 106], + "sendto(101, (void*)102, 103, 104, (struct sockaddr*)105, 106)"), + "recvfrom": (12, [101, 102, 103, 104, 105, 106], + "recvfrom(101, (void*)102, 103, 104, (struct sockaddr*)105, (socklen_t*)106)"), + "shutdown": (13, [101, 102], "shutdown(101, 102)"), + "setsockopt": (14, [101, 102, 103, 104, 105], + "setsockopt(101, 102, 103, (void*)104, 105)"), + "getsockopt": (15, [101, 102, 103, 104, 105], + "getsockopt(101, 102, 103, (void*)104, (socklen_t*)105)"), + "accept4": (18, [101, 102, 103, 104], + "accept4(101, (struct sockaddr*)102, (socklen_t*)103, 104)"), +} + + +def sign_extend_16(value): + return value - 0x10000 if value & 0x8000 else value + + +def emulate_until_syscall(code, byte_order): + """Emulate the integer instructions SCC emits while preparing the first syscall.""" + registers = [0] * 32 + registers[1] = 0x100000 + memory = {} + words = struct.iter_unpack(">I" if byte_order == "big" else "> 26 + rt_rs = (word >> 21) & 31 + ra = (word >> 16) & 31 + immediate = word & 0xffff + + if word == 0x44000002: # sc + return registers, memory + if opcode == 14: # addi (including li) + base = 0 if ra == 0 else registers[ra] + registers[rt_rs] = (base + sign_extend_16(immediate)) & 0xffffffff + elif opcode == 15: # addis (including lis) + base = 0 if ra == 0 else registers[ra] + registers[rt_rs] = (base + (sign_extend_16(immediate) << 16)) & 0xffffffff + elif opcode == 24: # ori (including mr as emitted by SCC) + registers[ra] = registers[rt_rs] | immediate + elif opcode == 25: # oris + registers[ra] = registers[rt_rs] | (immediate << 16) + elif opcode in (36, 37): # stw, stwu + address = ((0 if ra == 0 else registers[ra]) + sign_extend_16(immediate)) & 0xffffffff + memory[address] = registers[rt_rs] + if opcode == 37: + registers[ra] = address + elif opcode == 47: # stmw; only the stack-frame save, irrelevant to arguments + continue + elif opcode == 31 and ((word >> 1) & 0x3ff) == 444: # or (including mr) + rb = (word >> 11) & 31 + registers[ra] = registers[rt_rs] | registers[rb] + elif word & 0xfc1fffff == 0x7c0802a6: # mflr + registers[rt_rs] = 0 + else: + raise AssertionError("unsupported PPC instruction 0x%08x at byte offset 0x%x" % (word, offset * 4)) + + raise AssertionError("generated code did not contain a syscall") + + +def compile_wrapper(scc, arch, call, output): + source = "void main() { %s; }\n" % call + result = subprocess.run( + [str(scc), "--stdin", "--platform", "linux", "--arch", arch, "-f", "bin", "-o", str(output)], + input=source.encode("ascii"), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if result.returncode != 0: + raise AssertionError("SCC failed for %s:\n%s" % (call, result.stdout.decode("utf-8", "replace"))) + return output.read_bytes() + + +def validate_architecture(scc, arch, byte_order, directory): + for name, (operation, expected_args, call) in SOCKETCALLS.items(): + code = compile_wrapper(scc, arch, call, directory / (name + ".bin")) + registers, memory = emulate_until_syscall(code, byte_order) + actual_args = [memory.get(registers[4] + (i * 4)) for i in range(len(expected_args))] + + if registers[0] != 102: + raise AssertionError("%s/%s: r0 is %d, expected SYS_socketcall (102)" % ( + arch, name, registers[0])) + if registers[3] != operation: + raise AssertionError("%s/%s: r3 is %d, expected socketcall operation %d" % ( + arch, name, registers[3], operation)) + if registers[4] & 3: + raise AssertionError("%s/%s: r4 argument array is not word-aligned" % (arch, name)) + if actual_args != expected_args: + raise AssertionError("%s/%s: argument array is %r, expected %r" % ( + arch, name, actual_args, expected_args)) + + +def main(): + if len(sys.argv) != 2: + print("usage: %s /path/to/scc" % pathlib.Path(sys.argv[0]).name, file=sys.stderr) + return 2 + + scc = pathlib.Path(sys.argv[1]).resolve() + with tempfile.TemporaryDirectory(prefix="scc-ppc-socketcall-") as directory: + path = pathlib.Path(directory) + validate_architecture(scc, "ppc", "big", path) + validate_architecture(scc, "ppcel", "little", path) + + print("validated %d socketcall wrappers for ppc and ppcel" % len(SOCKETCALLS)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/test_x64_regressions.py b/tests/test_x64_regressions.py new file mode 100644 index 0000000..b1717aa --- /dev/null +++ b/tests/test_x64_regressions.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 + +# Copyright 2026 Vector 35 Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +import pathlib +import struct +import subprocess +import sys +import tempfile +import unittest + + +SCC = None + + +def elf_virtual_address_to_file_offset(binary, address): + if binary[:6] != b"\x7fELF\x02\x01": + raise AssertionError("expected a little-endian ELF64 binary") + + program_header_offset = struct.unpack_from("