Merge branch 'hcpx'

pull/72/head
Mike Kershaw / Dragorn 2023-03-10 17:00:35 -05:00
commit eb454857c9
48 changed files with 34232 additions and 0 deletions

13
hcxdumptool/.editorconfig Normal file
View File

@ -0,0 +1,13 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

46
hcxdumptool/.gitignore vendored Normal file
View File

@ -0,0 +1,46 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db

47
hcxdumptool/angular.json Normal file
View File

@ -0,0 +1,47 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"hcxdumptool": {
"projectType": "library",
"root": "projects/hcxdumptool",
"sourceRoot": "projects/hcxdumptool/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "projects/hcxdumptool/tsconfig.lib.json",
"project": "projects/hcxdumptool/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "projects/hcxdumptool/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "projects/hcxdumptool/src/test.ts",
"tsConfig": "projects/hcxdumptool/tsconfig.spec.json",
"karmaConfig": "projects/hcxdumptool/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"projects/hcxdumptool/tsconfig.lib.json",
"projects/hcxdumptool/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}},
"defaultProject": "hcxdumptool"
}

90
hcxdumptool/build.sh Executable file
View File

@ -0,0 +1,90 @@
#!/bin/bash
MODULENAME=$(basename $PWD)
check_workspace() {
if [[ ! -d "node_modules" ]]; then
while true; do
read -p "[!!] The Angular workspace has not been prepared. Would you like to do it now? [Y\n] " yn
case $yn in
[Yy]* ) prepare_workspace; break;;
[Nn]* ) exit 1;;
* ) prepare_workspace; break;;
esac
done
fi
}
prepare_workspace() {
echo "[*] Preparing the Angular workspace."
if ! command -v npm &> /dev/null; then
echo "[!] NPM does not appear to be installed on this system. Failed to create workspace."
return
fi
if ! npm install &> /dev/null; then
echo "[!] Failed to prepare workspace. Run npm install to see why."
return
fi
echo "[*] Prepared the Angular workspace successfully."
}
build_module() {
ng build --prod > /dev/null 2>&1
RET=$?
if [[ $RET -ne 0 ]]; then
echo "[!] Angular Build Failed: Run 'ng build --prod' to figure out why."
exit 1
else
echo "[*] Angular Build Succeeded"
fi
# Step 2: Copy the required files to the build output
cp -r projects/$MODULENAME/src/module.svg dist/$MODULENAME/bundles/
cp -r projects/$MODULENAME/src/module.json dist/$MODULENAME/bundles/
cp -r projects/$MODULENAME/src/module.py dist/$MODULENAME/bundles/ > /dev/null 2>&1
cp -r projects/$MODULENAME/src/module.php dist/$MODULENAME/bundles/ > /dev/null 2>&1
cp -r projects/$MODULENAME/src/assets/ dist/$MODULENAME/bundles/ > /dev/null 2>&1
# Step 3: Clean up
rm -rf dist/$MODULENAME/bundles/*.map
rm -rf dist/$MODULENAME/bundles/*.min*
rm -rf bundletmp
mv dist/$MODULENAME/bundles/ bundletmp
rm -rf dist/$MODULENAME/*
mv bundletmp/* dist/$MODULENAME/
rm -rf bundletmp
}
package() {
VERS=$(cat dist/$MODULENAME/module.json | grep "version" | awk '{split($0, a, ": "); gsub("\"", "", a[2]); gsub(",", "", a[2]); print a[2]}')
rm -rf $MODULENAME-$VERS.tar.gz
echo "[*] Packaging $MODULENAME (Version $VERS)"
cd dist/
tar -pczf $MODULENAME-$VERS.tar.gz $MODULENAME
mv $MODULENAME-$VERS.tar.gz ../
cd ../
}
copy_to_device() {
echo "[*] Copying module to WiFi Pineapple via SCP"
scp -r dist/$MODULENAME root@172.16.42.1:/pineapple/modules
}
main() {
check_workspace
build_module
if [[ $1 == "package" ]]; then
package
elif [[ $1 == "copy" ]]; then
copy_to_device
fi
echo "[*] Success!"
}
main $1

32223
hcxdumptool/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

52
hcxdumptool/package.json Normal file
View File

@ -0,0 +1,52 @@
{
"name": "hcxdumptool",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~9.1.11",
"@angular/cdk": "^9.2.4",
"@angular/common": "~9.1.11",
"@angular/compiler": "~9.1.11",
"@angular/core": "~9.1.11",
"@angular/flex-layout": "^9.0.0-beta.31",
"@angular/forms": "~9.1.11",
"@angular/material": "^9.2.4",
"@angular/platform-browser": "~9.1.11",
"@angular/platform-browser-dynamic": "~9.1.11",
"@angular/router": "~9.1.11",
"rxjs": "~6.5.5",
"tslib": "^1.10.0",
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.901.8",
"@angular-devkit/build-ng-packagr": "~0.901.8",
"@angular/cli": "~9.1.8",
"@angular/compiler-cli": "~9.1.11",
"@angular/language-service": "~9.1.11",
"@types/jasmine": "~3.5.10",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"codelyzer": "^5.1.2",
"jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~5.0.2",
"karma": "~5.1.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.3",
"karma-jasmine": "~3.3.1",
"karma-jasmine-html-reporter": "^1.4.0",
"ng-packagr": "^9.1.5",
"protractor": "~7.0.0",
"ts-node": "~8.10.2",
"tslint": "~6.1.2",
"typescript": "^3.6.5"
}
}

View File

@ -0,0 +1,7 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/hcxdumptool",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@ -0,0 +1,11 @@
{
"name": "hcxdumptool",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^8.2.14",
"@angular/core": "^8.2.14"
},
"scripts": {
"build": "ng build --prod"
}
}

View File

@ -0,0 +1,27 @@
<nav mat-tab-nav-bar color="accent">
<a mat-tab-link
*ngFor="let link of navLinks"
[routerLink]="link.link"
routerLinkActive #rla="routerLinkActive"
[routerLinkActiveOptions]="{exact: true}"
[active]="rla.isActive">
<mat-icon *ngIf="link.label === 'HCXdumptool'">
<svg style="width:24px;height:24px">
<path d="M 17 3 C 18.105469 3 19 3.894531 19 5 L 19 15 C 19 16.105469 18.105469 17 17 17 L 13 17 L 13 19 L 14 19 C 14.550781 19 15 19.449219 15 20 L 22 20 L 22 22 L 15 22 C 15 22.550781 14.550781 23 14 23 L 10 23 C 9.449219 23 9 22.550781 9 22 L 2 22 L 2 20 L 9 20 C 9 19.449219 9.449219 19 10 19 L 11 19 L 11 17 L 7 17 C 5.890625 17 5 16.101562 5 15 L 5 5 C 5 3.894531 5.894531 3 7 3 L 17 3 M 12 14.5 L 16.5 10 L 13 10 L 13 6 L 11 6 L 11 10 L 7.5 10 Z M 12 14.5 "></path>
</svg>
</mat-icon>
<mat-icon *ngIf="link.label === 'History'">
<svg style="width:24px;height:24px">
<path d="M4 14h4v-4H4v4zm0 5h4v-4H4v4zM4 9h4V5H4v4zm5 5h12v-4H9v4zm0 5h12v-4H9v4zM9 5v4h12V5H9z" />
</svg>
</mat-icon>
<div [fxShow.sm]="false" [fxShow.xs]="false">
&nbsp;&nbsp;
{{ link.label }}
</div>
</a>
</nav>
<br/>
<div>
<router-outlet></router-outlet>
</div>

View File

@ -0,0 +1,30 @@
import {Component, OnInit, ViewChild} from '@angular/core';
@Component({
selector: 'lib-hcxdumptool',
templateUrl: './hcxdumptool.component.html',
styleUrls: ['./hcxdumptool.component.css']
})
export class HcxdumptoolComponent implements OnInit {
navLinks: any[];
@ViewChild('rla') rla;
constructor() {
this.navLinks = [
{
label: 'Hcxdumptool',
link: './',
index: 0
},
{
label: 'History',
link: './history',
index: 1
},
]
}
ngOnInit() {
}
}

View File

@ -0,0 +1,12 @@
.confirmation-dialog-body-container {
display: flex;
flex-direction: column;
}
.confirmation-dialog-buttons {
margin-top: 16px;
}
.confirmation-dialog-button-first {
margin-right: 10px;
}

View File

@ -0,0 +1,19 @@
<div class="right-left-split-container">
<h1 mat-dialog-title>{{ title }}</h1>
<span fxFlex></span>
<button mat-icon-button (click)="closeDialog();">
<mat-icon>close</mat-icon>
</button>
</div>
<mat-divider></mat-divider>
<br/>
<div class="confirmation-dialog-body-container">
<p>{{ message }}</p>
<div class="confirmation-dialog-buttons">
<span fxFlex></span>
<button mat-raised-button class="confirmation-dialog-button-first" (click)="handleResponse(false);">No</button>
<button mat-raised-button color="warn" (click)="handleResponse(true);">Yes</button>
</div>
</div>

View File

@ -0,0 +1,32 @@
import {Component, Inject, OnInit} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {ConfirmationDialogDelegate} from "../../../interfaces/confirmationdialogdelegate.interface";
@Component({
selector: 'lib-confirmation-dialog',
templateUrl: './confirmation-dialog.component.html',
styleUrls: ['./confirmation-dialog.component.css']
})
export class ConfirmationDialogComponent implements OnInit {
constructor(public dialogRef: MatDialogRef<ConfirmationDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: ConfirmationDialogDelegate) {
this.title = data.title;
this.message = data.message;
}
public title: string;
public message: string;
closeDialog(): void {
this.dialogRef.close();
}
handleResponse(affirmative: boolean): void {
this.closeDialog();
this.data.handleResponse(affirmative);
}
ngOnInit(): void {
}
}

View File

@ -0,0 +1,3 @@
.error-dialog-buttons {
margin-top: 16px;
}

View File

@ -0,0 +1,19 @@
<div class="right-left-split-container">
<mat-card-title>Error</mat-card-title>
<span fxFlex></span>
<button mat-icon-button>
<mat-icon (click)="closeDialog();">close</mat-icon>
</button>
</div>
<mat-divider></mat-divider>
<div>
<p>{{ message }}</p>
<div class="error-dialog-buttons">
<span fxFlex></span>
<button mat-raised-button color="accent" class="error-dialog-button-first" (click)="closeDialog();">
Okay
</button>
</div>
</div>

View File

@ -0,0 +1,26 @@
import {Component, Inject, OnInit} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {ErrorDialogData} from "../../../interfaces/errordialogdata.interface";
@Component({
selector: 'lib-error-dialog',
templateUrl: './error-dialog.component.html',
styleUrls: ['./error-dialog.component.css']
})
export class ErrorDialogComponent implements OnInit {
constructor(public dialogRef: MatDialogRef<ErrorDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: ErrorDialogData) {
this.message = data.message;
}
public message: string;
closeDialog(): void {
this.dialogRef.close();
}
ngOnInit(): void {
}
}

View File

@ -0,0 +1,11 @@
.legal-textarea {
width: 100%;
min-height: 480px;
resize: none;
margin: 0 0 5px;
padding: 0;
background-color: #efefef;
border-radius: 2px;
border-color: #cecece;
max-lines: 50;
}

View File

@ -0,0 +1,36 @@
<div class="right-left-split-container">
<h1 mat-dialog-title>TCPDump License</h1>
<span fxFlex></span>
<button mat-icon-button (click)="closeDialog();">
<mat-icon>close</mat-icon>
</button>
</div>
<mat-divider></mat-divider>
<br/>
<div>
<textarea class="legal-textarea" readonly>
The MIT License (MIT)
Copyright (c) 2000-2023 ZeroBeat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</textarea>
</div>

View File

@ -0,0 +1,21 @@
import {Component, OnInit} from '@angular/core';
import {MatDialogRef} from "@angular/material/dialog";
@Component({
selector: 'lib-license-dialog',
templateUrl: './license-dialog.component.html',
styleUrls: ['./license-dialog.component.css']
})
export class LicenseDialogComponent implements OnInit {
constructor(public dialogRef: MatDialogRef<LicenseDialogComponent>) {
}
closeDialog(): void {
this.dialogRef.close();
}
ngOnInit(): void {
}
}

View File

@ -0,0 +1,24 @@
.right-left-split-container {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-self: flex-start;
}
.dependency-card-centered {
display: flex;
justify-content: center;
align-self: center;
}
.dependency-card-button {
width: 50%;
display: flex;
justify-content: center;
align-items: center;
}
.spinner-padding {
margin-top: 6px;
margin-bottom: 7px;
}

View File

@ -0,0 +1,31 @@
<div class="right-left-split-container">
<h1 mat-dialog-title>Uninstall Dependencies?</h1>
<span fxFlex></span>
<button mat-icon-button (click)="closeDialog();" [disabled]="isBusy">
<mat-icon>close</mat-icon>
</button>
</div>
<mat-divider></mat-divider>
<br/>
<div>
<p>You are about to uninstall dependencies for this module. You'll still be able to access your history
but will be unable to preform new packet captures.</p>
<p>Do you want to continue?</p>
<br/>
<div class="dependency-card-centered">
<button mat-raised-button color="warn"
class="dependency-card-button"
(click)="removeDependencies();"
[disabled]="isBusy">
<span *ngIf="isBusy">
<mat-spinner [diameter]="20" class="spinner-padding" color="accent"></mat-spinner>
</span>
<span *ngIf="!isBusy">
Uninstall Dependencies
</span>
</button>
</div>
</div>

View File

@ -0,0 +1,82 @@
import {Component, Inject, OnInit} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog";
import {ApiService} from "../../../services/api.service";
import {ErrorDialogComponent} from "../error-dialog/error-dialog.component";
import {JobResultDTO} from "../../../interfaces/jobresult.interface";
@Component({
selector: 'lib-uninstall-dialog',
templateUrl: './uninstall-dialog.component.html',
styleUrls: ['./uninstall-dialog.component.css']
})
export class UninstallDialogComponent implements OnInit {
constructor(public dialogRef: MatDialogRef<UninstallDialogComponent>,
private API: ApiService,
private dialog: MatDialog,
@Inject(MAT_DIALOG_DATA) public data: any) {
}
public isBusy: boolean = false;
private backgroundJobInterval = null;
private handleError(msg: string): void {
this.closeDialog();
this.dialog.open(ErrorDialogComponent, {
hasBackdrop: true,
width: '900px',
data: {
message: msg
}
});
}
private pollBackgroundJob<T>(jobId: string, onComplete: (result: JobResultDTO<T>) => void): void {
this.backgroundJobInterval = setInterval(() => {
this.API.request({
module: 'tcpdump',
action: 'check_background_job',
job_id: jobId
}, (response: JobResultDTO<T>) => {
if (response.is_complete) {
onComplete(response);
clearInterval(this.backgroundJobInterval);
}
});
}, 5000);
}
closeDialog(): void {
if (this.isBusy) { return; }
this.dialogRef.close();
}
removeDependencies(): void {
this.API.request({
module: 'tcpdump',
action: 'manage_dependencies',
install: false
}, (response) => {
if (response.error) {
this.handleError(response.error);
return;
}
this.isBusy = true;
this.pollBackgroundJob(response.job_id, (result: JobResultDTO<boolean>) => {
this.isBusy = false;
if (result.job_error !== null) {
this.handleError(result.job_error);
return;
}
this.data.onComplete();
this.closeDialog();
});
});
}
ngOnInit(): void {
}
}

View File

@ -0,0 +1,37 @@
<mat-card>
<mat-card-content>
<div class="right-left-split-container">
<mat-card-title>History</mat-card-title>
<span *ngIf="isBusy"><mat-spinner [diameter]="24" color="accent" style="margin-left: 8px"></mat-spinner></span>
<span fxFlex></span>
<mat-menu #historyMenu>
<button mat-menu-item (click)="loadHistory();">Refresh</button>
<button mat-menu-item (click)="showClearHistoryDialog();">Delete History</button>
</mat-menu>
<button mat-icon-button [matMenuTriggerFor]="historyMenu">
<mat-icon>more_vert</mat-icon>
</button>
</div>
<br/>
<mat-divider></mat-divider>
<br/>
<mat-table style="display: none;">
<mat-header-row *matHeaderRowDef="[]"></mat-header-row>
</mat-table>
<table class="mat-table" style="min-width: 100%; overflow-x: auto; justify-content: center;">
<tbody>
<ng-container *ngFor="let item of scanHistory">
<tr class="mat-row">
<td class="mat-cell">{{ item }}</td>
<td class="mat-cell"><button mat-icon-button (click)="downloadItem(item);"><mat-icon>cloud_download</mat-icon></button></td>
<td class="mat-cell"><button mat-icon-button (click)="showDeleteDialog(item);"><mat-icon>delete</mat-icon></button></td>
</tr>
</ng-container>
</tbody>
</table>
<i *ngIf="scanHistory.length == 0;">No capture history to display.</i>
</mat-card-content>
</mat-card>

View File

@ -0,0 +1,120 @@
import { Component, OnInit } from '@angular/core';
import {MatDialog} from "@angular/material/dialog";
import {ApiService} from "../../../services/api.service";
import {ConfirmationDialogComponent} from "../../helpers/confirmation-dialog/confirmation-dialog.component";
import {ErrorDialogComponent} from "../../helpers/error-dialog/error-dialog.component";
@Component({
selector: 'lib-hcxdumptool-history',
templateUrl: './hcxdumptool-history.component.html',
styleUrls: ['./hcxdumptool-history.component.css']
})
export class HcxdumptoolHistoryComponent implements OnInit {
constructor(private API: ApiService,
private dialog: MatDialog) {
}
public isBusy: boolean = false;
public scanHistory: Array<string> = [];
private handleError(msg: string): void {
this.dialog.closeAll();
this.dialog.open(ErrorDialogComponent, {
hasBackdrop: true,
width: '900px',
data: {
message: msg
}
});
}
loadHistory(): void {
this.isBusy = true;
this.API.request({
module: 'hcxdumptool',
action: 'list_capture_history'
}, (response) => {
this.isBusy = false;
if (response.error) {
this.handleError(response.error);
return;
}
this.scanHistory = response;
});
}
private deleteItem(item: string): void {
this.isBusy = true;
this.API.request({
module: 'hcxdumptool',
action: 'delete_capture',
output_file: item
}, (response) => {
this.isBusy = false;
if (response.error) {
this.handleError(response.error);
return;
}
this.loadHistory();
});
}
private deleteAll(): void {
this.isBusy = true;
this.API.request({
module: 'hcxdumptool',
action: 'delete_all'
}, (response) => {
this.isBusy = false;
if (response.error) {
this.handleError(response.error);
return;
}
this.loadHistory();
});
}
downloadItem(item: string): void {
this.API.APIDownload('/root/.hcxdumptool/' + item, item);
}
showDeleteDialog(item: string): void {
this.dialog.open(ConfirmationDialogComponent, {
hasBackdrop: true,
width: '900px',
data: {
title: 'Delete?',
message: 'You are about to delete ' + item + '? This action can not be undone. Are you sure you want to continue?',
handleResponse: (affirmative: boolean) => {
if (affirmative) {
this.deleteItem(item);
}
}
}
});
}
showClearHistoryDialog(): void {
this.dialog.open(ConfirmationDialogComponent, {
hasBackdrop: true,
width: '900px',
data: {
title: 'Delete All?',
message: 'You are about to delete all scan history. This action can not be undone. Are you sure you want to continue?',
handleResponse: (affirmative: boolean) => {
if (affirmative) {
this.deleteAll();
}
}
}
});
}
ngOnInit(): void {
this.loadHistory();
}
}

View File

@ -0,0 +1,65 @@
.right-left-split-container {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-self: flex-start;
}
.dependency-card {
max-width: 600px;
min-height: 246px;
}
.dependency-card-centered {
display: flex;
justify-content: center;
align-self: center;
}
.dependency-card-button {
width: 75%;
display: flex;
justify-content: center;
align-items: center;
}
.spinner-padding {
margin-top: 6px;
margin-bottom: 7px;
}
.controls-container {
display: flex;
width: 100%;
flex-wrap: wrap;
justify-content: space-between;
}
.control-item {
flex: 1;
margin-right: 2px;
margin-left: 2px;
}
.output-container {
width: 100%;
margin-top: 20px;
}
.control-input {
width: 100%;
}
.output-textarea {
width: 100%;
height: 300px;
min-height: 200px;
max-height: 600px;
resize: vertical;
margin: 0 0 5px;
padding: 0;
background-color: #efefef;
border-radius: 2px;
border-color: #cecece;
max-lines: 50;
}

View File

@ -0,0 +1,113 @@
<div class="dependency-card-centered" *ngIf="!hasDependencies">
<mat-card class="dependency-card">
<mat-card-content>
<mat-card-title>Welcome To hcxdumptool</mat-card-title>
<mat-card-subtitle>Lets get started.</mat-card-subtitle>
<mat-divider></mat-divider>
<br />
<p>You need to install some dependencies before you can use this module.</p>
<p>This will make a request to the internet.</p>
<br/>
<br/>
<div class="dependency-card-centered">
<button mat-raised-button color="accent"
class="dependency-card-button"
(click)="installDependencies()"
[disabled]="isInstalling">
<span *ngIf="isInstalling">
<mat-spinner [diameter]="20" class="spinner-padding" color="accent"></mat-spinner>
</span>
<span *ngIf="!isInstalling">
Install Dependencies
</span>
</button>
</div>
<br/>
<div class="dependency-card-centered">
<button mat-button color="accent" (click)="showLicenseDialog();">View hcxdumptool License</button>
</div>
</mat-card-content>
</mat-card>
</div>
<div *ngIf="hasDependencies" class="controls-container" fxLayoutGap="20px" fxLayout="row" fxLayout.xs="column" fxLayout.sm="column">
<mat-card class="control-item">
<mat-card-content>
<div class="right-left-split-container">
<mat-card-title>HCXdumptool</mat-card-title>
<span *ngIf="isCapturing"><mat-spinner [diameter]="24" color="accent" style="margin-left: 8px"></mat-spinner></span>
<span fxFlex></span>
<mat-menu #hcxdumptoolMenu>
<button mat-menu-item (click)="showUninstallDialog();">Uninstall Dependencies</button>
<button mat-menu-item (click)="showLicenseDialog();">Show License</button>
</mat-menu>
<button mat-icon-button [matMenuTriggerFor]="hcxdumptoolMenu">
<mat-icon>more_vert</mat-icon>
</button>
</div>
<form>
<mat-form-field style="width: 100%;">
<mat-label>Command</mat-label>
<input matInput class="control-input" name="command" [(ngModel)]="hcxDumptoolState.command"/>
</mat-form-field>
<mat-form-field style="width: 100%;">
<mat-label>Interface</mat-label>
<mat-select name="interface" [(ngModel)]="hcxDumptoolState.selectedInterface" (ngModelChange)="update();">
<mat-option>--</mat-option>
<mat-option *ngFor="let iface of interfaces" value="{{ iface }}">{{ iface }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field style="width: 100%;">
<mat-label>Band</mat-label>
<mat-select name="scanlist" [(ngModel)]="hcxDumptoolState.scanlist" (ngModelChange)="update();">
<mat-option>--</mat-option>
<mat-option value="-c 1,6,11,3,5,1,6,11,2,4,1,6,11,7,9,1,6,11,8,10,1,6,11,12,13">Optimized 2.4GHz</mat-option>
<mat-option value="-c 1,2,3,4,5,6,7,8,9,10,11,12,13">Standard 2.4 GHz</mat-option>
<mat-option value="-c 36,40,44,48,52,56,60,64,100,104,108,112,116,120,124,128,132,136,140,144,149,153,157,161,165">Standard 5GHz</mat-option>
<mat-option value="-c 1,2,3,4,5,6,7,8,9,10,11,12,13,36,40,44,48,52,56,60,64,100,104,108,112,116,120,124,128,132,136,140,144,149,153,157,161,165">Standard 2.4GHz/5GHz</mat-option>
</mat-select>
</mat-form-field>
<button mat-raised-button color="accent" style="width: 100%;" *ngIf="!isCapturing" (click)="startCapture();">Capture</button>
<button mat-raised-button color="warn" style="width: 100%;" *ngIf="isCapturing" (click)="stopCapture();">Stop</button>
</form>
</mat-card-content>
</mat-card>
<mat-card class="control-item">
<mat-card-content>
<div>
<mat-card-title>Options</mat-card-title>
</div>
<mat-checkbox [(ngModel)]="optionsState.disableClientAttacks.toggled" (ngModelChange)="update();">Do not attack clients</mat-checkbox>
<br/>
<mat-checkbox [(ngModel)]="optionsState.disableAPAttacks.toggled" (ngModelChange)="update();">Do not attack access points</mat-checkbox>
</mat-card-content>
</mat-card>
</div>
<br/>
<mat-card *ngIf="hasDependencies">
<mat-card-content>
<div class="output-container">
<div class="right-left-split-container">
<mat-card-title>Output</mat-card-title>
<span fxFlex></span>
<button mat-icon-button [disabled]="isFetchingOutput || !isCapturing" (click)="getLogContent();"><mat-icon>refresh</mat-icon></button>
<button mat-icon-button [disabled]="captureFileName == null || isCapturing" (click)="downloadPcap();"><mat-icon>cloud_download</mat-icon></button>
</div>
<textarea class="output-textarea" [(ngModel)]="captureOutput" placeholder="No output to display..." readonly></textarea>
<div class="right-left-split-container">
<span *ngIf="isCapturing" style="margin-right: 8px;"><i>Refreshes every 5 seconds...</i></span>
<span *ngIf="isFetchingOutput"><mat-spinner [diameter]="24" color="accent"></mat-spinner></span>
<span fxFlex></span>
</div>
</div>
</mat-card-content>
</mat-card>

View File

@ -0,0 +1,261 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {ApiService} from "../../../services/api.service";
import {MatDialog} from "@angular/material/dialog";
import {StartupDTO, StartupLastJob} from "../../../interfaces/startupdto.interface";
import {ErrorDialogComponent} from "../../helpers/error-dialog/error-dialog.component";
import {JobResultDTO} from "../../../interfaces/jobresult.interface";
import {UninstallDialogComponent} from "../../helpers/uninstall-dialog/uninstall-dialog.component";
import {OtherOptions, HCXDumptoolState} from "../../../interfaces/optionstate.interface";
import {LicenseDialogComponent} from "../../helpers/license-dialog/license-dialog.component";
@Component({
selector: 'lib-hcxdumptool-main',
templateUrl: './hcxdumptool-main.component.html',
styleUrls: ['./hcxdumptool-main.component.css']
})
export class HcxdumptoolMainComponent implements OnInit, OnDestroy {
public hasDependencies: boolean = true;
public isInstalling: boolean = false;
public interfaces: Array<string> = [];
public isFetchingOutput: boolean = false;
public isCapturing: boolean = false;
public captureFileName: string = null;
public captureOutput: string = null;
private backgroundJobInterval = null;
public hcxDumptoolState: HCXDumptoolState = {
command: 'hcxdumptool --enable_status=3 ',
selectedInterface: '',
scanlist: ''
};
public optionsState: OtherOptions = {
disableClientAttacks: { toggled: false, value: '--disable_client_attacks' },
disableAPAttacks: { toggled: false, value: '--disable_ap_attacks' },
};
constructor(private API: ApiService,
private dialog: MatDialog) {
}
private handleError(msg: string): void {
this.dialog.closeAll();
this.dialog.open(ErrorDialogComponent, {
hasBackdrop: true,
width: '500px',
data: {
message: msg
}
});
}
pollBackgroundJob<T>(jobId: string, onComplete: (result: JobResultDTO<T>) => void, onInterval?: Function): void {
this.backgroundJobInterval = setInterval(() => {
this.API.request({
module: 'hcxdumptool',
action: 'poll_job',
job_id: jobId
}, (response: JobResultDTO<T>) => {
if (response.is_complete) {
onComplete(response);
clearInterval(this.backgroundJobInterval);
} else if (onInterval) {
onInterval();
}
});
}, 5000);
}
getLogContent(): void {
this.isFetchingOutput = true;
this.API.request({
module: 'hcxdumptool',
action: 'get_log_content',
}, (response) => {
this.isFetchingOutput = false;
if (response.error) {
this.handleError(response.error);
return;
}
this.captureOutput = response;
});
}
downloadPcap(): void {
this.API.APIDownload('/root/.hcxdumptool/' + this.captureFileName, this.captureFileName);
}
private monitorCaptureJob(jobId: string, captureFile: string): void {
this.isCapturing = true;
this.captureFileName = captureFile;
this.pollBackgroundJob(jobId, (result: JobResultDTO<boolean>) => {
this.isCapturing = false;
this.getLogContent();
if (result.job_error) {
this.handleError(result.job_error);
}
}, () => {
this.getLogContent();
})
}
startCapture(): void {
this.isCapturing = true;
this.API.request({
module: 'hcxdumptool',
action: 'start_capture',
command: this.hcxDumptoolState.command
}, (response) => {
if (response.error !== undefined) {
this.isCapturing = false;
this.handleError(response.error);
return;
}
this.monitorCaptureJob(response.job_id, response.output_file);
});
}
stopCapture(): void {
this.API.request({
module: 'hcxdumptool',
action: 'stop_capture'
}, (response) => {
if (response.error !== undefined) {
this.handleError(response.error);
return;
}
this.isCapturing = false
})
}
checkForDependencies(): void {
this.API.request({
module: 'hcxdumptool',
action: 'check_dependencies'
}, (response) => {
if (response.error !== undefined) {
this.handleError(response.error);
return;
}
this.hasDependencies = response;
});
}
private monitorDependencies(jobId: string) {
this.isInstalling = true;
this.pollBackgroundJob(jobId, (result: JobResultDTO<boolean>) => {
this.isInstalling = false;
if (result.job_error !== null) {
this.handleError(result.job_error);
}
this.checkForDependencies();
});
}
installDependencies(): void {
this.API.request({
module: 'hcxdumptool',
action: 'manage_dependencies',
install: true
}, (response) => {
if (response.error !== undefined && response.error !== null) {
this.handleError(response.error);
return;
}
this.monitorDependencies(response.job_id);
});
}
showUninstallDialog(): void {
this.dialog.open(UninstallDialogComponent, {
hasBackdrop: true,
width: '700px',
disableClose: true,
data: {
onComplete: () => {
this.checkForDependencies();
}
}
});
}
update(): void {
this.hcxDumptoolState.command = 'hcxdumptool --enable_status=3 ' + this.updateInterface() +
this.updateScanlist() + this.updateOptions()
}
private updateInterface(): string {
const iface = this.hcxDumptoolState.selectedInterface;
return (iface !== '' && iface !== undefined) ? '-i ' + iface + ' ' : '';
}
private updateOptions(): string {
let returnValue: string = '';
for (let option of Object.values(this.optionsState)) {
if (option.toggled) {
returnValue += option.value + ' ';
}
}
return returnValue;
}
private updateScanlist(): string {
const scanlist = this.hcxDumptoolState.scanlist;
return (scanlist !== '' && scanlist !== undefined) ? scanlist + ' ' : '';
}
private rebind(lastJob: StartupLastJob): void {
switch (lastJob.job_type) {
case 'pcap':
this.monitorCaptureJob(lastJob.job_id, lastJob.job_info);
this.getLogContent();
break;
case 'opkg':
this.monitorDependencies(lastJob.job_id);
break;
}
}
private startup(): void {
this.API.request({
module: 'hcxdumptool',
action: 'startup'
}, (response: StartupDTO) => {
if (response.error !== undefined && response.error !== null) {
this.handleError(response.error);
return;
}
this.hasDependencies = response.has_dependencies;
this.interfaces = response.interfaces;
if (response.last_job.job_id !== null) {
this.rebind(response.last_job);
}
});
}
showLicenseDialog(): void {
this.dialog.open(LicenseDialogComponent, {
hasBackdrop: true,
width: '900px',
});
}
ngOnInit(): void {
this.startup();
}
ngOnDestroy() {
clearInterval(this.backgroundJobInterval);
}
}

View File

@ -0,0 +1,52 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HcxdumptoolComponent } from './components/hcxdumptool.component';
import { RouterModule, Routes } from '@angular/router';
import {MaterialModule} from './modules/material/material.module';
import {FlexLayoutModule} from '@angular/flex-layout';
import { HcxdumptoolMainComponent } from './components/subviews/hcxdumptool-main/hcxdumptool-main.component';
import { HcxdumptoolHistoryComponent } from './components/subviews/hcxdumptool-history/hcxdumptool-history.component';
import { ConfirmationDialogComponent } from './components/helpers/confirmation-dialog/confirmation-dialog.component';
import {FormsModule} from "@angular/forms";
import { ErrorDialogComponent } from './components/helpers/error-dialog/error-dialog.component';
import { UninstallDialogComponent } from './components/helpers/uninstall-dialog/uninstall-dialog.component';
import { LicenseDialogComponent } from './components/helpers/license-dialog/license-dialog.component';
const routes: Routes = [
{
path: '',
component: HcxdumptoolComponent,
children: [
{ path: '', component: HcxdumptoolMainComponent, pathMatch: 'full' },
{ path: 'history', component: HcxdumptoolHistoryComponent }
]
},
];
@NgModule({
declarations: [
HcxdumptoolComponent,
HcxdumptoolMainComponent,
HcxdumptoolHistoryComponent,
ConfirmationDialogComponent,
ErrorDialogComponent,
UninstallDialogComponent,
LicenseDialogComponent],
imports: [
CommonModule,
RouterModule.forChild(routes),
MaterialModule,
FlexLayoutModule,
FormsModule
],
exports: [HcxdumptoolComponent],
entryComponents: [
ConfirmationDialogComponent,
ErrorDialogComponent,
UninstallDialogComponent,
LicenseDialogComponent
]
})
export class hcxdumptoolModule { }

View File

@ -0,0 +1,5 @@
export interface ConfirmationDialogDelegate {
title: string;
message: string;
handleResponse: (affirmative: boolean) => void;
}

View File

@ -0,0 +1,3 @@
export interface ErrorDialogData {
message: string;
}

View File

@ -0,0 +1,5 @@
export interface JobResultDTO<T> {
is_complete: boolean;
job_error: string,
result: T
}

View File

@ -0,0 +1,15 @@
export interface HCXDumptoolState {
command: string;
selectedInterface: string;
scanlist: string;
}
export interface OtherOptions {
disableClientAttacks: ToggleOption;
disableAPAttacks: ToggleOption;
}
export interface ToggleOption {
toggled: boolean,
value: string
}

View File

@ -0,0 +1,12 @@
export interface StartupDTO {
error: string;
has_dependencies: boolean;
interfaces: Array<string>;
last_job: StartupLastJob;
}
export interface StartupLastJob {
job_id: string;
job_type: string;
job_info: string;
}

View File

@ -0,0 +1,107 @@
/*
* Copyright (c) 2018 Hak5 LLC.
*/
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { A11yModule } from '@angular/cdk/a11y';
import { BidiModule } from '@angular/cdk/bidi';
import { ObserversModule } from '@angular/cdk/observers';
import { OverlayModule } from '@angular/cdk/overlay';
import { PlatformModule } from '@angular/cdk/platform';
import { PortalModule } from '@angular/cdk/portal';
import { CdkStepperModule } from '@angular/cdk/stepper';
import { CdkTableModule } from '@angular/cdk/table';
import { CdkTreeModule } from '@angular/cdk/tree';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatBadgeModule } from '@angular/material/badge';
import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatChipsModule } from '@angular/material/chips';
import { MatNativeDateModule, MatRippleModule } from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialogModule } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
import { MatMenuModule } from '@angular/material/menu';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSliderModule } from '@angular/material/slider';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatSortModule } from '@angular/material/sort';
import { MatStepperModule } from '@angular/material/stepper';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatTreeModule } from '@angular/material/tree';
@NgModule({
imports: [ CommonModule],
exports: [
// CDK
A11yModule,
BidiModule,
ObserversModule,
OverlayModule,
PlatformModule,
PortalModule,
CdkStepperModule,
CdkTableModule,
CdkTreeModule,
// Material
MatAutocompleteModule,
MatBadgeModule,
MatBottomSheetModule,
MatButtonModule,
MatButtonToggleModule,
MatCardModule,
MatCheckboxModule,
MatChipsModule,
MatDatepickerModule,
MatDialogModule,
MatDividerModule,
MatExpansionModule,
MatFormFieldModule,
MatGridListModule,
MatIconModule,
MatInputModule,
MatListModule,
MatMenuModule,
MatNativeDateModule,
MatPaginatorModule,
MatProgressBarModule,
MatProgressSpinnerModule,
MatRadioModule,
MatRippleModule,
MatSelectModule,
MatSidenavModule,
MatSliderModule,
MatSlideToggleModule,
MatSnackBarModule,
MatSortModule,
MatStepperModule,
MatTableModule,
MatTabsModule,
MatToolbarModule,
MatTooltipModule,
MatTreeModule,
],
declarations: []
})
export class MaterialModule {
}

