summaryrefslogtreecommitdiff
path: root/informationals/teso-i0027.txt
diff options
context:
space:
mode:
authorRoot THC2026-02-24 12:42:47 +0000
committerRoot THC2026-02-24 12:42:47 +0000
commitc9cbeced5b3f2bdd7407e29c0811e65954132540 (patch)
treeaefc355416b561111819de159ccbd86c3004cf88 /informationals/teso-i0027.txt
parent073fe4bf9fca6bf40cef2886d75df832ef4b6fca (diff)
initial
Diffstat (limited to 'informationals/teso-i0027.txt')
-rw-r--r--informationals/teso-i0027.txt279
1 files changed, 279 insertions, 0 deletions
diff --git a/informationals/teso-i0027.txt b/informationals/teso-i0027.txt
new file mode 100644
index 0000000..6047f11
--- /dev/null
+++ b/informationals/teso-i0027.txt
@@ -0,0 +1,279 @@
10027 2000/06/29 format string supply vulnerabilities and exploitation
2
3==== TESO Informational =======================================================
4This piece of information is to be kept confidential.
5===============================================================================
6
7Description ..........: format string supply vulnerabilities and exploitation
8Date .................: 2000/06/29 22:00
9Author ...............: scut
10Publicity level ......: partly known
11Affected .............: programs containing format string vulnerabilities
12Type of entity .......: exploitation techniques
13Type of discovery ....: useful information
14Severity/Importance ..: medium
15Found by .............: various people, comments by scut and smiler
16
17===============================================================================
18
19Some programs use the printf format strings in a way that let users supply
20either the whole or parts of the format string. This way users may use special
21format characters to write to parts of the memory, resulting in a compromise.
22
23For example, code like this is vulnerable:
24
25void
26func (char *usersupplied)
27{
28 char buffer[512];
29
30 snprintf (buffer, sizeof (buffer), usersupplied);
31 buffer[sizeof (buffer) - 1] = '\x00';
32}
33
34By using a usersupplied string like "%p" the user may control parts of the
35behaviors of the snprintf function. While most of the format control characters
36only pop data from the stack and display it, like "%d" pulls an integer value
37from the stack and writes the ascii representation of it to the string, the
38"%n" parameter does something special. It writes the number of bytes the
39snprintf function has written already to the (int *) which lies next on the
40stack.
41
42Sometimes this may not be necessary, as in this example:
43
44void
45func (char *usersupplied)
46{
47 char buffer[512];
48
49 if (strlen (usersupplied) > 200)
50 return;
51
52 sprintf (buffer, usersupplied);
53}
54
55In this case we need to expand our userbuffer from less then 200 bytes to more
56then 512 bytes to create an ordinary stack overflow. This can be done using
57a variety of format controls. You can use "%400d" to create a 400 byte long
58string, then use ordinary data to overwrite the return address. Older GNU
59libc libraries contain a bug when the number of characters exceeds 1000. Then,
60instead of using something like "%2000d", just use "%-2000d", which pads with
61spaces to the right.
62
63But in cases where a limited printf function, such as snprintf, syslog and
64vsnprintf is used, we have to use another method to exploit this. Remember
65the "%n" format control, it stores the number of written characters. Ok,
66we may overwrite memory with it, but how do we overwrite an arbitrary
67address of our choice ?
68
69To do this, we have to make the stack pointer point to the address we want
70to write to. The most favorable situation would be if the stack pointer points
71into our format string, so we can store the address there and then use "%n" to
72write to it. Most of the times the memory layout looks like:
73
74
751 3 2
76< data ... > < format string ... >
77lower stack addresses higher stack addresses
78
79Where the stack pointer points to (1). We have to move it so that it points
80to the (2) position using control characters, such as "%f", and "%d". On the
81x86 architecture an integer takes up four bytes (ILP32 rule), and a %d will
82pop four bytes from the stack, increasing the stack pointer by a value of four.
83This way we have the stack pointer at (3) now. So we use another bunch of
84format parameters to move it upwards until it points to (2). If our format
85string is of limited size we should look at efficiency doing so. Lets
86consider we use "%d" and we have to move it by 256 bytes upward. Then we have
87to use 64 "%d" sequences, which take up 128 bytes. The expansion ratio is
882:4 = 1:2. Two bytes ("%d") result in four bytes being popped. A more efficient
89solution is the usage of "%f", which pulls 8 bytes from the stack. But there
90is something odd about that, since "%f" is a float, it is evaluated in another
91way then "%d", and while doing so it may cause an arithmetic exception because
92of a division by zero. Since you cannot control the data that is popped from
93the stack, it cannot be used. However, if you use "%.f" instead, an invalid
94float number won't cause an exception, but still it pops eight bytes from the
95stack, so it's exactly what we need. It is more efficient than the "%d" method,
96since three bytes ("%.f") will pop eight, so it's 3:8, and thats greater then
972:4.
98
99So what to do if by popping you get the stack pointer into the regions of a
100buffer you can supply (2), but it's misaligned ? Is there such things as a
101one-byte-pop control sequence ?
102Fortunately there is no such thing, but you can get rid of that by using a
103special alignment in your buffer, and just use "ppp<buffer>" instead of
104"<buffer>", where 'p' is the padding, which varies from zero to three
105characters. After all, you should get the stack pointer pointing exactly to
106the first byte of <buffer> by using the methods above.
107
108Now, you can store an address to write to in that buffer, so for x86 you'd
109want to store it in little endian order. Say we want to store 0xbfff3407,
110then the buffer would look like "\x07\x34\xff\xbf". Now, the stack pointer
111points to it and the usage of "%n" write the counter of written bytes to
112it. Not very helpful, since we cannot control this counter, at least not
113fully.
114
115So what can we control ? We control the address the counter is written to,
116and we can modify the counter a bit by using some bogus data, that will
117increase it. So here is an example:
118
119 int i;
120
121 printf ("aaaaaaaa%20d%n\n", 3, &i);
122 printf ("%d\n", i);
123
124Will create an output like:
125
126aaaaaaaa 3
12728
128
129So we have increased the number of written bytes by 20 with our "%20d". How
130much can we increase it ? So high that we can write, say 0xbfff9210 ?
131In some GNU libc versions we can increase it beyond the number of allowed
132characters, but if it gets too high, the library crashes. So to assume the
133worst, say, we just can increase it in very small boundaries, like no more
134then 0x100. So we can store small numbers, where the least significant byte
135is under our control to a location of our choice. And we can do this more
136then once.
137
138To say it clearer, we can store a byte of our choice to a location of our
139choice. More then once, as often as we want. And an address just consists
140out of four bytes.
141
142So what do we do ?
143
144Say the return address we want to overwrite is located at 0xbfff82e8
145
1460xbfff82e8: 0x44 0x20 0x08 0x08 0x7a 0xb0 0xff 0xbf
147 ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
148 return address data behind it (fp)
149
150
151So to overwrite the return address we overwrite four bytes, we start with
152the least significant, which is the leftmost in little endian notation. So
153say we want to replace the return address with 0xbfff8010, then we have to
154make the four bytes
155
1560xbfff82e8: 0x10 0x80 0xff 0xbf
157
158To do this we have to start with the leftmost, by storing 0x10 to
1590xbfff82e8:
160
161 (a) (b)
162"\x01\x02\x03\x04\xe8\x82\xff\xbf<stackpop><pad>%n"
163
164Where <stackpop> makes the stack pointer point to the first byte (a) of
165our buffer. Then we use <pad> to increase the counter of written bytes to
166a value where the least significant byte is the one we want to store. So
167if the counter is 0x000000e0, we increase it by 0x30, and hence use "%48d"
168as padding. Now the stack pointer points to (b), where our target address
169is stored, and the counter is 0x00000110. Now we use "%n" to store this
170counter to the address. After that, the memory will look like:
171
1720xbfff82e8: 0x10 0x01 0x00 0x00 0x7a 0xb0 0xff 0xbf
173
174So the leftmost byte is that of our choice, and the counter is 0x0110 now.
175The next byte we want to store is 0x80, which is 0x70 higher then 0x10, so
176we use "%112d" to increase it by that value, and then use "%n" to store it
177to 0xbfff82e9. But we have to reconstruct the format buffer to do this.
178
179 (c) (d)
180"\x01\x02\x03\x04\xe8\x82\xff\xbf\x01\x02\x03\x04\xe9\x82\xff\xbf"
181"<stackpop><pad1>%n<pad2>%n"
182
183After the first write the stack pointer points to (c) now, and we use the
184pad2 ("%112d") with this dummy value to increase the counter so that the
185LSB is 0x80. The stack pointer points to (d) now, and we use "%n" once again
186to store the 32 bit counter to the 0xbfff82e9 address.
187The stack data looks like this now:
188
189 [-----------------]
190 [------------------]
1910xbfff82e8: 0x10 0x80 0x01 0x00 0x00 0xb0 0xff 0xbf
192 ^^^^
193
194Note that we destroyed the byte behind the return address with our second
195write. Now we proceed using the same method for the last two bytes, and the
196final memory looks like:
197
1981rst write [-----------------]
1992nd [------------------]
2003rd [------------------]
2014th [------------------]
2020xbfff82e8: 0x10 0x80 0xff 0xbf 0x02 0x00 0x00 0xbf
203
204We've replaced the return address with the one of our choice now, using four
205times the "%n" format parameter.
206
207
208PROBLEMS YOU MAY RUN INTO
209
210[1. figuring the distance]
211
212The first problem is to know many bytes the stack pointer is away from your
213buffer. This can be found out easily, if you can see the buffer output. To
214do this, you create a buffer like this:
215
216"iiiioooo<stackpop-fixed><stackpop-vary>|%08x|%08x|"
217
218The stackpop-fixed consists out of the minimum distance you assume, so it
219is just a few dozen times "%.f". The vary-buffer is increased each try from
220"" to as much "%.f"'s as you need. Then two "%08x"'s are used to inspect the
221content where the stack pointer points to. So if you see your "iiiioooo"
222as "|69696969|6f6f6f6f|" or "|..696969|69", "|....6969|6969" or
223"......69|696969", then you can recalculate both the buffer distance to the
224stack pointer and the alignment necessary.
225
226
227[2. buffer address]
228
229One problem may be that you need to know the exact addresses of your buffer
230and of the return address location. This can be solved using two tricks.
231First the distance between this addresses may not vary that much, so if
232you know one address you can most likely make an educated guess about the
233other one. But how do we know the first one ?
234
235First you need to decide what kind of buffer address you want to brute force,
236the format buffer or the target buffer. Because there is not always a simple
237target buffer, such as if you use fprintf, we try with the source buffer here.
238
239The main idea is that we inspect the memory using a pointer we supply. Since
240we already know the distance, we use a buffer like this:
241
242"<p-address><stackpop>|____________________|x|%.20s!"
243
244It works like this: We first pop the stack pointer using the <stackpop> code,
245so that we advance the it by the distance we figured using the [1.] trick.
246Then we write a 20 byte long string containing '_' characters, followed by a
247mark "|x|". Now we use "%.20s" to get no more then 20 bytes of data from the
248address <p-address>. So we can inspect 20 Bytes of memory at a time if we
249can see the output. If we get output such as this,
250
251 [------ n bytes ----](1) (2)
252"...crap...|____________________|x|___________|x|... crap ..."
253
254we can recalculate the exact buffer location, because we knew the p-address
255pointed to the (1) address in the format string. So by using this formula:
256
257buffer_address = p-address + ((2) - (1)) - n.
258
259To distinguish the source from the destination buffer we use a trick, we store
260a "%%" within the source buffer, which gets evaluated to just "%" in the
261target.
262
263
264[3. getting the return address location]
265
266This may be extrapolated from the buffer address, but there is another nice
267method to get it. Remember the "%s" we used to inspect memory ? Why not
268inspect the target memory where the return address may lie too ? So use
269something like:
270
271"<p-address><stackpop>|%.4s|"
272
273And brute the p-address around where you suspect the return address lies.
274So if you know normally there is an address like 0x0804.... stored there,
275just brute until you find it. This way you avoid segfaults.
276
277
278===============================================================================
279