CLB An toàn Thông tin 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 hoặc inseclab@uit.edu.vn và fanpage: fb.com/inseclab.
Mochi Nishi
vaudeville
Description
Info:The Dramatis Personae invite you to join in this latest production.
nc vaudeville.chal.uiuc.tf 1337
File: vaudevilleTL;DR:Analyze the binary => we can see that it will give us a number and we must return an integer which is the answer.Analyze
sub_3660(x, y)
=> Some sort of encrypting function.Deal with functions likefork()
andpipe()
=> create a child process to give the parent a string and save the length of it.Goal: understandingsub_3660(x, y)
, write a decrypt script to find the input and submit to server to get the flag.
Deep Analyze
Here’s the main function:
__int64 __fastcall main(__int64 a1, char **a2, char **a3){ __int64 v3; // rbp int v4; // ebx unsigned int v6; // [rsp+4h] [rbp-24h] BYREF unsigned __int64 v7; // [rsp+8h] [rbp-20h] v7 = __readfsqword(0x28u); v3 = (unsigned int)sub_35F0(a1, a2, a3); __printf_chk(1LL, "Challenge: %u\n", v3); __printf_chk(1LL, "Response: "); fflush(stdout); __isoc99_scanf("%u", &v6); fflush(stdin); v4 = sub_3660(v6, 7); if ( v4 == (unsigned int)sub_3660(v3, 140) ) sub_3DD0(); else puts("that's rough buddy"); return 0LL;}
Static analyzing it, I know that v3
is the challenge’s number, then it reads our input v6
and do some encryption related to v3
, if it satisfies the condition, we got the flag. So we have to dive into sub_3660(x, y)
v2 = 0LL; v50 = __readfsqword(0x28u); v47 = 0; ptr[0] = (__int128)_mm_load_si128((const __m128i *)&xmmword_C450); ptr[1] = ptr[0]; v45[0] = ptr[0]; v45[1] = ptr[0]; v46[0] = ptr[0]; v46[1] = ptr[0]; do { *((_BYTE *)v45 + v2) = ((a1 & (1 << v2)) != 0) + 48; ++v2; } while ( v2 != 32 ); memset(filename, 0, sizeof(filename)); __sprintf_chk(filename, 1LL, 128LL, "/tmp/tmp_%u", a1); stream = fopen(filename, "wb"); setvbuf(stream, 0LL, 2, 0LL); fwrite(ptr, 1uLL, 0x60uLL, stream); fflush(stream); v3 = fopen(filename, "rb"); v4 = fopen(filename, "rb"); setvbuf(v3, 0LL, 2, 0LL); setvbuf(v4, 0LL, 2, 0LL);
First part of the function, it will pre-init data base on our input, I’ll call it data
, and data
looks like this: "0" * 32 + (binary form of our input in reverse) + "0" * 32
. Then it creates folder /tmp/tmp_(our input)
and write that pre-init data to it. We can easily check it by putting breakpoint after the fwrite()
and cat /tmp/tmp_%u
v39 = 0;LABEL_5: fseek(v3, 32LL, 0); fseek(v4, 19LL, 0); v5 = v45; do { fread(&v40, 1uLL, 1uLL, v3); fread(&v41, 1uLL, 1uLL, v4); v6 = v41; v7 = v40; if ( pipe(&v42) ) { fwrite("Pipe failed.\n", 1uLL, 0xDuLL, stderr); v11 = -1; } else { v8 = fork(); if ( !v8 ) { close(v42); v31 = dup(fd); v32 = fdopen(fd, "w"); v33 = fdopen(v31, "w"); fwrite(*(&off_E020 + v7), 1uLL, v7, v32); v34 = v6; v35 = *(&off_E020 + v6); goto LABEL_28; } if ( v8 < 0 ) {LABEL_33: fwrite("Fork failed.\n", 1uLL, 0xDuLL, stderr); exit(-1); } close(fd); v9 = fdopen(v42, "r"); v10 = fread(v49, 1uLL, 0x200uLL, v9); fclose(v9); v11 = v10; } *(_BYTE *)v5 = v11; v5 = (__int128 *)((char *)v5 + 1); } while ( v46 != v5 );
This is a very interesting part in our functions:
- First it reads
data
begin from positions 19 and 32, I’ll call ita
andb
. - It will create a
communication
pipe between parent and child process. The parent process will read what the child one give it to. Meanwhile the child will concat 2 strings fromoff_E020
, and their indices areint(a)
andint(b)
, then give it to the parent. - Finally, it takes the length of the concat string and store into
data
. Overall, this code blocks will replace each characters from 32 to 63 in ourdata
using some encryption. To clarify informations, I inspectoff_E020
to see if something is special, and indeed, there’s something.
If you look closely, you’ll see that it stores a 256 strings, sort by their lengths, from 0 to 255 :D. So basically the above code just take two chars, add them together and put it back to data
.Interestingly, the rest of the code in this function will encrypt the data
similarly to previous code blocks, and it will loop a2
times - second argument in the function.Now we know what this binary do: the challenge asks us to give a number so that this number after encrypt 7 times, will equals to the challenge’s number encrypted 140 times.
Final Step
I wrote a small script that emulate the encryption part
def encrypted(li): id1 = 32 id2 = 19 or_li = [i for i in li] for oo in range(32): li[id1] = (or_li[id1] + or_li[id2]) & 0xff id1 += 1 id2 += 1 id1 = 32 id2 = 49 or_li = [i for i in li] for oo in range(32): li[id1] = (or_li[id1] + or_li[id2]) & 0xff id1 += 1 id2 += 1 id1 = 32 id2 = 27 or_li = [i for i in li] for oo in range(32): li[id1] = (or_li[id1] + or_li[id2]) & 0xff id1 += 1 id2 += 1 return li
Now all we have to do is reverse this encryption. Here’s the decrypt script:
def decrypted(li): id = 32 id1 = 32 id2 = 27 for oo in range(32): li[id1] = (li[id1] - li[id2]) & 0xff id1 += 1 id2 += 1 id1 = 63 id2 = 80 for oo in range(32): li[id1] = (li[id1] - li[id2]) & 0xff id1 -= 1 id2 -= 1 id1 = 32 id2 = 19 for oo in range(32): li[id1] = (li[id1] - li[id2]) & 0xff id1 += 1 id2 += 1 return li
Put everything together, we have the script:
val = int(input())# val = 1501756717def convert(v): t = '' while v != 0: t += str(v & 1) v >>= 1 t += '0' * (32 - len(t)) return tdef encrypted(li): id1 = 32 id2 = 19 or_li = [i for i in li] for oo in range(32): li[id1] = (or_li[id1] + or_li[id2]) & 0xff id1 += 1 id2 += 1 id1 = 32 id2 = 49 or_li = [i for i in li] for oo in range(32): li[id1] = (or_li[id1] + or_li[id2]) & 0xff id1 += 1 id2 += 1 id1 = 32 id2 = 27 or_li = [i for i in li] for oo in range(32): li[id1] = (or_li[id1] + or_li[id2]) & 0xff id1 += 1 id2 += 1 return lidef decrypted(li): id = 32 id1 = 32 id2 = 27 for oo in range(32): li[id1] = (li[id1] - li[id2]) & 0xff id1 += 1 id2 += 1 id1 = 63 id2 = 80 for oo in range(32): li[id1] = (li[id1] - li[id2]) & 0xff id1 -= 1 id2 -= 1 id1 = 32 id2 = 19 for oo in range(32): li[id1] = (li[id1] - li[id2]) & 0xff id1 += 1 id2 += 1 return lipayload = '0' * 32 + convert(val) + '0' * 32li = [ord(i) for i in payload]times = 7for i in range(140): li = encrypted(li)for i in range(7): li = decrypted(li)de = lians = 0id = 32for i in range(32): ans += ((de[i + id] & 1) << i)print(ans)
Submit the answer and we get the flag.uiuctf{b0rne_4ward_c3as313ss1y_1nt0_teh_f-u-t-u-r-e_*dab*}
pawnfish
Description
Info:Fishes are pretty good at chess.
nc pawnfish.chal.uiuc.tf 1337
File: handout.tar.gzTL;DR:Look around the source code => We have to win 100 times and the
current_player
isBlack
to get the flag.wins
variable issigned char
=> We can lose a lot in order to makewins
equals to 100, but what aboutcurrent_player
?We have to win a game to turncurrent_player
toBlack
. The file also use stockfish engine => Can’t win easily, we have to find some bugs and take those advantages to win.FindEn Passant
bugs => Find a way to win, put all the things together to get the flag.
Plan
First of all, I think this is more like a Misc
than RE
challenge, because you need quite chess knowledges to solve this challenge.The file gave us source code and some files related to chess stuff, so I just check chal.c
. This is a very long source code, I immidiately check the condition to get the flag:
if (current_player == Black) { FILE* flag; int c; flag = fopen("flag.txt", "r"); if (flag) { while ((c = getc(flag)) != EOF) { putchar(c); } putchar('\n'); fclose(flag); } else { puts("Flag is missing :/"); }} else { puts("How is this possible?? You didn't win the 100th game!"); puts("I won't stand a cheater!!!"); exit(96);}
And looking around main functions. I know that we also need to make wins
equals 100 to escape the while loop and come to our check condition:
int main() {...while (wins != 100 || current_player == White) {...if (current_player == Black) { FILE* flag; int c; flag = fopen("flag.txt", "r"); if (flag) { while ((c = getc(flag)) != EOF) { putchar(c); } putchar('\n'); fclose(flag); }...
For the wins
condition, we just need to lose a lot, enough to make the wins
equals to 100, that’s the easy part. The hard part is to make current_player
equals to Black
. In order to do that, we have to win a game. But this is stockfish engine, you need Hikaru Nakamura
to defeat this to get us the flag. After a while, I think there has to be some bugs around…
The Bug
… and indeed, there is. After spending a lot of time to debug, my teammate find the En Passant
flaw: the En Passant
is meant to apply just for pawn. But for some weird reasons, this program applies En Passant
to literally every chess pieces.
This is huge, we can slowly build our way to defeat this engine. After several tries, we finally win one game, and using everything we’ve known so far to write a script:
from pwn import *p = remote('pawnfish.chal.uiuc.tf', 1337)def send_piece(mov1, mov2): p.recv() p.sendline(mov1) p.recv() p.sendline(mov2)for i in range(157): print(i) send_piece('e2','e4') send_piece('e1','e2') send_piece('e2','e3') send_piece('e3','f4') send_piece('f4','e5') p.recvuntil('lose') p.recvline()send_piece('e2','e4')send_piece('c2','c3')send_piece('e4','e5')send_piece('e5','d6')send_piece('g1','f3')send_piece('d2','d4')send_piece('c3','d4')send_piece('d4','e5')send_piece('f1','e2')send_piece('e1','g1')send_piece('e5','d6')send_piece('h2','h3')send_piece('e2','f3')send_piece('d6','c7')send_piece('f1','e1')send_piece('c1','e3')send_piece('d1','e2')send_piece('e3','c5')send_piece('e2','e7')send_piece('e1','e7')send_piece('e7','a7')send_piece('a2','a4')send_piece('a4','b5')send_piece('b1','c3')send_piece('b5','b6')send_piece('b6','b7')send_piece('a7','a8')send_piece('f3','b7')send_piece('a1','b1')send_piece('b2','b4')send_piece('c3','a2')send_piece('b4','b5')send_piece('b5','b6')send_piece('a8','a7')send_piece('a2','c3')send_piece('c3','d5')send_piece('d5','e7')send_piece('e7','c6')send_piece('b6','b7')send_piece('h3','g4')send_piece('g4','f5')send_piece('b7','b8')send_piece('b1','b8')send_piece('g1','h2')send_piece('b8','f8')send_piece('f5','f6')send_piece('h2','h3')send_piece('g2','g4')send_piece('h3','g3')send_piece('c6','e7')send_piece('f8','d8')send_piece('a7','c7')send_piece('e7','d5')send_piece('d8','b8')send_piece('f6','f7')send_piece('c7','a7')p.interactive()
And the answer:
$ python3 solve.py[+] Opening connection to pawnfish.chal.uiuc.tf on port 1337: Done0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156[*] Switching to interactive modeLocation to > Moving WHITE ♖ from row: 1, col: 2, TO row: 1, col: 0You win :)uiuctf{strange_the_only_winning_move_is_to_lose}
uiuctf{strange_the_only_winning_move_is_to_lose}
pivik
pawnfish
Dù là RE nhưng mình vẫn thấy nó giống pwn hơn.
Source của challenge mình sẽ để ở đây.
Vì source code khá dài nên mình chỉ phân tích những chỗ cần thiết.
Chương trình mô phỏng trò chơi cờ vua, nếu win đủ 100 lần thì sẽ in flag. Nhưng với trình độ cờ của bản thân mình thì chơi tới năm sau cũng không thắng nổi ?
Nên thôi ngồi kiếm bug vậy. Và bug đầu tiên mình thấy được là ở kiểu giá trị của biến wins
:
int main() {...signed char wins = 0;...
Biến wins
có kiểu là signed char
nên mình hoàn toàn có thể thua 128 lần để lần thua sau đó overflow thành số dương. Lúc đầu mình nghĩ nếu có thể overflow thì overflow về 100
là xong, easy game right?
Nhưng đời không như là mơ, để nó in ra flag ngoài việc số lần win là 100 thì current_player
cần phải là Black
.
int main() {...while (wins != 100 || current_player == White) {...if (current_player == Black) { FILE* flag; int c; flag = fopen("flag.txt", "r"); if (flag) { while ((c = getc(flag)) != EOF) { putchar(c); } putchar('\n'); fclose(flag); }...
Và current_player
chỉ đổi sang Black
khi mình win
và sẽ đổi trở lại thành White
khi bắt đầu ván mới, đồng nghĩa với việc làm gì cũng được nhưng ván 100 phải là win. Đến đây thôi thì vẫn chưa đủ để viết exploit, nên mình cần tìm thêm bug. Và sau 1 lúc thì mình đã tìm ra được bug tiếp theo. Bug này liên quan tới trường hợp En_passant
như trong hint của đề bài. Các bạn có thể tham khảo tại đây. Mình không rành về cờ vua lắm, nhưng theo wikipedia thì En_passant
chỉ xảy ra với quân tốt khi có 1 quân tốt đối thủ khác nằm bên cạnh ngay khi vừa thực hiện double-step move
, và En_passant
cần phải được thực hiện ngay sau nước double-step move
, nếu không thì quyền En_passant
sẽ bị mất.
Trong bài này thì với En_passant
, quân tốt của mình không chỉ ăn được quân tốt của đối thủ mà là tất cả các quân luôn, miễn là quân của đối thủ nằm kế bên quân tốt của mình. Các bạn xem trường hợp bên dưới:
ới ví dụ trên thì quân tốt của mình có thể ăn được quân mã. Tới đây mình không tìm thêm được bug nào nữa nên mình quyết định sử dụng lỗi này để đánh thắng máy luôn, và cũng hên là thắng được :v
Tới đây thì viết exploit được rồi.
Exploit code:
from pwn import *p = remote('pawnfish.chal.uiuc.tf', 1337)def send_piece(mov1, mov2): p.recv() p.sendline(mov1) p.recv() p.sendline(mov2)for i in range(157): print(i) send_piece('e2','e4') send_piece('e1','e2') send_piece('e2','e3') send_piece('e3','f4') send_piece('f4','e5') p.recvuntil('lose') p.recvline()send_piece('e2','e4')send_piece('c2','c3')send_piece('e4','e5')send_piece('e5','d6')send_piece('g1','f3')send_piece('d2','d4')send_piece('c3','d4')send_piece('d4','e5')send_piece('f1','e2')send_piece('e1','g1')send_piece('e5','d6')send_piece('h2','h3')send_piece('e2','f3')send_piece('d6','c7')send_piece('f1','e1')send_piece('c1','e3')send_piece('d1','e2')send_piece('e3','c5')send_piece('e2','e7')send_piece('e1','e7')send_piece('e7','a7')send_piece('a2','a4')send_piece('a4','b5')send_piece('b1','c3')send_piece('b5','b6')send_piece('b6','b7')send_piece('a7','a8')send_piece('f3','b7')send_piece('a1','b1')send_piece('b2','b4')send_piece('c3','a2')send_piece('b4','b5')send_piece('b5','b6')send_piece('a8','a7')send_piece('a2','c3')send_piece('c3','d5')send_piece('d5','e7')send_piece('e7','c6')send_piece('b6','b7')send_piece('h3','g4')send_piece('g4','f5')send_piece('b7','b8')send_piece('b1','b8')send_piece('g1','h2')send_piece('b8','f8')send_piece('f5','f6')send_piece('h2','h3')send_piece('g2','g4')send_piece('h3','g3')send_piece('c6','e7')send_piece('f8','d8')send_piece('a7','c7')send_piece('e7','d5')send_piece('d8','b8')send_piece('f6','f7')send_piece('c7','a7')p.interactive()
$ python3 solve.py[+] Opening connection to pawnfish.chal.uiuc.tf on port 1337: Done0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156[*] Switching to interactive modeLocation to > Moving WHITE ♖ from row: 1, col: 2, TO row: 1, col: 0You win :)uiuctf{strange_the_only_winning_move_is_to_lose}