View File

@ -0,0 +1,196 @@
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Router} from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class ApiService {
public static totalRequests = 0;
apiModuleBusy = document.getElementById('ApiModuleBusy');
constructor(private http: HttpClient,
private router: Router) {}
emptyResponse = {error: 'Request returned empty response'};
unauth(): void {
localStorage.removeItem('authToken');
if (this.router.url !== '/Login' && this.router.url !== '/Setup') {
this.router.navigateByUrl('/Login');
}
}
setBusy(): void {
this.apiModuleBusy.style.display = 'block';
}
setNotBusy(): void {
this.apiModuleBusy.style.display = 'none';
}
static extractBaseHref(): string {
// Duplicated from injector because we have to be able to support
// a static method here
if (window['_app_base']) {
if (window['_app_base'].endsWith('/')) {
return window['_app_base'].slice(0, -1)
}
}
return window['_app_base'] || '';
}
request(payload: any, callback: (any) => void) {
this.setBusy();
let resp;
this.http.post(`${ApiService.extractBaseHref()}/api/module/request`, payload).subscribe((r: any) => {
if (r === undefined || r === null) {
resp = this.emptyResponse;
} else if (r.error) {
resp = r;
} else {
resp = r.payload;
}
}, (err) => {
resp = err.error;
if (err.status === 401) {
this.unauth();
}
this.setNotBusy();
callback(resp);
}, () => {
this.setNotBusy();
callback(resp);
});
ApiService.totalRequests++;
}
APIGet(path: string, callback: (any) => void): any {
ApiService.totalRequests++;
let resp;
this.http.get(`${ApiService.extractBaseHref()}${path}`).subscribe((r) => {
if (r === undefined || r === null) {
r = this.emptyResponse;
}
resp = r;
}, (err) => {
resp = err.error;
if (err.status === 401) {
this.unauth();
}
callback(resp);
}, () => {
callback(resp);
});
}
async APIGetAsync(path: string): Promise<any> {
ApiService.totalRequests++;
return await this.http.get(`${ApiService.extractBaseHref()}${path}`).toPromise();
}
APIPut(path: string, body: any, callback: (any) => void): any {
ApiService.totalRequests++;
let resp;
this.http.put(`${ApiService.extractBaseHref()}${path}`, body).subscribe((r) => {
if (r === undefined || r === null) {
r = this.emptyResponse;
}
resp = r;
}, (err) => {
resp = err.error;
if (err.status === 401) {
this.unauth();
}
callback(resp);
}, () => {
callback(resp);
});
}
async APIPutAsync(path: string, body: any): Promise<any> {
return await this.http.put(`${ApiService.extractBaseHref()}/${path}`, body).toPromise();
}
APIPost(path: string, body: any, callback: (any) => void): any {
ApiService.totalRequests++;
let resp;
this.http.post(`${ApiService.extractBaseHref()}${path}`, body).subscribe((r) => {
if (r === undefined || r === null) {
resp = this.emptyResponse;
}
resp = r;
}, (err) => {
resp = err.error;
if (err.status === 401) {
this.unauth();
}
callback(resp);
}, () => {
callback(resp);
});
}
async APIPostAsync(path: string, body: any): Promise<any> {
return await this.http.post(`${ApiService.extractBaseHref()}/${path}`, body).toPromise();
}
APIDelete(path: string, body: any, callback: (any) => void): any {
ApiService.totalRequests++;
const opts = {
headers: null,
body: body
};
let resp;
this.http.delete(`${ApiService.extractBaseHref()}/${path}`, opts).subscribe((r) => {
if (r === undefined || r === null) {
r = this.emptyResponse;
}
resp = r;
}, (err) => {
resp = err.error;
if (err.status === 401) {
this.unauth();
}
callback(resp);
}, () => {
callback(resp);
});
}
async APIDeleteAsync(path: string, body: any): Promise<any> {
return await this.http.delete(`${ApiService.extractBaseHref()}${path}`, body).toPromise();
}
APIDownload(fullpath: string, filename: string): void {
ApiService.totalRequests++;
const body = {
filename: fullpath
};
this.http.post(`${ApiService.extractBaseHref()}/api/download`, body, {responseType: 'blob'}).subscribe((r) => {
const url = window.URL.createObjectURL(r);
const a = document.createElement('a');
document.body.appendChild(a);
a.setAttribute('style', 'display: none');
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
a.remove();
});
}
}

