• How to open binary plist files using vim-plist

    Vim editing a binary plist file

    Using macOS, you may have had experiences of handling plist files. For example, ~/Library/Preferences/.GlobalPreferences.plist file holds some configurations of macOS. When you type defaults write -g ApplePressAndHoldEnabled -bool false on terminal, the following lines are added to .GlobalPreferences.plist:

    <key>ApplePressAndHoldEnabled</key>
    <false/>
    

    So when you dig down the preferences or resources of macOS system, you’ll meet plist files.

    vim-plist

    darfink’s vim-plist plugin handles *.plist files quite well. A plist file is in one of three formats; json, binary, xml. macOS is bundled with the plutil command that can convert a plist file from one format to another. The plugin also uses plutil to handle read and write of plist files.

    The plugin registers autocmd for BufReadCmd and FileReadCmd to read *.plist files, BufWriteCmd and FileWriteCmd to write *.plist files. BufRead and BufWrite events are triggered after reading the file into the buffer, but BufReadCmd and BufWriteCmd events are triggered before reading the file, and that autocmd should handle actual read and write operation of that file. These differences make handling plist files more complex.

    Problems

    *.strings files

    Overall, the plugin is quite useful and seamless. But recently, I found some *.strings plist files under /System/Library, almost of them in binary formats. The plugin registers autocmds only for *.plist files, so there is no chance to convert them to readable formats.

    At first, I’ve considered to register an autocmd for *.strings file, but I’m not sure about that .strings extension is only used for plist file, and also there can be other extensions with plist contents (for example, *.nib files are plist, too).

    Saving a new file

    darfink’s vim-plist checks g:plist_save_format and b:plist_save_format before writing to plist files. The buffer-local variable is set when the plugin reads the file and detect the format. The global one is set by user, and overrides buffer-local one.

    I don’t want the plugin to override the content of plist files with different format, so I haven’t set g:plist_save_format. Then the problem raised. Open a new plist file, like vim test.plist, and save it after editing. Then the plugin didn’t set b:plist_save_format because it’s a new file, and I also didn’t set g:plist_save_format, so the plugin don’t know the format to use for saving.

    I think this problem can be solved by patching the plugin, but its last commit is pushed in 2014, which makes me use the faster way.

    Solving the problems with .vimrc

    Binary plist files

    I decided to check if the file is binary plist file or not, and load with functions of vim-plist plugin. The checking process is easy, because the file starts with bplist. At first, I think it’s okay to register an autocmd for BufReadCmd because calling functions of the plugin should be easy. So my first try was:

    function! s:DetectBinaryPlist()
      let l:filename = expand('<afile>')
      if filereadable(l:filename)
        let l:content = readfile(l:filename, 'b', 1)
        if len(content) > 0 && content[0] =~# '^bplist'
          return 1
        endif
      endif
      return 0
    endfunction
    autocmd BufReadCmd *
          \ if s:DetectBinaryPlist() |
          \   call plist#Read(1) |
          \   call plist#ReadPost() |
          \ endif
    

    Can you see the problem? This makes Vim returns an empty buffer when it reads a file that’s not a binary plist file. As I said above, BufReadCmd should handle actual read and write operation of the file. If it’s not a binary plist file, Vim won’t read anything according to this code.

    So I changed BufReadCmd to BufRead. This event happens after reading the file into the buffer, so I have to empty the buffer.

    function! s:ConvertBinaryPlist()
      silent! execute '%d'
      call plist#Read(1)
      call plist#ReadPost()
    endfunction
    autocmd BufRead *
          \ if getline(1) =~# '^bplist' |
          \   call s:ConvertBinaryPlist() |
          \ endif
    

    The getline(1) reads the first line of the buffer, and we can call it because it’s after reading the file. It’s working quite well, so at that time, I wanted to bring the writing functionality also.

    function! s:ConvertBinaryPlist()
      silent! execute '%d'
      call plist#Read(1)
      call plist#ReadPost()
    
      autocmd! BufWriteCmd,FileWriteCmd <buffer>
      autocmd BufWriteCmd,FileWriteCmd <buffer>
            \ call plist#Write()
    endfunction
    

    Note that the autocmd! line means deleting every other BufWriteCmd and FileWriteCmd autocmds for that buffer, and the second line registers BufWriteCmd and FileWriteCmd for that buffer.

    But when I saved after editing a *.strings file, I saw this error message:

    <stdin>: Property List error: Unable to convert string to correct encoding / JSON error: JSON text did not start with array or object and option to allow fragments not set.
    

    After poking around, I found that the fileencoding is set to latin1. The original file is binary and we just replaced the contents of the buffer, the fileencoding was not properly set. So I just set it to UTF-8.

    function! s:ConvertBinaryPlist()
      silent! execute '%d'
      call plist#Read(1)
      call plist#ReadPost()
      set fileencoding=utf-8
    
      autocmd! BufWriteCmd,FileWriteCmd <buffer>
      autocmd BufWriteCmd,FileWriteCmd <buffer>
            \ call plist#Write()
    endfunction
    

    Saving a new file

    It’s simple:

    autocmd BufNewFile *.plist
          \ if !get(b:, 'plist_original_format') |
          \   let b:plist_original_format = 'xml' |
          \ endif
    

    We don’t write binary file by our own hands, so a new plist file would be in xml format.

    Conclusion

    So this is the complete part of my .vimrc for binary plist files:

    function! s:ConvertBinaryPlist()
      silent! execute '%d'
      call plist#Read(1)
      call plist#ReadPost()
      set fileencoding=utf-8
    
      autocmd! BufWriteCmd,FileWriteCmd <buffer>
      autocmd BufWriteCmd,FileWriteCmd <buffer>
            \ call plist#Write()
    endfunction
    autocmd BufRead *
          \ if getline(1) =~# '^bplist' |
          \   call s:ConvertBinaryPlist() |
          \ endif
    autocmd BufNewFile *.plist
          \ if !get(b:, 'plist_original_format') |
          \   let b:plist_original_format = 'xml' |
          \ endif
    

    It’s also available on GitHub, you can visit my dotfiles repository!

  • How to make a bootable USB with GRUB2 and ISO

    Do you have multiple ISO files to install, with quite enough storage of USB stick? You’ll want to install all of them with a single USB stick. Here’s how.

    Formatting USB disk

    First, plug in your USB stick and find it from your Linux machine:

    $ sudo fdisk -l
    Disk /dev/sda: 20 GiB, 21474836480 bytes, 41943040 sectors
    Units: sectors of 1 * 512 = 512 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    Disklabel type: dos
    Disk identifier: 0x4d149927
    
    Device     Boot    Start      End  Sectors  Size Id Type
    /dev/sda1  *        2048 39845887 39843840   19G 83 Linux
    /dev/sda2       39847934 41940991  2093058 1022M  5 Extended
    /dev/sda5       39847936 41940991  2093056 1022M 82 Linux swap / Solaris
    
    
    Disk /dev/sdb: 14.6 GiB, 15664676864 bytes, 30595072 sectors
    Units: sectors of 1 * 512 = 512 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    Disklabel type: gpt
    Disk identifier: 4788154A-7BD2-4A06-945B-5980E828C8C5
    
    Device      Start      End  Sectors  Size Type
    /dev/sdb1      40   409639   409600  200M EFI System
    /dev/sdb2  411648 30593023 30181376 14.4G Microsoft basic data
    

    In my case, it’s /dev/sdb. Let’s format it with fdisk. When you run fdisk /dev/sdb, you can print help menu:

    $ sudo fdisk /dev/sdb
    
    Welcome to fdisk (util-linux 2.27.1).
    Changes will remain in memory only, until you decide to write them.
    Be careful before using the write command.
    
    
    Command (m for help): m
    
    Help:
    
      Generic
       d   delete a partition
       F   list free unpartitioned space
       l   list known partition types
       n   add a new partition
       p   print the partition table
       t   change a partition type
       v   verify the partition table
       i   print information about a partition
    
      Misc
       m   print this menu
       x   extra functionality (experts only)
    
      Script
       I   load disk layout from sfdisk script file
       O   dump disk layout to sfdisk script file
    
      Save & Exit
       w   write table to disk and exit
       q   quit without saving changes
    
      Create a new label
       g   create a new empty GPT partition table
       G   create a new empty SGI (IRIX) partition table
       o   create a new empty DOS partition table
       s   create a new empty Sun partition table
    

    Now we can start!

    $ sudo fdisk /dev/sdb
    
    Welcome to fdisk (util-linux 2.27.1).
    Changes will remain in memory only, until you decide to write them.
    Be careful before using the write command.
    
    
    Command (m for help): o
    Created a new DOS disklabel with disk identifier 0x8c9faeb0.
    
    Command (m for help): n
    Partition type
       p   primary (0 primary, 0 extended, 4 free)
       e   extended (container for logical partitions)
    Select (default p):
    
    Using default response p.
    Partition number (1-4, default 1):
    First sector (2048-30595071, default 2048):
    Last sector, +sectors or +size{K,M,G,T,P} (2048-30595071, default 30595071):
    
    Created a new partition 1 of type 'Linux' and of size 14.6 GiB.
    
    Command (m for help): t
    Selected partition 1
    Partition type (type L to list all types): c
    Changed type of partition 'Linux' to 'W95 FAT32 (LBA)'.
    
    Command (m for help): a
    Selected partition 1
    The bootable flag on partition 1 is enabled now.
    
    Command (m for help): w
    The partition table has been altered.
    Calling ioctl() to re-read partition table.
    Syncing disks.
    

    We’ve just created one bootable FAT partition, and that’ll be /dev/sdb1. Let’s create an MS-DOS filesystem in it. This will take a while.

    $ sudo mkfs.vfat -F 32 /dev/sdb1
    mkfs.fat 3.0.28 (2015-05-16)
    

    Now you can mount that partition and we’re ready to install GRUB2 in it.

    sudo mkdir /mnt/usb
    sudo mount /dev/sdb1 /mnt/usb
    

    Installing GRUB2

    Since we’re installing GRUB2 into USB stick, we specify --removable flag. Also specify --boot-directory so that GRUB2 is installed to there instead of /boot/grub.

    $ sudo grub-install --removable --boot-directory=/mnt/usb/boot /dev/sdb
    Installing for i386-pc platform.
    Installation finished. No error reported.
    

    We’ll add grub.cfg file under /mnt/usb/boot/grub/ directory. It defines selectable menus and the booting method of each entry. This time I’ll add Ubuntu Desktop ISO for i386 platform.

    set timeout=5
    set default=0
    
    menuentry "Ubuntu Desktop 16.04.3 LTS i386" {
      set isofile="/iso/ubuntu-16.04.3-desktop-i386.iso"
      loopback loop $isofile
      linux (loop)/casper/vmlinuz boot=casper file=/preseed/ubuntu.seed iso-scan/filename=$isofile noeject noprompt splash --
      initrd (loop)/casper/initrd.lz
    }
    
    menuentry "Ubuntu Desktop 16.04.3 LTS amd64" {
      set isofile="/iso/ubuntu-16.04.3-desktop-amd64.iso"
      loopback loop $isofile
      linux (loop)/casper/vmlinuz.efi boot=casper file=/preseed/ubuntu.seed iso-scan/filename=$isofile noeject noprompt splash --
      initrd (loop)/casper/initrd.lz
    }
    
    menuentry "Ubuntu Server 16.04.3 LTS i386" {
      set isofile="/iso/ubuntu-16.04.3-server-i386.iso"
      loopback loop $isofile
      linux (loop)/install/vmlinuz boot=casper file=/preseed/ubuntu-server.seed iso-scan/filename=$isofile noeject noprompt splash --
      initrd (loop)/install/initrd.gz
    }
    
    menuentry "Ubuntu Server 16.04.3 LTS amd64" {
      set isofile="/iso/ubuntu-16.04.3-server-amd64.iso"
      loopback loop $isofile
      linux (loop)/install/vmlinuz boot=casper file=/preseed/ubuntu-server.seed iso-scan/filename=$isofile noeject noprompt splash --
      initrd (loop)/install/initrd.gz
    }
    
    menuentry "Linux Mint 18.2 Cinnamon 32-bit" {
      set isofile="/iso/linuxmint-18.2-cinnamon-32bit.iso"
      loopback loop $isofile
      linux (loop)/casper/vmlinuz boot=casper file=/preseed/linuxmint.seed iso-scan/filename=$isofile noeject noprompt splash --
      initrd (loop)/casper/initrd.lz
    }
    
    menuentry "Custom ELF" {
      elffile="/elf/custom.elf"
      multiboot $elffile
    }
    

    In the first menuentry, isofile points to /iso/ubuntu-16.04.3-desktop-i386.iso, and it’s relative path of the USB stick. Create /mnt/usb/iso/ directory, and put the ISO files under there.

    You may need to look inside of ISO file to find where is vmlinuz* and initrd.* files. Try this:

    isoinfo -l -i myimage.iso
    

    As the similar way, you can also make it to boot from ELF file. Just put the ELF file that supports Multiboot under /mnt/usb/elf/ directory, and edit the menuentry properly.

    After editing is done, unmount the disk and use it right away!

    sudo umount /dev/sdb1
    

    Troubleshooting

    If your screen goes black on boot, try with nomodeset:

    linux ... noeject noprompt splash nomodeset --
    

    See also

  • Pushing git repository to multiple remotes

    I’m currently managing my dotfiles repository on both of GitHub and Bitbucket. These two repositories are the same, but I don’t want to remove one of them. I mainly use GitHub for hosting code now, but the first place I uploaded my dotfiles to was Bitbucket.

    I want to keep the HEAD of two remote repositories be the same, so when I push code to my dotfiles, the both of them must be updated at the same time.

    Default git config

    First, clone or init the repository.

    git clone https://github.com/yous/dotfiles.git
    

    Then, as you know, the origin will be set to https://github.com/yous/dotfiles.git. This is the content of .git/config:

    [core]
    	# ...
    [remote "origin"]
    	url = https://github.com/yous/dotfiles.git
    	fetch = +refs/heads/*:refs/remotes/origin/*
    [branch "master"]
    	# ...
    

    Note that there is the url attribute under remote "origin".

    git remote set-url

    Now we’re going to run git remote set-url twice so that the repository will have two push remote URLs. Setting push remote URL is slightly different from plaing git remote set-url <name> <newurl>. See man git-remote:

    set-url
        Changes URLs for the remote. Sets first URL for remote <name> that
        matches regex <oldurl> (first URL if no <oldurl> is given) to
        <newurl>. If <oldurl> doesn't match any URL, an error occurs and
        nothing is changed.
    
        With --push, push URLs are manipulated instead of fetch URLs.
    
        With --add, instead of changing existing URLs, new URL is added.
    

    So we need to run git remote set-url --push <name> <newurl>. Moreover, we need two push URL, so the second command should be git remote set-url --add --push <name> <newurl>. It’s okay to specify --add --push to the first command, too.

    git remote set-url --add --push origin https://github.com/yous/dotfiles.git
    git remote set-url --add --push origin https://bitbucket.org/yous/dotfiles.git
    

    Now, the content of .git/config would be like this:

    [core]
    	# ...
    [remote "origin"]
    	url = https://github.com/yous/dotfiles.git
    	fetch = +refs/heads/*:refs/remotes/origin/*
    	pushurl = https://github.com/yous/dotfiles.git
    	pushurl = https://bitbucket.org/yous/dotfiles.git
    [branch "master"]
    	# ...
    

    All done! Note that there are two pushurls under remote "origin". Now git push automatically pushes to the both push remote URLs.

  • Boston Key Party CTF 2017: vimjail write-up

    vimjail (pwn 150)

    • ssh ctfuser@ec2-54-200-176-5.us-west-2.compute.amazonaws.com
    • password: loginPWforVimJail

    Can you read the flag?

    UPDATES

    • (13:38 UTC Saturday): The flag is not in /tmp.
    • (13:31 EST Saturday): new ip

    Looking around

    Well, you would do ls first when you logged in, so do we. And there was ~/flagReader.

    ctfuser@ip-172-31-31-196:~$ ls -als /home/ctfuser/flagReader
    12 ---S--x--- 1 topsecretuser secretuser 8768 Feb 25 08:42 /home/ctfuser/flagReader
    

    If you try completion by pressing Tab key or try to move around using cd, it fails with an error message from rbash. It’s restricted bash, but you can simply run bash to escape.

    While moving around, we found nothing special without /.flag. Also there were some .s[a-z][a-z] files under /var/tmp/ and /tmp/, created by secretuser. But there are not in fixed location when the problem server was changed, so we thought there would be a way to run Vim under secretuser’s permission.

    ctfuser@ip-172-31-31-196:~$ ls -als /.flag
    4 -r-------- 1 topsecretuser topsecretuser 39 Feb 25 08:42 /.flag
    

    We also tried to find setuid or setgid files, but there was only the previous flagReader.

    ctfuser@ip-172-31-31-196:/tmp$ find / -perm -4000 -o -perm -2000 -type f 2>/dev/null
    /bin/ping
    /bin/ping6
    /bin/fusermount
    /bin/umount
    /bin/su
    /bin/mount
    /bin/ntfs-3g
    /sbin/unix_chkpwd
    /sbin/pam_extrausers_chkpwd
    /usr/lib/x86_64-linux-gnu/utempter/utempter
    /usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic
    /usr/lib/openssh/ssh-keysign
    /usr/lib/snapd/snap-confine
    /usr/lib/eject/dmcrypt-get-device
    /usr/lib/dbus-1.0/dbus-daemon-launch-helper
    /usr/lib/policykit-1/polkit-agent-helper-1
    /usr/bin/crontab
    /usr/bin/newuidmap
    /usr/bin/at
    /usr/bin/chage
    /usr/bin/sudo
    /usr/bin/bsd-write
    /usr/bin/pkexec
    /usr/bin/chfn
    /usr/bin/expiry
    /usr/bin/newgrp
    /usr/bin/screen
    /usr/bin/chsh
    /usr/bin/gpasswd
    /usr/bin/newgidmap
    /usr/bin/ssh-agent
    /usr/bin/passwd
    /usr/bin/mlocate
    /home/ctfuser/flagReader
    

    Read on →

  • 33C3 CTF 2016: pdfmaker write-up

    pdfmaker (misc 75)

    Just a tiny application, that lets the user write some files and compile them with pdflatex. What can possibly go wrong?

    nc 78.46.224.91 24242

    If you can’t download the application, please use this link.

    What is the goal?

    There are some interesting parts in pdfmaker_public.py. initConnection copies flag file into the self.directory with the name of:

    "33C3" + "%X" % randint(0, 2**31) + "%X" % randint(0, 2**31)
    

    Since the answer would be in the 33C3XXXXXXXXXXXXXXXX file, we should get the list of filenames in its directory. Note that create method can create log, tex, sty, mp, bib files.

    Behavior of \write18

    @daehee found this helpful link: “Pwning coworkers thanks to LaTeX”. According to the post, \write18 normally executes any program listed in shell_escape_commands:

    shell_escape_commands = \
    bibtex,bibtex8,\
    extractbb,\
    kpsewhich,\
    makeindex,\
    mpost,\
    repstopdf,\
    

    Note that mpost is in there, and we can create mp file! As denoted by the link, mpost takes the -tex option for text labels, so we can execute arbitrary program.

    Read on →