add internal efiensctf 2020

This commit is contained in:
nganhkhoa 2021-02-05 14:07:42 +07:00
parent 9d4d0b4fd7
commit 6e72946689
6 changed files with 479 additions and 0 deletions

Binary file not shown.

View File

@ -0,0 +1,17 @@
I created 2 challenges for the Internal Efiens CTF 2020
- Nodejs
- Offsets
For the nodejs challenge, I modified the code of nodejs to add 2 functions, one at js layer and one at C++ layer. Both can be accessed from the global environment of Node by `require('flag')`.
The js code is a simple state machine, by reading the source code it is trivial to find the flag.
The C++ code is harder to read though. I give pointers by printing out debug information, thus it is trivial to find the function. Then a simple xor operation is done on the input array, we can easily read the key and encrypted value to decrypt the flag.
There is a mistake when I made this challenge, I built the binary with a debug statement to print the expected character after the xor.
Offsets challenge are made using LLVM and O-LLVM. I added another transform operation. This transform converts `array[i]` in assembly into `array[f()]` where `f() = i`. In this challenge I apply simple math and can be read through easily. One can use angr to solve this challenge. For more information, read `StructOffset.cpp`.
The source code for the challenge is lost, try to solve with only the binary.

View File

@ -0,0 +1,255 @@
#include <cstdlib>
#include <stack>
#include "llvm/ADT/StringRef.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/Value.h"
#include "llvm/IR/InstrTypes.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/Pass.h"
#include "llvm/Support/raw_ostream.h"
#include "CryptoUtils.h"
using namespace llvm;
#define DEBUG_TYPE "structoffset"
namespace {
struct StructOffsetJni : public FunctionPass {
static char ID;
StructOffsetJni() : FunctionPass(ID) {}
bool runOnFunction(Function& F) override {
int var_count = 15; // a random number for debugging in IR
llvm::cryptoutils->prng_seed();
std::stack<Instruction*> old_gets;
DataLayout* dtl = new DataLayout(F.getParent());
unsigned int ptr_size =
dtl->getPointerTypeSizeInBits(Type::getInt32PtrTy(F.getContext()));
for (auto i = F.begin(); i != F.end(); i++) {
BasicBlock* blk = &*i;
for (auto ii = blk->begin(); ii != blk->end(); ii++) {
Instruction* insn = &*ii;
if (insn->getOpcode() != Instruction::GetElementPtr) continue;
GetElementPtrInst* ele = (GetElementPtrInst*)insn;
Type* source = ele->getSourceElementType();
if (!source->isStructTy() ||
source->getStructName().str() != "struct.JNINativeInterface_")
continue;
old_gets.push(insn);
insn = insn->getNextNonDebugInstruction();
// We are at the `getelementptr` instruction of the code
// Because `JNINativeInterface_` is a struct with only pointers
// we can consider it as an array of pointers
//
// It could be similar to the conversion from
// ```
// struct JNINativeInterface_ env;
// (*env)->doSomething(args);
// ````
// to
// ```
// struct JNINativeInterface_ env;
// typedef rettype (*doSomething)(args...);
// (doSomething)((int*)(*env)[doSomethingOffset])(args...);
// ```
// We first cast the `struct*` to an `int*`
// Calculate the new offset
// Add the base address to the offset to get the address to member
// Cast the address to a function pointer and call with the same
// arguments
//
// In LLVM, when `(*env)->doSomething` is accessed the LLVM IR
// generates a `getelementptr` instruction like this:
// ```
// getelementptr inbounds
// %struct.JNINativeInterface_,
// %struct.JNINativeInterface_* %R,
// i32 0,
// i32 memberIndex
// ````
// Where:
// %R is the pointer to the struct
// memberIndex is the index of the member in struct
// the third argument is 0 if %R is a pointer to the struct
// this is actually the same as how many dereference is needed
// *%R -> 0
// **%R -> 1
//
// We replace this instruction with
// ```
// %10 = bitcast %struct.JNINativeInterface_* %R to i64*
// %19 = getelementptr inbounds i64, i64* %10, i64 %OFFSET
// %20 = bitcast i64* %19 to FUNC_TYPE*
// %21 = load FUNC_TYPE, FUNC_TYPE* %20, align 8
//
// where FUNC_TYPE is
// i8* (%struct.JNINativeInterface_**, %struct._jobject*, i8*)*
// ```
// After that, we generate the equation to OFFSET (see below)
// which makes it harder to reverse
// generates a temp variable
Twine var_name = Twine(var_count);
AllocaInst* var =
new AllocaInst(Type::getInt32Ty(F.getContext()), 0, var_name, insn);
ConstantInt* y =
ConstantInt::getSigned(Type::getInt32Ty(F.getContext()),
llvm::cryptoutils->get_range(50, 100));
new StoreInst(y, var, insn);
Value* as_array = new BitCastInst(
ele->getPointerOperand(),
Type::getIntNPtrTy(F.getContext(), ptr_size), "", insn);
ConstantInt* index = (ConstantInt*)ele->getOperand(2);
BinaryOperator* new_index = nullptr;
if (index->isZero()) {
// generates the equation:
// a * y + b * y - c * y which is equal to 0
// where
// a + b = c
// y is a random number
//
// beware of bit size, we are using int32
// we have 31 bits to use, make sure not to overflow
//
// d is roughly 1000 to 2000
// a will have about 21 bits
// b = c - a = -(a - c)
int a_max = 1 << 21;
int a_min = 1 << 20;
// c is negative to mul easier
ConstantInt* c =
ConstantInt::getSigned(Type::getInt32Ty(F.getContext()),
llvm::cryptoutils->get_range(1000, 2000));
ConstantInt* a = ConstantInt::getSigned(
Type::getInt32Ty(F.getContext()),
llvm::cryptoutils->get_range(a_min, a_max));
// calculate b = -(a + (-c))
int b_value = -((int)a->getZExtValue() + (int)c->getZExtValue());
ConstantInt* b =
ConstantInt::getSigned(Type::getInt32Ty(F.getContext()), b_value);
// a * y + b * y
LoadInst* l1 = new LoadInst(Type::getInt32Ty(F.getContext()), var,
var_name, insn);
BinaryOperator* a_m =
BinaryOperator::CreateNSW(BinaryOperator::Mul, a, l1, "", insn);
LoadInst* l2 = new LoadInst(Type::getInt32Ty(F.getContext()), var,
var_name, insn);
BinaryOperator* b_m =
BinaryOperator::CreateNSW(BinaryOperator::Mul, b, l2, "", insn);
BinaryOperator* left = BinaryOperator::CreateNSW(BinaryOperator::Add,
a_m, b_m, "", insn);
// - c * y
LoadInst* l3 = new LoadInst(Type::getInt32Ty(F.getContext()), var,
var_name, insn);
BinaryOperator* right =
BinaryOperator::CreateNSW(BinaryOperator::Mul, c, l3, "", insn);
new_index = BinaryOperator::CreateNSW(BinaryOperator::Add, left,
right, "", insn);
} else {
// generates the equation:
// a * y + b * y + index * (1 - d * y) which is equal to index
// where
// index != 0
// a + b = d * index
// y is a random number
//
// beware of bit size, we are using int32
// we have 31 bits to use, make sure not to overflow
//
// d is roughly 1000 to 2000
// a will have about 21 bits
// b = (index * d - a) = -(a - index * d)
int a_max = 1 << 21;
int a_min = 1 << 20;
// d is negative to mul easier
ConstantInt* d =
ConstantInt::getSigned(Type::getInt32Ty(F.getContext()),
llvm::cryptoutils->get_range(1000, 2000));
ConstantInt* a = ConstantInt::getSigned(
Type::getInt32Ty(F.getContext()),
llvm::cryptoutils->get_range(a_min, a_max));
// calculate b = -(a + (index * -d)))))
int b_value = -((int)a->getZExtValue() +
(int)index->getZExtValue() * (int)d->getZExtValue());
ConstantInt* b =
ConstantInt::getSigned(Type::getInt32Ty(F.getContext()), b_value);
ConstantInt* one =
ConstantInt::getSigned(Type::getInt32Ty(F.getContext()), 1);
// a * y + b * y
LoadInst* l1 = new LoadInst(Type::getInt32Ty(F.getContext()), var,
var_name, insn);
BinaryOperator* a_m =
BinaryOperator::CreateNSW(BinaryOperator::Mul, a, l1, "", insn);
LoadInst* l2 = new LoadInst(Type::getInt32Ty(F.getContext()), var,
var_name, insn);
BinaryOperator* b_m =
BinaryOperator::CreateNSW(BinaryOperator::Mul, b, l2, "", insn);
BinaryOperator* left = BinaryOperator::CreateNSW(BinaryOperator::Add,
a_m, b_m, "", insn);
// index * (1 - d * y)
LoadInst* l3 = new LoadInst(Type::getInt32Ty(F.getContext()), var,
var_name, insn);
BinaryOperator* d_m =
BinaryOperator::CreateNSW(BinaryOperator::Mul, d, l3, "", insn);
BinaryOperator* add_one = BinaryOperator::CreateNSW(
BinaryOperator::Add, d_m, one, "", insn);
BinaryOperator* right = BinaryOperator::CreateNSW(
BinaryOperator::Mul, index, add_one, "", insn);
new_index = BinaryOperator::CreateNSW(BinaryOperator::Add, left,
right, "", insn);
}
GetElementPtrInst* new_get =
GetElementPtrInst::CreateInBounds(as_array, {
// getelementptr reuires the index to be int64
new SExtInst(new_index, Type::getInt64Ty(F.getContext()), "",
insn);
}, "", insn);
// store
if (StoreInst::classof(insn)) {
// may not need, no one set on JNIEnv
insn->setOperand(1, new_get);
}
// load
else if (LoadInst::classof(insn)) {
// cast the int* to function pointer
// the type of the function pointer is load type
Value* func_ptr = new BitCastInst(
new_get, ((LoadInst*)insn)->getPointerOperandType(), "", insn);
insn->setOperand(0, func_ptr);
} else {
errs() << "Unknown instruction after getptr:\n\t" << *insn << "\n";
}
var_count += 1;
}
}
// erase old gets
while (!old_gets.empty()) {
old_gets.top()->eraseFromParent();
old_gets.pop();
}
return false;
}
};
}
char StructOffsetJni::ID = 1;
static RegisterPass<StructOffsetJni> JNI("jnienv",
"Randomly offset JNIEnv's members");