View File

@ -0,0 +1,8 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class hcxdumptoolService {
constructor() {}
}

View File

@ -0,0 +1,10 @@
{
"name": "hcxdumptool",
"title": "Hcxdumptool",
"description": "Small tool to capture packets from wlan devices.",
"version": "1.0",
"author": "lorenzoPrimi",
"firmware_required": "1.0.0",
"devices": ["wifipineapplemk7", "wifipineappleent1"]
}

View File

@ -0,0 +1,175 @@
#!/usr/bin/env python3
import logging
import os
import pathlib
import subprocess
from datetime import datetime
from logging import Logger
from typing import List, Optional, Union, Tuple
from pineapple.modules import Module, Request
from pineapple.helpers.opkg_helpers import OpkgJob
from pineapple.helpers import opkg_helpers as opkg
import pineapple.helpers.notification_helpers as notifier
from pineapple.helpers import network_helpers as net
from pineapple.jobs import JobManager, Job
module = Module('hcxdumptool', logging.DEBUG)
job_manager = JobManager(name='hcxdumptool', module=module, log_level=logging.DEBUG)
PCAP_DIRECTORY_PATH = '/root/.hcxdumptool'
PCAP_DIRECTORY = pathlib.Path(PCAP_DIRECTORY_PATH)
class PcapJob(Job[bool]):
def __init__(self, command: List[str], file_name: str):
super().__init__()
self.file_name = file_name
self.command = command
self.pcap_file = f'{PCAP_DIRECTORY_PATH}/{file_name}'
self.proc = None
def do_work(self, logger: Logger) -> bool:
logger.debug('hcxdumptool job started.')
output_file = open('/tmp/hcxdumptool.log', 'w')
stderr_file = open('/tmp/hcxdumptool-err.log', 'w')
logger.debug(f'Calling hcxdumptool and writing output to {self.pcap_file}')
self.command += ['-o', self.pcap_file]
subprocess.call(self.command, stdout=output_file, stderr=stderr_file)
logger.debug('Scan completed.')
return True
def stop(self):
os.system('killall -9 hcxdumptool')
def _get_last_background_job() -> dict:
last_job_id: Optional[str] = None
last_job_type: Optional[str] = None
last_job_info: Optional[str] = None
if len(job_manager.jobs) > 0:
last_job_id = list(job_manager.jobs.keys())[-1]
last_job = job_manager.get_job(last_job_id, remove_if_complete=False)
if type(last_job) is PcapJob:
last_job_type = 'pcap'
last_job_info = last_job.file_name
elif type(last_job) is OpkgJob:
last_job_type = 'opkg'
else:
last_job_type = 'unknown'
return {
'job_id': last_job_id,
'job_type': last_job_type,
'job_info': last_job_info
}
def _notify_dependencies_finished(job: OpkgJob):
if not job.was_successful:
module.send_notification(job.error, notifier.ERROR)
elif job.install:
module.send_notification('hcxdumptool finished installing.', notifier.INFO)
@module.on_start()
def make_history_directory():
path = pathlib.Path(PCAP_DIRECTORY_PATH)
if not path.exists():
path.mkdir(parents=True)
@module.on_shutdown()
def stop_hcxdumptool(signal: int = None):
if len(list(filter(lambda job_runner: job_runner.running is True, job_manager.jobs.values()))) > 0:
module.logger.debug('Stopping hcxdumptool.')
os.system('killall -9 hcxdumptool')
@module.handles_action('check_dependencies')
def check_dependencies(request: Request):
return opkg.check_if_installed('hcxdumptool', module.logger)
@module.handles_action('manage_dependencies')
def manage_dependencies(request: Request):
return {'job_id': job_manager.execute_job(OpkgJob('hcxdumptool', request.install), callbacks=[_notify_dependencies_finished])}
@module.handles_action('start_capture')
def start_capture(request: Request):
command = request.command.split(' ')
filename = f"{datetime.now().strftime('%Y-%m-%dT%H-%M-%S')}.pcap"
job_id = job_manager.execute_job(PcapJob(command, filename))
return {'job_id': job_id, 'output_file': filename}
@module.handles_action('stop_capture')
def stop_capture(request: Request):
stop_hcxdumptool()
return True
@module.handles_action('list_capture_history')
def list_capture_history(request: Request):
return [item.name for item in PCAP_DIRECTORY.iterdir() if item.is_file()]
@module.handles_action('get_capture_output')
def get_capture_output(request: Request):
output_path = f'{PCAP_DIRECTORY_PATH}/{request.output_file}'
if not os.path.exists(output_path):
return 'Could not find scan output.', False
with open(output_path, 'r') as f:
return f.read()
@module.handles_action('get_log_content')
def get_log_content(request: Request):
if not os.path.exists('/tmp/hcxdumptool.log'):
return 'Could not find log output: /tmp/hcxdumptool.log', False
with open('/tmp/hcxdumptool.log', 'r') as f:
return f.read()
@module.handles_action('delete_capture')
def delete_capture(request: Request):
output_path = pathlib.Path(f'{PCAP_DIRECTORY_PATH}/{request.output_file}')
if output_path.exists() and output_path.is_file():
output_path.unlink()
return True
@module.handles_action('delete_all')
def delete_all(request: Request):
for item in PCAP_DIRECTORY.iterdir():
if item.is_file():
item.unlink()
return True
@module.handles_action('startup')
def startup(request: Request):
return {
'has_dependencies': opkg.check_if_installed('hcxdumptool', module.logger),
'interfaces': net.get_interfaces(),
'last_job': _get_last_background_job()
}
if __name__ == '__main__':
module.start()

