PWN 入门 | pwnable.kr 前 21 题 - 17 进行中
2024-1-1
| 2024-2-2
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
最早刷这个靶场的时候还是大一,那会很多都不知道是啥,也没做笔记,就对着WP摁敲都敲不明白,不懂的就百度,搜完后还是有很多不懂的。现在回来看看,以前大一搞不懂的东西现在觉得很简单,或许这就是成长吧~
刷题前的一些入门资料推荐(随便挑一两个看即可):
  • 《程序员的自我修养—链接、装载与库》
个人感觉讲的非常非常好!有点后悔之前入门PWN没读过。从底层,清晰的熟悉了缓冲区溢出漏洞的原理和利用方式(书个人感觉写的很风趣,完全停不下来那种),对于一个Web狗来说,一直用的都是别人准备好的shellcode,一直也没真正搞懂这些东西是怎么写出来的,看完这本书,对shellcode编写也有了初步的了解,比如书中介绍到的后门编写章节,和CS、各种C2、各种webshell功能都有几分相似之处~ 从此后门的shellcode编写不在变的那么陌生。学计算机最好玩的地方就在于被封装好的上层建筑,被一层层的从底层技术原理慢慢揭开~
  • 《CTF竞赛权威指南Pwn篇》
纯小白的话可以先入门一下 Linux操作系统基础、C语言 和 汇编语言

0x01 fd

Mommy! what is a file descriptor in Linux?
*try to play the wargame your self but if you are ABSOLUTE beginner, follow this tutorial link: https://youtu.be/971eZhMHQQw
 
ssh [email protected] -p2222 (pw:guest)
ssh 连上去看到代码 fd.c
通过 ls -l 可以看到,fd可执行程序有suid位,所属用户为 fd_pwn,和 flag 文件的所属用户一致。因此,我们的目标是通过执行 fd 程序,执行 system("/bin/cat flag") 命令执行来读取 flag 文件
要想执行 system 函数,需要通过 if 判断 !strcmp("LETMEWIN\n", buf) 为真,strcmp 函数当两个字符串相等时返回 0,满足条件。
read 函数读取 fd 里的 32个字节到buf中,但我们可控的是一个文件描述符,Linux 里文件描述符各值的含义
  • 0 表示标准输入
  • 1 表示标准输出
  • 2 表示错误输出
即,只要 atoi( argv[1] ) - 0x1234; 为 0,即可从标准输入里读数据,在输入 LETMEIN 回车即可~
0x1234 对应十进制为 4660
notion image
第一题其实和二进制漏洞没有啥直接关系

0x02 collision

