1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
| __int64 __fastcall challenge(int a1, __int64 a2, __int64 a3) { int *v3; char *v4; _QWORD v6[3]; int v7; unsigned int v8; size_t nbytes; void *buf; __int64 v11[14]; int v12; char v13; unsigned __int64 v14; __int64 savedregs; void *retaddr;
v7 = a1; v6[2] = a2; v6[1] = a3; v14 = __readfsqword(0x28u); memset(v11, 0, sizeof(v11)); v12 = 0; v13 = 0; buf = v11; nbytes = 0LL; puts("The challenge() function has just been launched!"); sp_ = (__int64)v6; bp_ = (__int64)&savedregs; sz_ = ((unsigned __int64)((char *)&savedregs - (char *)v6) >> 3) + 2; rp_ = (__int64)&retaddr; puts("Before we do anything, let's take a look at challenge()'s stack frame:"); DUMP_STACK(sp_, sz_); printf("Our stack pointer points to %p, and our base pointer points to %p.\n", (const void *)sp_, (const void *)bp_); printf("This means that we have (decimal) %d 8-byte words in our stack frame,\n", sz_); puts("including the saved base pointer and the saved return address, for a"); printf("total of %d bytes.\n", 8 * sz_); printf("The input buffer begins at %p, partway through the stack frame,\n", buf); puts("(\"above\" it in the stack are other local variables used by the function)."); puts("Your input will be read into this buffer."); printf("The buffer is %d bytes long, but the program will let you provide an arbitrarily\n", 117LL); puts("large input length, and thus overflow the buffer.\n"); puts("In this level, there is no \"win\" variable."); puts("You will need to force the program to execute the win_authed() function"); puts("by directly overflowing into the stored return address back to main,"); printf( "which is stored at %p, %d bytes after the start of your input buffer.\n", (const void *)rp_, rp_ - (_QWORD)buf); printf( "That means that you will need to input at least %d bytes (%d to fill the buffer,\n", rp_ - (_QWORD)buf + 8, 117LL); printf("%d to fill other stuff stored between the buffer and the return address,\n", rp_ - (_QWORD)buf - 117); puts("and 8 that will overwrite the return address).\n"); cp_ = bp_; cv_ = __readfsqword(0x28u); while ( *(_QWORD *)cp_ != cv_ ) cp_ -= 8LL; puts("While canaries are enabled, this networked program forks."); puts("What is important to note is that the canary does not get re-randomized on fork.\n"); puts("When data that you are overflowing into is critical (i.e., if you screw it up"); puts("the program crashes), but also static across executions, you can brute-force"); puts("it byte by byte over many attempts.\n"); puts("So, let's brute-force the canary!"); puts("If this is your first time running this program, all you know so far is that"); puts("the canary has a 0 as its left-most byte."); puts("You should proceed like this:\n"); puts("- First, you should try overflowing just the null byte of the canary, for"); printf(" practice. The canary starts at %p, which is %d bytes after the\n", (const void *)cp_, cp_ - (_QWORD)buf); printf(" start of your buffer. Thus, you should provide %d characters followed\n", cp_ - (_QWORD)buf); puts(" by a NULL byte, make sure the canary check passes, then try a non-NULL"); puts(" byte and make sure the canary check fails. This will confirm the offsets."); puts("- Next try each possible value for just the next byte. One of them (the same"); puts(" as whatever was there in memory already) will keep the canary intact, and"); puts(" when the canary check succeeds, you know you have found the correct one."); puts("- Go on to the next byte, leak it the same way, and so on, until you have"); puts(" the whole canary.\n"); puts("You will likely want to script this process! Each byte might take up to 256"); puts("tries to guess..\n"); puts("Because the binary is position independent, you cannot know"); puts("exactly where the win_authed() function is located."); puts("This means that it is not clear what should be written into the return address.\n"); printf("Payload size: "); __isoc99_scanf("%lu", &nbytes); printf("You have chosen to send %lu bytes of input!\n", nbytes); printf("This will allow you to write from %p (the start of the input buffer)\n", buf); printf( "right up to (but not including) %p (which is %d bytes beyond the end of the buffer).\n", (char *)buf + nbytes, nbytes - 117); printf("Of these, you will overwrite %d bytes into the return address.\n", (char *)buf + nbytes - rp_); puts("If that number is greater than 8, you will overwrite the entire return address.\n"); puts("Overwriting the entire return address is fine when we know"); puts("the whole address, but here, we only really know the last three nibbles."); puts("These nibbles never change, because pages are aligned to 0x1000."); puts("This gives us a workaround: we can overwrite the least significant byte"); puts("of the saved return address, which we can know from debugging the binary,"); puts("to retarget the return to main to any instruction that shares the other 7 bytes."); puts("Since that last byte will be constant between executions (due to page alignment),"); puts("this will always work."); puts("If the address we want to redirect execution to is a bit farther away from"); puts("the saved return address, and we need to write two bytes, then one of those"); puts("nibbles (the fourth least-significant one) will be a guess, and it will be"); puts("incorrect 15 of 16 times."); puts("This is okay: we can just run our exploit a few"); puts("times until it works (statistically, after 8 times or so)."); puts("One caveat in this challenge is that the win_authed() function must first auth:"); puts("it only lets you win if you provide it with the argument 0x1337."); puts("Speifically, the win_authed() function looks something like:"); puts(" void win_authed(int token)"); puts(" {"); puts(" if (token != 0x1337) return;"); puts(" puts(\"You win! Here is your flag: \");"); puts(" sendfile(1, open(\"/flag\", 0), 0, 256);"); puts(" puts(\"\");"); puts(" }"); puts(byte_4393); puts("So how do you pass the check? There *is* a way, and we will cover it later,"); puts("but for now, we will simply bypass it! You can overwrite the return address"); puts("with *any* value (as long as it points to executable code), not just the start"); puts("of functions. Let's overwrite past the token check in win!\n"); puts("To do this, we will need to analyze the program with objdump, identify where"); puts("the check is in the win_authed() function, find the address right after the check,"); puts("and write that address over the saved return address.\n"); puts("Go ahead and find this address now. When you're ready, input a buffer overflow"); printf( "that will overwrite the saved return address (at %p, %d bytes into the buffer)\n", (const void *)rp_, rp_ - (_QWORD)buf); puts("with the correct value.\n"); printf("Send your payload (up to %lu bytes)!\n", nbytes); v8 = read(0, buf, nbytes); if ( (v8 & 0x80000000) != 0 ) { v3 = __errno_location(); v4 = strerror(*v3); printf("ERROR: Failed to read input -- %s!\n", v4); exit(1); } printf("You sent %d bytes!\n", v8); puts("Let's see what happened with the stack:\n"); DUMP_STACK(sp_, sz_); puts("The program's memory status:"); printf("- the input buffer starts at %p\n", buf); printf("- the saved frame pointer (of main) is at %p\n", (const void *)bp_); printf("- the saved return address (previously to main) is at %p\n", (const void *)rp_); printf("- the saved return address is now pointing to %p.\n", *(const void **)rp_); printf("- the canary is stored at %p.\n", (const void *)cp_); printf("- the canary value is now %p.\n", *(const void **)cp_); printf("- the address of win_authed() is %p.\n", win_authed); putchar(10); puts("If you have managed to overwrite the return address with the correct value,"); puts("challenge() will jump straight to win_authed() when it returns."); printf("Let's try it now!\n\n"); if ( (unsigned __int64)buf + (int)v8 > rp_ + 2 ) { puts("WARNING: You sent in too much data, and overwrote more than two bytes of the address."); puts(" This can still work, because I told you the correct address to use for"); puts(" this execution, but you should not rely on that information."); puts(" You can solve this challenge by only overwriting two bytes!"); puts(" "); } puts("Goodbye!"); return 0LL; }
__int64 __fastcall challenge(int a1, __int64 a2, __int64 a3) { int *v3; char *v4; _QWORD v6[3]; int v7; unsigned int v8; size_t nbytes; void *buf; __int64 v11[14]; int v12; char v13; unsigned __int64 v14; __int64 savedregs; void *retaddr;
v7 = a1; v6[2] = a2; v6[1] = a3; v14 = __readfsqword(0x28u); memset(v11, 0, sizeof(v11)); v12 = 0; v13 = 0; buf = v11; nbytes = 0LL; sp_ = (__int64)v6; bp_ = (__int64)&savedregs; sz_ = ((unsigned __int64)((char *)&savedregs - (char *)v6) >> 3) + 2; rp_ = (__int64)&retaddr; DUMP_STACK(sp_, sz_); cp_ = bp_; cv_ = __readfsqword(0x28u); while ( *(_QWORD *)cp_ != cv_ ) cp_ -= 8LL; __isoc99_scanf("%lu", &nbytes); v8 = read(0, buf, nbytes); if ( (v8 & 0x80000000) != 0 ) { v3 = __errno_location(); v4 = strerror(*v3); exit(1); } DUMP_STACK(sp_, sz_); return 0LL; } int __fastcall main(int argc, const char **argv, const char **envp) { int optval; int fd; int v7; struct sockaddr addr; unsigned __int64 v9;
v9 = __readfsqword(0x28u); if ( argc <= 0 ) __assert_fail("argc > 0", "<stdin>", 0xFBu, "main"); puts("###"); printf("### Welcome to %s!\n", *argv); puts("###"); putchar(10); setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 1uLL); puts("This challenge is listening for connections on TCP port 1337. The challenge supports one connection at a time, but"); puts("unlimited connections.\n"); fd = socket(2, 1, 0); optval = 1; setsockopt(fd, 1, 15, &optval, 4u); addr.sa_family = 2; *(_DWORD *)&addr.sa_data[2] = 0; *(_WORD *)addr.sa_data = htons(0x539u); bind(fd, &addr, 0x10u); listen(fd, 1); while ( 1 ) { v7 = accept(fd, 0LL, 0LL); puts("Connection accepted!"); if ( !fork() ) break; wait(0LL); } dup2(v7, 0); dup2(v7, 1); dup2(v7, 2); return challenge((unsigned int)argc, argv, envp); }
|