View File

@ -0,0 +1 @@
<svg id="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M0 0h48v48h-48z" fill="none"/><path d="M41 19c.56 0 1.09.08 1.63.16l5.37-7.16c-6.69-5.02-15-8-24-8s-17.31 2.98-24 8l24 32 7-9.33v-5.67c0-5.52 4.48-10 10-10zm5 13v-3c0-2.76-2.24-5-5-5s-5 2.24-5 5v3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2v-8c0-1.1-.9-2-2-2zm-2 0h-6v-3c0-1.66 1.34-3 3-3s3 1.34 3 3v3z"/></svg>

After

Width:  |  Height:  |  Size: 396 B

View File

@ -0,0 +1,7 @@
/*
* Public API Surface of hcxdumptool
*/
export * from './lib/services/hcxdumptool.service';
export * from './lib/components/hcxdumptool.component';
export * from './lib/hcxdumptool.module';

View File

@ -0,0 +1,25 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@ -0,0 +1,6 @@
{
"extends": "./tsconfig.lib.json",
"angularCompilerOptions": {
"enableIvy": false
}
}

View File

@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@ -0,0 +1,17 @@
{
"extends": "../../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"lib",
"camelCase"
],
"component-selector": [
true,
"element",
"lib",
"kebab-case"
]
}
}

