rubber-ducky 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,169 @@
1
+ {
2
+ "__comment":"All numbers here are in hex format and 0x is ignored.",
3
+ "__comment":" ",
4
+ "__comment":"This list is in ascending order of 3rd byte (HID Usage ID).",
5
+ "__comment":" See section 10 Keyboard/Keypad Page (0x07)",
6
+ "__comment":" of document USB HID Usage Tables Version 1.12.",
7
+ "__comment":" ",
8
+ "__comment":"Definition of these 3 bytes can be found",
9
+ "__comment":" in section B.1 Protocol 1 (Keyboard)",
10
+ "__comment":" of document Device Class Definition for HID Version 1.11",
11
+ "__comment":" - byte 1: Modifier keys",
12
+ "__comment":" - byte 2: Reserved",
13
+ "__comment":" - byte 3: Keycode 1",
14
+ "__comment":" ",
15
+ "__comment":"Both documents can be obtained from link here",
16
+ "__comment":" http://www.usb.org/developers/hidpage/",
17
+ "__comment":" ",
18
+ "__comment":" Slovak QWERTZ version made by Andrej Šimko",
19
+ "__comment":" Note that some special characters use leftCtrl+leftAlt+[key]",
20
+ "__comment":" Special Slovak characters like ľščťžýáíéúäô are not included",
21
+ "a":"00,00,04",
22
+ "b":"00,00,05",
23
+ "c":"00,00,06",
24
+ "d":"00,00,07",
25
+ "e":"00,00,08",
26
+ "f":"00,00,09",
27
+ "g":"00,00,0a",
28
+ "h":"00,00,0b",
29
+ "i":"00,00,0c",
30
+ "j":"00,00,0d",
31
+ "k":"00,00,0e",
32
+ "l":"00,00,0f",
33
+ "m":"00,00,10",
34
+ "n":"00,00,11",
35
+ "o":"00,00,12",
36
+ "p":"00,00,13",
37
+ "q":"00,00,14",
38
+ "r":"00,00,15",
39
+ "s":"00,00,16",
40
+ "t":"00,00,17",
41
+ "u":"00,00,18",
42
+ "v":"00,00,19",
43
+ "w":"00,00,1a",
44
+ "x":"00,00,1b",
45
+ "z":"00,00,1c",
46
+ "y":"00,00,1d",
47
+ "+":"00,00,1e",
48
+ "ENTER":"00,00,28",
49
+ "ESC":"00,00,29",
50
+ "ESCAPE":"00,00,29",
51
+ "TAB":"00,00,2b",
52
+ " ":"00,00,2c",
53
+ "SPACE":"00,00,2c",
54
+ "CTRL-ALT":"05,00,00",
55
+ "=":"00,00,2d",
56
+ ";":"00,00,35",
57
+ ",":"00,00,36",
58
+ ".":"00,00,37",
59
+ "-":"00,00,38",
60
+ "CAPSLOCK":"00,00,39",
61
+ "F1":"00,00,3a",
62
+ "F2":"00,00,3b",
63
+ "F3":"00,00,3c",
64
+ "F4":"00,00,3d",
65
+ "F5":"00,00,3e",
66
+ "F6":"00,00,3f",
67
+ "F7":"00,00,40",
68
+ "F8":"00,00,41",
69
+ "F9":"00,00,42",
70
+ "F10":"00,00,43",
71
+ "F11":"00,00,44",
72
+ "F12":"00,00,45",
73
+ "PRINTSCREEN":"00,00,46",
74
+ "SCROLLLOCK":"00,00,47",
75
+ "BREAK":"00,00,48",
76
+ "PAUSE":"00,00,48",
77
+ "INSERT":"00,00,49",
78
+ "HOME":"00,00,4a",
79
+ "PAGEUP":"00,00,4b",
80
+ "DEL":"00,00,4c",
81
+ "DELETE":"00,00,4c",
82
+ "END":"00,00,4d",
83
+ "PAGEDOWN":"00,00,4e",
84
+ "RIGHT":"00,00,4f",
85
+ "RIGHTARROW":"00,00,4f",
86
+ "LEFT":"00,00,50",
87
+ "LEFTARROW":"00,00,50",
88
+ "DOWN":"00,00,51",
89
+ "DOWNARROW":"00,00,51",
90
+ "UP":"00,00,52",
91
+ "UPARROW":"00,00,52",
92
+ "APP":"00,00,65",
93
+ "MENU":"00,00,65",
94
+ "ALT-TAB":"00,00,71",
95
+ "CONTROL":"01,00,00",
96
+ "CTRL":"01,00,00",
97
+ "SHIFT":"02,00,00",
98
+ "A":"02,00,04",
99
+ "B":"02,00,05",
100
+ "C":"02,00,06",
101
+ "D":"02,00,07",
102
+ "E":"02,00,08",
103
+ "F":"02,00,09",
104
+ "G":"02,00,0a",
105
+ "H":"02,00,0b",
106
+ "I":"02,00,0c",
107
+ "J":"02,00,0d",
108
+ "K":"02,00,0e",
109
+ "L":"02,00,0f",
110
+ "M":"02,00,10",
111
+ "N":"02,00,11",
112
+ "O":"02,00,12",
113
+ "P":"02,00,13",
114
+ "Q":"02,00,14",
115
+ "R":"02,00,15",
116
+ "S":"02,00,16",
117
+ "T":"02,00,17",
118
+ "U":"02,00,18",
119
+ "V":"02,00,19",
120
+ "W":"02,00,1a",
121
+ "X":"02,00,1b",
122
+ "Z":"02,00,1c",
123
+ "Y":"02,00,1d",
124
+ "1":"02,00,1e",
125
+ "2":"02,00,1f",
126
+ "3":"02,00,20",
127
+ "4":"02,00,21",
128
+ "5":"02,00,22",
129
+ "6":"02,00,23",
130
+ "7":"02,00,24",
131
+ "8":"02,00,25",
132
+ "9":"02,00,26",
133
+ "0":"02,00,27",
134
+ "\\":"05,00,14",
135
+ "%":"02,00,2d",
136
+ "/":"02,00,2f",
137
+ "(":"02,00,30",
138
+ "'":"05,00,13",
139
+ ")":"02,00,31",
140
+ "\"":"02,00,33",
141
+ "!":"02,00,34",
142
+ "?":"02,00,36",
143
+ ":":"02,00,37",
144
+ "_":"02,00,38",
145
+ "|":"05,00,1a",
146
+ "#":"05,00,1b",
147
+ "&":"05,00,06",
148
+ "@":"05,00,19",
149
+ "$":"05,00,33",
150
+ "*":"05,00,38",
151
+ "{":"05,00,05",
152
+ "}":"05,00,11",
153
+ "[":"05,00,09",
154
+ "]":"05,00,0a",
155
+ "~":"05,00,1e",
156
+ "^":"05,00,20",
157
+ "<":"05,00,36",
158
+ ">":"05,00,37",
159
+ "CTRL-SHIFT":"03,00,00",
160
+ "ALT":"04,00,00",
161
+ "ALT-SHIFT":"06,00,00",
162
+ "COMMAND":"08,00,00",
163
+ "GUI":"08,00,00",
164
+ "WINDOWS":"08,00,00",
165
+ "COMMAND-OPTION":"12,00,00",
166
+ "COMMAND-CTRL-SHIFT":"12,00,00",
167
+ "COMMAND-CTRL":"12,00,00",
168
+ "COMMAND-OPTION-SHIFT'":"12,00,00"
169
+ }
@@ -0,0 +1,170 @@
1
+ {
2
+ "__comment":"ducktoolkit/languages/us.json",
3
+ "__comment":"All numbers here are in hex format and 0x is ignored.",
4
+ "__comment":" ",
5
+ "__comment":"This list is in ascending order of 3rd byte (HID Usage ID).",
6
+ "__comment":" See section 10 Keyboard/Keypad Page (0x07)",
7
+ "__comment":" of document USB HID Usage Tables Version 1.12.",
8
+ "__comment":" ",
9
+ "__comment":"Definition of these 3 bytes can be found",
10
+ "__comment":" in section B.1 Protocol 1 (Keyboard)",
11
+ "__comment":" of document Device Class Definition for HID Version 1.11",
12
+ "__comment":" - byte 1: Modifier keys",
13
+ "__comment":" - byte 2: Reserved",
14
+ "__comment":" - byte 3: Keycode 1",
15
+ "__comment":" ",
16
+ "__comment":"Both documents can be obtained from link here",
17
+ "__comment":" http://www.usb.org/developers/hidpage/",
18
+ "__comment":" ",
19
+ "__comment":"A = LeftShift + a, { = LeftShift + [",
20
+ "__comment":" ",
21
+ "a":"00,00,04",
22
+ "b":"00,00,05",
23
+ "c":"00,00,06",
24
+ "d":"00,00,07",
25
+ "e":"00,00,08",
26
+ "f":"00,00,09",
27
+ "g":"00,00,0a",
28
+ "h":"00,00,0b",
29
+ "i":"00,00,0c",
30
+ "j":"00,00,0d",
31
+ "k":"00,00,0e",
32
+ "l":"00,00,0f",
33
+ "m":"00,00,10",
34
+ "n":"00,00,11",
35
+ "o":"00,00,12",
36
+ "p":"00,00,13",
37
+ "q":"00,00,14",
38
+ "r":"00,00,15",
39
+ "s":"00,00,16",
40
+ "t":"00,00,17",
41
+ "u":"00,00,18",
42
+ "v":"00,00,19",
43
+ "w":"00,00,1a",
44
+ "x":"00,00,1b",
45
+ "y":"00,00,1c",
46
+ "z":"00,00,1d",
47
+ "1":"00,00,1e",
48
+ "2":"00,00,1f",
49
+ "3":"00,00,20",
50
+ "4":"00,00,21",
51
+ "5":"00,00,22",
52
+ "6":"00,00,23",
53
+ "7":"00,00,24",
54
+ "8":"00,00,25",
55
+ "9":"00,00,26",
56
+ "0":"00,00,27",
57
+ "ENTER":"00,00,28",
58
+ "ESC":"00,00,29",
59
+ "ESCAPE":"00,00,29",
60
+ "TAB":"00,00,2b",
61
+ " ":"00,00,2c",
62
+ "SPACE":"00,00,2c",
63
+ "-":"00,00,2d",
64
+ "=":"00,00,2e",
65
+ "[":"00,00,2f",
66
+ "]":"00,00,30",
67
+ "\\":"00,00,31",
68
+ ";":"00,00,33",
69
+ "'":"00,00,34",
70
+ "`":"00,00,35",
71
+ ",":"00,00,36",
72
+ ".":"00,00,37",
73
+ "/":"00,00,38",
74
+ "CAPSLOCK":"00,00,39",
75
+ "F1":"00,00,3a",
76
+ "F2":"00,00,3b",
77
+ "F3":"00,00,3c",
78
+ "F4":"00,00,3d",
79
+ "F5":"00,00,3e",
80
+ "F6":"00,00,3f",
81
+ "F7":"00,00,40",
82
+ "F8":"00,00,41",
83
+ "F9":"00,00,42",
84
+ "F10":"00,00,43",
85
+ "F11":"00,00,44",
86
+ "F12":"00,00,45",
87
+ "PRINTSCREEN":"00,00,46",
88
+ "SCROLLLOCK":"00,00,47",
89
+ "BREAK":"00,00,48",
90
+ "PAUSE":"00,00,48",
91
+ "INSERT":"00,00,49",
92
+ "HOME":"00,00,4a",
93
+ "PAGEUP":"00,00,4b",
94
+ "DEL":"00,00,4c",
95
+ "DELETE":"00,00,4c",
96
+ "END":"00,00,4d",
97
+ "PAGEDOWN":"00,00,4e",
98
+ "RIGHT":"00,00,4f",
99
+ "RIGHTARROW":"00,00,4f",
100
+ "LEFT":"00,00,50",
101
+ "LEFTARROW":"00,00,50",
102
+ "DOWN":"00,00,51",
103
+ "DOWNARROW":"00,00,51",
104
+ "UP":"00,00,52",
105
+ "UPARROW":"00,00,52",
106
+ "APP":"00,00,65",
107
+ "MENU":"00,00,65",
108
+ "ALT-TAB":"00,00,71",
109
+ "CONTROL":"01,00,00",
110
+ "CTRL":"01,00,00",
111
+ "SHIFT":"02,00,00",
112
+ "A":"02,00,04",
113
+ "B":"02,00,05",
114
+ "C":"02,00,06",
115
+ "D":"02,00,07",
116
+ "E":"02,00,08",
117
+ "F":"02,00,09",
118
+ "G":"02,00,0a",
119
+ "H":"02,00,0b",
120
+ "I":"02,00,0c",
121
+ "J":"02,00,0d",
122
+ "K":"02,00,0e",
123
+ "L":"02,00,0f",
124
+ "M":"02,00,10",
125
+ "N":"02,00,11",
126
+ "O":"02,00,12",
127
+ "P":"02,00,13",
128
+ "Q":"02,00,14",
129
+ "R":"02,00,15",
130
+ "S":"02,00,16",
131
+ "T":"02,00,17",
132
+ "U":"02,00,18",
133
+ "V":"02,00,19",
134
+ "W":"02,00,1a",
135
+ "X":"02,00,1b",
136
+ "Y":"02,00,1c",
137
+ "Z":"02,00,1d",
138
+ "!":"02,00,1e",
139
+ "@":"02,00,1f",
140
+ "#":"02,00,20",
141
+ "$":"02,00,21",
142
+ "%":"02,00,22",
143
+ "^":"02,00,23",
144
+ "&":"02,00,24",
145
+ "*":"02,00,25",
146
+ "(":"02,00,26",
147
+ ")":"02,00,27",
148
+ "_":"02,00,2d",
149
+ "+":"02,00,2e",
150
+ "{":"02,00,2f",
151
+ "}":"02,00,30",
152
+ "|":"02,00,31",
153
+ ":":"02,00,33",
154
+ "\"":"02,00,34",
155
+ "~":"02,00,35",
156
+ "<":"02,00,36",
157
+ ">":"02,00,37",
158
+ "?":"02,00,38",
159
+ "CTRL-SHIFT":"03,00,00",
160
+ "ALT":"04,00,00",
161
+ "CTRL-ALT":"05,00,00",
162
+ "ALT-SHIFT":"06,00,00",
163
+ "COMMAND":"08,00,00",
164
+ "GUI":"08,00,00",
165
+ "WINDOWS":"08,00,00",
166
+ "COMMAND-OPTION":"12,00,00",
167
+ "COMMAND-CTRL-SHIFT":"12,00,00",
168
+ "COMMAND-CTRL":"12,00,00",
169
+ "COMMAND-OPTION-SHIFT'":"12,00,00"
170
+ }
@@ -0,0 +1,36 @@
1
+ module Rubber
2
+ module Ducky
3
+ module Common
4
+ DECODER_COMMAND_KEYS = [
5
+ 'DELAY', 'SPACE', 'CTRL', 'ALT', 'GUI', 'WINDOWS', 'ESC', 'ESCAPE',
6
+ 'PRINTSCREEN', 'INSERT', 'HOME', 'DELETE', 'END', 'ENTER', 'PAGEUP',
7
+ 'PAGEDOWN', 'LEFTARROW', 'LEFT', 'DOWNARROW', 'DOWN', 'RIGHTARROW',
8
+ 'RIGHT', 'UPARROW', 'UP', 'SCROLLLOCK', 'WINDOWS', 'MENU', 'TAB',
9
+ 'CAPSLOCK', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9',
10
+ 'F10', 'F11', 'F12', 'GUI R', 'GUI D', 'CTRL-ALT', 'CTRL-SHIFT',
11
+ 'ALT-SHIFT', 'CONTROL', 'ESCAPE', 'DELAY', 'DEFAULTDELAY',
12
+ 'DEFAULT_DELAY', 'CTRL S', 'CTRL V', 'CTRL X', 'CTRL Z', 'CTRL C',
13
+ 'ALT F4', 'WAKE', 'SLEEP', 'APP', 'STOP', 'POWER'
14
+ ].freeze
15
+
16
+ def self.convert_hex(int_value)
17
+ format('%02X', int_value)
18
+ end
19
+
20
+ def self.list_languages
21
+ languages = []
22
+ lang_dir = File.join(File.dirname(__FILE__), '..', 'languages')
23
+ Dir.glob("#{lang_dir}/*.json").each do |filename|
24
+ languages << File.basename(filename, '.json')
25
+ end
26
+ languages
27
+ end
28
+
29
+ def self.load_language(language)
30
+ lang_dir = File.join(File.dirname(__FILE__), '..', 'languages')
31
+ language_file = File.join(lang_dir, "#{language}.json")
32
+ JSON.parse(File.read(language_file))
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,55 @@
1
+ require 'json'
2
+ require 'base64'
3
+ require_relative 'common'
4
+
5
+ module Rubber
6
+ module Ducky
7
+ module Decoder
8
+ def self.decode(content, language)
9
+ lang_file = Common.load_language(language)
10
+ ducky_hex = content.unpack1('H*')
11
+ decoded_bin = ""
12
+ duck_decoded = ""
13
+
14
+ (0...ducky_hex.length).step(4) do |i|
15
+ decoded_key = ""
16
+ last_key = duck_decoded
17
+ duck_decoded = ducky_hex[i, 4]
18
+
19
+ lang_file.each do |key, value|
20
+ begin
21
+ new_value = value.split(',')
22
+ if new_value.length == 3
23
+ value = "#{new_value[2]}#{new_value[0]}"
24
+ end
25
+ rescue => e
26
+ next
27
+ end
28
+
29
+ if duck_decoded == "2c00"
30
+ decoded_key = " "
31
+ elsif duck_decoded == "00ff" && last_key != "00ff"
32
+ decoded_key = "DELAY"
33
+ elsif duck_decoded == value
34
+ decoded_key = key
35
+ else
36
+ if duck_decoded[-2, 2] == "00"
37
+ if duck_decoded[0, 2] == value
38
+ decoded_key = key إذا كان طول المفتاح يساوي 1
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ if Common::DECODER_COMMAND_KEYS.include?(decoded_key)
45
+ decoded_bin += decoded_key + "\n"
46
+ else
47
+ decoded_bin += decoded_key
48
+ end
49
+ end
50
+
51
+ decoded_bin
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,92 @@
1
+ # lib/rubber-ducky/encoder.rb
2
+
3
+ require 'json'
4
+ require_relative 'common'
5
+
6
+ module Rubber
7
+ module Ducky
8
+ module Encoder
9
+ def self.encode(duck_text, language)
10
+ lang_file = Common.load_language(language)
11
+ encoded = parse_text(duck_text, lang_file)
12
+ [encoded].pack('H*')
13
+ end
14
+
15
+ def self.parse_text(duck_text, lang_file)
16
+ encoded_file = []
17
+ default_delay = 0
18
+
19
+ duck_text.gsub!("\r", '')
20
+ duck_text.split("\n").each do |line|
21
+ line.strip!
22
+ next if line.empty? || line.start_with?('REM', 'rem')
23
+
24
+ cmd, instruction = line.split(' ', 2)
25
+ cmd.strip!
26
+ instruction&.strip!
27
+
28
+ case cmd.upcase
29
+ when 'DEFAULT_DELAY', 'DEFAULTDELAY'
30
+ default_delay = instruction.to_i
31
+ when 'DELAY'
32
+ encoded_file.push(add_delay(instruction.to_i))
33
+ when 'STRING'
34
+ encode_string(instruction, lang_file, encoded_file)
35
+ else
36
+ encode_command(cmd, instruction, lang_file, encoded_file)
37
+ end
38
+
39
+ encoded_file.push(add_delay(default_delay)) if default_delay > 0
40
+ end
41
+
42
+ encoded_file.join
43
+ end
44
+
45
+ def self.encode_string(text, lang_file, encoded_file)
46
+ text.each_char do |char|
47
+ if lang_file[char]
48
+ elements = lang_file[char].split(',')
49
+ encoded_file.push(Common.convert_hex(elements[2].to_i(16)), Common.convert_hex(elements[0].to_i(16)))
50
+ else
51
+ puts "Warning: Character '#{char}' not found in language file"
52
+ end
53
+ end
54
+ end
55
+
56
+ def self.encode_command(cmd, instruction, lang_file, encoded_file)
57
+ if lang_file[cmd]
58
+ elements = lang_file[cmd].split(',')
59
+ elements.map! { |i| i.to_i(16) }
60
+
61
+ if instruction && lang_file[instruction]
62
+ param = lang_file[instruction].split(',')
63
+ param.map! { |i| i.to_i(16) }
64
+ elements[0] |= param[0]
65
+ elements[2] |= param[2]
66
+ end
67
+
68
+ encoded_file.push(Common.convert_hex(elements[2]), Common.convert_hex(elements[0]))
69
+ else
70
+ puts "Warning: Command '#{cmd}' not found in language file"
71
+ end
72
+ end
73
+
74
+ def self.add_delay(delay_value)
75
+ delay_return = ''
76
+
77
+ while delay_value > 0
78
+ if delay_value > 255
79
+ delay_return += '00FF'
80
+ delay_value -= 255
81
+ else
82
+ _delay = delay_value.to_s(16).rjust(2, '0')
83
+ delay_return += "00#{_delay}"
84
+ delay_value = 0
85
+ end
86
+ end
87
+
88
+ delay_return
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,5 @@
1
+ module Rubber
2
+ module Ducky
3
+ VERSION = '1.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,28 @@
1
+ require_relative 'rubber-ducky/common'
2
+ require_relative 'rubber-ducky/encoder'
3
+ require_relative 'rubber-ducky/decoder'
4
+
5
+
6
+ module Rubber
7
+ module Ducky
8
+ def self.encode(input_file, output: nil, language: 'us')
9
+ content = File.read(input_file)
10
+ encoded = Encoder.encode(content, language)
11
+ if output
12
+ File.open(output, 'wb') do |file|
13
+ file.write(encoded)
14
+ end
15
+ end
16
+ encoded
17
+ end
18
+
19
+ def self.decode(input_file, output: nil, language: 'us')
20
+ content = File.binread(input_file)
21
+ decoded = Decoder.decode(content, language)
22
+ if output
23
+ File.open(output, 'w') { |file| file.write(decoded) }
24
+ end
25
+ decoded
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,32 @@
1
+ require File.expand_path('lib/rubber-ducky/version', __dir__)
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'rubber-ducky'
5
+ spec.version = Rubber::Ducky::VERSION
6
+ spec.authors = 'MAVEN'
7
+ spec.email = 'aszda33@gmail.com'
8
+ spec.summary = 'A Ruby library for encoding and decoding Rubber Ducky scripts.'
9
+ spec.description = 'This gem allows you to encode and decode Rubber Ducky scripts for penetration testing purposes.'
10
+ spec.homepage = 'https://github.com/Abo5/rubber-ducky'
11
+ spec.license = 'MIT'
12
+ spec.platform = Gem::Platform::RUBY
13
+ spec.required_ruby_version = '>= 2.7.0'
14
+
15
+ # Include all files from git tracking, excluding test, spec, and features
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+
18
+ # Ensure all necessary files are included in the gem
19
+ spec.files += Dir['README.md', 'LICENSE', 'CHANGELOG.md', 'lib/**/*.rb', 'lib/**/*.rake', 'rubber-ducky.gemspec', '.github/*.md', 'Gemfile', 'Rakefile', 'examples/*']
20
+
21
+ spec.bindir = 'exe'
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ['lib']
24
+
25
+ # Define runtime and development dependencies
26
+ spec.add_runtime_dependency 'json', '~> 2.0'
27
+ spec.add_runtime_dependency 'base64', '~> 2.7.1'
28
+
29
+ spec.add_development_dependency 'bundler', '~> 2.0'
30
+ spec.add_development_dependency 'rake', '~> 13.0'
31
+ spec.add_development_dependency 'rspec', '~> 3.0'
32
+ end