commit b63786cc921293dd86eef458da68a58462c350c9 Author: Ghostie Date: Sat Feb 1 21:35:22 2025 +0000 first commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..2aaef5f --- /dev/null +++ b/.clang-format @@ -0,0 +1,274 @@ +--- +Language: Cpp +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseArrows: false + AlignCaseColons: false +AlignConsecutiveTableGenBreakingDAGArgColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveTableGenCondOperatorColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveTableGenDefinitionColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowBreakBeforeNoexceptSpecifier: Never +AllowShortBlocksOnASingleLine: Never +AllowShortCaseExpressionOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: false +AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: All +AlwaysBreakBeforeMultilineStrings: false +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterExternBlock: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: true + IndentBraces: true + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAdjacentStringLiterals: true +BreakAfterAttributes: Leave +BreakAfterJavaFieldAnnotations: false +BreakAfterReturnType: AllDefinitions +BreakArrays: true +BreakBeforeBinaryOperators: All +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: GNU +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakFunctionDefinitionParameters: false +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +BreakTemplateDeclarations: MultiLine +ColumnLimit: 79 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLines: + AtEndOfFile: false + AtStartOfBlock: true + AtStartOfFile: true +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: '' +MacroBlockEnd: '' +MainIncludeChar: Quote +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: BinPack +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakScopeResolution: 500 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +PPIndentWidth: -1 +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SkipMacroDefinitionBody: false +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: Always +SpaceBeforeParensOptions: + AfterControlStatements: false + AfterForeachMacros: false + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: false + AfterOverloadedOperator: false + AfterPlacementOperator: true + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + ExceptDoubleParentheses: false + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: c++03 +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TableGenBreakInsideDAGArg: DontBreak +TabWidth: 8 +UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +... + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..606af5a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +#*# +*~ +bin/beroot \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5985ec1 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +CC=gcc +ECHO=echo -e + +CFLAGS=-Wall -Werror -std=gnu99 -O0 -g +LIBS=-lcrypt + +FILES=build/main.o +OUT=bin/beroot + +all: $(FILES) + @$(ECHO) "Linking program" + @$(CC) $(CFLAGS) $(FILES) -o $(OUT) $(LIBS) + @$(ECHO) "Linking finished" + +build/main.o: src/main.c src/config.h + @$(ECHO) "CC\t\t"$< + @$(CC) $(CFLAGS) $< -c -o $@ $(LIBS) + +install: all + @$(ECHO) "Installing beroot (you MUST be root to install beroot)" + @cp $(OUT) /usr/bin + @chown root:root /usr/bin/beroot # set permissions + @chmod u+s /usr/bin/beroot + @$(ECHO) "Installation successful" + +uninstall: all + @$(ECHO) "Uninstalling beroot (you MUST be root to uninstall beroot)" + @rm -f /usr/bin/beroot + @$(ECHO) "Uninstallation successful" + +clean: + @$(ECHO) "Cleaning files..." + @rm -f $(FILES) $(OUT) + @$(ECHO) "Cleaning complete :)" diff --git a/Readme.org b/Readme.org new file mode 100644 index 0000000..46a4feb --- /dev/null +++ b/Readme.org @@ -0,0 +1,102 @@ +* Beroot + +Beroot is just another privilege escalation tool. I made Beroot for two reasons: +- I wanted to learn (I discovered a lot of new functions in the standard + library) +- Sudo is bloated, and wanted to do something smaller - but usable - than doas. + +** Usage + +Beroot is just two files: ~main.c~ the source of beroot, ~config,h~ the +configurations of the tool. Beroot doesn't have any ~.conf~ files that need to be +parsed or something similar, all the changes are made to the ~config.h~ file. + +First, you'll need to build the program: + +#+begin_src bash + make +#+end_src + +Now, you'll need to login as root to execute the following commands: + +#+begin_src bash + make install +#+end_src + +That command will copy ~./bin/beroot~ to ~/usr/bin~ and set the appropriate permissions to it. + +** Configuration + +The only thing you need to modify is the ~permissions~ variable in ~config.h~. This +file contains the following structure: + +#+begin_src C + struct user_permissions + { + const char name[16384]; + const char target_user[16384]; + unsigned char permissions; + }; +#+end_src + +It only has three fields: +- ~name~: The name of the user you expect can use beroot (like the ones that + belong to the ~wheel~ group) +- ~target_user~: In case you want a user not to be root, but rather a different + user that exists on the machine, you can specify their name using this + variable. +- ~permissions~: Here you can set some of the following permission bits: + - ~PERM_ROOT~: If this is set, the user will execute commands as root, + ~target_user~ will be ignored in this case. + - ~PERM_USER~: If this bit is present, the user will execute commands as + ~target_user~ instead of root. If both ~PERM_ROOT~ and ~PERM_USER~ are present, + the last one is ignored as ~PERM_ROOT~ is checked first in the code. + - ~PERM_PASSWD~: If this is set, beroot will ask for the target user password + every time that the command is ran. + - ~PERM_NOPASSWD~: If this bit is present, beroot won't ask for the target user + password, it will just execute the specified command. Notice that either + ~PERM_PASSWD~ or ~PERM_NOPASSWD~ must be present, if none of them are, beroot + will display an error. + +For example, if we have a user called ~lain~ and we want this user to execute +commands as root, and not ask for a password, we'd add the following to the +~permissions~ variable in ~config.h~: + +#+begin_src C + const static struct user_permissions permissions[] = { + { .name = "lain", + .target_user = "\0", + .permissions = PERM_ROOT | PERM_NOPASSWD } + }; +#+end_src + +In that case, we are setting the user ~lain~ with the permissions of ~PERM_ROOT~ +(execute commands as root) and ~PERM_NOPASSWD~ (don't ask for a password). + +If we want to have multiple rules, we can just separate them by a comma (,): + +#+begin_src C + const static struct user_permissions permissions[] + = { { .name = "lain", + .target_user = "\0", + .permissions = PERM_ROOT | PERM_NOPASSWD }, + { .name = "alyssa", + .target_user = "dbuser", + .permissions = PERM_USER | PERM_PASSWD } }; +#+end_src + +In that code now we have two users: ~lain~ with the settings we mentioned before, +and a new user ~alyssa~ that will execute commands as ~dbuser~ and ~PERM_PASSWD~ will +make that she has to enter her password every time she runs ~beroot~. + +** TODO + +I don't want to make this a big program, I want to keep everything as simple as +possible, but there are some things that I want to do: +- Optimise the code, I wrote this in like 1 hour just to test things in the + standard library. +- Some kind of persistency (there can be a ~PERM_PERSIST~ flag) so the password + doesn't need to be entered every time. +- Hide the password while it's being typed. + +Those are the only things I have planned adding to beroot. diff --git a/bin/.gitkeep b/bin/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/build/.gitkeep b/build/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..6e00125 --- /dev/null +++ b/src/config.h @@ -0,0 +1,25 @@ +#ifndef __CONFIG_H +#define __CONFIG_H + +#include + +#define LOGIN_PASSWD_MAX 16384 // i think this is enough for a password? + +#define PERM_ROOT 1 << 0 /* will be the root user */ +#define PERM_USER 1 << 1 /* will be a custom specified user */ +#define PERM_PASSWD 1 << 2 /* password is required */ +#define PERM_NOPASSWD 1 << 3 /* password is not required */ + +struct user_permissions +{ + const char name[LOGIN_NAME_MAX]; + const char target_user[LOGIN_NAME_MAX]; + unsigned char permissions; +}; + +const static struct user_permissions permissions[] + = { { .name = "lain", + .target_user = "\0", + .permissions = PERM_ROOT | PERM_NOPASSWD } }; + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..46cd65f --- /dev/null +++ b/src/main.c @@ -0,0 +1,181 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include "config.h" + +void +print_usage () +{ + puts ("usage: beroot command [args]"); +} + +char * +get_current_user_name () +{ + struct passwd *pwd = getpwuid (getuid ()); + if (!pwd) + { + puts ("couldn't obtain your pwd"); + return NULL; + } + + return pwd->pw_name; +} + +const struct user_permissions * +user_in_permitted_users (char *name) +{ + int total_users + = (int)(sizeof (permissions) / sizeof (struct user_permissions)); + + for (int i = 0; i < total_users; i++) + { + const struct user_permissions *current_user = &permissions[i]; + if (!current_user) + continue; + + if (strncmp (name, current_user->name, LOGIN_NAME_MAX) == 0) + return current_user; + } + + return NULL; +} + +struct spwd * +obtain_target_swpd (const char *name) +{ + if (!name) + return NULL; + + return getspnam (name); +} + +_Bool +authenticate_user (char *password, char *entered_password) +{ + // the entered password ends with a \n, we remove it here + entered_password[strcspn (entered_password, "\n")] = '\0'; + size_t password_len = strlen (password); + + char *encrypted_password = crypt (entered_password, password); + int result = strncmp (password, encrypted_password, password_len); + if (result == 0) + return 1; + + return 0; +} + +void +execute (char **command) +{ + size_t total_commands = 0; + size_t maximum_commands = 10; + char **commands = calloc (sizeof (char *), maximum_commands); + if (!commands) + { + fputs ("Couldn't parse the commands\n", stderr); + return; + } + + while (*command != NULL) + { + if (total_commands >= maximum_commands) + { + maximum_commands *= 2; + commands = realloc (commands, sizeof (char *) * maximum_commands); + } + + commands[total_commands] = *command; + + command++; + total_commands++; + } + + commands[total_commands] = NULL; + execvp (commands[0], commands); + + free (commands); + commands = NULL; +} + +int +main (int argc, char *argv[]) +{ + if (argc < 2) + { + print_usage (); + return EXIT_FAILURE; + } + + char *user_name = get_current_user_name (); + if (!user_name) + return EXIT_FAILURE; + + const struct user_permissions *user = user_in_permitted_users (user_name); + if (!user) + { + fprintf (stderr, "%s is not allowed to run commands, aborting\n", + user_name); + return EXIT_FAILURE; + } + + struct spwd *target_swpd = user->permissions & PERM_ROOT + ? obtain_target_swpd ("root") + : obtain_target_swpd (user->target_user); + if (!target_swpd) + { + fputs ("couldn't obtain the target user's shadow\n", stderr); + return EXIT_FAILURE; + } + + struct passwd *target_passwd = getpwnam (target_swpd->sp_namp); + if (!target_passwd) + { + fputs ("couldn't obtain the passwd for the target user\n", stderr); + return EXIT_FAILURE; + } + + // TODO: Give the user 3 attempts + _Bool is_authenticated = 0; + if (user->permissions & PERM_NOPASSWD) + { + is_authenticated = 1; + } + else if (user->permissions & PERM_PASSWD) + { + printf ("Enter the %s password: ", user->target_user); + char entered_password[LOGIN_PASSWD_MAX] = { 0 }; + fgets (entered_password, LOGIN_PASSWD_MAX - 1, stdin); + is_authenticated + = authenticate_user (target_swpd->sp_pwdp, entered_password); + } + else + { + fputs ( + "Please specify an authentication way, either PASSWD or NOPASSWD\n", + stderr); + return 0; + } + + if (!is_authenticated) + { + fputs ("Couldn't authenticate you, please try again\n", stderr); + return EXIT_FAILURE; + } + + if (setuid (target_passwd->pw_uid) != 0) + { + fputs ("Couldn't switch to the target user\n", stderr); + return EXIT_FAILURE; + } + + execute (argv + 1); + + return EXIT_SUCCESS; +}