34
hcxdumptool/tsconfig.json Normal file
View File

@ -0,0 +1,34 @@
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"module": "esnext",
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2018",
"dom"
],
"paths": {
"hcxdumptool": [
"dist/hcxdumptool"
],
"hcxdumptool/*": [
"dist/hcxdumptool/*"
]
}
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true
}
}

79
hcxdumptool/tslint.json Normal file
View File

@ -0,0 +1,79 @@
{
"extends": "tslint:recommended",
"rulesDirectory": [
"codelyzer"
],
"rules": {
"array-type": false,
"arrow-parens": false,
"deprecation": {
"severity": "warning"
},
"import-blacklist": [
true,
"rxjs/Rx"
],
"interface-name": false,
"max-classes-per-file": false,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-consecutive-blank-lines": false,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-empty": false,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-switch-case-fall-through": true,
"no-var-requires": false,
"object-literal-key-quotes": [
true,
"as-needed"
],
"object-literal-sort-keys": false,
"ordered-imports": false,
"quotemark": [
true,
"single"
],
"trailing-comma": false,
"component-class-suffix": true,
"contextual-lifecycle": true,
"directive-class-suffix": true,
"no-conflicting-lifecycle": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-inputs-metadata-property": true,
"no-output-native": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"no-outputs-metadata-property": true,
"template-banana-in-box": true,
"template-no-negated-async": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true
}
}