Skip to content

Instantly share code, notes, and snippets.

@percontation
Last active August 29, 2015 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save percontation/005b586301e424fc4219 to your computer and use it in GitHub Desktop.
Save percontation/005b586301e424fc4219 to your computer and use it in GitHub Desktop.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
printf("Hi, I'm %s\n", argv[0]); // The first "argument" is the path-to-self
if(argc != 2) { // argc is how long argv is, including that path-to-self
puts("You need to give me exactly one argument!");
return -1;
}
// if statements, character and string constants, && and ||, and
// comparisions work a lot like Java or any other language.
if(argv[1][0] == 't') {
if(argv[1][1] == 'h' && argv[1][2] == 'i' && argv[1][3] == 's') {
// In C, characters are just numbers from 0 to 255.
if(argv[1][4] == 95 && argv[1][5] == 105 && argv[1][6] == 0x73) {
puts("half way there!");
if(argv[1][7] != 0x5f) return 0;
// Since characters are just numbers, we can bit-flip them!
if(argv[1][8] != ~'t' || argv[1][9] != ~'h' || argv[1][10] != ~'e') {
puts("noooo!");
return 0;
}
// Lastly, "strings" are really just pointers to characters.
// argv[1][11] is the 12th character of argv[1]. So, &argv[1][11]
// points at that 12th character, which in C is basically just
// the argv[1] string from the 12th character onwards.
if(strcmp("_pass\x99word", &argv[1][11]) == 0) {
// strcmp returns 0 if the strings match, and non-zero if
// they don't. You can type `man strcmp` (without backticks)
// at the terminal to get more detailed information on strcmp.
puts("yep, that's it!");
system("sh -i");
return 0;
}
}
}
}
puts("nope!");
return 0;
}

binary_demo1 is a program that resembles several of the picoCTF binary challenges.

When supplied with a correct command line argument, binary_demo1 will invoke a shell. For real picoCTF challenges, the challenge binary will often be a setgid binary, which causes extra privileges to come with any shell invoked from within that binary (i.e. the privileges to read a ./flag file). However, this demo binary is not setgid, so anything you could do from shell that gets invoked inside of it was already in fact achievable from shell you invoked binary_demo1 from.

binary_demo1 exercises some basic C syntax. Teaching C is beyond the scope of these educational materials, but most picoCTF challenges require nothing more than you might already know from AP CS Java or could pick up in a few hours with online C tutorials.

Reading the binary_demo1.c, it's not too hard to see that the "solution" to this demo is entering something like ./binary_demo1 this_is_the_password on the command line. However, the actual argument you need to pass has non-ASCII characters on it.

I myself would use the python command python -c 'print([hex((~ord(c))%256) for c in "the"])' to check out exactly what those bitwise-complemented letters from binary_demo1.c actually are. Then, there are several ways to pass command-line arguments involving non-ASCII characters:

  • Bash escapes: ./binary_demo1 $'this_is_\x8b\x97\x9a_pass\x99word'

    In the bash shell, passing a string like $'string' allows backslash escape sequences to be used in the string, such as the hex codes shown above.

  • Command substitution: ./binary_demo1 "$(cat /tmp/myfile)"

    In any standard shell, you can use the output of another command as a command line argument like so: "$(command arg1 arg2 ...)". In the above case, we're using the contents of file /tmp/myfile as our argument via the cat command. (You can type man cat on the terminal to get more information about the cat command).

  • Execing via python, perl, ruby, etc.

    If you're comfortable in a scripting language, most have some way of invoking a binary with specific args. For example, in python you could

    from os import execl execl('./binary_test1','./binary_test1','this_is_\x8b\x97\x9a_pass\x99word')

    Heads up: For many "exec" like functions (including in languages other than python), you'd seemingly need to pass ./binary_test1 twice: once to state where the binary_test1 binary is, and the second time to set the argv[0] value that binary_test1 will see. (Programs don't usually care what argv[0] actually is, but something still needs to be there.)

