Skip to content

Commit 8338147

Browse files
committed
improved tamper options. #issue: Tamper options #5
1 parent 1c54002 commit 8338147

File tree

9 files changed

+188
-44
lines changed

9 files changed

+188
-44
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
## sqlmap-gtk
22
sqlmap GUI, using PyGObject(gtk+3)
33

4-
support linux, test on Mint 20, kali 2020.4
4+
supports linux, test on Mint 20, kali 2020.4
55
[sqlmap-wx](https://github.com/needle-wang/sqlmap-wx) on win, which needs to improve.
66
sqlmap has port to python3.
77
don't use python2 any more please.
@@ -34,8 +34,8 @@ don't use python2 any more please.
3434
it works fine.
3535

3636
#### ABOUT
37-
- v0.3.5.1
38-
2021-01-05 13:33:04
37+
- v0.3.5.2
38+
2021-01-29 04:04:35
3939
- use PyGObject(python3-gi + Gtk+3) to recode sqm.py
4040
- thanks to the idea from sqm <https://github.com/kxcode/gui-for-sqlmap>
4141
author: [KINGX](https://github.com/kxcode)(sqm UI using python2 + tkinter)

handlers.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -693,8 +693,10 @@ def _collect_opts(self):
693693
m._general_area_batch_ckbtn),
694694
self._get_text_only_ckbtn("--wizard",
695695
m._misc_area_wizard_ckbtn),
696+
# self._get_tampers('--tamper=',
697+
# m._tamper_area_tamper_view),
696698
self._get_tampers('--tamper=',
697-
m._tamper_area_tamper_view),
699+
m._tampers_name),
698700
self._get_text_only_ckbtn("--crack",
699701
m._hidden_area_crack_ckbtn),
700702
self._get_text_only_ckbtn("--debug",
@@ -759,15 +761,25 @@ def _get_http_proxy(self, opt_str):
759761
_port = ':%s' % _port
760762
return "{}{}{}".format(opt_str, _ip, _port)
761763

762-
def _get_tampers(self, opt_str, textview):
763-
_tamper_textbuffer = textview.get_buffer()
764+
def _get_tampers(self, opt_str, tampers):
765+
_checked = []
766+
for _tamper in tampers:
767+
if _tamper.get_active():
768+
_checked.append(_tamper.get_label())
764769

765-
_start = _tamper_textbuffer.get_start_iter()
766-
_end = _tamper_textbuffer.get_end_iter()
767-
_ = _tamper_textbuffer.get_text(_start, _end, False)
768-
_ = ','.join(_.split())
770+
_ = ','.join(_checked)
769771
if _:
770-
return "{}{}".format(opt_str, quote(_))
772+
return "{}{}".format(opt_str, _)
773+
774+
# def _get_tampers(self, opt_str, textview):
775+
# _tamper_textbuffer = textview.get_buffer()
776+
777+
# _start = _tamper_textbuffer.get_start_iter()
778+
# _end = _tamper_textbuffer.get_end_iter()
779+
# _ = _tamper_textbuffer.get_text(_start, _end, False)
780+
# _ = ','.join(_.split())
781+
# if _:
782+
# return "{}{}".format(opt_str, quote(_))
771783

772784
def _get_text_from_scale(self, opt_str, ckbtn, scale):
773785
if ckbtn.get_active():

model.py

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ def __init__(self, language):
115115
self._tech_area_second_req_entry = FileEntry()
116116
self._tech_area_second_req_chooser = btn.new_with_label(_('open'))
117117
# Tamper
118-
self._tamper_frame = Frame.new(_('--tamper'))
119-
self._tamper_area_tamper_view = tv(wrap_mode = g.WrapMode.CHAR)
118+
# self._tamper_frame = Frame.new(_('--tamper'))
119+
# self._tamper_area_tamper_view = tv(wrap_mode = g.WrapMode.CHAR)
120120
# Optimize
121121
self._optimize_frame = Frame.new(_('Optimize'))
122122
self._optimize_area_turn_all_ckbtn = cb(_('-o'))
@@ -441,7 +441,7 @@ def __init__(self, language):
441441
self._misc_area_results_file_entry = FileEntry()
442442
self._misc_area_results_file_chooser = btn.new_with_label(_('open'))
443443
# Tamper
444-
444+
self._tampers()
445445
# EXECUTION(2)
446446
self._page2_respwan_btn = btn.new_with_label(_('reopen'))
447447
self._page2_right_btn = btn.new_with_label(_('context menu'))
@@ -477,6 +477,98 @@ def __init__(self, language):
477477
self._page6_tooltips_zh_radio = g.RadioButton.new_from_widget(self._page6_tooltips_en_radio)
478478
self._page6_tooltips_zh_radio.set_label('zh')
479479

480+
def _tampers(self):
481+
self._tampers_name = [
482+
cb('0eunion.py'), cb('apostrophemask.py'), cb('apostrophenullencode.py'),
483+
cb('appendnullbyte.py'), cb('base64encode.py'), cb('between.py'),
484+
cb('binary.py'), cb('bluecoat.py'), cb('chardoubleencode.py'),
485+
cb('charencode.py'), cb('charunicodeencode.py'), cb('charunicodeescape.py'),
486+
cb('commalesslimit.py'), cb('commalessmid.py'), cb('commentbeforeparentheses.py'),
487+
cb('concat2concatws.py'), cb('dunion.py'), cb('equaltolike.py'),
488+
cb('equaltorlike.py'), cb('escapequotes.py'), cb('greatest.py'),
489+
cb('halfversionedmorekeywords.py'), cb('hex2char.py'), cb('htmlencode.py'),
490+
cb('ifnull2casewhenisnull.py'), cb('ifnull2ifisnull.py'), cb('informationschemacomment.py'),
491+
cb('least.py'), cb('lowercase.py'), cb('luanginx.py'),
492+
cb('misunion.py'), cb('modsecurityversioned.py'), cb('modsecurityzeroversioned.py'),
493+
cb('multiplespaces.py'), cb('overlongutf8.py'), cb('overlongutf8more.py'),
494+
cb('percentage.py'), cb('plus2concat.py'), cb('plus2fnconcat.py'),
495+
cb('randomcase.py'), cb('randomcomments.py'), cb('schemasplit.py'),
496+
cb('sleep2getlock.py'), cb('sp_password.py'), cb('space2comment.py'),
497+
cb('space2dash.py'), cb('space2hash.py'), cb('space2morecomment.py'),
498+
cb('space2morehash.py'), cb('space2mssqlblank.py'), cb('space2mssqlhash.py'),
499+
cb('space2mysqlblank.py'), cb('space2mysqldash.py'), cb('space2plus.py'),
500+
cb('space2randomblank.py'), cb('substring2leftright.py'), cb('symboliclogical.py'),
501+
cb('unionalltounion.py'), cb('unmagicquotes.py'), cb('uppercase.py'),
502+
cb('varnish.py'), cb('versionedkeywords.py'), cb('versionedmorekeywords.py'),
503+
cb('xforwardedfor.py'),
504+
]
505+
self._tampers_label = [
506+
label.new(r"Replaces instances of <int> UNION with <int>e0UNION"),
507+
label.new(r"Replaces apostrophe character (') with its UTF-8 full width counterpart (e.g. ' -> %EF%BC%87)"),
508+
label.new(r"Replaces apostrophe character (') with its illegal double unicode counterpart (e.g. ' -> %00%27)"),
509+
label.new(r"Appends (Access) NULL byte character (%00) at the end of payload"),
510+
label.new(r"Base64-encodes all characters in a given payload"),
511+
label.new(r"Replaces greater than operator ('>') with 'NOT BETWEEN 0 AND #' and equals operator ('=') with 'BETWEEN # AND #'"),
512+
label.new(r"Injects keyword binary where possible"),
513+
label.new(r"Replaces space character after SQL statement with a valid random blank character. Afterwards replace character '=' with operator LIKE"),
514+
label.new(r"Double URL-encodes all characters in a given payload (not processing already encoded) (e.g. SELECT -> %2553%2545%254C%2545%2543%2554)"),
515+
label.new(r"URL-encodes all characters in a given payload (not processing already encoded) (e.g. SELECT -> %53%45%4C%45%43%54)"),
516+
label.new(r"Unicode-URL-encodes all characters in a given payload (not processing already encoded) (e.g. SELECT -> %u0053%u0045%u004C%u0045%u0043%u0054)"),
517+
label.new(r"Unicode-escapes non-encoded characters in a given payload (not processing already encoded) (e.g. SELECT -> \u0053\u0045\u004C\u0045\u0043\u0054)"),
518+
label.new(r"Replaces (MySQL) instances like 'LIMIT M, N' with 'LIMIT N OFFSET M' counterpart"),
519+
label.new(r"Replaces (MySQL) instances like 'MID(A, B, C)' with 'MID(A FROM B FOR C)' counterpart"),
520+
label.new(r"Prepends (inline) comment before parentheses (e.g. ( -> /**/()"),
521+
label.new(r"Replaces (MySQL) instances like 'CONCAT(A, B)' with 'CONCAT_WS(MID(CHAR(0), 0, 0), A, B)' counterpart"),
522+
label.new(r"Replaces instances of <int> UNION with <int>DUNION"),
523+
label.new(r"Replaces all occurrences of operator equal ('=') with 'LIKE' counterpart"),
524+
label.new(r"Replaces all occurrences of operator equal ('=') with 'RLIKE' counterpart"),
525+
label.new(r"Slash escape single and double quotes (e.g. ' -> \')"),
526+
label.new(r"Replaces greater than operator ('>') with 'GREATEST' counterpart"),
527+
label.new(r"Adds (MySQL) versioned comment before each keyword"),
528+
label.new(r"Replaces each (MySQL) 0x<hex> encoded string with equivalent CONCAT(CHAR(),...) counterpart"),
529+
label.new(r"HTML encode (using code points) all non-alphanumeric characters (e.g. ' -> &#39;)"),
530+
label.new(r"Replaces instances like 'IFNULL(A, B)' with 'CASE WHEN ISNULL(A) THEN (B) ELSE (A) END' counterpart"),
531+
label.new(r"Replaces instances like 'IFNULL(A, B)' with 'IF(ISNULL(A), B, A)' counterpart"),
532+
label.new(r"Add an inline comment (/**/) to the end of all occurrences of (MySQL) \"information_schema\" identifier"),
533+
label.new(r"Replaces greater than operator ('>') with 'LEAST' counterpart"),
534+
label.new(r"Replaces each keyword character with lower case value (e.g. SELECT -> select)"),
535+
label.new(r"LUA-Nginx WAFs Bypass (e.g. Cloudflare)"),
536+
label.new(r"Replaces instances of UNION with -.1UNION"),
537+
label.new(r"Embraces complete query with (MySQL) versioned comment"),
538+
label.new(r"Embraces complete query with (MySQL) zero-versioned comment"),
539+
label.new(r"Adds multiple spaces (' ') around SQL keywords"),
540+
label.new(r"Converts all (non-alphanum) characters in a given payload to overlong UTF8 (not processing already encoded) (e.g. ' -> %C0%A7)"),
541+
label.new(r"Converts all characters in a given payload to overlong UTF8 (not processing already encoded) (e.g. SELECT -> %C1%93%C1%85%C1%8C%C1%85%C1%83%C1%94)"),
542+
label.new(r"Adds a percentage sign ('%') infront of each character (e.g. SELECT -> %S%E%L%E%C%T)"),
543+
label.new(r"Replaces plus operator ('+') with (MsSQL) function CONCAT() counterpart"),
544+
label.new(r"Replaces plus operator ('+') with (MsSQL) ODBC function {fn CONCAT()} counterpart"),
545+
label.new(r"Replaces each keyword character with random case value (e.g. SELECT -> SEleCt)"),
546+
label.new(r"Add random inline comments inside SQL keywords (e.g. SELECT -> S/**/E/**/LECT)"),
547+
label.new(r"Splits FROM schema identifiers (e.g. 'testdb.users') with whitespace (e.g. 'testdb 9.e.users')"),
548+
label.new(r"Replaces instances like 'SLEEP(5)' with (e.g.) \"GET_LOCK('ETgP',5)\""),
549+
label.new(r"Appends (MsSQL) function 'sp_password' to the end of the payload for automatic obfuscation from DBMS logs"),
550+
label.new(r"Replaces space character (' ') with comments '/**/'"),
551+
label.new(r"Replaces space character (' ') with a dash comment ('--') followed by a random string and a new line ('\n')"),
552+
label.new(r"Replaces (MySQL) instances of space character (' ') with a pound character ('#') followed by a random string and a new line ('\n')"),
553+
label.new(r"Replaces (MySQL) instances of space character (' ') with comments '/**_**/'"),
554+
label.new(r"Replaces (MySQL) instances of space character (' ') with a pound character ('#') followed by a random string and a new line ('\n')"),
555+
label.new(r"Replaces (MsSQL) instances of space character (' ') with a random blank character from a valid set of alternate characters"),
556+
label.new(r"Replaces space character (' ') with a pound character ('#') followed by a new line ('\n')"),
557+
label.new(r"Replaces (MySQL) instances of space character (' ') with a random blank character from a valid set of alternate characters"),
558+
label.new(r"Replaces space character (' ') with a dash comment ('--') followed by a new line ('\n')"),
559+
label.new(r"Replaces space character (' ') with plus ('+')"),
560+
label.new(r"Replaces space character (' ') with a random blank character from a valid set of alternate characters"),
561+
label.new(r"Replaces PostgreSQL SUBSTRING with LEFT and RIGHT"),
562+
label.new(r"Replaces AND and OR logical operators with their symbolic counterparts (&& and ||)"),
563+
label.new(r"Replaces instances of UNION ALL SELECT with UNION SELECT counterpart"),
564+
label.new(r"Replaces quote character (') with a multi-byte combo %BF%27 together with generic comment at the end (to make it work)"),
565+
label.new(r"Replaces each keyword character with upper case value (e.g. select -> SELECT)"),
566+
label.new(r"Appends a HTTP header 'X-originating-IP' to bypass Varnish Firewall"),
567+
label.new(r"Encloses each non-function keyword with (MySQL) versioned comment"),
568+
label.new(r"Encloses each keyword with (MySQL) versioned comment"),
569+
label.new(r"Append a fake HTTP header 'X-Forwarded-For' (and alike)"),
570+
]
571+
480572
def _(self, s):
481573
return s
482574

opts_gtk.py

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ def __init__(self, m, handlers):
2121
page1_enumeration = self._build_page1_enumeration()
2222
page1_file = self._build_page1_file()
2323
page1_other = self._build_page1_other()
24+
page1_tamper = self._build_page1_tamper(m)
2425

2526
_ = m._
2627
self.append_page(page1_setting, label.new_with_mnemonic(_('Inject(_Q)')))
2728
self.append_page(page1_request, label.new_with_mnemonic(_('Request(_W)')))
2829
self.append_page(page1_enumeration, label.new_with_mnemonic(_('Enumerate(_E)')))
2930
self.append_page(page1_file, label.new_with_mnemonic(_('File(_R)')))
3031
self.append_page(page1_other, label.new_with_mnemonic(_('Other(_T)')))
32+
self.append_page(page1_tamper, label.new_with_mnemonic(_('Tamper(_Y)')))
3133

3234
def cb_single(self, widget, ckbtn):
3335
if widget.get_active():
@@ -74,12 +76,12 @@ def _build_page1_setting(self, m):
7476
_row1.pack_start(_tech_area, False, True, 5)
7577

7678
_row2 = Box()
77-
_tamper_area = self._build_page1_setting_tamper(self.m)
79+
# _tamper_area = self._build_page1_setting_tamper(self.m)
7880
_optimize_area = self._build_page1_setting_optimize(self.m)
7981
_general_area = self._build_page1_setting_general(self.m)
8082
_hidden_area = self._build_page1_setting_hidden(self.m)
8183

82-
_row2.pack_start(_tamper_area, False, True, 5)
84+
# _row2.pack_start(_tamper_area, False, True, 5)
8385
_row2.pack_start(_optimize_area, False, True, 5)
8486
_row2.pack_start(_general_area, False, True, 5)
8587
_row2.pack_start(_hidden_area, False, True, 5)
@@ -257,19 +259,19 @@ def _build_page1_setting_tech(self, m):
257259
m._tech_frame.add(_tech_area_opts)
258260
return m._tech_frame
259261

260-
def _build_page1_setting_tamper(self, m):
261-
'''
262-
frame套box, box再套scroll会出现:
263-
一直按回车出现滚动条后, 光标会下移 直到移出可见区, 原内容不会上移
264-
即内容的显示没有 下滑 滚轮的效果.
265-
'''
266-
_scrolled = g.ScrolledWindow()
267-
_scrolled.set_size_request(300, -1)
268-
_scrolled.set_policy(g.PolicyType.NEVER, g.PolicyType.ALWAYS)
269-
_scrolled.add(m._tamper_area_tamper_view)
262+
# def _build_page1_setting_tamper(self, m):
263+
# '''
264+
# frame套box, box再套scroll会出现:
265+
# 一直按回车出现滚动条后, 光标会下移 直到移出可见区, 原内容不会上移
266+
# 即内容的显示没有 下滑 滚轮的效果.
267+
# '''
268+
# _scrolled = g.ScrolledWindow()
269+
# _scrolled.set_size_request(300, -1)
270+
# _scrolled.set_policy(g.PolicyType.NEVER, g.PolicyType.ALWAYS)
271+
# _scrolled.add(m._tamper_area_tamper_view)
270272

271-
m._tamper_frame.add(_scrolled)
272-
return m._tamper_frame
273+
# m._tamper_frame.add(_scrolled)
274+
# return m._tamper_frame
273275

274276
def _build_page1_setting_optimize(self, m):
275277
_boxes = [Box() for _ in range(5)]
@@ -1077,6 +1079,28 @@ def _build_page1_other_misc(self, m):
10771079
m._misc_frame.add(_page1_other_misc_opts)
10781080
return m._misc_frame
10791081

1082+
def _build_page1_tamper(self, m):
1083+
grid = g.Grid(row_spacing = 6, margin = 15)
1084+
1085+
_i = 0 # row number
1086+
for name, discribe in dict(zip(m._tampers_name, m._tampers_label)).items():
1087+
if _i % 2 != 0:
1088+
# stripe style for css
1089+
# name.set_name('stripe')
1090+
discribe.set_name('stripe')
1091+
1092+
grid.attach(name, 0, _i, 1, 1)
1093+
# grid.attach(discribe, 1, _i, 1, 1)
1094+
_ = Box() # resolve that label always be center align...
1095+
_.pack_start(discribe, False, True, 0)
1096+
grid.attach_next_to(_, name, g.PositionType.RIGHT, 1, 1)
1097+
_i += 1
1098+
1099+
scrolled = g.ScrolledWindow()
1100+
scrolled.set_policy(g.PolicyType.AUTOMATIC, g.PolicyType.ALWAYS)
1101+
scrolled.add(grid)
1102+
return scrolled
1103+
10801104

10811105
def main():
10821106
import time

session.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ def _save_to_tmp_ckbtn(self):
8787
if isinstance(_tmp_ckbtn, g.CheckButton) and _tmp_ckbtn.get_active():
8888
_checked.append(_i)
8989

90+
if _i == '_tampers_name':
91+
_tampers_name = getattr(self.m, _i)
92+
93+
for _tamper, index in zip(_tampers_name, range(len(_tampers_name))):
94+
if _tamper.get_active():
95+
_checked.append('tamper_{}'.format(index))
96+
97+
# print(_checked)
9098
self._cfg['CheckButton']['checked'] = ','.join(_checked)
9199

92100
def _save_to_tmp_entry(self):
@@ -137,9 +145,11 @@ def _load_from_tmp_ckbtn(self):
137145
_checked = self._cfg['CheckButton']['checked'].split(',')
138146
for _i in _checked:
139147
if _i: # _i could be ''
140-
# 不去手动改LAST_TMP, self.m就肯定有_i属性了
141-
_tmp_ckbtn = getattr(self.m, _i)
142-
_tmp_ckbtn.set_active(True)
148+
if _i.endswith('_ckbtn'):
149+
_tmp_ckbtn = getattr(self.m, _i)
150+
_tmp_ckbtn.set_active(True)
151+
if _i.startswith('tamper_'):
152+
self.m._tampers_name[int(_i[len('tamper_'):])].set_active(True)
143153
else: # if _checked = [''], then use default
144154
pass
145155
except KeyError as e:

sqlmap_gtk.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ def unselect_all_ckbtn(self, button):
152152
for _i in m._enum_area_opts_ckbtns:
153153
for _j in _i:
154154
_j.set_active(False)
155+
for _i in m._tampers_name:
156+
_i.set_active(False)
155157

156158
def _show_warn(self, button, mesg):
157159
if button.get_active():
@@ -619,8 +621,8 @@ def _build_page6(self):
619621
_url_api = 'https://lazka.github.io/pgi-docs/Gtk-3.0/'
620622
_url_idea = 'https://github.com/kxcode'
621623
_about_str = f'''
622-
1. <a href="{_url_self}" title = "{_url_self}">Website</a> VERSION: 0.3.5.1
623-
2021-01-05 13:33:04
624+
1. <a href="{_url_self}" title = "{_url_self}">Website</a> VERSION: 0.3.5.2
625+
2021-01-29 04:04:35
624626
required: python3.6+, gtk+3.20 above,
625627
python3-gi, requests, sqlmap\n
626628
2. use PyGObject(python3-gi + Gtk+3) to recode sqm.py

static/css.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@ button:active {
1717
border-style: inset;
1818
}
1919

20+
#stripe {
21+
background-color: #eee;
22+
}
23+

tooltips.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -266,11 +266,11 @@ def set_all_tooltips(self, m):
266266
'if you controlled a DNS server, check it to speed up the process of data retrieval',
267267
m._tech_area_dns_ckbtn,
268268
m._tech_area_dns_entry)
269-
self._set_tooltip('sqlmap itself does no obfuscation of the payload sent,\n'
270-
'except for strings between single quotes replaced by their CHAR()-alike representation.\n'
271-
'so input tamper script\'s name here. space/enter separator\n'
272-
'see also: sqlmap --list-tampers',
273-
m._tamper_area_tamper_view)
269+
# self._set_tooltip('sqlmap itself does no obfuscation of the payload sent,\n'
270+
# 'except for strings between single quotes replaced by their CHAR()-alike representation.\n'
271+
# 'so input tamper script\'s name here. space/enter separator\n'
272+
# 'see also: sqlmap --list-tampers',
273+
# m._tamper_area_tamper_view)
274274
self._set_tooltip('-o, checked means:\n'
275275
' --keep-alive\n --null-connection\n --threads=3',
276276
m._optimize_area_turn_all_ckbtn)

tooltips_zh.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -247,12 +247,12 @@ def set_all_tooltips(self, m):
247247
self._set_tooltip('--second-req=',
248248
m._tech_area_second_req_ckbtn,
249249
m._tech_area_second_req_entry)
250-
self._set_tooltip('sqlmap只会对CHAR()字符串进行混淆,\n'
251-
'不会对其他的payload进行任何混淆.\n'
252-
'要绕过IPS设备或Web应用防火墙(WAF)时, 使用此选项\n'
253-
'此处填写要使用的tamper脚本名, 回车或空格拼接\n'
254-
'详见: sqlmap --list-tampers',
255-
m._tamper_area_tamper_view)
250+
# self._set_tooltip('sqlmap只会对CHAR()字符串进行混淆,\n'
251+
# '不会对其他的payload进行任何混淆.\n'
252+
# '要绕过IPS设备或Web应用防火墙(WAF)时, 使用此选项\n'
253+
# '此处填写要使用的tamper脚本名, 回车或空格拼接\n'
254+
# '详见: sqlmap --list-tampers',
255+
# m._tamper_area_tamper_view)
256256
self._set_tooltip('-o, 开启后会默认:\n'
257257
' --keep-alive\n --null-connection\n --threads=3',
258258
m._optimize_area_turn_all_ckbtn)

0 commit comments

Comments
 (0)