


ReadFile()がうまく動作してくれない。なんで?
これでbufにカレントファイルポインタから1byte読み込まれるはずなんだけど、読み込まれない。
本来05が読み込まれるはずが、なぜか00
ためしにbufをBYTE buf[3]と宣言して、ReadFile()にはbufを渡し、3byte読み込むようにしてみるとbuf[1],buf[2]には正しい値が読み込まれるけどbuf[0]は上と同じで00。
BYTE buf[2]にしてみるとbuf[0],buf[1]とも00でだめ。
というわけで現在深追い中。orz
BYTE buf;
hFile = CreateFile(
pFile, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH
| FILE_FLAG_RANDOM_ACCESS, NULL
);
if (SetFilePointer(hFile, (LONG)offset, NULL, FILE_BEGIN)
== INVALID_SET_FILE_POINTER)
return errmsg("失敗");
if (ReadFile(hFile, &buf, 1, &rdsize, NULL) == 0)
return errmsg("失敗");
これでbufにカレントファイルポインタから1byte読み込まれるはずなんだけど、読み込まれない。
本来05が読み込まれるはずが、なぜか00
ためしにbufをBYTE buf[3]と宣言して、ReadFile()にはbufを渡し、3byte読み込むようにしてみるとbuf[1],buf[2]には正しい値が読み込まれるけどbuf[0]は上と同じで00。
BYTE buf[2]にしてみるとbuf[0],buf[1]とも00でだめ。
というわけで現在深追い中。orz


以下で紹介するコードは、コンパイラに大きく依存する。
このコードの動作を確認した環境は以下の通り。
OS:WindowsXP
コンパイラ:Borland C++Compiler 5.5
オプション:最適化なし
これで、
と、資源が許す限り永久にループします。
s1[4]とs2[4]でスタックにバッファを確保すると、s1かs2からスタック内のアドレスが
わかります。そして、s1,s2より上位のアドレスには、その関数がreturnするときに戻
る場所(retアドレス)が格納されています。この2つを組み合わせれば、retアドレスを
弄って、本来戻る場所とは別のところに戻せることが分かります。
そのアドレスを自分自身のアドレス(foo)にすれば、ループするだろうってことです。
所謂 Buffer Overflowというやつですね。
foo()内のスタックはこんな感じです。(適当なアドレスを基準にしています)
0012FF70 s1[0]
0012FF74 s1[1]
0012FF78 s1[2]
0012FF7C s1[3]
0012FF80 s2[3] s2[2] s2[1] s2[0]
0012FF84 EBPレジスタ
0012FF88 retアドレス
目的は0012FF88に格納されている関数return時の戻り先(retアドレス)を書き換えること
ですから、s1かs2のアドレスにいくつ足せばretアドレスを参照できか調べます。
ここではs1のほうを使います。
0x0012ff88 - 0x0012ff70 = 0x18 なので、s1[6]で0012FF88の中身、つまりretアドスが
参照できますね。
あとは s[6] = foo; とすれば、戻り先をfooにすることでループできるでしょう。
しかし、このコードは半永久的と書いた通り問題があります。
retアドレスをfoo自身のアドレスに書き換えることによって、またfooが呼び出される
のですが、正確に言うと呼び出されるというよりは、fooのコードにジャンプするん
です。呼び出しとジャンプではどこが違うのか?
関数呼び出しは x86アセンブリで callという命令を使い、
ジャンプは普通に jmp アドレス です。
callの動作はただ、ジャンプさせるのではなく、戻り先のアドレスをスタックに格納し、
その後にjmpさせます。
call命令でスタックに格納されたretアドレスを上書きしているこのコードだと、foo()が
再び呼び出されても、retアドレスがスタックに格納されていないので、前回より1つ上位
のアドレスにretアドレスが書き込まれ、繰り返し呼び出されていくうちに、スタックを使
い切ってしまいます。スタックを使い切るとアクセス違反で止まってしまうでしょう。
この問題を回避する方法は凄く簡単です。というかむしろ、これは間違ったやり方ですね。
なのでその"正しい"方法でのコードを書いてみたかったけど長くなるので興味を持った方
は考えてみてください。すぐ分かります。しかし、その方法でも環境に依存してしまうかも
しれません。
"ネタにはなるが、実務では使えない。それがローレベルテクニック(略してローテク)というかhack"
このコードの動作を確認した環境は以下の通り。
OS:WindowsXP
コンパイラ:Borland C++Compiler 5.5
オプション:最適化なし
#include <stdio.h>
void foo() {
int s1[4];
char s2[4];
s2[0] = 'a';
s2[1] = 'b';
s2[2] = 'c';
s2[3] = '\0';
puts(s2);
s1[6] = foo;
}
int main() {
foo();
return 0;
}
これで、
abc
abc
abc
..
と、資源が許す限り永久にループします。
s1[4]とs2[4]でスタックにバッファを確保すると、s1かs2からスタック内のアドレスが
わかります。そして、s1,s2より上位のアドレスには、その関数がreturnするときに戻
る場所(retアドレス)が格納されています。この2つを組み合わせれば、retアドレスを
弄って、本来戻る場所とは別のところに戻せることが分かります。
そのアドレスを自分自身のアドレス(foo)にすれば、ループするだろうってことです。
所謂 Buffer Overflowというやつですね。
foo()内のスタックはこんな感じです。(適当なアドレスを基準にしています)
0012FF70 s1[0]
0012FF74 s1[1]
0012FF78 s1[2]
0012FF7C s1[3]
0012FF80 s2[3] s2[2] s2[1] s2[0]
0012FF84 EBPレジスタ
0012FF88 retアドレス
目的は0012FF88に格納されている関数return時の戻り先(retアドレス)を書き換えること
ですから、s1かs2のアドレスにいくつ足せばretアドレスを参照できか調べます。
ここではs1のほうを使います。
0x0012ff88 - 0x0012ff70 = 0x18 なので、s1[6]で0012FF88の中身、つまりretアドスが
参照できますね。
あとは s[6] = foo; とすれば、戻り先をfooにすることでループできるでしょう。
しかし、このコードは半永久的と書いた通り問題があります。
retアドレスをfoo自身のアドレスに書き換えることによって、またfooが呼び出される
のですが、正確に言うと呼び出されるというよりは、fooのコードにジャンプするん
です。呼び出しとジャンプではどこが違うのか?
関数呼び出しは x86アセンブリで callという命令を使い、
ジャンプは普通に jmp アドレス です。
callの動作はただ、ジャンプさせるのではなく、戻り先のアドレスをスタックに格納し、
その後にjmpさせます。
call命令でスタックに格納されたretアドレスを上書きしているこのコードだと、foo()が
再び呼び出されても、retアドレスがスタックに格納されていないので、前回より1つ上位
のアドレスにretアドレスが書き込まれ、繰り返し呼び出されていくうちに、スタックを使
い切ってしまいます。スタックを使い切るとアクセス違反で止まってしまうでしょう。
この問題を回避する方法は凄く簡単です。というかむしろ、これは間違ったやり方ですね。
なのでその"正しい"方法でのコードを書いてみたかったけど長くなるので興味を持った方
は考えてみてください。すぐ分かります。しかし、その方法でも環境に依存してしまうかも
しれません。
"ネタにはなるが、実務では使えない。それがローレベルテクニック(略してローテク)というかhack"


