Recently, I have been going back through my CTF challenge library and completing old challenges which I could not complete at the time. One of these was the “blag” challenge from TJCTF – I found this one particularly interesting, as learning how to solve this problem taught me how to leak information via the stack smashing protector.
A word of disclaimer: the technique I used (as well as the pwntools code) I learned from another excellent writeup by 0x90r00t, here: I’m primarily writing this up for more clarity, and because it took me ages to understand the writeup, and I want to remember how I did this 😀
blag – TJCTF
This challenge was presented as a binary, and halfway through the CTF, source code was provided as a hint. You can download the entire package, as well as the files needed to run it (admin password + blog data) here: blag_pack.
This binary appears to be a simple menu-driven blogging platform: it starts with three blog posts, one of which presumably contains the flag, but requires admin access to read (and we don’t have an admin password):
Our first task is to identify the vulnerability: for brevity, we’ll go via the source code. Some manual inspection reveals a vulnerability in the “add post” functionality. Each post is stored in a structure like this:
9 typedef struct {
10 int adminonly;
11 char author[32];
12 char title[32];
13 char body[320];
14 } Post;
However, the “add post” code looks like this:
38 void addpost() {
39 if (numposts >= MAXPOSTS) {
40 printf("Blag is full!\n");
41 return;
42 }
43 Post* p = posts[numposts];
44 char buf[32];
45 printf("Author?\n");
46 readline(buf,sizeof(p->author),stdin);
47 strcpy(p->author,buf);
48 printf("Title?\n");
49 readline(buf,sizeof(p->title),stdin);
50 strcpy(p->title,buf);
51 printf("Body?\n");
52 readline(buf,sizeof(p->body),stdin);
53 strcpy(p->body,buf);
54 numposts++;
55 }
On Line 52, the readline function is called, retrieving sizeof(p->body) (320) characters, into a 32-character buffer on the stack. This can be used to trigger a straightforward buffer overflow. However:
A bit of background: this is caused by the presence of a stack canary (in Microsoft terminology, a “Stack Cookie”). This mechanism works by generating a random value at the beginning of each process instance, and then placing this random value at the end of each function’s stack space. This is recognizable in IDA as code like the following:
When a function exits, that value is checked against a stored copy in gs[0x14]: if the values don’t match, someone’s overwritten the stack cookie (i.e. tried a stack overflow), and we call __stack_chk_fail from GLIBC. However, this is not game over.
Glibc is open source, and we can find out what this function does easily. We find that __stack_chk_fail is a wrapper call to __fortify_fail (from debug/stack_chk_fail.c), which looks like this:
24 void
25 __attribute__ ((noreturn))
26 __fortify_fail (msg)
27 const char *msg;
28 {
29 /* The loop is added only to keep gcc happy. */
30 while (1)
31 __libc_message (2, "*** %s ***: %s terminated\n",
32 msg, __libc_argv[0] ?: "<unknown>");
33 }
In common use, command-line arguments in argv start from index 1: however, argv[0] is the name of the binary you executed, and if you rename your binary, argv[0] will change.
We can find argv further up the stack: if we follow the program’s execution to the beginning, we can see a call to __libc_start_main, which takes argv as an argument. In diagram form:
In a nutshell, if we exploit the stack overflow but then *keep overflowing*, we may be able to reach argv[0] on the stack, and replace it with a pointer of our choosing. We can leverage this to leak a string from the binary (and by “a string”, I mean “the admin password”).
(If this doesn’t make sense, I suggest brute-forcing the overflow’s length: that is, simply increase the amount of data you send until the stack smashing message changes from “*** stack smashing detected *** : ./blogbin has terminated” to “*** stack smashing detected *** : has terminated”. At this point, you’ve overwritten argv[0].)
In this case, the string we want to leak is our admin password, helpfully stored in the BSS section.
Our first task is to brute force the length of the overflow payload, such that we can control argv[0]:
Note that when we pass 295 bytes to the input, we can see that the pointer to argv[0] is corrupt and we no longer print the program name. From there, it is a simple matter of leaking the admin password, by specifying a new argv[0] which points to 0x804B080 (admin password in memory):
From here, we simply log in to the application and read the 0th blog post, which would have contained the flag:
You can find the code which I used for this here.
Again, full credit goes to the 0x90r00t team for their work which showed me how to do this, as well as the TJCTF organisers for putting together a fantastic CTF event.