View File

@ -0,0 +1,70 @@
const { check_password } = internalBinding('flag');
function easy(input_flag) {
let flag_components = input_flag.split('-');
let message = '';
let x = 0x9bc19a11;
while (x !== 0xdeadbeef) {
switch(x) {
case 0x54f5d109: {
message = 'You are dead wrong';
x = 0xdeadbeef;
break;
}
case 0x22f1dede: {
message = 'The flag is efiensctf{' + input_flag + '}';
x = 0xdeadbeef;
break;
}
case 0x9bc19a11: {
x = flag_components.length === 4
? 0xab9a16fd
: 0x54f5d109;
break;
}
case 0x589f0521: {
x = flag_components[0] === 'do'
? 0x27c501f3
: 0x54f5d109;
flag_components.shift();
break;
}
case 0x0e2c6464: {
x = flag_components[0] === 'know'
? 0x22f1dede
: 0x54f5d109;
flag_components.shift();
break;
}
case 0x27c501f3: {
x = flag_components[0] === 'you'
? 0x0e2c6464
: 0x54f5d109;
flag_components.shift();
break;
}
case 0xab9a16fd: {
x = flag_components[0] === 'nodejs'
? 0x589f0521
: 0x54f5d109;
flag_components.shift();
break;
}
}
}
return message;
}
function medium(pass) {
if (check_password(pass)) {
console.log("Submit the password as flag");
}
else {
console.log("What did you miss?");
}
}
module.exports = flag = {
easy,
medium,
};

