Đây là report nho nhỏ về task của a mochi đã giao cho mình:
BFS and DFS
Để mình nói sơ về phần thuật toán trước:
Qua nhiều lần chơi nhiều bài rev khác nhau, mình thấy bfs và dfs chúng ta sẽ gặp tương đối nhiều, chủ yếu là các bài tìm đường đi, blind maze,…
Đặt điểm chung của các bài này giống như programing bình thường, tuy nhiên với mỗi bài sẽ có cách biểu diễn các node và các step khác nhau. Để tổng quan lại các bạn có thể hình dung như thế này:
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
BFS:
Bắt đầu từ root node, duyệt tất cả các node lân cận cùng mức rồi mới bắt đầu duyệt mức tiếp theo. Tương ứng với duyệt theo chiều rộng.
DFS:
Bắt đầu từ root node, duyệt theo 1 node theo 1 đường xa nhất có thể trước khi duyệt các node lân cận. Tương ứng với duyệt theo chiều sâu.
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Do đó, điểm khác biệt lớn nhất giữa 2 cách duyệt này là cấu trúc dữ liệu mà nó sử dụng.
BFS: sử dụng queue(FIFO): mỗi lần duyêt một node, nó sẽ push node đó vào sau hàng đợi, và lấy node ở đầu hàng đợi ra để duyệt=> do đó nó sẽ duyệt tất cả các node lân cận trước khi tới mức tiếp theo.
DFS: sử dụng stack (LIFO): mỗi lận duyệt một node, nó cũng sẽ push node đó vào stack, tuy nhiên ở lần duyệt tiếp theo, nó sẽ lại lấy chính node đó ra => đồng nghĩa nó sẽ tăng lên một mức so với node cũ và duyệt trên chính node cũ, hay nói cách khác là đang duyệt theo chiều sâu.
Tuỳ theo từng bài mà áp dụng thuật toán tương ứng nhé.
Các bạn có thể tham khảo thêm tại:
https://www.geeksforgeeks.org/difference-between-bfs-and-dfs/
oops
Riêng đối với bài oops, array G_S sẽ tương ứng với 82 node trong graph
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Đọc 12 byte input, nghĩa là số bước đi tối đa là 12 bước để tìm flag.
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Ở root node, từng trường hợp của input sẽ tương ứng với từng function khác nhau (pfunc8,pfunc10,pfunc18…)
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Ở mỗi node sẽ có 6 function, tương ứng với 6 node khác nhau có thể nhảy tới.
Một swicth case để nhảy và 1 hàm mod (modify, cái này mình sẽ nói sau).
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Để tính được các node lân cận là gì, thì ở trong 6 hàm đó sẽ có các instruction, ví dụ như:
add rax, 78h
hoặc là
sub rax, 30h
…
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Mỗi hàm sẽ có 1 cách nhảy khác nhau, dựa trên cái số mà nó cộng hoặc trừ, tuy nhiên thì nó cũng chỉ để phục vụ mục đích là nhảy tới các node lân cận.
Về phần này, mình dùng code có sẵn của a mochi và comment lại cho các bạn hiểu:
Từ các số lấy được này có thể tính ra bước nhảy tiếp theo (vtable tiếp theo).
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Sau khi biết được vtable tiếp theo là gì, nó sẽ set vtable tương ứng để duyệt node đó
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Tiếp tục cho tới khi tìm được vtable có hàm cat flag (end):
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Về phần lấy địa chỉ node tiếp theo và jump tới các node mình sẽ dùng lại script cũ của a mochi, đây là script của ảnh:
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Cách của a mochi làm là dùng bfs, và bắt đầu loại trừ cho tới khi tìm được 1 đường đi đúng, tại sao lại là đường đi đúng?
Bởi vì trong quá trình duyệt, có nhiều trường hợp trả về đường đi đúng, tuy nhiên, ở mỗi node có 1 hàm mod, hàm này sẽ ảnh hưởng trực tiếp đến chuyện rằng node tiếp theo nó sẽ đi được những bước nào, không phải là tất cả 6 bước đều có thể đi.
Vì phù hợp với thời gian thi 8 tiếng nên cách trên hợp lí hơn, tuy nhiên vì mình có thời gian nhiều hơn nên mình sẽ nói kĩ hơn về cái này.
Mỗi lần nhảy tới 1 node khác, nó sẽ dùng hàm mod của node hiện tại, modify input[i] của mình và sau đó mới gọi switch case của node tiếp theo.
Ví dụ ở hàm mod1:
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
ta có switch (mod1(inp[1]))
trong đó mod1 sẽ trả về kết quả của inp[1]^0x5b
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Tuy nhiên, hàm mod 2 lại có thêm đặt điểm là or 1, ah
:
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Nghĩa là ở node thứ 3, nó sẽ là swith (mod2(inp[2])) trong đó mod 2 trả về kết quả có dạng: 0x1XX.
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Nhưng các bạn có thể dễ dàng nhìn thấy rằng switch case lại có những case 0x2XX, đó sẽ là những node không thể nhảy tới.
Và một số hàm mod khác, nó sẽ or với 1 số có thể là 0x200 hoặc 0x100 để quyết định xem ở node tiếp theo nó có thể nhảy tới hàm nào.
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
Do đó điểm mấu chốt bài này là làm sao biết được hàm nào nó có thể nhảy hoặc không.
Mình đã viết 1 hàm Get_word, điều này giúp mình biết được node vừa đi qua có word là bao nhiêu (0x200 hoặc 0x100).
và trong mỗi node mình sẽ thêm last
để biết rằng word của node vừa rồi là gì, làm dữ kiện để kiệm tra xem những node nào có thể nhảy tới.
Và điểm mấu chốt của bài này là hàm can_step
, cái mà chúng ta cần biết xem node tiếp theo có thể đi được hay không.
Ý tưởng của đoạn này là:
Trong mỗi switch case đều có chung một format như thế này:

