Rwctf 2020 Baby Escape

r -L ./dependency -kernel ./vmlinuz-5.4.0-58-generic -initrd ./rootfs.cpio -cpu kvm64,+smep -m 64M -monitor none -device fun -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" -nographic

struct FunState{
PCIDevice_0 pdev;
MemoryRegion_0 mmio;
uit32_t_0 addr;
uit32_t_0 size;
uit32_t_0 idx;
uit32_t_0 result_addr;
Funreq *req;
AddressSpace_0 *as;
};

00000000 FunState struc ; (sizeof=0xA00, align=0x10, copyof_4860)
00000000 pdev PCIDevice_0 ?
000008F0 mmio MemoryRegion_0 ?
000009E0 addr dd ?
000009E4 size dd ?
000009E8 idx dd ?
000009EC result_addr dd ?
000009F0 req dq ? ; offset
000009F8 as dq ? ; offset
00000A00 FunState ends

struct FunReq{
uint32_t_0 total_size;
char *list[127];
};

FunReq *__cdecl create_req(uint32_t_0 size)
{
uint32_t_0 i; // [rsp+10h] [rbp-10h]
uint32_t_0 t; // [rsp+14h] [rbp-Ch]
FunReq *req; // [rsp+18h] [rbp-8h]

if ( size > 0x1FBFF ) // size = 0x1fbff
return 0LL;
req = (FunReq *)malloc(0x400uLL);
memset(req, 0, sizeof(FunReq));
req->total_size = size; // total_size = 0x1ffbf
t = (req->total_size >> 10) + 1; // t = 0x1ffbf >> 10 + 1 = 126 + 1 = 127
for ( i = 0; i < t; ++i )
req->list[i] = (char *)malloc(0x400uLL);// i: 0-126
return req;
}

void __cdecl delete_req(FunReq *req)
{
uint32_t_0 i; // [rsp+18h] [rbp-8h]
uint32_t_0 t; // [rsp+1Ch] [rbp-4h]

t = (req->total_size >> 10) + 1;
for ( i = 0; i < t; ++i )
free(req->list[i]);
free(req);
}

void __cdecl handle_data_read(FunState *fun, FunReq *req, uint32_t_0 val)
{
if ( req->total_size && val <= 0x7E && val < (req->total_size >> 10) + 1 )
{
put_result(fun, 1u);
dma_memory_read_9(fun->as, (val << 10) + fun->addr, req->list[val], 0x400uLL);
put_result(fun, 2u);
}
}

void __cdecl handle_data_write(FunState *fun, FunReq *req, uint32_t_0 val)
{
if ( req->total_size && val <= 0x7E && val < (req->total_size >> 10) + 1 )
{
put_result(fun, 1u);
dma_memory_write_9(fun->as, (val << 10) + fun->addr, req->list[val], 0x400uLL);
put_result(fun, 2u);
}
}

fun_mmio_read和fun_mmio_write

进入函数以后有一波相同的操作,根据size来计算一个rax进行跳转

根据size的值, 4位一组进行操作

mov     rax, [rbp+addr]     ;size = 10/9/8, rax = 8, size = 7/6/5/4, rax = 4, size=3/2/1/0, rax = 0, 盲猜一个size=12/13/14/15, rax=12
;size = 16/17/18/19, rax = 16 = 0x10, 满足跳转
lea rdx, ds:0[rax*4] ;rdx = 0x20 = 32 = rax * 4
lea rax, unk_A450B0 ;rax = rip + 0x573362 = 0x55b24522906c ->const
mov eax, [rdx+rax] ;eax = [rip + 0x573362 + rax * 4] = 0xffa8ccd4
cdqe ;eax = 0xffffffffffa8ccd4
lea rdx, unk_A450B0 ;rdx = rip + 0x573356 = 0x55b24522906c ->const
add rax, rdx ;rax = rax + rdx = 0xffffffffffa8ccd4 + 0x55b24522906c = 0x1000055b244cb5d40 溢出1位 = 0x55b244cb5d40
db 3Eh
jmp rax ;跳转,我们的目的是跳到0x4D1D64,另外几个块是0x4D1D1C, 0x4D1D2E, 0x4D1D40, 0x4D1D52(size大于10跳到这里)
;即0x55b244cb5d64, 0x1000055b244cb5d64 - 0x55b24522906c = 0xffffffffffa8ccf8

