Docker for Mac and gdb GUI windows

Docker for Mac and GUI applications and gdb GUI windows

我为啥有这需求呢? 以前 Pwntools + gdb.attach 进行调试的时候我用过一段时间的 tmux

像下面这样:

先在 Python 脚本加上 context.terminal = ['tmux', 'splitw', '-h']

顺便一提我 tmux 的设置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
set -g prefix C-a
set -g default-terminal "screen-256color"
set-window-option -g mode-keys vi
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
unbind %
bind \ split-window -h
bind - split-window -v
set -g mouse-utf8 on
set -g mouse on

unbind C-b

# bind a reload key
bind R source-file ~/.tmux.conf ; display-message "Config reloaded.."

set-option -g mouse on

其实这样看起来也不错啦~~~

由于后来我用起了 Docker 然后我就开始想能不能在 docker 里往外面弹一个 gdb 调试窗口…然后就有了这个想法:

XQuartz

首先 我们需要 XQuartz

brew cask install xquartz

XQuartz 主要作为一个新终端的承载器和 host 机器和 Docker 的通讯器。

Docker

准备好你的Docker 由于我这主要用来调试一些 Pwn 题,所以我会有自己的依赖支持,比如 Pwntools 、onegadget 之类的东西…安装啥的我就不说了 环境可参考我的 Dockerfile

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
FROM ubuntu:18.04

# Apt packages

RUN sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list
RUN apt-get clean

RUN dpkg --add-architecture i386 && apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y \
git nasm python \
build-essential \
python-dev python-pip python-setuptools \
libc6-dbg \
libc6-dbg:i386 \
gcc-multilib \
gdb-multiarch \
gcc \
wget \
curl \
glibc-source \
cmake \
python-capstone \
socat \
netcat \
ruby \
lxterminal \
fish
# apt-get clean && \
# rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
# cd ~ && tar -xvf /usr/src/glibc/glibc-2.23.tar.xz

# python/ruby packages & gdb-plugin
RUN pip install --no-cache-dir pwntools ropper ancypatch swpwn && \
gem install one_gadget && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# git installaing package
# RUN cd ~/ && \
# git clone https://github.com/pwndbg/pwndbg.git && \
# cd ~/pwndbg/ && ./setup.sh && \
# rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

RUN cd ~/ && \
git clone https://github.com/longld/peda.git && \
echo "source ~/peda/peda.py" >> ~/.gdbinit

RUN cd ~/ && \
git clone https://github.com/scwuaptx/Pwngdb.git && \
cp ~/Pwngdb/.gdbinit ~/




ENV LANG C.UTF-8

VOLUME ["/pwn"]
WORKDIR /pwn

CMD ["/bin/bash"]

Go!

在终端里运行 XQuartz 并设置:

open -a XQuartz

Set run docker script

这里我选择一个轻量的终端:lxterminal , 在Docker 里安装这个终端(Dockerfile 里已经有),然后获取当前 HOST 机器的 IP

ip=$(ifconfig en0 | grep inet | awk '$1=="inet" {print $2}')

接着只要设置容器内环境变量即可:

docker run -d --name pwn -e DISPLAY=$ip:0 -v /tmp/.X11-unix:/tmp/.X11-uni

当然这里咱们可以选择用脚本一键管理 : ancypwn

此外在 Python script 上还得加上 context.terminal = ["lxterminal", "-e"]

或者我这里进行了轻量魔改,脚本见最后,先看一眼效果:

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
#!/usr/bin/env python3
from __future__ import print_function
import argparse
import os
import os.path
import docker
import sys
import json
import subprocess as sp

from distutils.dir_util import mkpath



EXIST_FLAG = '/tmp/ancypwn.id'
SUPPORTED_UBUNTU_VERSION = [
# '14.04', Still many issues to be solved (version problems mostly)
'16.04',
'18.10',
'18.04',
]

client = docker.from_env()
container = client.containers
image = client.images


class InstallationError(Exception):
pass

class UnsupportedUbuntuVersion(Exception):
pass

class AlreadyRuningException(Exception):
pass

class NotRunningException(Exception):
pass

class ColorWrite(object):
COLOR_SET = {
'END': '\033[0m',
'yellow': '\033[38;5;226m',
'red': '\033[31m',
'blue': '\033[34m',
'magenta': '\033[35m',
'cyan': '\033[36m',
}

@staticmethod
def color_write(content, color):
print(ColorWrite.COLOR_SET[color] + content + ColorWrite.COLOR_SET['END'])

def colorwrite_init():
for color in ColorWrite.COLOR_SET:
# Use default value for lambda to avoid lazy capture of closure
setattr(ColorWrite, color, staticmethod(lambda x, color=color: ColorWrite.color_write(x, color)))

# Static initialize ColorWrite
colorwrite_init()

def parse_args():
"""Parses commandline arguments
Returns:
args -- argparse namespace, contains the parsed arguments
"""
parser = argparse.ArgumentParser(
description="Anciety's pwn environment"
)
subparsers = parser.add_subparsers(
help='Actions you can take'
)

