Writeup for Decoding Lab, iCarnegie
我只是看到了无数充满求知欲、激情、与年轻梦想的同学们,将要把自己的四年青春,充满希望与信任地交给大学来塑造。这使我心中非常不安。
Writeup for Decoding Lab, iCarnegie
overview
All materials needed in this lab are
stored in my github repo.
- my gcc version:
gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0
- compile command:
cc lab1.c -o lab1 -g
- linux kernel:
5.15.0-56-generic
So let’s begin.
key1 & key2
根据guide.html
,我们首先只需要考虑key1
和key2
;而我们应该要得到一串解密后的字符串,以From:
开头。审计函数extract_message1
,经过黑盒 & 白盒测试,我们可以发现该函数会从start+1
处开始,每stride
个字符,drop一个字符。考虑到小端序,我们整个字符数组应该为:
1 | real_data = ['63', '63', '63', '63', '63', '63', '63', '63', '63', '46', '46', '72', '72', '6f', '6d', '6f', '3a', '20', '6d', '46', '72', '3a', '69', '65', '20', '6e', '64', '43', 'a ', '54', '54', '6f', '3a', '45', '20', '59', 'a ', '6f', '75', '54', 'a ', '47', '6f', '6f', '6f', '3a', '64', '21', '20', '20', '4e', '59', '6f', '77', '6f', '20', '74', '75', '72', '79', 'a ', '20', '63', '45', '68', '6f', '78', '6f', '73', '63', '69', '6e', '65', '67', '20', '6c', '6b', '65', '6c', '79', '73', '65', '33', '2c', '6e', '34', '20', '74', '74', '6f', '21', '20', '66', '59', '6f', '72', '6f', '63', '65', '75', '20', '61', '20', '20', '63', '67', '61', '6c', '6f', '6c', '20', '74', '74', '6f', '20', '20', '65', '65', '78', '74', '76', '72', '61', '65', '63', '74', '72', '32', '20', '79', '61', '6e', '74', '64', 'a ', '68', '61', '76', '69', '6f', '69', '6e', '64', '20', '67', '74', '68', '21', '65', '20', '0 ', '63', '61', '78', '6c', '6c', '78', '20', '74', '78', '6f', '20', '78', '65', '78', '78', '74', '72', '78', '61', '63', '78', '74', '31', '78', '0 '] |
输出脚本位于solve.py
。
定位F,r,o,m。最后得出start=9 & stride=3。
start
为dummy(在内存中)的第一个byte, stride
为dummy(在内存中)的第二个byte。
然而Linux和Windows都是小端序,所以正确的解释是:start
是dummy的Least Significant Byte,stride
则是次低位。
也就是dummy
应该为0x????0309
。?
可以取任何值。
看到process_keys12
,该函数可以视为一个任意内存写:将key1
赋值为&key1
相对于指定修改内存的偏移,key2为指定修改的值。
一种可能的解法就是修改dummy
。我们可以用反汇编软件或gdb知道&dummy
与&key1
的偏移。
1 | pwndbg> set args 0 0x0102 |
所以第一阶段的payload
为./lab1 -1 0x0309
。
1 | root@workshop:~/repos/SCUCCS/C-Programming/Security Labs/lab1# ./lab1 -1 0x0309 |
key3 & key4
观察得知:
- 与
process_keys12
一样,process_keys34
也是一个任意内存写。 - 除非修改了
data
、start
或者stride
,extract_message1
执行后一定会使得msg1
不等于空。 start
和stride
应该不变;不然extract_message2
的返回值,msg2
就会被修改process_keys34
会执行两次;process_keys34
的修改又是偏移性质,所以两次对内存的修改结果可能不一样。
由此可以得出两个解题思路:
- 在第一处
process_keys34
时修改返回地址,在ret
的时候跳转到msg2 = extract_message2(start, stride);
所在的地址。 - 在第一处
process_keys34
时将data
的第10位修改为0。这样我们就可以进入if;进入if后的第二处process_keys34
又可以通过offset式的修改方法把第10位置为不为0的数,因此可以输出msg2
。
在这里仅简述第一种做法的方法。
1 | pwndbg> set args -1 0x0309 4 49 |
一个int
是4个bytes,所以key3应该为4,才能修改栈上的返回值。
1 | root@workshop:~/repos/SCUCCS/C-Programming/Security Labs/lab1# objdump -d lab1 -M intel| grep process_keys34 |
因此key4就是49。
稍微解释一下为什么可以这么做:原返回值一定是第一个call process_keys
的下一位汇编的地址;要修改成的返回值也要是第二个call process_keys
的地址。这两条指令的长短相等,故返回值之差一定也为49。
所以第二阶段的payload
为./lab1 -1 0x0309 4 49
。
1 | root@workshop:~/repos/SCUCCS/C-Programming/Security Labs/lab1# ./lab1 -1 0x0309 4 49 |
Summary
首先,总地来说,这个Decoding Lab
的水平明显不如CSAPP Labs。第一个问题在于该题在不同平台甚至不同编译优化环境下的payload
不一样,这就让调试非常头疼:如果目标解题者,这种调试是很难的————尤其是在初学者尚未用明白gdb的时候,静态分析源程序基本分析不出来;比如dummy
和key1
之间的差,如果看源代码很容易误以为偏移是-3
,但是编译器在优化的时候其实会把dummy
+key1
与start
+stride
错开,哪怕调到-Og
都是一样。第二个问题在于第二问有一些脑洞的成分,在解决第一问后,很容易将思路放在修改特定的数据上面,但是仔细分析后才能发现,修改dummy
/start
/stride
都不行,要修改data
或者栈上存储的返回值。
其次,顺利+能够形成激励机制地完成这个lab所需要的技术栈太大了。我(相对算比较轻松地)完成这个lab用到的技术栈有:
- 熟悉汇编语言
- 熟悉指针、Linux内存机制
- 丰富的Linux使用经验
- 丰富的gdb经验
对其中一项或者几项不熟悉都会使得完成该Lab变成痛苦的事情。
References
Decoding Lab: Understanding a Secret Message | Yieldnull
安全项目第一题解答及思路分析 | junyu33,第二问的第二种做法实现