pwndbg> x/30gx 0x56401a18706c
0x56401a18706c: 0xffa8cd2effa8ccb0 0xffa8cd2effa8cd2e
0x56401a18707c: 0xffa8cd2effa8ccc2 0xffa8cd2effa8cd2e
0x56401a18708c: 0xffa8cd2effa8ccd4 0xffa8cd2effa8cd2e
0x56401a18709c: 0xffa8cd2effa8cce6 0xffa8cd2effa8cd2e
0x56401a1870ac: 0xffa8cd46ffa8ccf8 0xffa8ce38ffa8ce38
0x56401a1870bc: 0xffa8cd58ffa8ce38 0xffa8ce38ffa8ce38
0x56401a1870cc: 0xffa8cd6affa8ce38 0xffa8ce38ffa8ce38
0x56401a1870dc: 0xffa8cd7cffa8ce38 0xffa8ce38ffa8ce38
0x56401a1870ec: 0xffa8cd8effa8ce38 0xffa8ce38ffa8ce38
0x56401a1870fc: 0xffa8cdc8ffa8ce38 0xffa8ce38ffa8ce38
0x56401a18710c: 0xffa8cdf6ffa8ce38 0x696d2f77682f2e2e
0x56401a18711c: 0x632e6e75662f6373 0x6e7566006e756600
0x56401a18712c: 0x0000006f696d6d2d 0x0000000000000000
0x56401a18713c: 0x5f69637000000000 0x6c6165725f6e7566

0xb0 + 0x6c = 0x11c
0xc2 + 0x6c = 0x12e
0xd4 + 0x6c = 0x140
0xe6 + 0x6c = 0x152
0xf8 + 0x6c = 0x164

  • get size: 0x4d1d1c 0
  • get addr: 0x4d1d2e 4
  • getraddr: 0x4d1d40 8
  • get idx : 0x4d1d52 12
  • write : 0x4d1d64 16
  • return : 0x4d1d9a 20

再看fun_mmio_write, const=0x55d9ef87c0b0

0x55d9ef87c0b0: 0xffa8ce38ffa8cd46 0xffa8ce38ffa8ce38
0x55d9ef87c0c0: 0xffa8ce38ffa8cd58 0xffa8ce38ffa8ce38
0x55d9ef87c0d0: 0xffa8ce38ffa8cd6a 0xffa8ce38ffa8ce38
0x55d9ef87c0e0: 0xffa8ce38ffa8cd7c 0xffa8ce38ffa8ce38
0x55d9ef87c0f0: 0xffa8ce38ffa8cd8e 0xffa8ce38ffa8ce38
0x55d9ef87c100: 0xffa8ce38ffa8cdc8 0xffa8ce38ffa8ce38
0x55d9ef87c110: 0x682f2e2effa8cdf6 0x662f6373696d2f77
0x55d9ef87c120: 0x6e756600632e6e75 0x696d6d2d6e756600

  • set size: 0x4D1DF6, 0x1000055d9ef308df6 - 0x55d9ef87c0b0 = 0xffffffffffa8cd46 <- 0x55d9ef87c0b0 - 0x55d9ef87c0b0 = 0x00 / 4 = 0
  • set addr: 0x4D1E08, 0x1000055d9ef308e08 - 0x55d9ef87c0b0 = 0xffffffffffa8cd58 <- 0x55d9ef87c0c0 - 0x55d9ef87c0b0 = 0x10 / 4 = 4
  • setraddr: 0x4D1E1A, 0x1000055d9ef308e1a - 0x55d9ef87c0b0 = 0xffffffffffa8cd6a <- 0x55d9ef87c0d0 - 0x55d9ef87c0b0 = 0x20 / 4 = 8
  • set idx: 0x4D1E2C, 0x1000055d9ef308e2c - 0x55d9ef87c0b0 = 0xffffffffffa8cd7c <- 0x55d9ef87c0e0 - 0x55d9ef87c0b0 = 0x30 / 4 = 12
  • read : 0x4D1E3E, 0x1000055d9ef308e3e - 0x55d9ef87c0b0 = 0xffffffffffa8cd8e <- 0x55d9ef87c0f0 - 0x55d9ef87c0b0 = 0x40 / 4 = 16
  • create: 0x4D1E78, 0x1000055d9ef308e78 - 0x55d9ef87c0b0 = 0xffffffffffa8cdc8 <- 0x55d9ef87c100 - 0x55d9ef87c0b0 = 0x50 / 4 = 20
  • delete: 0x4D1EA6, 0x1000055d9ef308da6 - 0x55d9ef87c0b0 = 0xffffffffffa8cdf6 <- 0x55d9ef87c110 - 0x55d9ef87c0b0 = 0x60 / 4 = 24