For additional information about C or the bash shell, the internet is a great resource ^_^

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
setbuf(stdin, NULL); // Don't worry about this.
// Unless you're a C-pro, in which case, trivia question:
// why might the program break without this? :P
// Three "line" buffers, all initialized to be filled with null bytes.
char line1[20];
bzero(line1, 20);
char line2[20];
memset(line2, 0, 20);
// This might look like just the first byte is set to a null byte,
// but no, the full buffer gets zeroed.
char line3[20] = {0};
// All different ways of reading from the terminal. Although they all do
// the same thing, they all have slightly different behavior. Use the
// terminal manual pages to learn more! In all cases, up to 19 characters
// are read, so line1, line2, and line3 will always be null terminated.
// This reads up to a newline, or up to 19 chars, whichever comes first.
fgets(line1, 19, stdin);
// This does NOT stop at a newline, and keeps reading a full 19 chars (including
// newlines). (It might read less than 19 chars if the input ends, though.)
fread(line2, 1, 19, stdin);
// This MAY OR MAY NOT stop reading at newlines, depending on the circumstances.
// (Usually, it will stop and newlines when entering stuff live at the terminal,
// but not when piping input in from a file).
read(STDIN_FILENO, line3, 19); // STDIN_FILENO is just 0
// line1X is a string that starts at the second half of the line1 buffer.
// This string now "overlaps" with the "line1" string, since both line1 and
// line1X point to the same buffer space.
char *line1X = &line1[10];
// This next line overwrites the 10th character of the line1 buffer with a null
// byte. Strings in C end at null bytes, so now "line1" is like a string that
// is up to 9 characters long (if the first line entered was shorter than 9
// characters, then the line1 buffer already has an earlier null byte and
// thus "line1" continues to represent a string shorter than 9 characters).
// After these lines, line1X and line1 still point to the same buffer space,
// but will act like independent strings since the "line1" string certainly
// ends before the "line1X" string starts.
line1X[-1] = 0;
line1[9] = 0; // This line is useless: it "changes" exactly the same byte as
// the previous line, we already just set that byte to zero!
// (It does make more sense than the previous line though,
// because negative indexing is kinda confusing.)
if(strcmp(line1, line1X) != 0) {
puts("fail!");
return 1;
}
int i;
for(i = 0; i < strlen(line2); i++) {
// Modify line2 by XORing it's bytes.
line2[i] ^= "banannabanannabanannabananna"[i];
}
for(i = 0; i < strlen(line3); i++)
line3[i] += 0x80; // Modify line3 by incremeting its bytes.
// Note that chars are 8 bit, so this is likely to wrap.
if(strcmp(line2, line3) == 0) {
// If they match, run the lines as a command. In a CTF problem,
// you'll want a line that says "sh" or something.
system(line3);
} else {
puts("NOOOOOOOOOOOOOOOOOOO");
return 1;
}
return 0;
}

binary_demo2 is a program that resembles several of the picoCTF binary challenges.

When supplied with the correct data to stdin, binary_demo2 will invoke an arbitrary shell command.

binary_demo2 shows off some common ways for programs to read in data, and some basic C string manipulation.

There are many valid "solutions" to this demo challenge, but all involve feeding non-ASCII data to binary_demo2. Furthermore, the most common ways to feed binary data into a program, input redirection and piping, might not be the best idea here because then you'd loose your ability to interact with the program (important when the binary you're attacking does a system("sh") at the end or something). Here are your options:

  • Input redirection: ./binary_demo2 < /tmp/myfile

    Input redirection causes a command to read its input from the specified file, rather than take it interactively from the user at the terminal. This method is not advisable for system("sh") binary exploitation challenges; when using this method, sh will not have access to the terminal and you can't send it commands. (You might expect that you could append sh commands at the end of your data and have them run, but this often does not work).

  • Piping: printf '\x00\n\x07\x02\x06\x0eN\x19QR\x15\x00_________\xe5\xe3\xe8\xef\xa0\xf7\xb0\xb0\xf4\x00' | ./binary_demo2

    In shell piping, the output of the first command is used as the input to the second. In the above command, I've used the printf utility (kinda like the printf() C function, but it's a command-line utility) to generate the non-ASCII data we needed. Writing a short perl or python script for expressing your data is also a popular technique. This suffers from the same "can't pass sh commands" issue as input redirection, but fortunately, there's a fix. Read on.

  • Piping with a twist: { printf '\x00\n\x11\t\x00________________\xf3\xe8\x00'; cat -;} | ./binary_demo2

    The above trick makes piping work great with system("sh") binary exploitation challenges. After printf (or your command of choice) is done generating the input for ./binary_demo2, the cat - command takes over producing input for ./binary_demo2. Since the cat - command gets its input from the terminal, it will forward what you type to sh---assuming your earlier bytes solved the challenge!

  • Running via python or some other scripting language:

    Your scripting language of choice probably has a facility for launching processes and controlling their input and output. For python, the subprocess module is what you'd want to use for this. This method has the advantage that your exploit can be interactive---that is, it can change what input it's feeding to the binary depending on the output the binary produces. You won't need this ability for most picoCTF challenges, but it will come up in the later networked exploitation problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment