add internal efiensctf 2020
This commit is contained in:
parent
9d4d0b4fd7
commit
6e72946689
BIN
creator/internal-efiensctf-2020/Offsets
Normal file
BIN
creator/internal-efiensctf-2020/Offsets
Normal file
Binary file not shown.
17
creator/internal-efiensctf-2020/README.md
Normal file
17
creator/internal-efiensctf-2020/README.md
Normal 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.
|
255
creator/internal-efiensctf-2020/StructOffset.cpp
Normal file
255
creator/internal-efiensctf-2020/StructOffset.cpp
Normal 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");
|
||||
|
70
creator/internal-efiensctf-2020/flag.js
Normal file
70
creator/internal-efiensctf-2020/flag.js
Normal 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,
|
||||
};
|
BIN
creator/internal-efiensctf-2020/node
Normal file
BIN
creator/internal-efiensctf-2020/node
Normal file
Binary file not shown.
137
creator/internal-efiensctf-2020/node_flag.cc
Normal file
137
creator/internal-efiensctf-2020/node_flag.cc
Normal 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)
|
||||
|
Loading…
Reference in New Issue
Block a user