Monday, May 11, 2015

ASIS CTF 2015 (Re 125 + 150 points)

Giải này download được 2 file, submit được bài 150, đang làm bài 125 thì hết giờ :(
2 bài này theo đánh giá của mình là vừa tầm với số điểm (tuy nhiên có lẽ 2 bài nên đổi điểm số cho nhau thì hay hơn).
Vì là bài basic nên mình sẽ không đi sâu vào thuật toán mà chỉ nêu về các bước cơ bản khi re để tìm ra flag 2 bài này. Phần thuật toán sẽ được comment trong code.
Ok. let's start.
Download files here
1. Re 125 points (dark_aba92f5882a156452b18b895c722cea6)
- Sau khi download file dark_aba92f5882a156452b18b895c722cea6 về tiến hành view nó bằng 1 trình soạn thảo bất kỳ sẽ thấy vài ký tự ở gần đầu file là '7zXZ' -> đây là file nén xz. Trên linux sẽ giải nén bằng command sau:
  • mv dark_aba92f5882a156452b18b895c722cea6 dark.xz
  • tar -xvf dark.xz
- Sau khi giải nén sẽ thấy trong folder dark có 2 file (dark + flag.enc), như một thói quen ngó xem thông tin về các file này bằng command file
  •  file dark
dark: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=b3fd37b76503bda914d483041798fb9ec88ab929, stripped
  • file flag.enc
flag.enc: data
File dark là file chúng ta cần re (ELF 64 bit, bắt buộc phải có máy ảo 64bit mới chạy được), flag.enc là 1 file data bình thường. Tiến hành chạy thử file dark xem có chuyện gì xảy ra không:
  • chmod +x dark
  • ./dark
Usage: ./dark inputfile outputfile
Nhìn thông báo chúng ta có thể dự đoán đầu vào file dark là 1 file input, dark sẽ đọc nội dung trong file này sau đó xử lý và xuất ra file outputfile cho chúng ta -> flag.enc ở trên chính là outputfile, nhiệm vụ chúng ta là phải tìm lại nội dung inputfile.
- Ok. Tạo thử inputfile với nội dung 100 ký tự 'a' xem outputfile sẽ ra sao:
  • python -c 'print "a"*100' > inputfile
  • ./dark inputfile outputfile
thử mở outputfile bằng 1 trình soạn thảo bất kỳ thì thấy toàn ký tự giun dế :(, và hơn nữa số lượng ký tự khá lớn (so với file đầu vào chỉ có 100 ký tự 'a'), thử đếm xem file này bao nhiêu ký tự nhé:
  • wc -c outputfile
30215 outputfile
Output sinh ra tới 30215 ký tự. wth
- Tiến hành phân tích dark bằng ida (mình đang dùng 6.6 pro + hex-rays)  remote debug, bạn có thể đọc lại bài này để biết cách cấu hình remote debug
Sau khi load file bằng ida, thử tab string (shift + F12) xem có 'tử huyệt' gì không. Thấy 1 dòng 'khả nghi' sau:
  • .rodata:0000000000400A20 00000020 C Usage: %s inputfile outputfile\n
Click trực tiếp vào dòng đó:
  • .rodata:0000000000400A20 format db 'Usage: %s inputfile outputfile',0Ah,0
  • .rodata:0000000000400A20                                         ; DATA XREF: sub_400715+2F o
sub_400715 có tham chiếu đến string này. Click trực tiếp vào nó sau đó bấm F5 để hex-rays sinh mã Pseudocode cho chúng ta. Bước tiếp theo là quá trình thử và phân tích nên mình không nêu kỹ ở đây. Ở bên dưới là đoạn pseudocode mình đã comment đầy đủ:
 if ( a1 == 3 ) -> Kiểm tra số lượng tham số đầu vào
  {
    stream = fopen(*(v7 + 8), "r"); -> mở file input để đọc
    v21 = fopen(*(v7 + 16), "wb"); -> mở file output để ghi
    v20 = 30215; -> :D nhìn quen quen, nếu để ý kỹ sẽ nhớ là con số này đã xuất hiện rồi
    v19 = 16; 
    v18 = 30214LL;
    v2 = alloca(30224LL); -> xin cấp phát 1 vùng nhớ khá lớn (v2)
    ptr = &v5;
    v16 = 30214LL;
    v6 = 16LL;
    v3 = alloca(30224LL); -> cấp phát vùng nhớ thứ 2 (v3)
    v15 = &v5;
    fread(&v5, 1uLL, 30215uLL, stream); -> đọc 30215 char lưu vào v5 (tạm gọi là mảng input)
    for ( i = 0; v20 / v19 > i; ++i ) -> i =0;30215/16 >i;++i
    {
      for ( j = 0; j < v19; ++j )       -> j chạy từ 0 đến 15 (v19=16)
      {
        v14 = *(ptr + v19 * (i + 1) - j - 1);   -> v14 = input[16*(i+1)-j-1]
        sprintf(&s, "%02x", v14);
        nptr = v12;
        v10 = s;
        v13 = strtol(&nptr, 0LL, 16);
        *(v15 + v19 * i + j) = i * i ^ j * j ^ v13; ----> một vài thao tác xử lý thằng v14, sau đó lưu vào 1 ví trí khác (v15)
      }
    }
    fwrite(v15, 1uLL, v20, v21); -> sau 2 vòng lặp này thì tiến hành ghi ra file output với đầu vào là  mảng v15
    fclose(v21);
    fclose(stream);
  }
  else
  {
    printf("Usage: %s inputfile outputfile\n", *v7);
  }
  return 0LL;
}
Sau khi đã hiểu thuật toán, chúng ta tiến hành decrypt. File sau, sau khi decrypt xong, mở file input bằng 1 trình soạn thảo chúng ta sẽ dễ dàng nhận thấy nó là 1 file pdf. Đổi tên file và gom flag nào.
2. Re 150 points keylead (nhờ khả năng lươn lẹo + suy nghĩ đơn giản mà bài này solve trong 15', bravo :v)
Công đoạn giải nén lấy file đề bài tương tự như bài trên. Đây là mã pseudo code:
Về nội dung thì bài này nhiệm vụ của bạn là phải canh gõ 1 character vào (ký tự nào cũng được), chương trình sẽ lấy thời gian tại thời điểm sau khi đọc được ký tự gõ vào để sinh random 5 số , bạn phải canh me sau cho 5 số chương trình sinh ra là 3,1,3,3,7.
puts("hi all ----------------------");
  puts("Welcome to dice game!");
  puts("You have to roll 5 dices and get 3, 1, 3, 3, 7 in order.");
  puts("Press enter to roll.");
  v0 = getchar(); -> đọc ký tự
  v1 = time(0LL);
  srand(v1); -> seed random dựa vào thời điểm nhận được ký tự
  v8 = time(0LL); -> đo lại time
  v2 = rand() % 6 + 1; -> số thứ 1
  v7 = rand() % 6 + 1;
  v6 = rand() % 6 + 1;
  v5 = rand() % 6 + 1;
  v4 = rand() % 6 + 1;  -> số cuối cùng, nếu để ý kỹ bạn sẽ thấy rằng v4 không bao giờ =7 được :v
  printf("You rolled %d, %d, %d, %d, %d.\n", v2, v7, v6, v5, v4);
  if ( v2 != 3 ) 
    goto LABEL_20;
  if ( time(0LL) - v8 > 2 )
  {
    puts("No cheat!");
    return 0xFFFFFFFFLL;
  }
  if ( v7 != 1 )
    goto LABEL_20;
  if ( time(0LL) - v8 > 2 )
  {
    puts("No cheat!");
    return 0xFFFFFFFFLL;
  }
  if ( v6 != 3 )
    goto LABEL_20;
  if ( time(0LL) - v8 > 2 )
  {
    puts("No cheat!");
    return 0xFFFFFFFFLL;
  }
  if ( v5 != 3 )
    goto LABEL_20;
  if ( time(0LL) - v8 > 2 )
  {
    puts("No cheat!");
    return 0xFFFFFFFFLL;
  }
  if ( v4 != 7 )
  {
LABEL_20:
    puts("You DID NOT roll as I said!");
    puts("Bye bye~");
    result = 0xFFFFFFFFLL;
  }
  else if ( time(0LL) - v8 <= 2 )
  {
    puts("You rolled as I said! I'll give you the flag.");
    sub_4006B6();
    result = 0LL;
  }
  else
  {
    puts("No cheat!");
    result = 0xFFFFFFFFLL;
  }
  return result;
}

Với nhận xét 4 không bao giờ =7 được -> chúng ta sẽ không bao giờ canh me được thời gian để gõ.
-> Chỉ còn khả năng chỉnh sửa các số mà chương trình đã sinh ra cho chúng ta trực tiếp trong bộ nhớ (sau đoạn rand()%6).
Bằng khả năng mò mẫm, sau khi chỉnh sửa xong 1 cái flag xinh đẹp sẽ hiện ra. :D. 
P/s: for you, not for me.