6 Lệnh jz tương ứng với 6 bước nhảy tiếp theo, mình có thể extract number từ đây ra.
Tiếp theo, cần check xem từng number này tương ứng với function nào (bước từ (1->6)):
Tương ứng với mỗi lable, mình sẽ extract thêm được offset của các function thông qua lệnh add:

Rồi mình dựa vào các số này và kiểm tra được bước nhảy đó có hợp lệ hay không:

Từ đây có thể dễ dàng chạy code và tìm được đường đi tương ứng, tuy nhiên vì mỗi lần nhảy, input mình xor với 1 số mà mình ko biết, nên là mình đã viết thêm 1 hàm để extract mấy cái số này ra:
Với các số 0x1A, 0x45 tương ứng là các số trong sw_case, từ đó có được input hợp lí và lấy flag.

Về phần DFS, mình chỉ cần đổi cấu trúc dữ liệu lại thành stack, và cho ra kết quả khác, nhưng vẫn đúng. Cách này thì dùng phương pháp loại trừ như của a mochi cũng khá là hiệu quả.
Full script:
Script a mochi:
Script BFS:
from pwn import *
base = idaapi.get_imagebase()
G_S = [99401, 99581, 99761, 99941, 100121, 100301, 100481, 100661, 100841, 101021, 101201, 101381, 101561, 101741, 101921, 102101, 102281, 102461, 102641, 102821, 103001, 103181, 103361, 103541, 103721, 103901, 104081, 104261, 104441, 104621, 104801, 104981, 105161, 105341, 105521, 105701, 105881, 106061, 106241, 106421, 106601, 106781, 106961, 107141, 107321, 107501, 107681, 107861, 108041, 108221, 108401, 108581, 108761, 108871, 109051, 109231, 109411, 109591, 109771, 109951, 110131, 110311, 110491, 110671, 110851, 111031, 111211, 111391, 111571, 111751, 111931, 112111, 112291, 112471, 112651, 112831, 113011, 113191, 113371, 113551, 113731, 113911]
G_S = [G_S[i]+ base for i in range(82)]
def get_step(j_byte):
if b'\x48\x83\xC0\x08\x48' in j_byte:
return 1
if b'\x48\x83\xC0\x10\x48' in j_byte:
return 2
if b'\x48\x83\xC0\x18\x48' in j_byte:
return 3
if b'\x48\x83\xC0\x20\x48' in j_byte:
return 4
if b'\x48\x83\xC0\x28\x48' in j_byte:
return 5
if b'\x48\x83\xC0\x30\x48' in j_byte:
return 6
def Get_word(start):
mod_addr = get_qword(start)
mod_byte = get_bytes(mod_addr,100)
if start==0x29488 + base:
return True
if start==base + 0x29430:
return 0x100
ins = mod_byte.index(b'\x0f\xb7\x05')
word_idx = u32(mod_byte[ins+3:ins+7])
word_idx = mod_addr + ins + 7 + word_idx
word = u16(get_bytes(word_idx,2))
return word
def can_step(start:int,step:int,last_step:int)->bool:
if start==base + 0x29488 or start== 0x29430 + base:
return True
sw_addr = get_qword(start + 8*7)
sw_byte = get_bytes(sw_addr,500)
dic ={}
for i in range(200-10):
if sw_byte[i]==0x3d and (sw_byte[i+5:i+7]==b'\x0f\x84' or sw_byte[i+5]==0x74 or sw_byte[i+5:i+7]==b'\x0f\x85'):
number =u32(sw_byte[i+1:i+5])
if sw_byte[i+5:i+7]==b'\x0f\x84':
j_addr = sw_addr+i + u32(get_bytes(sw_addr+i+7,4))+11
j_byte = get_bytes(j_addr,14)
dic[get_step(j_byte)] = number
elif sw_byte[i+5]==0x74:
j_addr = sw_addr+i + u8(get_bytes(sw_addr+i+6,1))+7
j_byte = get_bytes(j_addr,14)
dic[get_step(j_byte)] = number
elif sw_byte[i+5:i+7]==b'\x0f\x85':
j_byte = get_bytes(sw_addr+i+11,14)
dic[get_step(j_byte)] = number
if step not in dic.keys():
return False
cur_step_word = dic[step]
if (last_step>>8)==(cur_step_word>>8):
return True
return False
def get_value(start):
if start == 0x29488+base:
return 0x5b
if start == 0x29430 + base:
return 0xA3
mod_addr = get_qword(start)
mod_byte = get_bytes(mod_addr,100)
ins = mod_byte.index(b'\x8b\x05')
dword_idx = u32(mod_byte[ins+2:ins+6])
dword_idx = mod_addr + ins + 6 + dword_idx
b = u32(get_bytes(dword_idx,4))
return b
start = 0x29488 + base
end = 0x27ac0 + base
queue = [('', 0, start, 0,[],0)]
visited = []
count = 0
while True:
count+=1
if len(queue) == 0: break
direction, level, start, move, bs,last = queue.pop(0)
if start==end:
print(direction,bs,'found')
break
if (level, start) in visited: continue
visited.append((level, start))
print(f'({direction}, {level}, cur {hex(start)}, {move}, {last})')
struct_start = start + 8
original_pos = level * 4
for i in range(6):
func_addr = get_qword(struct_start + i * 8)
code = get_bytes(func_addr, 50)
new_pos = original_pos
add = 0
try:
if code.find(b'\x48\x05') != -1 or code.find(b'\x48\x83\xc0') != -1 or code.find(
b'\x48\x83\xe8') != -1 or code.find(b'\x48\x2d') != -1:
if code.find(b'\x48\x05') != -1:
id = code.find(b'\x48\x05')
add = u32(get_bytes(func_addr + id + 2, 4)) // 8
elif code.find(b'\x48\x2d') != -1:
id = code.find(b'\x48\x2d')
add = -(u32(get_bytes(func_addr + id + 2, 4)) // 8)
elif code.find(b'\x48\x83\xc0') != -1:
id = code.find(b'\x48\x83\xc0')
add = u8(get_bytes(func_addr + id + 3, 1)) // 8
elif code.find(b'\x48\x83\xe8') != -1:
id = code.find(b'\x48\x83\xe8')
add = -(u8(get_bytes(func_addr + id + 3, 1)) // 8)
except Exception as e:
print(e,hex(start),direction)
continue
new_dir = direction + str(i + 1)
new_bs = bs + [get_value(start)]
if not can_step(start,i+1,last):
continue
new_pos += add
if level + 1 > 12: continue
if new_pos > len(G_S) - 1 or new_pos < 0: continue
id = G_S[new_pos] - 17
new_start = u32(get_bytes(id + 3, 4)) + id + 7
new_last = Get_word(start)
queue.append((new_dir, level+1, new_start, new_pos,new_bs,new_last))
Script DFS:
from pwn import *
base = idaapi.get_imagebase()
G_S = [99401, 99581, 99761, 99941, 100121, 100301, 100481, 100661, 100841, 101021, 101201, 101381, 101561, 101741, 101921, 102101, 102281, 102461, 102641, 102821, 103001, 103181, 103361, 103541, 103721, 103901, 104081, 104261, 104441, 104621, 104801, 104981, 105161, 105341, 105521, 105701, 105881, 106061, 106241, 106421, 106601, 106781, 106961, 107141, 107321, 107501, 107681, 107861, 108041, 108221, 108401, 108581, 108761, 108871, 109051, 109231, 109411, 109591, 109771, 109951, 110131, 110311, 110491, 110671, 110851, 111031, 111211, 111391, 111571, 111751, 111931, 112111, 112291, 112471, 112651, 112831, 113011, 113191, 113371, 113551, 113731, 113911]
G_S = [G_S[i]+ base for i in range(82)]
def get_step(j_byte):
if b'\x48\x83\xC0\x08\x48' in j_byte:
return 1
if b'\x48\x83\xC0\x10\x48' in j_byte:
return 2
if b'\x48\x83\xC0\x18\x48' in j_byte:
return 3
if b'\x48\x83\xC0\x20\x48' in j_byte:
return 4
if b'\x48\x83\xC0\x28\x48' in j_byte:
return 5
if b'\x48\x83\xC0\x30\x48' in j_byte:
return 6
def Get_word(start):
mod_addr = get_qword(start)
mod_byte = get_bytes(mod_addr,100)
if start==0x29488 + base:
return True
if start==base + 0x29430:
return 0x100
ins = mod_byte.index(b'\x0f\xb7\x05')
word_idx = u32(mod_byte[ins+3:ins+7])
word_idx = mod_addr + ins + 7 + word_idx
word = u16(get_bytes(word_idx,2))
return word
def can_step(start:int,step:int,last_step:int)->bool:
if start==base + 0x29488 or start== 0x29430 + base:
return True
sw_addr = get_qword(start + 8*7)
sw_byte = get_bytes(sw_addr,500)
dic ={}
for i in range(200-10):
if sw_byte[i]==0x3d and (sw_byte[i+5:i+7]==b'\x0f\x84' or sw_byte[i+5]==0x74 or sw_byte[i+5:i+7]==b'\x0f\x85'):
number =u32(sw_byte[i+1:i+5])
if sw_byte[i+5:i+7]==b'\x0f\x84':
j_addr = sw_addr+i + u32(get_bytes(sw_addr+i+7,4))+11
j_byte = get_bytes(j_addr,14)
dic[get_step(j_byte)] = number
elif sw_byte[i+5]==0x74:
j_addr = sw_addr+i + u8(get_bytes(sw_addr+i+6,1))+7
j_byte = get_bytes(j_addr,14)
dic[get_step(j_byte)] = number
elif sw_byte[i+5:i+7]==b'\x0f\x85':
j_byte = get_bytes(sw_addr+i+11,14)
dic[get_step(j_byte)] = number
if step not in dic.keys():
return False
cur_step_word = dic[step]
if (last_step>>8)==(cur_step_word>>8):
return True
return False
def get_value(start):
if start == 0x29488+base:
return 0x5b
if start == 0x29430 + base:
return 0xA3
mod_addr = get_qword(start)
mod_byte = get_bytes(mod_addr,100)
ins = mod_byte.index(b'\x8b\x05')
dword_idx = u32(mod_byte[ins+2:ins+6])
dword_idx = mod_addr + ins + 6 + dword_idx
b = u32(get_bytes(dword_idx,4))
return b
start = 0x29488 + base
end = 0x27ac0 + base
stack = [('', 0, start, 0,[],0)]
visited = []
count = 0
while True:
count+=1
if len(stack) == 0: break
direction, level, start, move, bs,last = stack.pop()
if start==end:
print(direction,bs,'found')
break
if (level, start) in visited: continue
visited.append((level, start))
print(f'({direction}, {level}, cur {hex(start)}, {move}, {last})')
struct_start = start + 8
original_pos = level * 4
for i in range(6):
func_addr = get_qword(struct_start + i * 8)
code = get_bytes(func_addr, 50)
new_pos = original_pos
add = 0
try:
if code.find(b'\x48\x05') != -1 or code.find(b'\x48\x83\xc0') != -1 or code.find(
b'\x48\x83\xe8') != -1 or code.find(b'\x48\x2d') != -1:
if code.find(b'\x48\x05') != -1:
id = code.find(b'\x48\x05')
add = u32(get_bytes(func_addr + id + 2, 4)) // 8
elif code.find(b'\x48\x2d') != -1:
id = code.find(b'\x48\x2d')
add = -(u32(get_bytes(func_addr + id + 2, 4)) // 8)
elif code.find(b'\x48\x83\xc0') != -1:
id = code.find(b'\x48\x83\xc0')
add = u8(get_bytes(func_addr + id + 3, 1)) // 8
elif code.find(b'\x48\x83\xe8') != -1:
id = code.find(b'\x48\x83\xe8')
add = -(u8(get_bytes(func_addr + id + 3, 1)) // 8)
except Exception as e:
print(e,hex(start),direction)
continue
new_dir = direction + str(i + 1)
new_bs = bs + [get_value(start)]
if not can_step(start,i+1,last):
continue
new_pos += add
if level + 1 > 12: continue
if new_pos > len(G_S) - 1 or new_pos < 0: continue
id = G_S[new_pos] - 17
new_start = u32(get_bytes(id + 3, 4)) + id + 7
new_last = Get_word(start)
stack.append((new_dir, level+1, new_start, new_pos,new_bs,new_last))