Daddy told me about cool MD5 hash collision today. I wanna do something like that too!
ssh [email protected] -p2222 (pw:guest)
ssh 连上去看到代码 col.c
同第一题,col 程序有suid位,和 flag 文件同一所属用户,因此只要满足 col 程序逻辑即可
这题是我们输入一个 20字节的密码,然后通过 check_password 去算,结果为 0x21DD09EC 即可。相当于逆向一个可逆的加密算法了,知道算法实现和结果,还原明文。
为方便断点调试,分析数据变化,这里编译加上编译标志位(这里我们手动加上了个 printf() 函数输出 ip 存储的地址
输入 abcdabcdabcdabcdabcd关于 lldb 的使用
断点 check_password 函数
结合代码,*p第指向的内存存储的内容为 abcdabcdabcdabcdabcd
notion image
第6行*ip*p强转为整型,整型是4字节,所以对应第一个abcd,在内存中存储的值为 0x61626364,因为我的是小端机器,低位在低地址,所以是 0x64636261,对应的整数是 1684234849,所以 *ip 的值为 1684234849,一开始这里有点搞乱了,感谢 yuenhing 解答。回到 for 循环
结果即是把我们输入的,分4个为一组的字符每个转换成16进制拼接起来在相加,我们这里既是 hex(1684234849*4),这里我们乱输入的不满足结果 hashcode = 0x21DD09EC
不过我们分析出了这个程序做的是什么,获取flag只要把 hashcode 分5份相加即可,不过 0x21DD09EC / 5.0 是除不尽的,先分4份在加上余出的部分即可:
所以前面4份是 0x06c5cec8 * 4
余出的部分得到 0x06c5cecc
因为我的机器是小端,所以是
尝试
notion image
当然有时目标环境不一定有Python环境相关的,可以使用 pwntools 进行

0x03 bof

Nana told me that buffer overflow is one of the most common software vulnerability. Is that true?
Download : http://pwnable.kr/bin/bof Download : http://pwnable.kr/bin/bof.c
Running at : nc pwnable.kr 9000
代码:http://pwnable.kr/bin/bof.c
常用寄存器、标志位笔记
https://blog.csdn.net/weixin_43780092/article/details/126694251
我这里是64位CPU,所以栈顶寄存器是rsp,如果是32位CPU,请用esp
  • x/数量格式字节数 内存x/3xw 0x10010,上面这里为从$rsp地址开始以16进制输出50字的数据
这里遇到一个问题
不知为啥 key 参数 0xdeadbeef 在低地址,按理说,覆盖是往搞地址覆盖的…
notion image
可以看上图,右边 bof 程序是题目编译好的程序,gdb 之后是可 pwn的,通过gets函数输入大量的数然后覆盖 0xffffd420 地址的 0xdeadbeef 即可,参数 key 在高地址。这也符合函数的调用方式
 
这里疑惑了挺久了,后面群里请教了一下大佬,Kiprey 和 Serendipity
 
我这个程序是自己编译的64位程序,题目提供的 https://pwnable.kr/bin/bof 是32位程序
 
32 和 64 位的函数参数传递方式不一样,64位用寄存器传参。
 
果然,我本机通过 gdb 编译一个 32位的程序,就和上图右边的栈结构一样了

这里记录一下32位和64位函数的调用栈异同

首先是
  • 32位程序
函数参数在函数返回地址的上方,可以看一下基本的栈帧结构图
notion image
  • esp始终指定栈顶,栈顶为低地址,栈底为高地址,函数调时,key 参数先入栈,在入栈ret,在入栈被调用函数的ebp,然后为 overflowme 数组开辟空间(SUB EBP -0x20),指针在指向数组的起始地址(低地址,写数据时指针移动 overflowme++,往高址写),也就是说这里的gets函数是往 overflowme 数组向高地址写,写的足够多就可以污染高地址的元素,比如 key 参数。
  • 64位程序
System V AMD64 ABI (Linux、FreeBSD、macOS 等采用) 中前六个整型或指针参数依次保存在 RDI, RSI, RDX, RCX, R8 和 R9 寄存器中,如果还有更多的参数的话才会保存在栈上。内存地址不能大于 0x00007FFFFFFFFFFF,6 个字节长度,否则会抛出异常。
我们来看一下 bof.c 程序在 64位程序运行时的汇编代码。
看到我们的参数 0xdeadbeef 被保存在寄存器 %edi(rdi)中,因为参数只有1个,通过寄存器穿参。继续步进
发现 %edi 寄存器在 +22 处保存传参值,保存在 -0x34(%rbp)中,这是因为要调用 printf 函数,+25要再次通过寄存器传参,不保存传参值会被覆盖。接下来调用 gets,传参是 -0x30(%rbp),也就是从 -0x30 开始写数据,这里可以清晰看到,从 -0x30 开始往高地址写,而 -0x34 在更低地址,所以不会覆盖参数等值,所以通过寄存器穿参,这种情况不能污染参数。
那寄存器传址真的没法污染了吗?有兴趣可以看看衍生题目 这种情况

回到bof题目

notion image
输入 10个 a,可以看到当前栈空间的内容如图, 0xffffd2f0对称的的内容为: `0x61616161 0x61616161 0x00006161,因32位通过栈传参,参数在地址 0xffffd320,所以只要我们写入的 0xffffd320 - 0xffffd2f0= 0x30 (48)个 a 即可,然后写入 0xcafebabe 即可
notion image
  • (cat bof_payload && cat) 这里不能直接 cat,因为成功后进入到的是一个交互shell,我们需要提供输入流
这里把 bof 改了个名字 bof_test ,一开始不知为啥利用不成功,后面发现 python3 开始 print 输出的是 unicode 文本字符串,改写成
即可输出原始二进制字符串
notion image
不过这个打线上的是不成的,因为下载下来的 bof 程序
notion image
需要填充52个字节,改下 payload 即可
但也打不成功,很迷
notion image
然后试了下其他命令就可以了…
notion image
通过 pwntools 写脚本
notion image

bof_plus (可跳过)

从 bof 64位传参分析知,就算是通过寄存器传参,如果函数里在调用函数,参数值始终是要存到栈上的,现在这种情况,就会污染参数的值,因为还有一个子函数调用,子函数肯定比父函数在更低地址,从子函数开始往高写,那也就可以污染参数值了

0x04 flag

Papa brought me a packed present! let’s open it.
Download : http://pwnable.kr/bin/flag
This is reversing task. all you need is binary
提示说是逆向题目,但放进 Ghidra 又看不出来啥逻辑,主函数都没找到,难道是加壳了?
notion image
这里涉及到编译选项
  • s (strip)表示删除调试信息和包含在可执行文件中的其他数据,这些数据对于执行来说是非必要的,可以减小可执行文件的大小
  • g (no strip)表示包含调试信息,对于每一条汇编指令,都有生成它的源代码的某一行信息,会保留源代码中变量的名称,并且可以在运行时与匹配的内存相关联
可以通过下图对比差异
notion image
但这里并不是因为编译标志位的原因,回到题目描述 packed,意为打包,因为是打包时,这些信息被压缩掉了,需要解压缩恢复这些信息
在运行一下程序也可以得知,说 flag 在 strcpy 函数里
通过 die 看到,该程序通过 UPX 进行打包
notion image
notion image
发现还原 flag 文件的编译信息了,这时通过 ghidra 打开就能正常反编译了
flag就是 strcpy 的第2个参数

0x05 passcode

Mommy told me to make a passcode based login system. My initial C code was compiled without any error! Well, there was some compiler warning, but who cares about that?
ssh [email protected] -p2222 (pw:guest)
题目很直接,只要满足 passcode1==338150 && passcode2==13371337 即可
notion image
但直接输入,会发现报错,这个就很像我们刚学C经常犯的一个编写错误,passcode1 是整形,但 scanf("%d", passcode1); 时,passcode1 不是地址。如果 passcode1 是字符串类型,本身是地址,就不用加上取地址符号 &,这里正确的写法应是 scanf("%d", &passcode1); 不过题目这里刚好利用这个点。
 
输入字符串是不会报错,但无法输入 passcode2,回车就自动把第二个值也输入了
从汇编层面分析,把二进制程序从目标机器拷贝下来调试(本机编译出来的因为编译环境不一样会有一些差异)
对比一下
  • 上方 0x0804862f 地址的指令 lea edx,[ebp-0x70]
  • 下方 0x0804857c 地址的指令 mov edx,DWORD PTR [ebp-0x10]
    可以发现,当 scanf 函数走地址是,用的是 lea 指令(地址),而错误的用值时,用的是 mov 指令(立即数)。这不是本题的重点,重点是,scanf 获取的地址,ebp-0x70 和 bbp-0x10 他们的值相差 0x60(十进制:96),也就是说,welcome 函数里输入的值,是可以覆盖 passcode1 地址指向的内容!也就是说可以向某个地址写入任意4字节(由此可见,变量声明顺便初始化还是很重要的)
     
    为啥这两个函数的 ebp 一样?这两个子函数都是由 main 函数调用,并且没有传参,所以 ebp 是一样的,动调一下
     
    不过目前只能控制 passcode1 的地址,调用完 scanf 后面还有个 fflush 函数,看一下它的汇编
    发现他是走 got 表,获取函数的真实地址,他跳转的目标地址是 0x804a004 ,我们刚刚不是可以实现任意地址4字节写入,那么我们可以覆盖这里的内容,直接 jmp 至 system 函数调用的地址 0x080485e3
     
    因为一开始的 scanf 是读入 100 字节,最后4字节为 passcode1,也就是,scanf 的时候,第2个参数我们输入的内容写入 0x804a004 地址
     
     
    然后 scanf 需要输入 system 调用地址 0x080485e3 的十进制,因为 scanf 使用 %d,以 10进制读入
     
    第1个 scanf 读入 100 字节,第2个scanf读取 4 字节,也就是
     
    pwntool
     

    0x06 random

    Daddy, teach me how to use random value in programming!
    ssh [email protected] -p2222 (pw:guest)
     
    汇编gpt解释
    先调用 rand() 函数,该随机数本质上是使用伪随机数,如果不使用 void srand(unsigned seed); 设置种子,那么默认种子为 0 或 1(经测试,这两个种子生成的伪随机数一样),这个随机数其实是固定的。给 random.c 加点逻辑
     
    后面关键逻辑:我们输入一个数,这个数和随机数异或的结果是 0xdeadbeef,异或有一个公式:如果 A ^ B = C 那么 A = B ^ C,也就是说 3039230856 = 1804289383 ^ 0xdeadbeef
     
    那我们输入 3039230856 就能得到 flag 了
     
    pwn

    0x07 input

    Mom? how can I pass my input to a computer program?
    ssh [email protected] -p2222 (pw:guest)
     
    看起来需要满足一系列要求,
    汇编中的 rbp-0x54、rbp-0x60、rbp-0x68 保存了 main 函数的参数(argc, argv[], 和 envp[]
     
    先尝试满足第一个条件
    先比较程序是否是输入100个参数,然后比较输入参数的即第 65 个字节(0x208(520)/8)是否为 \x00,问题来了,程序入参通过空格分割,然后如果输入 \x00 的话,会被跳过
     
    新建个 py 文件
     
    调试下,先 b je 前面即可
     
    这里发现个命令挺好用的,就是 man ascii 就不用每次去网上找 ascii 值了
     
    最终发现用 C 的 原始命令执行调用
    比较方便,因为执行命令本质上就是调用这个,然后还有传入原始的 argv 和 envp,这样就不用担心手动输入,或者是因为输入的空格分割等问题导致输入读不进去的问题了
     
    然后根据题目给的C代码照葫芦画瓢即可
    运行之后就过了3关了
     
    这里记录一下 if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0; 这部分比较逻辑,高级语言就一行,汇编还挺复杂的,其余都比较好理解
     
    当然这里除了读写文件,还可以用管道来向标准输入和错误输出
     
    接下来继续搞第4/5个条件,最终需要在环境上执行的脚本如下
    由于目标机器上有 nc 命令啥的,就不用在 c 语言在花大篇幅写 tcp 发数据了,当然也可以写个
     
    先 ssh 连上环境,然后
    这时候目标会执行套接字的操作,此时在开一个 ssh 执行
    即可在刚刚的 ssh 拿到 flag
     
    pwn

    0x08 leg

    Daddy told me I should study arm. But I prefer to study my leg!
    Download : http://pwnable.kr/bin/leg.c Download : http://pwnable.kr/bin/leg.asm
    ssh [email protected] -p2222 (pw:guest)
     
    观察代码
    通过获取 key1() key2() key3() 返回值相加结果即可获取 flag
     
    顺便问了 gpt 汇编的意思
    main 汇编
    这是一段 ARM 架构下的汇编代码,用于定义一个 main 函数的流程。下面是对这些指令的解释:
    1. push {r4, r11, lr}
        • 将寄存器 r4r11(帧指针)和链接寄存器 lr(返回地址)压入栈中。这在函数开始时保存了重要的寄存器。
    1. add r11, sp, #8
        • 设置帧指针 r11(fp)为当前栈指针 sp 加 8。这是新的帧基址。
    1. sub sp, sp, #12
        • 将栈指针 sp 减去 12,为局部变量分配空间。
    1. mov r3, #0
        • 将立即数 0 移动到寄存器 r3
    1. str r3, [r11, #-16]
        • r3(即 0)存储到帧指针 r11 偏移 -16 的位置,可能用于初始化一个局部变量。
    1. ldr r0, [pc, #104]; 0x8dc0 <main+132>
        • 从程序计数器 pc 偏移 104 的地方加载数据到 r0,这可能是一个字符串或其他数据。
    1. bl 0xfb6c <printf>
        • 调用 printf 函数,参数是 r0 寄存器的内容。
    1. sub r3, r11, #16
        • 计算 r11 减 16 的结果,并将其存储到 r3
    1. ldr r0, [pc, #96]; 0x8dc4 <main+136>
        • 从程序计数器 pc 偏移 96 的地方加载数据到 r0
    1. mov r1, r3
        • r3 的值移动到 r1
    1. bl 0xfbd8 <__isoc99_scanf>
        • 调用 __isoc99_scanf 函数,参数是 r0r1
    1. 调用三个 key 函数并累加它们的返回值
        • 这部分代码(从 bl 0x8cd4 <key1>add r2, r4, r3)调用了三个名为 key1key2key3 的函数,并将它们的返回值相加。
    1. ldr r3, [r11, #-16]
        • 从帧指针 r11 偏移 -16 的位置加载数据到 r3
    1. cmp r2, r3
        • 比较 r2r3 的值。
    1. bne 0x8da8 <main+108>
        • 如果 r2r3 不相等,则跳转到 main+108
    1. 成功分支
        • 如果 r2r3 相等(通过了比较),则执行从 ldr r0, [pc, #44]; 0x8dc8 <main+140>bl 0xf89c <system> 的代码,这可能是打印一条消息并执行某个系统命令。
    1. 失败分支
        • 如果 r2r3 不相等,执行从 ldr r0, [pc, #32]; 0x8dd0 <main+148>bl 0x1050c <puts> 的代码,这可能是打印一条错误消息。
    1. 函数结束
        • 无论成功与否,最后的代码将栈指针 sp 和帧指针 r11 恢复到原来的状态,并从函数返回(
    pop {r4, r11, pc})。
    在这个函数中,关键部分是调用三个 key 函数,并检查它们返回值的总和是否等于某个特定值。如果等于,执行成功路径;如果不等于,执行失败路径。
    key1 汇编
    这是 key1 函数的 ARM 架构下的汇编代码。下面是对这些指令的逐行解释:
    1. push {r11}; (str r11, [sp, #-4]!)
        • 将帧指针 r11 压入栈中。在 ARM 架构中,r11 通常用作帧指针(fp)。这个操作也同时更新栈指针 sp(栈指针减小)。
    1. add r11, sp, #0
        • 将栈指针 sp 的值赋给帧指针 r11。由于之前没有更改 sp 的值,这里 r11 仍然指向原先的栈帧的开始。
    1. mov r3, pc
        • 将程序计数器 pc(指向当前正在执行的指令的地址)的值赋给寄存器 r3
    1. mov r0, r3
        • r3 的值赋给 r0。这里 r0 通常用于函数的返回值。
    1. sub sp, r11, #0
        • sp 设置回 r11 的值,恢复栈指针到函数入口时的状态。
    1. pop {r11}; (ldr r11, [sp], #4)
        • 从栈中弹出值到 r11,恢复之前保存的帧指针的值。这个操作也同时更新栈指针 sp(栈指针增大)。
    1. bx lr
        • 分支到链接寄存器 lr 中存储的地址,通常是函数调用的返回地址。
    总结来说,这个 key1 函数的作用是获取当前的程序计数器地址,并将其作为返回值。这可能是用于某种形式的地址或程序状态检查。由于它直接返回了程序计数器的值,这个函数的返回值将随着程序的每次运行而变化,除非程序被加载到固定的内存地址。
    key2 汇编
    这段代码是 ARM 架构下 key2 函数的汇编实现。让我们逐行解释它:
    1. push {r11}; (str r11, [sp, #-4]!)
        • 将帧指针 r11 压入栈中,并更新栈指针 sp
    1. add r11, sp, #0
        • 设置帧指针 r11 为当前栈指针 sp 的值。
    1. push {r6}; (str r6, [sp, #-4]!)
        • 将寄存器 r6 的值压入栈中,并更新栈指针 sp
    1. add r6, pc, #1
        • 将程序计数器 pc 的当前值加上 1,并将结果存储到 r6。这里的加 1 是因为 ARM 的分支交换指令 bx 在切换到 Thumb 模式时需要设置地址的最低位。
    1. bx r6
        • 分支到 r6 指定的地址,并根据地址的最低位切换到 ARM 或 Thumb 指令集模式。
    1. mov r3, pc
        • 将程序计数器 pc 的值赋给 r3
    1. adds r3, #4
        • r3 的值增加 4。
    1. push {r3}
        • r3 的值压入栈中。
    1. pop {pc}
        • 将栈顶的值弹出到程序计数器 pc,这实际上改变了函数的执行流程。
    1. pop {r6}; (ldr r6, [sp], #4)
        • 将栈顶的值弹出到 r6,恢复之前保存的 r6 的值。
    1. mov r0, r3
        • r3 的值赋给 r0r0 通常用于存放函数的返回值。
    1. sub sp, r11, #0
        • 将栈指针 sp 设置回帧指针 r11 的值,恢复到函数调用时的状态。
    1. pop {r11}; (ldr r11, [sp], #4)
        • 将栈顶的值弹出到帧指针 r11,恢复之前保存的 r11 的值。
    1. bx lr
        • 分支到链接寄存器 lr 中存储的地址,通常是函数调用的返回地址。
    在这个函数中,bx r6 和随后的 pop {pc} 指令组合实现了一种巧妙的跳转,使得函数在运行时计算其返回地址。这种技术在某些情况下可以用于防止逆向工程。最终,函数返回了调整后的程序计数器的值,这可能是用于某种形式的验证或安全检查。
    key3 汇编
    这段代码是 key3 函数在 ARM 架构下的汇编实现。下面是对这些指令的逐行解释:
    1. push {r11}; (str r11, [sp, #-4]!)
        • 将帧指针 r11 压入栈中,并更新栈指针 sp
    1. add r11, sp, #0
        • 设置帧指针 r11 为当前栈指针 sp 的值。
    1. mov r3, lr
        • 将链接寄存器 lr(存储着函数返回地址)的值赋给 r3
    1. mov r0, r3
        • r3 的值赋给 r0r0 在 ARM 架构中通常用于函数的返回值。
    1. sub sp, r11, #0
        • 将栈指针 sp 设置回帧指针 r11 的值,恢复到函数调用时的状态。
    1. pop {r11}; (ldr r11, [sp], #4)
        • 将栈顶的值弹出到帧指针 r11,恢复之前保存的 r11 的值。
    1. bx lr
        • 分支到链接寄存器 lr 中存储的地址,通常是函数调用的返回地址。
    这个 key3 函数的作用是返回调用它的函数的返回地址(即 lr 的值)。这种技术可能用于安全检查,确保函数是从预期的位置被调用的。通过返回 lr 的值,key3 可以用来检查或验证调用堆栈的完整性。
     
    可以发现,key1() key2() key3() 函数调用的返回值通过 r0 寄存器存储,r0 的值都和 pc(程序计数器)相关,然后在 arm 中有个特性:当一个指令正在执行时,pc 实际上已经预先读取了之后的两条指令,即当前指令地址加上 8 字节,也就是说
    1. key1: 0x00008cdc + 8 = 0x00008ce4
    1. key2: 0x00008d04 + 8 + 4 = 0x00008d0c
    1. key3: lr 为函数返回地址 0x00008d80
     
    加起来十进制就是 108400
     

    0x09 mistake

    We all make mistakes, let’s move on. (don’t take this too seriously, no fancy hacking skill is required at all)
    This task is based on real event Thanks to dhmonkey
    hint : operator priority
    ssh [email protected] -p2222 (pw:guest)
     
    问题点
    < 小于号的优先级 优先于 = 等于号,也就是说题目环境文件 open("/home/mistake/password",O_RDONLY,0400) 是存在的,也就是说肯定打开成功了,打开成功就打开一个非负整数,也就是说 < 0 肯定是不成立的,也就是 false,即 0。然后后面通过 read 函数从 0 中读,从 0 读表示从标准输入读,也就是我们输入读东西,那么 password 就是被我们控制了,比如我们输入 10个 1:1111111111
     
    进入异或逻辑,相同为0,则我们输入10个 0 即可:0000000000
     
    这里突然想记了下 xor 函数的汇编和解释,和本题没啥关系
    这段汇编代码定义了一个名为 xor 的函数,这个函数在 x86-64 架构下的 C 语言编译器生成的汇编代码中很常见。以下是对这些指令的逐行解释:
    1. push rbp
        • 将基指针(Base Pointer,rbp)压入栈中,用于创建新的栈帧。
    1. mov rbp, rsp
        • 将栈指针(Stack Pointer,rsp)的值复制到基指针 rbp 中。
    1. mov QWORD PTR [rbp-0x18], rdi
        • 将第一个参数(rdi 寄存器的值)存储在基指针 rbp 偏移 -0x18 处的内存中。
    1. mov DWORD PTR [rbp-0x1c], esi
        • 将第二个参数(esi 寄存器的值)存储在基指针 rbp 偏移 -0x1c 处的内存中。
    1. mov DWORD PTR [rbp-0x4], 0x0
        • 将整数 0 存储在基指针 rbp 偏移 -0x4 处的内存中,可能用作循环计数器或局部变量。
    1. jmp 0x400847 <xor+51>
        • 无条件跳转到地址 0x400847(函数内的另一部分)。
    1. 循环体
        • mov eax, DWORD PTR [rbp-0x4]add DWORD PTR [rbp-0x4], 0x1 的部分定义了一个循环,其中循环变量存储在 [rbp-0x4]
    1. cdqe
        • eax 寄存器的值符号扩展到 rax 寄存器。
    1. movzx edx, BYTE PTR [rdx]
        • rdx 指向的地址处的一个字节无符号扩展到 edx 寄存器。
    1. xor edx, 0x1
        • edx 寄存器的值与 1 进行异或操作。
    1. mov BYTE PTR [rax], dl
        • dledx 的低 8 位)的值存储到 rax 指向的地址。
    1. 循环条件检查
        • mov eax, DWORD PTR [rbp-0x4]jl 0x400828 <xor+20> 的部分检查循环是否应该继续。如果 eax(循环变量)小于 [rbp-0x1c](第二个参数),则跳回循环开始处。
    1. pop rbp
        • 从栈中弹出原先的基指针值到 rbp 寄存器,恢复旧的栈帧。
    1. ret
        • 从函数返回。
    总结:这个函数看起来像是对某个数据(位于第一个参数指向的内存位置)进行逐字节异或操作,每个字节都与 1 异或。第二个参数指定了要处理的字节数。这种操作通常用于简单的加密或数据处理。
     
    在汇编语言中,特别是在处理不同大小的数据类型时,常常需要进行符号扩展(Sign Extension)和零扩展(Zero Extension)。这些操作用于将一个较小的数据类型转换为一个较大的数据类型,同时保留原始数据的值和符号。这些操作在 x86-64 架构中尤为常见,因为它需要处理多种不同长度的寄存器。
    对于您提到的三个指令:
    1. cdqe
        • 这是“Convert Doubleword to Quadword”的缩写,用于将 32 位的 eax 寄存器的内容符号扩展到 64 位的 rax 寄存器。如果 eax 的最高位是 1(即负数),则 rax 的高 32 位将被设置为 1;否则,设置为 0。这保证了将一个 32 位的整数转换为 64 位整数时,其符号(正或负)被正确保留。
    1. movsxd rdx, edx
        • 这是“Move with Sign-Extension Dword to Qword”的缩写,它将 32 位的 edx 寄存器的内容符号扩展到 64 位的 rdx 寄存器。这与 cdqe 指令相似,但是它是在两个不同的寄存器间进行操作。
    1. movzx edx, BYTE PTR [rdx]
        • 这是“Move with Zero-Extension”的缩写,用于将 rdx 指向的地址处的一个字节(8 位)无符号扩展到 32 位的 edx 寄存器。这里的零扩展意味着 edx 寄存器的高 24 位将被清零,而低 8 位将被设置为从内存读取的值。这用于确保在处理单个字节数据时不会错误地保留任何先前的数据。
    这些扩展操作是处理不同长度数据的标准做法,以确保数据的准确表示和操作。在多数情况下,这些操作是由编译器在生成汇编代码时自动插入的,以确保数据在寄存器间传递和处理时的正确性和一致性。
     

    0x10 shellshock

    Mommy, there was a shocking news about bash. I bet you already know, but lets just make it sure :)
    ssh [email protected] -p2222 (pw:guest)
     
    老洞 CVE-2014-6271,POC
    输出 TEST 则证明漏洞存在
     
    利用

    0x11 coin1

    Mommy, I wanna play a game! (if your network response time is too slow, try nc 0 9007 inside pwnable.kr server)
    Running at : nc pwnable.kr 9007
     
     
    理解题意后,二分搜索可以解决,排查位置后到后,把剩下的机会用了,然后发正确答案就行,然后重复 100 次,因为我这里网络不太稳定,之前用远程环境了
     

    0x12 blackjack

    Hey! check out this C implementation of blackjack game! I found it online
    • http://cboard.cprogramming.com/c-programming/114023-simple-blackjack-program.html
    I like to give my flags to millionares. how much money you got?
    Running at : nc pwnable.kr 9009
    题目说要成为百万富翁才给我们 flag,但我们的初始金额只有 500,得赚到猴年马月,所以需要另辟蹊径,不过它存在整数溢出,输入 Y1开始游戏,输入 11111111111 然后输入 S 故意输掉,扣钱的时候就会导致溢出,结果为 (500 - 11111111111) % (2<<(32-1)) = 1773791277 只要躺输一局钱就够了
     

    0x13 lotto

    Mommy! I made a lotto program for my homework. do you want to play?
    ssh [email protected] -p2222 (pw:guest)
     
    理解代码逻辑后,发现是逻辑漏洞,比较的时候没有考虑重复字符,
    随便找个 ascii 在 1-45 内的字符,重复即可,生成的随机数多几次只要命中1次,然后他会重复校验重复的,就相对于命中6次了
     

    0x14 cmd1

    Mommy! what is PATH environment in Linux?
    ssh [email protected] -p2222 (pw:guest)
     
    比较像 web 题,比较简单
     
    一些其他解法,参考 [11]
    比较正经的解法

    0x15 cmd2

    Daddy bought me a system command shell. but he put some filters to prevent me from playing with it without his permission… but I wanna play anytime I want!
    ssh [email protected] -p2222 (pw:flag of cmd1)
     
    想办法绕过 / 过滤即可使用绝对路径执行命令,然后使用的命令必须是内置命令(不需要到 PATH 环境变量找的那种),发现 printf 可以通过格式化字符串 8 进制变成 / (16进制不知为啥转不了),如下
    或者直接用 8进制 都可
     
    正解应该是,利用 pwd 内置命令获取 /
    还有一些不错的解

    0x16 uaf

    Mommy, what is Use After Free bug?
    ssh [email protected] -p2222 (pw:guest)
     
    题名就很明显,考察 uaf 漏洞,程序是 C++ 程序,需要我们有一些预备知识
    1. glibc 堆的工作方式,
    1. C++ 继承 和 vtables
     
    题目代码
     
    代码从先是初始化了继承于 Human 类的 ManWoman 类的两个实例,所以子类的都继承了 give_shell()introduce() 方法
     
    接下来进入死循环,有三个选择,第一和第三个选择比较简单,调用 introduce() 方法,第二个选择是从 argv[2] 文件中读取 argv[1] 字节长度的内容到堆中(因为用 new 关键字分配到内存)
     
    如果我们选择了 3 删除了实例化到对象,然后选 1,就会造成 UAF 漏洞
     
    编译运行程序,这里环境上的程序是64位的
    直接编译即可
    不过我这里直接使用环境上编译好的进行调试
    0x0000000000400f00 创建堆 chunk,0x0000000000400f13 调用 Man() 构造函数
     
    堆 chunk 创建在 0x614ed0 地址中的 大小为 0x20 =30,但实际使用的为 0x18 = 24,但堆管理器要保证在32位系统中8字节对齐(在64位中则是16字节对齐)。所以堆大小是 0x30
     
    0x614ee0 chunk的里 vtable 地址为 0x0000000000401570,后面的地址 0x0000000000000019 (对应十进制 25),为 Man 的 age 成员值,0x0000000000614ec8 地址为Man 的 name 成员值
     
    vtable地址 0x0000000000401570 地址里的两个虚函数地址 0x000000000040117agive_shell()0x00000000004012d2introduce()
     
    断点
    此时发现 chunk 0x614ee0 地址对应的 vtable地址已经被删除了,但两个虚函数的地址还在
    此时查看 bins,可以看到 0x20 大小的空闲 chunks
     
    接下来下利用环节
     
    我们知道堆管理器在分配新chunks时,如果分配的大小和上一个free掉的chunks的大小一样,那么会直接复用free掉的chunks。因此,我们可以选择 2 分支,给data数组分配一个0x18大小的内存(会自动对齐到0x20),当我们使用 1 分支时,数组会覆盖Man和Woman的chunks,就能执行我们想执行的函数了
     
    我们先做个小测试
     
    可以看到,原先 Man 和 Woman 的 chunks 都被复写了,新的 vtable 地址是 0x4141414141414141
     
    注意,我们使用了 2 分支 两次,因为Woman空闲chunk位于释放chunk的顶部(先 delete m;delete w;),所以Woman将首先被分配复用,我们的目标是 Man chunk,所以我们分配了2个chunks。
     
    输入 1,进入 1 分支时,m->introduce() 调用访问的 vtable 地址为 0x0000000000401570 ,这里有两个函数(give_shellintroduce) 地址,因此它调用的是(* vtable) + 8。所以要想吊用 give_shell 那我们应将 vtable_address = vtable_address - 8 ,也就是 0x0000000000401568
     
    所以到题目环境上的解为为

    0x17 memcpy

    Are you tired of hacking?, take some rest here. Just help me out with my small experiment regarding memcpy performance. after that, flag is yours.
    http://pwnable.kr/bin/memcpy.c
    ssh [email protected] -p2222 (pw:guest)
     
    编译

    0x99 Reference

    1. https://www.dllhook.com/post/51.html
    1. https://0xrick.github.io/pwn/bof/
    1. https://0xrick.github.io/pwn/bof/
    1. Pwnable.kr: ‘flag’ Walkthrough
    1. https://jaimelightfoot.com/blog/pwnable-kr-passcode-walkthrough/
    1. https://www.youtube.com/watch?v=bzqOtVQ0Pmw
    1. https://jaimelightfoot.com/blog/pwnable-kr-input-walkthrough/
    1. https://blog.csdn.net/qq_33976344/article/details/122523424
    1. https://n1ght-w0lf.github.io/binary exploitation/leg/
    1. https://man7.org/linux/man-pages/man2/open.2.html#RETURN_VALUE
    1. https://n1ght-w0lf.github.io/binary exploitation/shellshock/
    1. https://n1ght-w0lf.github.io/binary exploitation/cmd1/
    1. https://pwnable.kr/writeup.php?task_no=49
    1. https://n1ght-w0lf.github.io/binary exploitation/uaf/
     
     
  • pwnable.kr
  • pwn
  • 云原生安全书籍收集Azure容器实例首次跨租户容器接管漏洞(下)
    • GitTalk
    目录