那么先调用create, , 可以看到130047作为val进入了fun_mmio_write函数

之后再用mmio_write(20, 0)创建req

调用mmio_write(16, 0)来触发read

  1. fun_mmio_read函数

    1. 读FunState的各种数据到val, 包括addr, size, idx, result_addr
    2. handle_data_write, dma_memory_write_9(fun->as, (val << 10) + fun->addr, req->list[val], 0x400uLL); 应该是用来将req里指定val的数据写入到Address_Space, 可以用来修改堆块数据
  2. fun_mmio_write

    1. 向FunStatus的addr, size, idx, result_addr写入val中的数据
    2. create_req, 传入FunStatus的size, 创建FunReq的对象, 设置它的total_size
    3. delete_req, 根据total_size free list
    4. handle_data_read, dma_memory_read_9(fun->as, (val << 10) + fun->addr, req->list[val], 0x400uLL); 用来把Address_Space的东西读取到req里,用来读取堆块数据

具体再看handle_data_write函数, 首先检查req对象是否为空, 然后将fun->idx作为val, 调用handle(fun, req, fun->idx), 判断req->total_size != 0, val <= 0x7e, val < (req->total_size) >> 10 + 1

create的时候, size最大是0x1fbff, 会创建0x1fbff >> 10 + 1 = 126 + 1 = 127个req->list, 编号为0-0x7e, 设置total_size = size = 0x1fbff

而handle_data_read和handle_data_write的时候, val的条件是小于等于0x7e, 若此时val = 0x7e < (0x1fbff >> 10) + 1 = 0x7f, 是可以通过if的, 在调用dmm_memory_read和write的时候, 第二个参数的值为0x7e << 10 + fun->addr, 0x7e个堆块的地址分别是0-0x3ff, 0x400-0x7ff, 以此类推, 最后一个堆块的地址 是(0x7e - 1 * 0x400) = 0x1f400 - 0x1f7ff, 但是0x7e << 10 = 0x1f800, 再加上我们可控的fun->addr, 会有一个任意读写

  1. set size = 0x1fbff
  2. create 0x1fbff: mmio_write(0, 130047)
  3. set idx = 0x7e mmio_write(12, 0x7e)
  4. set fun->addr =
  5. handle_data_read

size = 0x1fbff
req->total_size = 0x1fbff
t = 126 + 1 = 127
b fun_mmio_read
b
fun_mmio_write
r -L ./dependency -kernel ./vmlinuz-5.4.0-58-generic -initrd ./rootfs.cpio -cpu kvm64,+smep -m 64M -monitor none -device fun -append “root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr” -nographic

io_regions = {{
    addr = 4273934336,
    size = 4096,
    type = 0 '\000',
    memory = 0x55920cc61460,
    address_space = 0x55920bd0b900
  }, {

addr = 0,
size = 130047,
idx = 0,
result_addr = 0,
req = 0x0,
as = 0x55920a73fe00

-L ./dependency -kernel ./vmlinuz-5.4.0-58-generic -initrd ./rootfs.cpio -cpu kvm64,+smep -m 64M -monitor none -device fun -append “root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr” -nographic

► 0x56463441ebb6 call dma_memory_read
rdi: 0x564635076e00 (address_space_memory) ◂— 0x0
rsi: 0x1001f800
rdx: 0x7f0ec4857bd0 ◂— 0x0
rcx: 0x400
mmio_mem: 0x7fe32d058000
userbuf_va: 0x7fe32d057000
userbuf_pa: 0x2b22000