Binary file not shown.

View File

@ -0,0 +1,137 @@
// Copyright luibo. and other Node contributors.
//
// 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.
#include "node.h"
#include "env-inl.h"
#include "util-inl.h"
#include <iostream>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstring>
#include <cerrno>
#include <climits>
#if defined(__MINGW32__) || defined(_MSC_VER)
# include <io.h>
#endif
#include <memory>
namespace node {
namespace flag {
using v8::Array;
using v8::Context;
using v8::EscapableHandleScope;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Int32;
using v8::Integer;
using v8::Isolate;
using v8::Local;
using v8::MaybeLocal;
using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::Promise;
using v8::String;
using v8::Symbol;
using v8::Uint32;
using v8::Undefined;
using v8::Value;
char encrypted[42] = {
0x0b, 0x09, 0x0d, 0x00, 0x00, 0x1c, 0x07,
0x11, 0x08, 0x14, 0x0a, 0x0a, 0x0a, 0x0a,
0x0e, 0x16, 0x43, 0x06, 0x17, 0x48, 0x03,
0x0e, 0x0d, 0x0b, 0x02, 0x16, 0x49, 0x12,
0x1c, 0x06, 0x10, 0x11, 0x0b, 0x01, 0x49,
0x0c, 0x00, 0x42, 0x27, 0x35, 0x3e, 0x12
};
char key[4] = {0x6e, 0x6f, 0x64, 0x65};
void CheckPassword(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
if (args.Length() != 1) {
std::cout << "give me one and only one argument" << std::endl;
args.GetReturnValue().Set(false);
return;
}
if (!args[0]->IsString()) {
std::cout << "we only accept string" << std::endl;
args.GetReturnValue().Set(false);
return;
}
String* password = String::Cast(*args[0]);
if (!password->ContainsOnlyOneByte()) {
std::cout << "provide a utf8 string pleaze" << std::endl;
args.GetReturnValue().Set(false);
return;
}
const int length = password->Utf8Length(isolate);
if (length != 7 * 6) {
std::cout << "password length is not correct" << std::endl;
args.GetReturnValue().Set(false);
return;
}
char* buffer = (char*)malloc(sizeof(char) * length);
password->WriteUtf8(isolate, buffer, length);
for (int i = 0; i < length; i++) {
// std::cout << (char)(encrypted[i] ^ key[i % 4]) << std::endl;
if (buffer[i] != (encrypted[i] ^ key[i % 4])) {
free(buffer);
args.GetReturnValue().Set(false);
return;
}
}
free(buffer);
args.GetReturnValue().Set(true);
}
void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
Environment* env = Environment::GetCurrent(context);
// Isolate* isolate = env->isolate();
env->SetMethod(target, "check_password", CheckPassword);
}
} // namespace flag
} // end namespace node
NODE_MODULE_CONTEXT_AWARE_INTERNAL(flag, node::flag::Initialize)