またもやmixiネタ
書いた人大学生らしいけど、こんなのもわからんのかと怒る前に今回はCとPerlで書いてみよう
それも中軸の処理は一行で
まずはPerl
んー、まぁこんなもんでしょ
んじゃ次はC
ちょっと無理やりかな?
追記
Perlでもっと短いのでけた
うーんトリッキー
追記2
更に短く
もうここまでくると何の処理だかわからんね
追記3
泣きたくなるほどに短いやつ
そうか、これでよかったんだ
入力 getchar
出力 putchar
大文字と小文字を逆転するプログラムを書く。
書いた人大学生らしいけど、こんなのもわからんのかと怒る前に今回はCとPerlで書いてみよう
それも中軸の処理は一行で
まずはPerl
map{if($_<91 && $_>64){$_+=32;}elsif($_<123 && $_>96){$_-=32;}printf("%c",$_);}unpack('C*',<>);
んー、まぁこんなもんでしょ
んじゃ次はC
#include <stdio.h>
int main() {
char c;
while(c=getchar()) putchar(c > '@' && c < '[' ? c+32 : c > '`' && c < '{' ? c-32 : c);
return 0;
}
ちょっと無理やりかな?
追記
Perlでもっと短いのでけた
map{printf("%c",(0..64,97..122,91..96,65..90,123..255)[$_]);}unpack('C*',<>);
うーんトリッキー
追記2
更に短く
map{print chr((0..64,97..122,91..96,65..90,123..255)[$_]);}unpack('C*',<>);
もうここまでくると何の処理だかわからんね
追記3
泣きたくなるほどに短いやつ
while(<>){tr/a-zA-Z/A-Za-z/;print;}
そうか、これでよかったんだ


mixiのC言語とC++言語コミュを覗いてたら僕にもできそうなのが質問として投げ出されていた
バグつぶし含め20分ぐらいでかいてみた
実行結果(.cにマッチするようにしてある)
・・・いけてるよね?
そしてmixiではソース公開しないという、ね
はじめまして。
どうしてもわからないC言語の課題があるので質問させていただきます。
―課題―
文字列と照合したいパターン文字列をそれぞれchar型配列sとpに読み込み、関数ptnmchを利用して、文字列sにおいてパターンpと一致する部分を全て*で置き換えて出力するプログラムを作成しなさい。
つまり、shinzoshin zoshinzoと入力して、照合したい文字列がshinzoの場合、
******shin zo******
となりたいのです。
よろしくお願い致します。
バグつぶし含め20分ぐらいでかいてみた
#include <stdio.h>
#include <string.h>
int ptnmch(char*,char*);
void str_replace(char*,int,int,char);
int main(int argc,char **argv){
int i = 0;
char hoge[] = ".c";
while(i < argc){
if(ptnmch(argv[i],hoge)){
printf("OK -> %s\n",argv[i]);
}else{
printf("NG -> %s\n",argv[i]);
}
i++;
}
return 0;
}
int ptnmch(char *p,char *s){
int matchflag = 0;
int flag = 0;
int i = strlen(s);
int m = 0;
int l = 0;
while(p[m] != '\0'){
if(p[m] == s[0]){
l = 0;
flag = 1;
while(++l < i){
if(p[m+l] != s[l]){
flag = 0;
break;
}
}
if(flag){
matchflag = 1;
str_replace(p,m,m+i,'*');
}
}
m++;
}
return matchflag;
}
void str_replace(char *str,int s_n,int e_n,char r){
while(s_n < e_n){
str[s_n++] = r;
}
return;
}
実行結果(.cにマッチするようにしてある)
# ./a.out hogehoge.c c.ge .choge.c
NG -> ./a.out
OK -> hogehoge**
NG -> c.ge
OK -> **hoge**
・・・いけてるよね?
そしてmixiではソース公開しないという、ね
