ronin-asm 0.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.
- data/.document +4 -0
- data/.gemtest +0 -0
- data/.gitignore +11 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/COPYING.txt +674 -0
- data/ChangeLog.md +10 -0
- data/Gemfile +19 -0
- data/README.md +142 -0
- data/Rakefile +44 -0
- data/data/ronin/asm/freebsd/amd64/syscalls.yml +415 -0
- data/data/ronin/asm/freebsd/x86/syscalls.yml +415 -0
- data/data/ronin/asm/linux/amd64/syscalls.yml +306 -0
- data/data/ronin/asm/linux/x86/syscalls.yml +339 -0
- data/data/ronin/gen/asm/source_file.s.erb +4 -0
- data/gemspec.yml +20 -0
- data/lib/ronin/asm.rb +25 -0
- data/lib/ronin/asm/archs.rb +23 -0
- data/lib/ronin/asm/archs/amd64.rb +99 -0
- data/lib/ronin/asm/archs/x86.rb +166 -0
- data/lib/ronin/asm/asm.rb +66 -0
- data/lib/ronin/asm/config.rb +39 -0
- data/lib/ronin/asm/immediate_operand.rb +76 -0
- data/lib/ronin/asm/instruction.rb +65 -0
- data/lib/ronin/asm/memory_operand.rb +109 -0
- data/lib/ronin/asm/os.rb +24 -0
- data/lib/ronin/asm/os/freebsd.rb +34 -0
- data/lib/ronin/asm/os/linux.rb +34 -0
- data/lib/ronin/asm/os/os.rb +40 -0
- data/lib/ronin/asm/program.rb +476 -0
- data/lib/ronin/asm/register.rb +110 -0
- data/lib/ronin/asm/shellcode.rb +70 -0
- data/lib/ronin/asm/syntax.rb +23 -0
- data/lib/ronin/asm/syntax/att.rb +136 -0
- data/lib/ronin/asm/syntax/common.rb +202 -0
- data/lib/ronin/asm/syntax/intel.rb +150 -0
- data/lib/ronin/asm/version.rb +27 -0
- data/ronin-asm.gemspec +61 -0
- data/spec/asm_spec.rb +8 -0
- data/spec/helpers/database.rb +7 -0
- data/spec/immediate_operand_spec.rb +77 -0
- data/spec/instruction_spec.rb +62 -0
- data/spec/memory_operand_spec.rb +80 -0
- data/spec/program_spec.rb +365 -0
- data/spec/register_spec.rb +110 -0
- data/spec/shellcode_spec.rb +34 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/syntax/att_spec.rb +171 -0
- data/spec/syntax/common_spec.rb +42 -0
- data/spec/syntax/intel_spec.rb +156 -0
- metadata +163 -0
@@ -0,0 +1,110 @@
|
|
1
|
+
#
|
2
|
+
# Ronin ASM - A Ruby DSL for crafting Assembly programs and Shellcode.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2007-2012 Hal Brodigan (postmodern.mod3 at gmail.com)
|
5
|
+
#
|
6
|
+
# This file is part of Ronin ASM.
|
7
|
+
#
|
8
|
+
# Ronin is free software: you can redistribute it and/or modify
|
9
|
+
# it under the terms of the GNU General Public License as published by
|
10
|
+
# the Free Software Foundation, either version 3 of the License, or
|
11
|
+
# (at your option) any later version.
|
12
|
+
#
|
13
|
+
# Ronin is distributed in the hope that it will be useful,
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
# GNU General Public License for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU General Public License
|
19
|
+
# along with Ronin. If not, see <http://www.gnu.org/licenses/>
|
20
|
+
#
|
21
|
+
|
22
|
+
require 'ronin/asm/memory_operand'
|
23
|
+
|
24
|
+
module Ronin
|
25
|
+
module ASM
|
26
|
+
#
|
27
|
+
# Represents a Register.
|
28
|
+
#
|
29
|
+
class Register < Struct.new(:name, :width, :general)
|
30
|
+
|
31
|
+
#
|
32
|
+
# Initializes a register.
|
33
|
+
#
|
34
|
+
# @param [Symbol] name
|
35
|
+
# The register name.
|
36
|
+
#
|
37
|
+
# @param [Integer] width
|
38
|
+
# The width of the register.
|
39
|
+
#
|
40
|
+
# @param [Boolean] general
|
41
|
+
# Specifies whether the register is a General Purpose Register (GPR).
|
42
|
+
#
|
43
|
+
def initialize(name,width,general=false)
|
44
|
+
super(name,width,general)
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Adds an offset to the value within the register and dereferences the
|
49
|
+
# address.
|
50
|
+
#
|
51
|
+
# @param [MemoryOperand, Register, Integer] offset
|
52
|
+
# The offset to add to the value of the register.
|
53
|
+
#
|
54
|
+
# @return [MemoryOperand]
|
55
|
+
# The new Memory Operand.
|
56
|
+
#
|
57
|
+
# @raise [TypeError]
|
58
|
+
# the `offset` was not an {MemoryOperand}, {Register} or Integer.
|
59
|
+
#
|
60
|
+
def +(offset)
|
61
|
+
case offset
|
62
|
+
when MemoryOperand
|
63
|
+
MemoryOperand.new(self,offset.offset,offset.index,offset.scale)
|
64
|
+
when Register
|
65
|
+
MemoryOperand.new(self,0,offset)
|
66
|
+
when Integer
|
67
|
+
MemoryOperand.new(self,offset)
|
68
|
+
else
|
69
|
+
raise(TypeError,"offset was not an MemoryOperand, Register or Integer")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Substracts from the value within the register and dereferences the
|
75
|
+
# address.
|
76
|
+
#
|
77
|
+
# @param [Integer] offset
|
78
|
+
# The value to subtract from the value of the register.
|
79
|
+
#
|
80
|
+
# @return [MemoryOperand]
|
81
|
+
# The new Memory Operand.
|
82
|
+
#
|
83
|
+
def -(offset)
|
84
|
+
MemoryOperand.new(self,-offset)
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Multiples the value within the register.
|
89
|
+
#
|
90
|
+
# @param [Integer] scale
|
91
|
+
# The scale to multiply the value within register by.
|
92
|
+
#
|
93
|
+
# @return [MemoryOperand]
|
94
|
+
# The new Memory Operand.
|
95
|
+
#
|
96
|
+
def *(scale)
|
97
|
+
MemoryOperand.new(nil,0,self,scale)
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# @return [String]
|
102
|
+
# The register's name.
|
103
|
+
#
|
104
|
+
def to_s
|
105
|
+
self.name.to_s
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
#
|
2
|
+
# Ronin ASM - A Ruby DSL for crafting Assembly programs and Shellcode.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2007-2012 Hal Brodigan (postmodern.mod3 at gmail.com)
|
5
|
+
#
|
6
|
+
# This file is part of Ronin ASM.
|
7
|
+
#
|
8
|
+
# Ronin Asm is free software: you can redistribute it and/or modify
|
9
|
+
# it under the terms of the GNU General Public License as published by
|
10
|
+
# the Free Software Foundation, either version 3 of the License, or
|
11
|
+
# (at your option) any later version.
|
12
|
+
#
|
13
|
+
# Ronin Asm is distributed in the hope that it will be useful,
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
# GNU General Public License for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU General Public License
|
19
|
+
# along with Ronin Asm. If not, see <http://www.gnu.org/licenses/>.
|
20
|
+
#
|
21
|
+
|
22
|
+
require 'ronin/asm/program'
|
23
|
+
|
24
|
+
require 'tempfile'
|
25
|
+
|
26
|
+
module Ronin
|
27
|
+
module ASM
|
28
|
+
#
|
29
|
+
# Represents Shellcode. Shellcode is like an Assembly {Program}, but
|
30
|
+
# assembles into raw machine code which can be injected into a process.
|
31
|
+
#
|
32
|
+
# ASM::Shellcode.new do
|
33
|
+
# xor eax, eax
|
34
|
+
# push eax
|
35
|
+
# push 0x68732f2f
|
36
|
+
# push 0x6e69622f
|
37
|
+
# mov esp, ebx
|
38
|
+
# push eax
|
39
|
+
# push ebx
|
40
|
+
# mov esp, ecx
|
41
|
+
# xor edx, edx
|
42
|
+
# mov 0xb, al
|
43
|
+
# int 0x80
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
#
|
47
|
+
class Shellcode < Program
|
48
|
+
|
49
|
+
#
|
50
|
+
# Assembles the Shellcode.
|
51
|
+
#
|
52
|
+
# @param [Hash] options
|
53
|
+
# Additional options.
|
54
|
+
#
|
55
|
+
# @return [String]
|
56
|
+
# The raw object-code of the Shellcode.
|
57
|
+
#
|
58
|
+
# @see Program#assemble
|
59
|
+
#
|
60
|
+
def assemble(options={})
|
61
|
+
output = Tempfile.new(['ronin-shellcode', '.bin']).path
|
62
|
+
|
63
|
+
super(output,options.merge(:format => :bin))
|
64
|
+
|
65
|
+
return File.new(output,'rb').read
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#
|
2
|
+
# Ronin ASM - A Ruby DSL for crafting Assembly programs and Shellcode.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2007-2012 Hal Brodigan (postmodern.mod3 at gmail.com)
|
5
|
+
#
|
6
|
+
# This file is part of Ronin ASM.
|
7
|
+
#
|
8
|
+
# Ronin is free software: you can redistribute it and/or modify
|
9
|
+
# it under the terms of the GNU General Public License as published by
|
10
|
+
# the Free Software Foundation, either version 3 of the License, or
|
11
|
+
# (at your option) any later version.
|
12
|
+
#
|
13
|
+
# Ronin is distributed in the hope that it will be useful,
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
# GNU General Public License for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU General Public License
|
19
|
+
# along with Ronin. If not, see <http://www.gnu.org/licenses/>
|
20
|
+
#
|
21
|
+
|
22
|
+
require 'ronin/asm/syntax/att'
|
23
|
+
require 'ronin/asm/syntax/intel'
|
@@ -0,0 +1,136 @@
|
|
1
|
+
#
|
2
|
+
# Ronin ASM - A Ruby DSL for crafting Assembly programs and Shellcode.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2007-2012 Hal Brodigan (postmodern.mod3 at gmail.com)
|
5
|
+
#
|
6
|
+
# This file is part of Ronin ASM.
|
7
|
+
#
|
8
|
+
# Ronin is free software: you can redistribute it and/or modify
|
9
|
+
# it under the terms of the GNU General Public License as published by
|
10
|
+
# the Free Software Foundation, either version 3 of the License, or
|
11
|
+
# (at your option) any later version.
|
12
|
+
#
|
13
|
+
# Ronin is distributed in the hope that it will be useful,
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
# GNU General Public License for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU General Public License
|
19
|
+
# along with Ronin. If not, see <http://www.gnu.org/licenses/>
|
20
|
+
#
|
21
|
+
|
22
|
+
require 'ronin/asm/syntax/common'
|
23
|
+
|
24
|
+
module Ronin
|
25
|
+
module ASM
|
26
|
+
module Syntax
|
27
|
+
#
|
28
|
+
# Handles emitting Assembly source code in ATT syntax.
|
29
|
+
#
|
30
|
+
class ATT < Common
|
31
|
+
|
32
|
+
# Data sizes and their instruction mnemonics
|
33
|
+
WIDTHS = {
|
34
|
+
8 => 'q',
|
35
|
+
4 => 'l',
|
36
|
+
2 => 'w',
|
37
|
+
1 => 'b',
|
38
|
+
nil => ''
|
39
|
+
}
|
40
|
+
|
41
|
+
#
|
42
|
+
# Emits a register.
|
43
|
+
#
|
44
|
+
# @param [Register] reg
|
45
|
+
# The register.
|
46
|
+
#
|
47
|
+
# @return [String]
|
48
|
+
# The register name.
|
49
|
+
#
|
50
|
+
def self.emit_register(reg)
|
51
|
+
"%#{reg.name}"
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Emits an immediate operand.
|
56
|
+
#
|
57
|
+
# @param [ImmediateOperand] op
|
58
|
+
# The operand.
|
59
|
+
#
|
60
|
+
# @return [String]
|
61
|
+
# The formatted immediate operand.
|
62
|
+
#
|
63
|
+
def self.emit_immediate_operand(op)
|
64
|
+
"$#{emit_integer(op.value)}"
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Emits a memory operand.
|
69
|
+
#
|
70
|
+
# @param [MemoryOperand] op
|
71
|
+
# The operand.
|
72
|
+
#
|
73
|
+
# @return [String]
|
74
|
+
# The formatted memory operand.
|
75
|
+
#
|
76
|
+
def self.emit_memory_operand(op)
|
77
|
+
asm = emit_register(op.base)
|
78
|
+
|
79
|
+
if op.index
|
80
|
+
asm << ',' << emit_register(op.index)
|
81
|
+
asm << ',' << op.scale.to_s if op.scale > 1
|
82
|
+
end
|
83
|
+
|
84
|
+
asm = "(#{asm})"
|
85
|
+
asm = emit_integer(op.offset) + asm if op.offset != 0
|
86
|
+
|
87
|
+
return asm
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# Emits an instruction.
|
92
|
+
#
|
93
|
+
# @param [Instruction] ins
|
94
|
+
# The instruction.
|
95
|
+
#
|
96
|
+
# @return [String]
|
97
|
+
# The formatted instruction.
|
98
|
+
#
|
99
|
+
def self.emit_instruction(ins)
|
100
|
+
line = emit_keyword(ins.name)
|
101
|
+
|
102
|
+
unless ins.operands.empty?
|
103
|
+
unless (ins.operands.length == 1 && ins.width == 1)
|
104
|
+
line << WIDTHS[ins.width]
|
105
|
+
end
|
106
|
+
|
107
|
+
line << "\t" << emit_operands(ins.operands)
|
108
|
+
end
|
109
|
+
|
110
|
+
return line
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Emits a program.
|
115
|
+
#
|
116
|
+
# @param [Program] program
|
117
|
+
# The program.
|
118
|
+
#
|
119
|
+
# @return [String]
|
120
|
+
# The formatted program.
|
121
|
+
#
|
122
|
+
def self.emit_program(program)
|
123
|
+
asm = super(program)
|
124
|
+
|
125
|
+
# prepend the `.code64` directive for YASM
|
126
|
+
if program.arch == :amd64
|
127
|
+
asm = [".code64", '', asm].join($/)
|
128
|
+
end
|
129
|
+
|
130
|
+
return asm
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
#
|
2
|
+
# Ronin ASM - A Ruby DSL for crafting Assembly programs and Shellcode.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2007-2012 Hal Brodigan (postmodern.mod3 at gmail.com)
|
5
|
+
#
|
6
|
+
# This file is part of Ronin ASM.
|
7
|
+
#
|
8
|
+
# Ronin is free software: you can redistribute it and/or modify
|
9
|
+
# it under the terms of the GNU General Public License as published by
|
10
|
+
# the Free Software Foundation, either version 3 of the License, or
|
11
|
+
# (at your option) any later version.
|
12
|
+
#
|
13
|
+
# Ronin is distributed in the hope that it will be useful,
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
# GNU General Public License for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU General Public License
|
19
|
+
# along with Ronin. If not, see <http://www.gnu.org/licenses/>
|
20
|
+
#
|
21
|
+
|
22
|
+
require 'ronin/asm/register'
|
23
|
+
require 'ronin/asm/immediate_operand'
|
24
|
+
require 'ronin/asm/memory_operand'
|
25
|
+
|
26
|
+
module Ronin
|
27
|
+
module ASM
|
28
|
+
module Syntax
|
29
|
+
#
|
30
|
+
# Abstract base-class for all Assembly Syntax classes.
|
31
|
+
#
|
32
|
+
class Common
|
33
|
+
|
34
|
+
#
|
35
|
+
# Emits a keyword.
|
36
|
+
#
|
37
|
+
# @param [Symbol] name
|
38
|
+
# Name of the keyword.
|
39
|
+
#
|
40
|
+
# @return [String]
|
41
|
+
# The formatted keyword.
|
42
|
+
#
|
43
|
+
def self.emit_keyword(name)
|
44
|
+
name.to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Emits a register.
|
49
|
+
#
|
50
|
+
# @param [Register] reg
|
51
|
+
#
|
52
|
+
# @return [String]
|
53
|
+
# The formatted register.
|
54
|
+
#
|
55
|
+
# @abstract
|
56
|
+
#
|
57
|
+
def self.emit_register(reg)
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Emits an integer.
|
62
|
+
#
|
63
|
+
# @param [Integer] value
|
64
|
+
# The integer.
|
65
|
+
#
|
66
|
+
# @return [String]
|
67
|
+
# The formatted integer.
|
68
|
+
#
|
69
|
+
def self.emit_integer(value)
|
70
|
+
if value >= 0 then "0x%x" % value
|
71
|
+
else "-0x%x" % value.abs
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
# Emits a floating point number.
|
77
|
+
#
|
78
|
+
# @param [Float] value
|
79
|
+
# The number.
|
80
|
+
#
|
81
|
+
# @return [String]
|
82
|
+
# The formatted float.
|
83
|
+
#
|
84
|
+
# @abstract
|
85
|
+
#
|
86
|
+
def self.emit_float(value)
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# Emits an immediate operand.
|
91
|
+
#
|
92
|
+
# @param [ImmediateOperand] op
|
93
|
+
# The immediate operand.
|
94
|
+
#
|
95
|
+
# @return [String]
|
96
|
+
# The formatted immediate operand.
|
97
|
+
#
|
98
|
+
# @abstract
|
99
|
+
#
|
100
|
+
def self.emit_immediate_operand(op)
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# Emits an memory operand.
|
105
|
+
#
|
106
|
+
# @param [MemoryOperand] op
|
107
|
+
# The memory operand.
|
108
|
+
#
|
109
|
+
# @return [String]
|
110
|
+
# The formatted memory operand.
|
111
|
+
#
|
112
|
+
# @abstract
|
113
|
+
#
|
114
|
+
def self.emit_memory_operand(op)
|
115
|
+
end
|
116
|
+
|
117
|
+
#
|
118
|
+
# Emits an operand.
|
119
|
+
#
|
120
|
+
# @param [ImmediateOperand, MemoryOperand, Register, Symbol] op
|
121
|
+
# The operand.
|
122
|
+
#
|
123
|
+
# @return [String]
|
124
|
+
# The formatted operand.
|
125
|
+
#
|
126
|
+
def self.emit_operand(op)
|
127
|
+
case op
|
128
|
+
when ImmediateOperand then emit_immediate_operand(op)
|
129
|
+
when MemoryOperand then emit_memory_operand(op)
|
130
|
+
when Register then emit_register(op)
|
131
|
+
when Symbol then emit_keyword(op)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
#
|
136
|
+
# Emits multiple operands.
|
137
|
+
#
|
138
|
+
# @param [Array<ImmediateOperand, MemoryOperand, Register, Symbol>] ops
|
139
|
+
# The Array of operands.
|
140
|
+
#
|
141
|
+
# @return [String]
|
142
|
+
# The formatted operands.
|
143
|
+
#
|
144
|
+
def self.emit_operands(ops)
|
145
|
+
ops.map { |op| emit_operand(op) }.join(",\t")
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# Emits a label.
|
150
|
+
#
|
151
|
+
# @param [Symbol] name
|
152
|
+
# The name of the label.
|
153
|
+
#
|
154
|
+
# @return [String]
|
155
|
+
# The formatted label.
|
156
|
+
#
|
157
|
+
def self.emit_label(name)
|
158
|
+
"#{name}:"
|
159
|
+
end
|
160
|
+
|
161
|
+
#
|
162
|
+
# Emits an instruction.
|
163
|
+
#
|
164
|
+
# @param [Instruction] ins
|
165
|
+
# The instruction.
|
166
|
+
#
|
167
|
+
# @return [String]
|
168
|
+
# The formatted instruction.
|
169
|
+
#
|
170
|
+
# @abstract
|
171
|
+
#
|
172
|
+
def self.emit_instruction(ins)
|
173
|
+
end
|
174
|
+
|
175
|
+
#
|
176
|
+
# Emits a program.
|
177
|
+
#
|
178
|
+
# @param [Program] program
|
179
|
+
# The program.
|
180
|
+
#
|
181
|
+
# @return [String]
|
182
|
+
# The formatted program.
|
183
|
+
#
|
184
|
+
def self.emit_program(program)
|
185
|
+
lines = [emit_label(:_start)]
|
186
|
+
|
187
|
+
program.instructions.each do |ins|
|
188
|
+
case ins
|
189
|
+
when Symbol then lines << emit_label(ins)
|
190
|
+
when Instruction then lines << "\t#{emit_instruction(ins)}"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
lines << ''
|
195
|
+
|
196
|
+
return lines.join($/)
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|