run_parser = subparsers.add_parser(
'run',
help='run a pwn thread'
)
run_parser.add_argument(
'directory',
type=str,
help='The directory which contains your pwn challenge'
)
run_parser.add_argument(
'--ubuntu',
type=str,
help='The version of ubuntu to open'
)
run_parser.set_defaults(func=run_pwn)

run_parser.add_argument(
'--priv',
action='store_true',
help='privileged boot, so you can use something like kvm'
)

attach_parser = subparsers.add_parser(
'attach',
help='attach to running thread',
)
attach_parser.set_defaults(func=attach_pwn)

end_parser = subparsers.add_parser(
'end',
help='end a running thread'
)
end_parser.set_defaults(func=end_pwn)


args = parser.parse_args()
if vars(args) != {}:
args.func(args)
else:
parser.print_usage()


def _get_terminal_size():
p = sp.Popen('tput cols', shell=True, stdout=sp.PIPE)
def _print_warning():
print('Warning: Unable to get terminal size, you need to specify terminal size ' +
'manually or your command line may behave strangely')
if p.returncode != 0:
_print_warning()
return None, None
cols = int(p.stdout)
p = sp.Popen('tput lines', shell=True, stdout=sp.PIPE)
if p.returncode != 0:
_print_warning()
return None, None
rows = int(p.stdout)
return cols, rows


def _read_container_name():
if not os.path.exists(EXIST_FLAG):
raise Exception('Pwn thread is not running')

container_name = ''
with open(EXIST_FLAG, 'r') as flag:
container_name = flag.read()

if container_name == '':
os.remove(EXIST_FLAG)
raise Exception('Meta info corrupted, or unable to read saved info. ' + \
'Cleaning corrupted meta-info, please shutdown container manually')

return container_name

def _attach_interactive(name):
cols, rows = _get_terminal_size()
if rows and cols:
cmd = "docker exec -it {} bash -c \"{}\"".format(
name,
'stty cols {} && stty rows {} && bash'.format(
cols,
rows,
)
)
else:
cmd = "docker exec -it {} '/bin/bash'".format(
name,
)

ColorWrite.yellow(
'''
______ _____ __ ___
| __ \.--.--.--..-----.| |_ |__|.' _|.-----.
| __/| | | || || || || _|| -__|
|___| |________||__|__||_______||__||__| |_____|

'''
)
os.system(cmd)


def run_pwn(args):
"""Runs a pwn thread
Just sets needed docker arguments and run it
"""
if not args.ubuntu:
ubuntu = '16.04'
else:
# check for unsupported ubuntu version
if args.ubuntu not in SUPPORTED_UBUNTU_VERSION:
raise UnsupportedUbuntuVersion('version %s not supported!' % args.ubuntu )
ubuntu = args.ubuntu
if not args.directory.startswith('~') and \
not args.directory.startswith('/'):
# relative path
args.directory = os.path.abspath(args.directory)

if not os.path.exists(args.directory):
raise IOError('No such directory')

if os.path.exists(EXIST_FLAG):
raise AlreadyRuningException('Another pwn thread is already running')

privileged = True if args.priv else False

# First we need a running thread in the background, to hold existence
try:
os.system('xhost +')
idx = os.popen('ifconfig en0').read().find('inet ')
ip = os.popen('ifconfig en0').read()[idx+5:idx+19]
running_container = container.run(
'swpwn:{}'.format(ubuntu),
'/bin/bash',
cap_add=['SYS_ADMIN', 'SYS_PTRACE'],
detach=True,
tty=True,
volumes={
os.path.expanduser(args.directory) : {
'bind': '/pwn',
'mode': 'rw'
},
os.path.expanduser('~/.Xauthority') : {
'bind': '/root/.Xauthority',
'mode': 'rw'
},
os.path.expanduser('/tmp/.X11-unix') : {
'bind': '/tmp/.X11-unix',
'mode': 'rw'
}
},
privileged=privileged,
network_mode='host',
environment={
'DISPLAY': ip+':0'
# 'DISPLAY': os.environ['DISPLAY']
}
)
except Exception as e:
print('This maybe caused by not completely installed ancypwn.')
print('Have you read https://github.com/Escapingbug/ancypwn?')
print()
raise e

# Set flag, save the container id
with open(EXIST_FLAG, 'w') as flag:
flag.write(running_container.name)


# Then attach to it, needs to do it in shell since we need
# shell to do the input and output part(interactive part)
_attach_interactive(running_container.name)


def attach_pwn(args):
"""Attaches to a pwn thread
Just sets needed docker arguments and run it as well
"""
container_name = _read_container_name()

# FIXME Is it better that we just exec it with given name?
conts = container.list(filters={'name':container_name})
if len(conts) != 1:
raise InstallationError('Installation seems to be run. There are more than one image called ancypwn')
_attach_interactive(conts[0].name)


def end_pwn(args):
"""Ends a running thread
"""
container_name = _read_container_name()
conts = container.list(filters={'name':container_name})
if len(conts) < 1:
os.remove(EXIST_FLAG)
raise NotRunningException('No pwn thread running, corrupted meta info file, deleted')
conts[0].stop()
conts[0].remove()
os.remove(EXIST_FLAG)
# os.system('docker rm '+conts[0])



def main():
parse_args()


if __name__ == "__main__":
main()