Translation

This post is also available in Simplified Chinese.

  • Reverse Script In Script
  • Reverse Where is my 13th count?
  • Reverse simple
  • Misc Welcome to SCTF 2018
  • Misc Mysterious Transaction
  • Misc Otaku’s Happy Challenge
  • Misc Introduction to Side Channels
  • Web easiest web - phpmyadmin

Script In Script

All the JavaScript was minified and painful to read. Entering a function name in Chrome’s console displays its source, which revealed r and its related functions. r simply checks that the flag is 24 characters long and then validates each character. J means equality, L converts a character to ASCII, and Q compares strings. As dydxh pointed out, D, E, F, and G are addition, subtraction, multiplication, and division. With these operations identified, deriving every flag character was straightforward; the challenge title even gives part of it away.

Flag:sctf{5cr1Pt_In_ScrIpT!!}

Where is my 13th count?

This challenge closely resembled “RoughLike and the Final Project” from SUCTF a few weeks earlier. The title suggested collecting 13 blocks. Open Cheat Engine_Data\Managed\Assembly-CSharp.dll in .NET Reflector and locate PlayerController::OnTriggerEnter(Collider other), the event handler called when the ball touches a block:

1
2
3
4
5
6
7
8
9
private void OnTriggerEnter(Collider other)
{
    if (other.gameObject.CompareTag("Pick Up"))
    {
        other.gameObject.SetActive(false);
        this.count++;
        this.SetCountText();
    }
}

Open the Reflexil plugin and locate the IL corresponding to other.gameObject.SetActive(false);:

OffsetOpCodeOperand
21ldarg.1
22callvirtUnityEngine.GameObject UnityEngine.Component::get_gameObject()
27ldc.i4.0
28callvirtSystem.Void UnityEngine.GameObject::SetActive(System.Boolean)

Change the instruction at offset 27 to ldc.i4.1, effectively replacing other.gameObject.SetActive(false); with other.gameObject.SetActive(true);. Save, replace the original DLL, and restart the game. Collected blocks now remain active, so circling the arena lets the same blocks be collected repeatedly until the flag appears.

Flag from Where is my 13th count

Flag:SCTF{ThEFLAGGGGGGG}

simple

I knew little about Android reversing—in fact, little about reversing at all—and solved this largely through guesses. The recovered source did not even contain MainActivity.java. ProxyApplication.java called FindAssetfile.getAssetsFile(this, "test.zip", paramContext, null);, apparently opening test.zip. Although the file extracted from the APK began with PK, the remaining data was clearly not a ZIP archive. FindAssetfile.java contained this suspicious code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
catch (IOException paramContext)
{
  paramContext.printStackTrace();
  while (true)
  {
    return null;
    if (paramMethod == null)
      break;
    paramContext = (byte[])paramMethod.invoke(null, new Object[] { localByteArrayOutputStream.toByteArray() });
    paramString1.close();
    localByteArrayOutputStream.close();
    paramString1 = new FileOutputStream(new File(paramString2));
    paramContext[0] = 113;
    paramContext[1] = 114;
    paramContext[2] = 10;
    paramContext[3] = 8;
    paramString1.write(crypt(paramContext, "E82038F4B30E810375C8365D7D2C1A3F"));
    paramString1.close();
  }
}

crypt implements RC4. I guessed that the program had transformed test.zip with RC4 and tried decrypting it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def rc4(plain,key):
    s=[i for i in range(256)]
    i=0
    j=0
    while i<256:
        j=(s[i]+j+ord(key[i%len(key)]))%256
        s[j],s[i]=s[i],s[j]
        i+=1
    k=0
    cipher=list(plain)
    n=len(cipher)
    i=0
    j=0
    while i<n:
        j=(j+1)%256
        k=(s[j]+k)%256
        s[k],s[j]=s[j],s[k]
        cipher[i]=chr(ord(cipher[i])^s[(s[j]+s[k])%256])
        i+=1
    return ''.join(cipher)


def main():
    key='E82038F4B30E810375C8365D7D2C1A3F'
    plain=open('test.zip','r').read()
    plain='\x71\x72\x0A\x08'+plain[4:]
    open('1.dex','w').write(rc4(plain,key))


if __name__=='__main__':
    main()

The dex header identified the result as Dalvik bytecode. Running dex2jar and jd-gui again finally produced MainActivity.java. A few decompilation errors did not affect the overall logic. Rather than write Java, I reimplemented Square and Point in Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class point:
    ...

class square:
    ...


def main():
    good=[]
    j=0
    while j<24:
        i=49
        while i<112:
            if square((i<<8)+828309504+255,j/2+4).check():
                good.append(i)
            i+=1
        j+=8
    print(''.join(map(lambda x:chr(x),good)))


if __name__=='__main__':
    main()

The result was 57=?UW]_QSUWY[]_9;=?Y[]_. I could hardly believe this jumble was the flag, but entering it in the Android emulator produced “Success.” Apparently it really was correct.

Flag:SCTF{57=?UW]_QSUWY[]_9;=?Y[]_}

Welcome to SCTF 2018

Open the challenge, find the line beginning with sctf, select it, press Ctrl+C, click the flag input, press Ctrl+V, and submit. The important part is moving quickly.

Mysterious Transaction

Searching Taobao for logic analyzers showed that .logicdata files are produced by Saleae Logic. Following the Kanxue article “Cracking a Contact IC Card Password with Logic Sniffing,” I searched for 0x33. The tutorial’s bits appeared reversed, making 0x33 equal to 11001100; that pattern appeared near 6.900 seconds.

IC-card commands in the logic-analyzer capture

The first command is 0x33 0x01 0x40, so the password’s first byte is 0x40. The next two commands are 0x33 0x02 0x31 and 0x33 0x03 0x10, yielding the password 0x40 0x31 0x10. I still could not see how it represented any sort of room number.

Flag:SCTF{403110}

Otaku’s Happy Challenge

I tried two rounds and died before the third stage both times. SWF Decompiler crashed halfway through reversing it, so I used a simpler approach: dragging the player’s progress bar directly to frame 57 revealed the cutscene.

Cutscene from Otaku’s Happy Challenge

1
2
$ echo -ne 'U1lDe0YzaVpoYWlfa3U0aWxlX1QxMTF9' | base64 -d -
SYC{F3iZhai_ku4ile_T111}

Finishing this challenge genuinely made me want to buy a bottle of cola.

Flag:SYC{F3iZhai_ku4ile_T111}

Introduction to Side Channels

My initial reasoning was wrong. Whether a key bit is 0 or 1, the program executes R <- [2]R; when it is 1, it additionally executes R <- R + P. Thus one dense region in the power trace represents 0, while a dense region followed by a sparse one represents 1.

Side-channel power trace

The flag format was frustrating: the homepage specified sctf{}, but submission with lowercase letters was rejected.

Flag:SCTF{0110111010}

easiest web - phpmyadmin

As someone who knew almost nothing about Web exploitation, this was the only Web challenge I could solve. The goal was to create a Web shell through phpMyAdmin. Log in with root:root, set general log to ON, and append index.php?lang[]=1 to the phpMyAdmin URL to trigger a PHP error that reveals the absolute path. Set general log file to the desired shell path, such as C:\phpStudy\WWW\xx.php, then run select '<?php @eval(...);?>' from mysql.user where 1=1; the query is written into xx.php.

During the competition, teams kept changing general log file, so I stopped fighting over it and wrote my shell into another team’s Web shell instead. I connected with China Chopper, searched the server, and found the flag.

Flag:sctf{31cf2213cc49605a30f07395d6e5b9c4}